
Análisis en profundidad y prueba de concepto para CVE-2026-27280, una escritura fuera de límites en dng_render_task::ProcessArea del Adobe DNG SDK, accesible a través de la API de decodificación de imágenes de Android, con causa raíz, evaluación de explotabilidad y análisis de brechas de RCE.
dng_render_task::ProcessArea (Adobe DNG SDK / Android)Alcanzable desde un archivo DNG ordinario a través de la API pública de decodificación de imágenes de Android, y
reproducido en un Galaxy S25 usando el propio /system/lib64/libdng_sdk.so del teléfono.
DNG_REQUIRE(srcArea.Size() == dstArea.Size()) + indexación de srcArea)#12 AImageDecoder_decodeImage /system/lib64/libjnigraphics.so
#11 android::ImageDecoder::decode /system/lib64/libhwui.so
#09 SkCodec::getPixels /system/lib64/libhwui.so
#08 SkRawCodec::onGetPixels /system/lib64/libhwui.so
#07 dng_render::Render() /system/lib64/libdng_sdk.so
#06 SkDngHost::PerformAreaTask /system/lib64/libhwui.so
#00 dng_render_task::ProcessArea /system/lib64/libdng_sdk.so <-- vulnerable
ProcessArea asigna un buffer temporal del tamaño de un tile sobre srcArea pero lo indexa con coordenadas de dstArea.
Dado que srcArea = dstArea + fSrcOffset y fSrcOffset es el DefaultCropOrigin del archivo — distinto de cero en todo DNG real de cámara — el puntero de destino se
desplaza fuera de la asignación por fSrcOffset * rowStep * pixelSize, y la lectura de la máscara escribe
allí. Es un desplazamiento, no un desbordamiento de enteros, por lo que no necesita una asignación enorme:
por eso es alcanzable en un teléfono.
01-vulnerability/ las versiones vulnerable y corregida de dng_render.cpp, y el diff 2471->2502
02-poc/ generadores, cargadores, sondas (fuentes) + binarios arm64 precompilados
03-samples/ los archivos DNG
04-evidence/ registros ASAN, tombstones del dispositivo, salida de las sondas
05-analysis/ causa raíz, generador, mediciones de vecinos, análisis de brecha para RCE
El trabajo que va más allá del veredicto anterior: refuta dos de sus afirmaciones mediante
medición, cierra la brecha de "se usa un puntero corrupto", y determina exactamente por qué la
cadena se detiene. Lee primero 06-rce-lab/README.md; los archivos evidence/*.md son los
informes por experimento, y src/ + apk/ son los harnesses reproducibles.
adb push 02-poc/bin-arm64/poc_aimg 03-samples/trigger_natural_croporigin.dng /data/local/tmp/
adb shell "cd /data/local/tmp && chmod 755 poc_aimg && ./poc_aimg trigger_natural_croporigin.dng"
# -> Segmentation fault, tombstone frames inside libdng_sdk.so
Recompilar los cargadores arm64 requiere los flags propios de dng_sdk de Android (de Android.bp), o los
layouts de las estructuras no coincidirán con la librería incluida:
-DUNIX_ENV=1 -DqDNGBigEndian=0 -DqDNGThreadSafe=1 -DqDNGUseLibJPEG=1 \
-DqDNGUseXMP=0 -DqDNGValidateTarget=1 -DqDNGUsingSanitizer=1 -DqLinux=1 -DqAndroid=1
Esto corresponde a CVE-2026-27280 (DNG SDK 1.7.1 2471 y anteriores, CWE-787, CVSS 3.1 7.8 ALTA), publicado el 2026-03-10 — coincidente por rango de versiones y clase, no confirmado por Adobe al nombrar la función.
Probado, en la ruta real de decodificación de Android, en un dispositivo parcheado hasta 2026-01-01:
escritura fuera de límites con destino elegido por el atacante (granularidad de 4 bytes, en ambas
direcciones, independiente de ASLR); alcanzable desde un DNG simple a través de AImageDecoder,
demostrado matando Samsung Gallery en su cargador de miniaturas; corrupción silenciosa y determinista
de vecinos; un puntero corrupto que llega a free() (N1 cerrado); un
heap-spray expresado por el formato DNG (ImageStats × SubIFD); la escritura apuntada a
esos objetos rociados, 6/10 vs 0/10.
No es RCE, en la ruta de Android, y por una razón raíz: el código puede escribir un único
valor, 0x3F800000. Todo se deriva de eso — la escritura es contigua (sin stride
sobre cabeceras de Scudo), no hay cero que escribir (sin corrupción de longitud/refcount), y la
sobrescritura parcial que preserva etiquetas tiene exactamente un desplazamiento de objetivo fijo. Un valor es
un intento contra una reserva aleatorizada de 256 MB, y ninguna cantidad de volumen de spray o
concurrencia añade intentos. En el SDK de escritorio el conjunto escribible es de 256 valores → 256 intentos
a través de una banda de ~67 MB, por eso el 7.8 es correcto allí.
Retractado del veredicto anterior (05-analysis/05-exploitability-verdict.md),
falsificado por medición: "un decodificador real no ofrece una primitiva de spray" y "la
superposición no es dirigible desde dentro de una sola decodificación". El bloque no es PAC, BTI ni los
checksums de Scudo — es el único valor escribible, una propiedad del código que la corrección 2502 de Adobe
elimina de raíz al indexar con srcArea.
No se construyó ningún exploit funcional, y ninguno se publica aquí.
| archivo | qué hace |
|---|
trigger_natural_croporigin.dng | geometría natural (DefaultCropOrigin = 8,8); provoca un fallo en AImageDecoder |
benign_reference.dng | mismo archivo con origen 0 — decodifica correctamente, se usa para sondear el heap |
silent_corruption.dng | clase de tamaño pequeña: escribe fuera de límites sin fallar (5/5) |
far_write_crash.dng | desplazamiento grande: falla de forma visible |
| archivo | contenido |
|---|
01-root-cause.md | el bug, la corrección, la forma de la escritura |
02-dng-generator.md | cómo se construye el archivo disparador, las dos perillas de ajuste, trampas |
03-neighbours.md | cifras de controlabilidad, qué hay junto al buffer, mitigaciones |
04-what-is-missing-for-rce.md | qué está probado (P1-P8), qué no (N1-N3), los pasos restantes |
05-exploitability-verdict.md | el veredicto anterior — superado por 06-rce-lab, conservado para el registro |
| archivo | contenido |
|---|
EXP1-stride-vs-scudo.md | stride vs cabeceras de Scudo: posible en abstracto, imposible en la ruta de Android (planes==1) |
EXP2-N1-corrupted-pointer-used.md | un puntero corrupto llega a free() — N1 cerrado |
EXP2-real-process.md | la escritura en el proceso real de Samsung Gallery, nada plantado |
EXP-imagestats-spray.md | el heap-spray que el veredicto anterior llamó imposible, expresado por el formato DNG |
EXP-aimed-spray.md | apuntar la escritura a objetos rociados: 6/10 vs 0/10 |
EXP-small-class-in-app.md | punteros de código al alcance dentro de un proceso real de una app |
EXP-band-commit.md / EXP-concurrency.md | por qué falla el compromiso del objetivo: un valor escribible = un intento |
EXP-write-read-mismatch.md | un segundo defecto + la auditoría completa del código fuente de ProcessArea |
EXP-value-1.0f.md | la ruta de 1.0f como dato, cerrada estructuralmente |