
Analisi approfondita e proof-of-concept per CVE-2026-27280, una scrittura fuori dai limiti in dng_render_task::ProcessArea dell'Adobe DNG SDK, raggiungibile tramite l'API di decodifica delle immagini di Android, con causa principale, valutazione dello sfruttamento e analisi del divario per RCE.
dng_render_task::ProcessArea (Adobe DNG SDK / Android)Raggiungibile da un normale file DNG tramite l'API pubblica di decodifica immagini di Android, e
riprodotto su un Galaxy S25 usando la /system/lib64/libdng_sdk.so del telefono stesso.
DNG_REQUIRE(srcArea.Size() == dstArea.Size()) + indicizzazione 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 <-- vulnerabile
ProcessArea mappa un buffer temporaneo delle dimensioni di una tile su srcArea ma lo indicizza con le
coordinate di dstArea. Poiché srcArea = dstArea + fSrcOffset e fSrcOffset è il
DefaultCropOrigin del file — diverso da zero in ogni DNG reale da fotocamera — il puntatore di
destinazione viene spostato fuori dall'allocazione di fSrcOffset * rowStep * pixelSize, e la rilettura
della maschera vi scrive. È uno spostamento, non un overflow di interi, quindi non richiede un'allocazione
enorme: è per questo che è raggiungibile su un telefono.
01-vulnerability/ le versioni vulnerabile e corretta di dng_render.cpp, e il diff 2471->2502
02-poc/ generatori, loader, probe (sorgenti) + binari arm64 precompilati
03-samples/ i file DNG
04-evidence/ log ASAN, tombstone del dispositivo, output dei probe
05-analysis/ causa principale, generatore, misurazioni dei vicini, analisi del gap per RCE
Il lavoro che va oltre il verdetto precedente: smentisce due delle sue affermazioni tramite
misurazione, chiude il gap "un puntatore corrotto viene usato", e individua esattamente perché la
catena si ferma. Leggi prima 06-rce-lab/README.md; i file evidence/*.md sono i
resoconti per esperimento, e src/ + apk/ sono gli harness riproducibili.
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, frame tombstone dentro libdng_sdk.so
La ricompilazione dei loader arm64 richiede i flag dng_sdk di Android (da Android.bp), altrimenti
i layout delle struct non corrisponderanno alla libreria installata:
-DUNIX_ENV=1 -DqDNGBigEndian=0 -DqDNGThreadSafe=1 -DqDNGUseLibJPEG=1 \
-DqDNGUseXMP=0 -DqDNGValidateTarget=1 -DqDNGUsingSanitizer=1 -DqLinux=1 -DqAndroid=1
Corrisponde a CVE-2026-27280 (DNG SDK 1.7.1 2471 e precedenti, CWE-787, CVSS 3.1 7.8 ALTA), pubblicato il 2026-03-10 — abbinato per intervallo di versioni e classe, non confermato da Adobe che nomina la funzione.
Dimostrato, sul percorso di decodifica Android reale, su un dispositivo aggiornato al 2026-01-01:
scrittura fuori dai limiti con destinazione scelta dall'attaccante (granularità di 4 byte, in entrambe
le direzioni, indipendente da ASLR); raggiungibile da un semplice DNG tramite AImageDecoder,
dimostrato uccidendo la Galleria Samsung nel suo loader di miniature; corruzione silenziosa e
deterministica dei vicini; un puntatore corrotto che raggiunge free() (N1 chiuso); un
heap-spray espresso dal formato DNG (ImageStats × SubIFD); la scrittura mirata su quegli
oggetti spruzzati, 6/10 vs 0/10.
Non RCE, sul percorso Android, e per una sola ragione di fondo: il codice può scrivere un singolo
valore, 0x3F800000. Tutto deriva da questo — la scrittura è contigua (nessuno stride
sugli header Scudo), non c'è uno zero da scrivere (nessuna corruzione di lunghezza/refcount), e la
sovrascrittura parziale che preserva i tag ha esattamente un offset di destinazione fisso. Un valore è
un colpo solo contro una prenotazione di 256 MB randomizzata, e nessuna quantità di volume di spray o
concorrenza aggiunge colpi. Sul SDK desktop l'insieme scrivibile è di 256 valori → 256 colpi
su una banda di ~67 MB, motivo per cui il 7.8 è corretto lì.
Ritirato dal verdetto precedente (05-analysis/05-exploitability-verdict.md),
falsificato dalla misurazione: "un decoder reale non offre una primitiva di spray" e "la
sovrapposizione non è orientabile dall'interno di una singola decodifica". Il blocco non è PAC, BTI o i
checksum di Scudo — è il singolo valore scrivibile, una proprietà del codice che il fix 2502 di Adobe
rimuove alla radice indicizzando con srcArea.
Nessun exploit funzionante è stato costruito, e nessuno è pubblicato qui.
| file | cosa fa |
|---|
trigger_natural_croporigin.dng | geometria naturale (DefaultCropOrigin = 8,8); fa crashare AImageDecoder |
benign_reference.dng | stesso file con origine 0 — decodifica correttamente, usato per sondare l'heap |
silent_corruption.dng | classe di dimensione piccola: scrive fuori dai limiti senza crash (5/5) |
far_write_crash.dng | spostamento grande: fault visibile |
| file | contenuto |
|---|
01-root-cause.md | il bug, il fix, la forma della scrittura |
02-dng-generator.md | come viene costruito il file trigger, le due manopole di regolazione, le insidie |
03-neighbours.md | numeri di controllabilità, cosa si trova accanto al buffer, mitigazioni |
04-what-is-missing-for-rce.md | cosa è dimostrato (P1-P8), cosa non lo è (N1-N3), i passi rimanenti |
05-exploitability-verdict.md | il verdetto precedente — sostituito da 06-rce-lab, conservato per la cronaca |
| file | contenuto |
|---|
EXP1-stride-vs-scudo.md | stride vs header Scudo: possibile in astratto, impossibile sul percorso Android (planes==1) |
EXP2-N1-corrupted-pointer-used.md | un puntatore corrotto raggiunge free() — N1 chiuso |
EXP2-real-process.md | la scrittura nel processo reale della Galleria Samsung, nulla di piantato |
EXP-imagestats-spray.md | l'heap-spray che il verdetto precedente definiva impossibile, espresso dal formato DNG |
EXP-aimed-spray.md | mirare la scrittura sugli oggetti spruzzati: 6/10 vs 0/10 |
EXP-small-class-in-app.md | puntatori a codice raggiungibili dentro un processo reale di un'app |
EXP-band-commit.md / EXP-concurrency.md | perché committare il target fallisce: un valore scrivibile = un colpo solo |
EXP-write-read-mismatch.md | un secondo difetto + l'audit completo del sorgente di ProcessArea |
EXP-value-1.0f.md | la via del 1.0f-come-dato, chiusa strutturalmente |