
CVE-2026-74943, Use-after-free in Firefox RasterImage (sec-high)
Dieses Verzeichnis enthält ein ehrliches Reproduktionsgerüst für einen
thread-übergreifenden Use-after-free in mozilla::image::RasterImage::OnSurfaceDiscarded. Es ist dafür gedacht,
gegen einen lokal heruntergeladenen verwundbaren Firefox-Build und ausschließlich mit
synthetischen Bilddaten ausgeführt zu werden. Es berührt niemals ein Live- oder Drittsystem.
Passendes PoC-Repository: https://github.com/defineid/Revenant
Writeup: writeup-rasterimage-uaf.html in diesem Portfolio.
Diese wurden während der ursprünglichen Untersuchung durch Disassemblieren des verwundbaren Builds (libxul.so, Firefox
140.9.0esr) bestätigt; der eingecheckte Screenshot
assets/writeup-uaf-disasm.png zeigt diese Analyse. Die Funktionsnamen stammen aus der
offiziellen Mozilla-Breakpad-Symboldatei für genau diesen Build; das ausgelieferte
libxul.so ist gestrippt. Sie wurden in der Umgebung, in der dieses PoC-Paket
zusammengestellt wurde, nicht erneut disassembliert (siehe Verifikationsstatus).
| Fakt | Beleg | Quelle |
|---|---|---|
Die verwundbare Funktion ist RasterImage::OnSurfaceDiscarded(SurfaceKey const&) | FUNC 3eba200 16c 0 mozilla::image::RasterImage::OnSurfaceDiscarded(...) | Mozilla-Symbolserver .sym |
Frühes Byte-Lesen bei this+0xC0 (Writeup: mIsBeingDestroyed) | 3eba20a: cmp BYTE PTR [rdi+0xc0],0x0 mit rdi = this | objdump -d libxul.so |
AddRef auf den RasterImage-Refcount bei this+0x40 | 3eba25a: lock inc QWORD PTR [rbx+0x40] (zweimal ausgeführt), rbx = this | objdump -d libxul.so |
Ein 0x20-Byte-Runnable wird allokiert und erfasst this | 3eba264: mov edi,0x20 → call moz_xmalloc@plt; 3eba280: mov QWORD PTR [rax+0x10],rbx | objdump -d libxul.so |
Der Runnable wird ausgeliefert, dann wird this released | 3eba298: call QWORD PTR [rcx+0x28] (virtueller Dispatch); 3eba29b: lock dec QWORD PTR [rbx+0x40] | objdump -d libxul.so |
Wenn der Refcount 0 erreicht, läuft ~RasterImage und dann free | je 3eba345 → call 3ebbe90 <~RasterImage> → call free@plt | objdump + .sym |
Der ausgelieferte Runnable erfasst das Lambda OnSurfaceDiscarded::$_0 | FUNC 5279d90 ... RunnableFunction<...OnSurfaceDiscarded(...)::$_0>::Run() | Mozilla-Symbolserver .sym |
| Feld | Wert |
|---|---|
| CVE | CVE-2026-74943 |
| Komponente | Core / Graphics: ImageLib |
| Klasse | Use-After-Free (CWE-416) |
| Schweregrad | sec-high (web-erreichbare Speichersicherheit) |
| Behoben in | Firefox 154 · ESR 140.14 |
| Bug | bugzilla.mozilla.org/show_bug.cgi?id=2057308 (sicherheitsbeschränkt) |
SurfaceCacheImpl::Remove benachrichtigt das besitzende Image, dass eine dekodierte Oberfläche
verworfen wurde, indem es OnSurfaceDiscarded über einen rohen, nicht besitzenden
ImageKey-Zeiger aufruft, auf einem Decoder-/ImageIO-Thread, während der Surface-
Cache-Mutex gehalten wird. In dem verwundbaren Build tut dieser Handler Folgendes:
this+0xC0 (cmp BYTE PTR [rdi+0xc0],0x0).
Das Writeup identifiziert dies als mIsBeingDestroyed, eine frühere Mitigation.this, indem er den RasterImage-Refcount bei
this+0x40 inkrementiert (lock inc QWORD PTR [rbx+0x40]). Dies ist ein einfaches
Add-Ref, kein atomares Add-Ref-if-Nonzero.moz_xmalloc(0x20) einen RunnableFunction, der this erfasst
(mov QWORD PTR [rax+0x10],rbx), liefert ihn an ein globales Event-Target aus
(call QWORD PTR [rcx+0x28]) und gibt dann die lokale Referenz frei
(lock dec QWORD PTR [rbx+0x40]).Da das Lesen des Flags und das Inkrementieren des Zählers zwei getrennte
Operationen auf einem lock-freien Refcount sind, können sie den Moment überspannen, in dem der Haupt-
Thread die letzte Referenz fallen lässt. Der Decoder-Thread macht dann AddRef von null aus
und erweckt ein Objekt wieder zum Leben, dessen Destruktor bereits festgeschrieben ist. Wenn der
Destruktor fertig ist und die RasterImage freet, läuft der eingereihte Runnable später
auf dem Haupt-Thread und liest freigegebenen Speicher.
Der Fix referenziert das Bild nicht mehr direkt: OnSurfaceDiscarded erfasst
eine starke Referenz auf den separat referenzgezählten ProgressTracker und
stellt das Bild im Hauptthread über den schwachen Rückverweis des Trackers wieder
her, wobei es mit einem atomaren Add-Ref-If-Live befördert wird; das Flag
mIsBeingDestroyed wird entfernt.
Eine gewöhnliche Webseite im Content-Prozess erzeugt Druck auf den Surface-Cache (viele verwerfbare dekodierte Surfaces) und gibt dabei den Frame frei, der die Bildanfragen besitzt. Es ist kein Klick, keine Berechtigung, keine Erweiterung und keine Einstellungsänderung erforderlich. Das Rennen besteht zwischen der Verwerfungsbenachrichtigung des Decoder-Threads und der finalen Freigabe desselben Bildes durch den Hauptthread.
https://ftp.mozilla.org/pub/firefox/releases/140.9.0esr/ herunter.
Ein ASAN-Build (Nightly asan oder ein lokaler Build mit
--enable-address-sanitizer) macht den Use-after-Free als sauberen Bericht
beobachtbar statt als gelegentlichen stillen Absturz.<canvas>; er lädt nichts aus dem
Netzwerk.Öffnen Sie trigger.html im verwundbaren Build, idealerweise über einen
file://-Pfad oder einen lokalen statischen Server, den Sie kontrollieren:
/path/to/firefox --new-instance --profile ./poc-profile ./trigger.html
Standardmäßig führt der Treiber eine enge Microtask-Schleife für maximale Decode/Release-Überlappung aus. Durch Anhängen von ?tick=N an die URL wird auf eine nachgebende setTimeout(N)-Schleife umgeschaltet, die genauso stark arbeitet, aber die Event-Loop atmen lässt, was es einem automatisierten Treiber ermöglicht, die Seite während der Ausführung zu steuern und Screenshots zu machen. Das eingecheckte trigger-running.png wurde genau auf diese Weise aufgenommen, indem der lokale 140.11.0esr-Build über Marionette mit ?tick=4 gesteuert wurde: Es zeigt die Harness live (Zyklen und Teardowns steigen). Dieser Build ist gepatcht, sodass kein Absturz auftritt; das Bild beweist nur, dass der Treiber läuft. Im Headless-Modus werden die Off-Screen-Bilder nicht gezeichnet, sodass der Zähler „images decoded" dort bei 0 bleibt; ein headed verwundbarer Build dekodiert sie, und dort wird die Race ausgelöst.
Lass die Seite laufen. Sie dekodiert wiederholt viele verschiedene große Bilder (um den Surface-Cache zu füllen und Eviction zu erzwingen), während sie den DOM-Teilbaum abbaut, dem sie gehören (um die finale Freigabe des besitzenden Bildes voranzutreiben). Dies maximiert die Überlappung von „surface discarded" im Decoder-Thread mit „last release" im Main-Thread. Races sind probabilistisch; lass es laufen.