
CVE-2026-74943, utilisation après libération dans Firefox RasterImage (sec-high)
Ce répertoire contient un échafaudage de reproduction honnête pour un
use-after-free inter-thread dans mozilla::image::RasterImage::OnSurfaceDiscarded. Il est destiné
à être exécuté contre une version vulnérable de Firefox téléchargée localement en utilisant uniquement
des données d'image synthétiques. Il ne touche jamais aucun système actif ou tiers.
Dépôt PoC correspondant : https://github.com/defineid/Revenant
Writeup : writeup-rasterimage-uaf.html dans ce portfolio.
Ceux-ci ont été confirmés en désassemblant la version vulnérable (libxul.so, Firefox
140.9.0esr) lors de la recherche originale ; la capture d'écran commitée
assets/writeup-uaf-disasm.png correspond à cette analyse. Les noms de fonctions proviennent du
fichier de symboles Breakpad officiel de Mozilla pour cette version exacte ; le
libxul.so fourni est strippé. Ils n'ont pas été re-désassemblés dans l'environnement
qui a assemblé ce paquet PoC (voir Statut de vérification).
| Fait | Preuve | Source |
|---|---|---|
La fonction vulnérable est RasterImage::OnSurfaceDiscarded(SurfaceKey const&) | FUNC 3eba200 16c 0 mozilla::image::RasterImage::OnSurfaceDiscarded(...) | Serveur de symboles Mozilla .sym |
Lecture d'un octet précoce à this+0xC0 (writeup : mIsBeingDestroyed) | 3eba20a: cmp BYTE PTR [rdi+0xc0],0x0 avec rdi = this | objdump -d libxul.so |
AddRef sur le compteur de références RasterImage à this+0x40 | 3eba25a: lock inc QWORD PTR [rbx+0x40] (émis deux fois), rbx = this | objdump -d libxul.so |
Un runnable de 0x20 octets est alloué et capture this | 3eba264: mov edi,0x20 → call moz_xmalloc@plt ; 3eba280: mov QWORD PTR [rax+0x10],rbx | objdump -d libxul.so |
Le runnable est dispatché, puis this est Released | 3eba298: call QWORD PTR [rcx+0x28] (dispatch virtuel) ; 3eba29b: lock dec QWORD PTR [rbx+0x40] | objdump -d libxul.so |
Le compteur de références atteignant 0 exécute ~RasterImage puis free | je 3eba345 → call 3ebbe90 <~RasterImage> → call free@plt | objdump + .sym |
Le runnable dispatché capture la lambda OnSurfaceDiscarded::$_0 | FUNC 5279d90 ... RunnableFunction<...OnSurfaceDiscarded(...)::$_0>::Run() | Serveur de symboles Mozilla .sym |
| Champ | Valeur |
|---|---|
| CVE | CVE-2026-74943 |
| Composant | Core / Graphics: ImageLib |
| Classe | Use-After-Free (CWE-416) |
| Gravité | sec-high (sécurité mémoire atteignable depuis le web) |
| Corrigé dans | Firefox 154 · ESR 140.14 |
| Bug | bugzilla.mozilla.org/show_bug.cgi?id=2057308 (accès restreint pour raisons de sécurité) |
SurfaceCacheImpl::Remove notifie l'image propriétaire qu'une surface décodée a été
discardée en appelant OnSurfaceDiscarded via un pointeur ImageKey brut, non-owning,
sur un thread decoder / ImageIO, tout en détenant le mutex du cache de surfaces. Dans la version vulnérable, ce handler :
this+0xC0 tôt (cmp BYTE PTR [rdi+0xc0],0x0).
Le writeup l'identifie comme mIsBeingDestroyed, une mitigation antérieure.this en incrémentant le compteur de références RasterImage à
this+0x40 (lock inc QWORD PTR [rbx+0x40]). Il s'agit d'un simple
add-ref, et non d'un add-ref-if-nonzero atomique.moz_xmalloc(0x20) un RunnableFunction qui capture this
(mov QWORD PTR [rax+0x10],rbx), le dispatche vers une cible d'événement globale
(call QWORD PTR [rcx+0x28]), puis libère la référence locale
(lock dec QWORD PTR [rbx+0x40]).Parce que la lecture du flag et l'incrémentation du compteur sont deux opérations
distinctes sur un compteur de références sans verrou, elles peuvent chevaucher le moment où le thread principal
abandonne la dernière référence. Le thread decoder fait alors un AddRef depuis zéro,
ressuscitant un objet dont le destructeur s'est déjà engagé. Lorsque le
destructeur se termine et free le RasterImage, le runnable mis en file s'exécute plus tard
sur le thread principal et lit une zone mémoire libérée.
Le correctif cesse de référencer directement l'image : OnSurfaceDiscarded capture une référence forte vers le ProgressTracker compté séparément par référence et récupère l'image sur le thread principal via la référence faible inverse du tracker, en la promouvant avec un add-ref-if-live atomique ; l'indicateur mIsBeingDestroyed est supprimé.
Une page web ordinaire dans le processus de contenu exerce une pression sur le cache de surfaces (de nombreuses surfaces décodées jetables) tout en libérant le frame qui possède les requêtes d'images. Aucun clic, permission, extension ou modification de préférence n'est requis. La course se produit entre la notification de rejet du thread de décodage et la libération finale de la même image par le thread principal.
https://ftp.mozilla.org/pub/firefox/releases/140.9.0esr/.
Une version ASAN (Nightly asan ou une version locale compilée avec --enable-address-sanitizer) rend le use-after-free observable sous forme de rapport propre plutôt que de plantage silencieux occasionnel.<canvas> à l'exécution ; il ne charge rien depuis le réseau.Ouvrez trigger.html dans la version vulnérable, idéalement depuis un chemin file:// ou un serveur statique local que vous contrôlez :
/path/to/firefox --new-instance --profile ./poc-profile ./trigger.html
Par défaut, le pilote exécute une boucle de microtâches serrée pour maximiser le chevauchement décodage/libération. L'ajout de ?tick=N à l'URL le fait basculer vers une boucle setTimeout(N) cédante qui tourne tout aussi intensément mais laisse respirer la boucle d'événements, ce qui permet à un pilote automatisé d'avancer et de prendre des captures d'écran de la page pendant son exécution. Le fichier trigger-running.png commité a été capturé exactement de cette manière, en pilotant la build locale 140.11.0esr via Marionette avec ?tick=4 : il montre le harnais en action (cycles et démontages en augmentation). Cette build est patchée, donc aucun crash ne se produit ; l'image prouve seulement que le pilote s'exécute. En mode headless, les images hors écran ne sont pas peintes, donc le compteur « images decoded » y reste à 0 ; une build vulnérable en mode headed les décode et c'est là que la course est exercée.
Laissez la page s'exécuter. Elle décode de manière répétée de nombreuses grandes images distinctes (pour remplir le cache de surfaces et forcer l'éviction) tout en démontant le sous-arbre DOM qui les possède (pour déclencher la libération finale de l'image propriétaire). Cela maximise le chevauchement entre « surface discarded » sur le thread du décodeur et « last release » sur le thread principal. Les courses sont probabilistes ; laissez-la tourner.