
CVE-2026-74943, uso después de liberación en Firefox RasterImage (sec-high)
Este directorio contiene un andamiaje de reproducción honesto para un
use-after-free entre hilos en mozilla::image::RasterImage::OnSurfaceDiscarded. Está pensado
para ejecutarse contra una compilación vulnerable de Firefox descargada localmente usando únicamente
datos de imagen sintéticos. Nunca toca ningún sistema activo ni de terceros.
Repositorio PoC correspondiente: https://github.com/defineid/Revenant
Writeup: writeup-rasterimage-uaf.html en este portafolio.
Estos fueron confirmados desensamblando la compilación vulnerable (libxul.so, Firefox
140.9.0esr) durante la investigación original; la captura de pantalla incluida en el repositorio
assets/writeup-uaf-disasm.png es ese análisis. Los nombres de funciones provienen del
archivo de símbolos oficial de Mozilla Breakpad para esa compilación exacta; el
libxul.so distribuido está despojado de símbolos. No fueron re-desensamblados en el entorno
que ensambló este paquete PoC (véase Estado de verificación).
| Hecho | Evidencia | Fuente |
|---|---|---|
La función vulnerable es RasterImage::OnSurfaceDiscarded(SurfaceKey const&) | FUNC 3eba200 16c 0 mozilla::image::RasterImage::OnSurfaceDiscarded(...) | Servidor de símbolos de Mozilla .sym |
Lectura temprana de un byte en this+0xC0 (writeup: mIsBeingDestroyed) | 3eba20a: cmp BYTE PTR [rdi+0xc0],0x0 con rdi = this | objdump -d libxul.so |
AddRef sobre el contador de referencias de RasterImage en this+0x40 | 3eba25a: lock inc QWORD PTR [rbx+0x40] (emitido dos veces), rbx = this | objdump -d libxul.so |
Se asigna un runnable de 0x20 bytes que captura this | 3eba264: mov edi,0x20 → call moz_xmalloc@plt; 3eba280: mov QWORD PTR [rax+0x10],rbx | objdump -d libxul.so |
El runnable se despacha, luego se hace Release de this | 3eba298: call QWORD PTR [rcx+0x28] (despacho virtual); 3eba29b: lock dec QWORD PTR [rbx+0x40] | objdump -d libxul.so |
El contador de referencias llegando a 0 ejecuta ~RasterImage y luego free | je 3eba345 → call 3ebbe90 <~RasterImage> → call free@plt | objdump + .sym |
El runnable despachado captura la lambda OnSurfaceDiscarded::$_0 | FUNC 5279d90 ... RunnableFunction<...OnSurfaceDiscarded(...)::$_0>::Run() | Servidor de símbolos de Mozilla .sym |
| Campo | Valor |
|---|---|
| CVE | CVE-2026-74943 |
| Componente | Core / Graphics: ImageLib |
| Clase | Use-After-Free (CWE-416) |
| Severidad | sec-high (seguridad de memoria alcanzable desde la web) |
| Corregido en | Firefox 154 · ESR 140.14 |
| Bug | bugzilla.mozilla.org/show_bug.cgi?id=2057308 (restringido por seguridad) |
SurfaceCacheImpl::Remove notifica a la imagen propietaria que una superficie decodificada fue
descartada llamando a OnSurfaceDiscarded a través de un puntero ImageKey crudo, no propietario,
en un hilo de decodificador / ImageIO, mientras mantiene el mutex de la caché de superficies.
En la compilación vulnerable ese manejador:
this+0xC0 de forma temprana (cmp BYTE PTR [rdi+0xc0],0x0).
El writeup identifica esto como mIsBeingDestroyed, una mitigación anterior.this incrementando el contador de referencias de RasterImage en
this+0x40 (lock inc QWORD PTR [rbx+0x40]). Esto es un
add-ref simple, no un add-ref-atómico-si-distinto-de-cero.moz_xmalloc(0x20)-asigna un RunnableFunction que captura this
(mov QWORD PTR [rax+0x10],rbx), lo despacha a un objetivo de evento global
(call QWORD PTR [rcx+0x28]), y luego libera la referencia local
(lock dec QWORD PTR [rbx+0x40]).Debido a que leer el flag e incrementar el contador son dos operaciones separadas
sobre un contador de referencias sin bloqueo, pueden abarcar el momento en que el hilo principal
suelta la última referencia. El hilo del decodificador entonces hace AddRef desde cero,
resucitando un objeto cuyo destructor ya se ha comprometido. Cuando el
destructor termina y hace free del RasterImage, el runnable encolado se ejecuta después
en el hilo principal y lee almacenamiento liberado.
La corrección deja de referenciar la imagen directamente: OnSurfaceDiscarded captura una referencia fuerte al ProgressTracker con conteo de referencias independiente y recupera la imagen en el hilo principal a través de la referencia débil inversa del tracker, promoviéndola con un add-ref-if-live atómico; la bandera mIsBeingDestroyed se elimina.
Una página web ordinaria en el proceso de contenido provoca presión sobre la caché de superficies (muchas superficies decodificadas descartables) mientras libera el frame que posee las solicitudes de imagen. No se requiere ningún clic, permiso, extensión ni cambio de preferencia. La carrera se produce entre la notificación de descarte del hilo del decodificador y la liberación final del mismo recurso por parte del hilo principal.
https://ftp.mozilla.org/pub/firefox/releases/140.9.0esr/.
Una compilación ASAN (Nightly asan o una compilación local con --enable-address-sanitizer)
hace que el use-after-free sea observable como un informe limpio en lugar de un
fallo silencioso ocasional.<canvas> en tiempo de ejecución; no carga nada desde la red.Abre trigger.html en la compilación vulnerable, preferiblemente desde una ruta file:// o un
servidor estático local que controles:
/path/to/firefox --new-instance --profile ./poc-profile ./trigger.html
Por defecto, el driver ejecuta un bucle ajustado de microtareas para maximizar el solapamiento entre decodificación y liberación. Añadir ?tick=N a la URL lo cambia a un bucle de cesión mediante setTimeout(N) que trabaja igual de intensamente pero deja respirar al bucle de eventos, lo que permite que un driver automatizado avance y capture la página mientras se ejecuta. El archivo trigger-running.png incluido en el repositorio se capturó exactamente así, conduciendo la compilación local 140.11.0esr a través de Marionette con ?tick=4: muestra el arnés en vivo (ciclos y desmontajes en aumento). Esa compilación está parcheada, por lo que no se produce ningún fallo; la imagen solo demuestra que el driver se ejecuta. En modo headless las imágenes fuera de pantalla no se pintan, por lo que el contador de "imágenes decodificadas" permanece en 0 allí; una compilación vulnerable con interfaz gráfica las decodifica y es donde se ejercita la condición de carrera.
Deja que la página se ejecute. Decodifica repetidamente muchas imágenes grandes distintas (para llenar la caché de superficies y forzar la expulsión) mientras desmonta el subárbol del DOM que las posee (para provocar la liberación final de la imagen propietaria). Esto maximiza el solapamiento entre "superficie descartada" en el hilo del decodificador y "última liberación" en el hilo principal. Las condiciones de carrera son probabilísticas; déjala ejecutándose.