Index-out-of-range panics in image renderers
GHSA-6524-w46v-6399 · CVE assignment pending
Summary
Missing input validation in pdfcpu's grayscale and Indexed image renderers allows a crafted PDF to crash any application that extracts or renders images. Images with BitsPerComponent values of 9..16 freeze the packed-pixel loop (8/bpc == 0 via integer division), so the pixel counter never advances while the byte cursor walks off the image buffer. Independently, Indexed color-space images with a short lookup palette index out of bounds. Both paths raise an unrecovered index out of range runtime panic, killing the host process — fault.Catch only recovers internal fault.Panic values, not runtime.Error.
Impact
- Denial of Service — unrecovered runtime panic terminates the process.
- Attack vector: network — small crafted PDF via upload; no authentication.
- Scope:
api.ExtractImages,RenderImage,WriteImage— any image-processing pipeline using pdfcpu.
Root Cause
In pkg/pdfcpu/writeImage.go, renderDeviceGrayToPNG() (and its Indexed siblings) runs a packed-pixel loop that assumes bpc divides 8:
// writeImage.go:393 (same shape at 559, 606, 640, 668)
i := 0
for y := 0; y < im.h; y++ {
for x := 0; x < im.w; {
p := b[i] // ← panics once i >= len(b)
for j := 0; j < 8/im.bpc && x < im.w; j++ { ... x++ }
i++
}
}
With bpc > 8, 8/im.bpc == 0: the inner loop never executes, x never advances, and i walks off the end of b. Image validation only rejects bpc <= 0 plus size limits — there is no upper bound on bpc, and the declared ErrUnsupported16BPC error is never raised in the vulnerable version.
The second sink indexes a short Indexed lookup palette without a length check (the CalRGB branch performs no validation at all):
// writeImage.go:606 — renderIndexedRGBToPNG
l := 3 * int(ind)
img.Set(x, y, color.NRGBA{R: lookup[l], G: lookup[l+1], B: lookup[l+2], A: alpha})
Proof of Concept
Two independent crashes, each confirmed at three levels (direct sink, public entry point, end-to-end PDF):
# Sink 1: bpc 9..16
PDFImage{bpc: 9, w: 2, h: 1, Content: 8 bytes}
→ panic: index out of range [8] with length 8
# Sink 2: Indexed/CalRGB with 3-byte palette, first image byte 0xFF
[/Indexed [/CalRGB] 0 <000000>], image byte 0xFF
→ l = 765 on a 3-byte slice
→ panic: index out of range [765] with length 3
# End-to-end: crafted PDF parses cleanly (api.ReadContext OK),
# then api.ExtractImages panics — fault.Catch does not recover it.
The finding was discovered with LLM-assisted review (Strix) and independently replicated with local unit and end-to-end tests against pdfcpu v0.15.0.
Disclosure Timeline
| Date | Event |
|---|---|
| 2026-09-21 | Vulnerability discovered and PoC-confirmed (3-tier local reproduction) |
| 2026-10-03 | Report submitted via GitHub Private Vulnerability Reporting |
| 2026-10-04 | Advisory published — GHSA-6524-w46v-6399 |
| Fixed in | pdfcpu v0.16.1 |
Fix
Reject bpc > 8 in the renderer entry point and validate lookup-palette length against the index range:
// Reject unsupported bit depths before the packed loop:
if im.bpc <= 0 || im.bpc > 8 {
return ErrUnsupportedBitDepth
}
// Validate palette size before indexed lookup:
if len(lookup) < n << bpc {
return errShortLookupTable
}
Fixed in pdfcpu v0.16.1. Advisory published 2026-10-04; CVE assignment pending.