Panics por índice fuera de rango en renderizadores de imagen
GHSA-6524-w46v-6399 · Asignación de CVE pendiente
Resumen
La falta de validación de entrada en los renderizadores de imagen en escala de grises e Indexed de pdfcpu permite a un PDF creado a medida crashear cualquier aplicación que extraiga o renderice imágenes. Imágenes con valores BitsPerComponent de 9..16 congelan el bucle de píxeles empaquetados (8/bpc == 0 por división entera), así que el contador de píxeles nunca avanza mientras el cursor de bytes se sale del buffer de imagen. Independientemente, imágenes de espacio de color Indexed con una paleta de lookup corta indexan fuera de rango. Ambos caminos levantan un index out of range panic de runtime sin recuperar, matando el proceso anfitrión — fault.Catch solo recupera valores internos fault.Panic, no runtime.Error.
Impacto
- Denegación de Servicio — panic de runtime sin recuperar termina el proceso.
- Vector de ataque: red — PDF creado a medida pequeño vía subida; sin autenticación.
- Ámbito:
api.ExtractImages,RenderImage,WriteImage— cualquier pipeline de procesamiento de imagen usando pdfcpu.
Causa Raíz
En pkg/pdfcpu/writeImage.go, renderDeviceGrayToPNG() (y sus hermanos Indexed) ejecuta un bucle de píxeles empaquetados que asume bpc divide 8:
// writeImage.go:393 (misma forma en 559, 606, 640, 668)
i := 0
for y := 0; y < im.h; y++ {
for x := 0; x < im.w; {
p := b[i] // ← panics una vez i >= len(b)
for j := 0; j < 8/im.bpc && x < im.w; j++ { ... x++ }
i++
}
}
Con bpc > 8, 8/im.bpc == 0: el bucle interno nunca se ejecuta, x nunca avanza, y i camina fuera del final de b. La validación de imagen solo rechaza bpc <= 0 más límites de tamaño — no hay límite superior en bpc, y el error declarado ErrUnsupported16BPC nunca se lanza en la versión vulnerable.
El segundo sumidero indexa una paleta de lookup Indexed corta sin comprobación de longitud (la rama CalRGB no realiza validación alguna):
// 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})
Prueba de Concepto
Dos crasheos independientes, cada uno confirmado en tres niveles (sumidero directo, punto de entrada público, PDF extremo a extremo):
# Sumidero 1: bpc 9..16
PDFImage{bpc: 9, w: 2, h: 1, Content: 8 bytes}
→ panic: index out of range [8] with length 8
# Sumidero 2: Indexed/CalRGB con paleta de 3 bytes, primer byte de imagen 0xFF
[/Indexed [/CalRGB] 0 <000000>], image byte 0xFF
→ l = 765 en un slice de 3 bytes
→ panic: index out of range [765] with length 3
# Extremo a extremo: PDF creado parsea limpiamente (api.ReadContext OK),
# luego api.ExtractImages panics — fault.Catch no lo recupera.
El hallazgo se descubrió con revisión asistida por LLM (Strix) y se replicó independientemente con pruebas unitarias y extremo a extremo locales contra pdfcpu v0.15.0.
Cronología de Divulgación
| Fecha | Evento |
|---|---|
| 2026-09-21 | Vulnerabilidad descubierta y PoC-confirmado (reproducción local de 3 niveles) |
| 2026-10-03 | Informe enviado vía GitHub Private Vulnerability Reporting |
| 2026-10-04 | Aviso publicado — GHSA-6524-w46v-6399 |
| Corregido en | pdfcpu v0.16.1 |
Corrección
Rechazar bpc > 8 en el punto de entrada del renderizador y validar la longitud de la paleta de lookup contra el rango de índice:
// Rechazar profundidades de bit no soportadas antes del bucle empaquetado:
if im.bpc <= 0 || im.bpc > 8 {
return ErrUnsupportedBitDepth
}
// Validar tamaño de paleta antes de lookup indexado:
if len(lookup) < n << bpc {
return errShortLookupTable
}
Corregido en pdfcpu v0.16.1. Aviso publicado 2026-10-04; asignación de CVE pendiente.