
CVE-2026-74943, uso dopo la liberazione in Firefox RasterImage (sec-high)
Questa directory contiene uno scaffold di riproduzione onesto per un
use-after-free cross-thread in mozilla::image::RasterImage::OnSurfaceDiscarded. È pensato
per essere eseguito contro una build vulnerabile di Firefox scaricata localmente utilizzando solo
dati immagine sintetici. Non tocca mai alcun sistema live o di terze parti.
Repository PoC corrispondente: https://github.com/defineid/Revenant
Writeup: writeup-rasterimage-uaf.html in questo portfolio.
Questi sono stati confermati disassemblando la build vulnerabile (libxul.so, Firefox
140.9.0esr) durante la ricerca originale; lo screenshot committato
assets/writeup-uaf-disasm.png è quella analisi. I nomi delle funzioni provengono dal
file di simboli ufficiale Mozilla Breakpad per quella esatta build; il
libxul.so distribuito è stripped. Non sono stati ri-disassemblati nell'ambiente
che ha assemblato questo pacchetto PoC (vedi Stato di verifica).
| Fatto | Evidenza | Fonte |
|---|---|---|
La funzione vulnerabile è RasterImage::OnSurfaceDiscarded(SurfaceKey const&) | FUNC 3eba200 16c 0 mozilla::image::RasterImage::OnSurfaceDiscarded(...) | Mozilla symbol server .sym |
Lettura anticipata di un byte a this+0xC0 (writeup: mIsBeingDestroyed) | 3eba20a: cmp BYTE PTR [rdi+0xc0],0x0 con rdi = this | objdump -d libxul.so |
AddRef sul refcount di RasterImage a this+0x40 | 3eba25a: lock inc QWORD PTR [rbx+0x40] (emesso due volte), rbx = this | objdump -d libxul.so |
Viene allocato un runnable di 0x20 byte che cattura this | 3eba264: mov edi,0x20 → call moz_xmalloc@plt; 3eba280: mov QWORD PTR [rax+0x10],rbx | objdump -d libxul.so |
Il runnable viene dispatchato, poi this viene rilasciato | 3eba298: call QWORD PTR [rcx+0x28] (dispatch virtuale); 3eba29b: lock dec QWORD PTR [rbx+0x40] | objdump -d libxul.so |
Il refcount che raggiunge 0 esegue ~RasterImage poi free | je 3eba345 → call 3ebbe90 <~RasterImage> → call free@plt | objdump + .sym |
Il runnable dispatchato cattura la lambda OnSurfaceDiscarded::$_0 | FUNC 5279d90 ... RunnableFunction<...OnSurfaceDiscarded(...)::$_0>::Run() | Mozilla symbol server .sym |
| Campo | Valore |
|---|---|
| CVE | CVE-2026-74943 |
| Componente | Core / Graphics: ImageLib |
| Classe | Use-After-Free (CWE-416) |
| Gravità | sec-high (memory safety raggiungibile dal web) |
| Corretto in | Firefox 154 · ESR 140.14 |
| Bug | bugzilla.mozilla.org/show_bug.cgi?id=2057308 (ad accesso limitato per sicurezza) |
SurfaceCacheImpl::Remove notifica all'immagine proprietaria che una superficie decodificata è stata
scartata chiamando OnSurfaceDiscarded attraverso un puntatore ImageKey raw, non-owning,
su un thread decoder / ImageIO, mentre detiene il mutex della surface
cache. Nella build vulnerabile quel gestore:
this+0xC0 (cmp BYTE PTR [rdi+0xc0],0x0).
Il writeup lo identifica come mIsBeingDestroyed, una mitigazione precedente.this incrementando il refcount di RasterImage a
this+0x40 (lock inc QWORD PTR [rbx+0x40]). Si tratta di un semplice
add-ref, non di un atomico add-ref-if-nonzero.moz_xmalloc(0x20) un RunnableFunction che cattura this
(mov QWORD PTR [rax+0x10],rbx), lo dispatcha a un event target globale
(call QWORD PTR [rcx+0x28]), poi rilascia il riferimento locale
(lock dec QWORD PTR [rbx+0x40]).Poiché la lettura del flag e l'incremento del contatore sono due operazioni
separate su un refcount lock-free, possono scavalcare il momento in cui il thread
principale rilascia l'ultimo riferimento. Il thread decoder esegue quindi AddRef da zero,
resuscitando un oggetto il cui distruttore è già stato eseguito. Quando il
distruttore termina e fa free della RasterImage, il runnable in coda viene eseguito successivamente
sul thread principale e legge memoria liberata.
La correzione smette di fare riferimento direttamente all'immagine: OnSurfaceDiscarded cattura un
riferimento forte al ProgressTracker contato separatamente e recupera
l'immagine sul thread principale tramite il back-reference debole del tracker, promuovendola
con un add-ref-if-live atomico; il flag mIsBeingDestroyed viene rimosso.
Una normale pagina web nel processo di contenuto genera pressione sulla surface-cache (molte superfici decodificate scartabili) mentre rilascia il frame che possiede le richieste di immagine. Non è richiesto alcun clic, permesso, estensione o modifica delle preferenze. La race è tra la notifica di scarto del thread del decoder e il rilascio finale della stessa immagine da parte del thread principale.
https://ftp.mozilla.org/pub/firefox/releases/140.9.0esr/.
Una build ASAN (Nightly asan o una build locale con --enable-address-sanitizer)
rende l'use-after-free osservabile come report pulito invece che come
crash silenzioso occasionale.<canvas> a runtime; non carica nulla dalla rete.Apri trigger.html nella build vulnerabile, idealmente da un percorso file:// o da un
server statico locale sotto il tuo controllo:
/path/to/firefox --new-instance --profile ./poc-profile ./trigger.html
Per impostazione predefinita il driver esegue un ciclo stretto di microtask per massimizzare la sovrapposizione tra decodifica e rilascio. Aggiungendo ?tick=N all'URL lo si passa a un ciclo setTimeout(N) che cede il controllo, il quale lavora altrettanto intensamente ma lascia respirare l'event loop, ed è ciò che consente a un driver automatizzato di avanzare e catturare screenshot della pagina mentre è in esecuzione. Il file trigger-running.png committato è stato catturato esattamente in questo modo, pilotando la build locale 140.11.0esr tramite Marionette con ?tick=4: mostra l'harness in azione (cicli e teardown in aumento). Quella build è patchata, quindi non si verifica alcun crash; l'immagine dimostra solo che il driver funziona. In modalità headless le immagini fuori schermo non vengono disegnate, quindi il contatore "images decoded" resta a 0; una build vulnerabile con interfaccia grafica le decodifica ed è lì che la race viene esercitata.
Lascia che la pagina venga eseguita. Decodifica ripetutamente molte immagini grandi e distinte (per riempire la surface cache e forzare l'eviction) mentre smonta il sottoalbero DOM che le possiede (per guidare il rilascio finale dell'immagine proprietaria). Questo massimizza la sovrapposizione tra "surface discarded" sul thread del decoder e "last release" sul thread principale. Le race sono probabilistiche; lascialo in esecuzione.
heap-use-after-free il cui frame READ si trova all'interno di
RasterImage::OnSurfaceDiscardedInternal / del runnable inviato, con il
frame freed nel teardown di RasterImage::Release. Senza ASAN può manifestarsi
come un crash intermittente del processo di contenuto.ProgressTracker e scarta la
notifica quando la distruzione è già stata confermata.