
Proof-of-concept per CVE-2026-84118, una use-after-free nella GC di SpiderMonkey che porta a lettura/scrittura fuori dai limiti e potenziale esecuzione di codice. Include un trigger SEGV deterministico e ricerca sull'escalation a lettura arbitraria e bypass di ASLR.
Questo è uno di quei casi in cui Mozilla ha etichettato una Critica come Alta. Quindi qualcuno potrebbe voler andare a chiedere a Firefox di aggiornare il pagamento dei cacciatori di bug originali, se è questo il caso.
IN BREVE: Puoi eseguire una primitiva R di OOB su un puntatore controllato e poi iniziare a salire e scendere da quel puntatore. Quindi se punti il puntatore controllato a un array, congratulazioni, puoi fare cose divertenti.
La POC ha solo la parte SEGV, quindi il resto è un'avventura divertente nel puntare a un array e saltare nella WASM che preferisci.
Prova di concetto SEGV deterministica su build release per Firefox/SpiderMonkey 154.0.1. Corretto nella 155 (rifacimento AtomMarking). Gravità Mozilla: ALTA.
/path/to/release/js poc.js
Riprodotta su 154.0.1: 3/3 su una shell ottimizzata semplice (SIGSEGV, exit 139), 5/5 su una shell release+gczeal, stesso errore a ogni esecuzione:
SEGV all'indirizzo 0x000000000014 (READ)
JS::shadow::Zone::gcState() <- zone == nullptr, offset +0x14
La 154 confonde le bitmap di mark degli atom per-zone con il tracciamento dei riferimenti. Un
Symbol tenuto solo debolmente — qui, catturato come campo Value debole all'interno
di uno stub IC CacheIR di Baseline — e referenziato da una zona non raccolta non viene
mai ri-marcato quando la zona degli atom viene raccolta in una GC con ambito di zona. Il
risultato è un bordo stantio verso una cellula spazzata/rilocata.
Il colpo letale della build release è un secondo bug statico:
MightBeForwarded<JS::Symbol> è hardcoded false, quindi
IsForwarded restituisce false senza controllare (gc/Marking-inl.h:92-97) e
ogni percorso di correzione dei puntatori che ne dipende salta silenziosamente il bordo. Quando la
zona degli atom viene compattata, i bordi forti vengono aggiornati correttamente, ma questo
bordo CacheIR debole rimane permanentemente pendente al vecchio indirizzo del Symbol vittima.
(Le build di debug lo rilevano come Assertion failure: !t->isForwarded(), gc/Marking-inl.h:94.)
La PoC non lascia semplicemente un puntatore stantio verso heap riutilizzabile — fa sì che la pagina sottostante venga munmappata, così la dereferenziazione successiva va in errore.
Zone::availableChunks/fullChunks), quindi i chunk della zona degli atom contengono solo
arena della zona degli atom. Il destino di un chunk può essere controllato puramente da
cicli di vita di atom/symbol.c.eval(b) fa sì che la IC della chiamata di livello superiore catturi il
Symbol in un campo stub debole; b = undefined rilascia l'ultima root
forte; lo spray viene rilasciato; due GC con ambito di zona
(gc("zone"), schedulezone(c) + schedulezone("") + gc("zone") —
la stringa vuota pianifica la zona degli atom) spazzano lo spray morto,
compattano la vittima nell'arena dei magneti, saltano la correzione del bordo debole,
e liberano l'arena della vittima. Il suo chunk ora è completamente vuoto.gcparam("minEmptyChunkCount", 0) rende ogni chunk vuoto
espirabile; durante sleep(0.5) il BackgroundDecommitTask esegue
-> -> — un vero
munmap del chunk vittima. (Il decommit a livello di arena è solo
— zeri leggibili — il rilascio dell'intero chunk è l'unico
percorso munmap, motivo per cui l'isolamento dei chunk nel passaggio 1 è importante.)Questa non è solo una DoS. Escalation dimostrate (PoC e log nelle
directory di ricerca GC-NDAY/ e CHAIN/):
segv9): dopo il munmap, un
flip del semispazio minorgc() fa sì che la nursery rivendichi il VA liberato; uno
spray grezzo di double densi scrive una qword dell'attaccante sul campo zone
dell'header dell'arena stantia. La spazzatura dereferenzia poi
attacker_ptr + 0x14 — deterministico 3/3 a 0x424242424256.js::Zone* divulgato tramite un canale laterale
speculativo separato è stato piantato attraverso questo refill; il collector ha consumato i
metadati dell'arena contraffatti senza andare in errore (una piantagione di controllo
perturbata a 1 bit va in crash), dimostrando la composizione sconfitta-ASLR -> piantagione-UAF.expireEmptyChunkPoolFreeChunkPoolUnmapPagesmadvise(MADV_DONTNEED)gc() finale spazza i dati JIT:
sweepJitDataOnMainThread -> ICEntry::traceWeak ->
TraceWeakCacheIRStub legge il puntatore Symbol stantio, poi
SweepingTracer::onEdge (gc/Marking.cpp:3195) esegue l'aritmetica dei
chunk sull'indirizzo stantio per caricare il campo zone dell'header dell'arena.
La pagina è sparita/azzerata, quindi zone == nullptr, e
zone->isGCSweeping() legge nullptr + 0x14 (shadow::Zone::gcState)
-> SIGSEGV. Al momento dell'errore, rcx contiene il puntatore Symbol stantio e
rdx la base del chunk munmappato.