
Prueba de concepto que demuestra una vulnerabilidad de Use-After-Free en RTCEncodedFrameBase de Firefox a través de WebRTC Encoded Transforms, lo que permite corrupción de heap y posible RCE.
PoC para una vulnerabilidad Use-After-Free (UAF) en el componente RTCEncodedFrameBase de Firefox, explotable a través de la API WebRTC Encoded Transforms.
CVE-2025-14321: El destructor de RTCEncodedFrameBase no llama a DetachArrayBuffer() al liberar la memoria nativa del frame. Como resultado, los ArrayBuffers de JavaScript retienen punteros a memoria ya liberada (dangling pointers), permitiendo lectura y escritura arbitraria sobre el heap del proceso content de Firefox.
Este PoC ha sido probado en Mozilla Firefox 145.0.1 y Windows 10.
Firefox expone los frames de video/audio codificados como ArrayBuffers de JavaScript a través de NewArrayBufferWithUserOwnedContents(). Cuando el frame nativo (C++) se destruye, la memoria backing del ArrayBuffer se libera, pero el ArrayBuffer en JavaScript no se desvincula (detach). Esto viola la invariante de ownership y genera un dangling pointer.
Código vulnerable (dom/media/webrtc/jsapi/RTCEncodedFrameBase.cpp):
// ❌ Destructor no desvincula el ArrayBuffer
RTCEncodedFrameBase::~RTCEncodedFrameBase() = default;
Código parcheado:
// ✅ Desvincula el ArrayBuffer antes de liberar memoria nativa
RTCEncodedFrameBase::~RTCEncodedFrameBase() {
DetachData();
}
new Uint8Array(buf) lee datos de memoria reasignada a otros objetos del heapnew Uint8Array(buf).fill(0x41) corrompe objetos adyacentes en el heap (vtables, estructuras DOM, etc.)Estas primitivas son suficientes para demostrar la vulnerabilidad. Con heap shaping adicional, podrían escalar a ejecución remota de código (RCE).
media.peerconnection.scripttransform.enabled = true
media.peerconnection.ice.loopback = true
media.peerconnection.ice.no_host = false
media.peerconnection.ice.relay_only = false
media.peerconnection.ice.proxy_only = false
⚠ Reiniciar Firefox tras modificar las preferencias.
El PoC intenta solicitar acceso a la cámara vía getUserMedia(). Esto no es obligatorio si las preferencias de ICE están configuradas correctamente. Su función es promover Firefox a RFC IP handling mode 1, que garantiza la generación de candidatos ICE host en escenarios donde las prefs por sí solas no son suficientes.
En la práctica:
getUserMedia() falla silenciosamente (NotFoundError) sin mostrar prompt. ICE conecta igual gracias a media.peerconnection.ice.loopback = true.Los Web Workers no funcionan desde file://. Servir por HTTP:
python3 -m http.server 8080
Abrir en Firefox vulnerable: http://localhost:8080/poc-cve-2025-14321.html
El PoC intenta solicitar acceso a la cámara. Si hay cámara disponible y el usuario acepta, Firefox promueve la sesión a RFC IP handling mode 1. Si getUserMedia() falla (VM sin cámara, permiso denegado), el PoC continúa con un canvas como fuente de frames — ICE conecta igualmente si media.peerconnection.ice.loopback = true está activo.
Se crea un <canvas> de 320×240 con animación a 30fps vía captureStream(30). Esto genera frames de video codificados de forma predecible. Una vez que ICE conecta y se usó la cámara, el PoC reemplaza el track de cámara por el de canvas vía replaceTrack() y libera la cámara.
Se crean dos RTCPeerConnection locales (pc1 ↔ pc2) con negociación SDP estándar y trickle ICE. Los candidatos se intercambian directamente entre peers.
Se asigna un RTCRtpScriptTransform al sender de pc1 antes de la negociación SDP. El worker intercepta cada frame codificado del pipeline de envío.
Para cada uno de los primeros 200 frames, el worker:
frame.data (ArrayBuffer que apunta a memoria nativa)leaks[])new Uint8Array(buf).fill(0x41)Al no reenviar, el GC destruye el wrapper C++ y libera la memoria nativa. Pero el ArrayBuffer de JavaScript retiene el puntero — dangling pointer creado.
A partir del frame 201, cada 3 frames el worker:
0x41, el allocator reutilizó esa memoria → UAF confirmado0x41 sobre el buffer (write primitive sobre heap ajeno)Este ciclo de lectura/escritura sobre memoria liberada causa corrupción progresiva del heap hasta que Firefox crashea la pestaña (equivalente a SIGSEGV reportado en el advisory).
[0.4s] ✓ Track inicial: canvas (fallback)
[0.4s] ✓ ¡CONEXIÓN ICE ESTABLECIDA!
[0.5s] ▶▶▶ ¡PRIMER FRAME ENCODED RECIBIDO! ◀◀◀
[0.5s] Leak #0: 1219B → 41414141414141414141414141414141
[2.7s] Leak #49: 1497B → 41414141414141414141414141414141
[9.8s] Leak #199: 1870B → 41414141414141414141414141414141
[9.9s] 200 ArrayBuffers retenidos → memoria nativa YA liberada
[9.9s] → Dangling pointers activos, esperando reuso…
[ CRASH DE PESTAÑA — heap corruption]
Nota: Si hay cámara disponible y se concede el permiso, el log mostrará
✓ getUserMedia concedido — ICE modo 1 activoy luego✓ Track reemplazado: cámara → canvastras la conexión ICE. En VMs sin cámara, el PoC usa canvas directamente sin prompt.
El crash de la pestaña confirma corrupción de heap: la escritura de 0x41 sobre memoria reasignada a otros objetos (vtables, DOM nodes) causa un acceso a memoria inválido en el proceso content.
✓ Buffer[0] detached correctamente — fix activo
ArrayBuffer detached — versión parcheada ✓
El DetachData() en el destructor invalida el ArrayBuffer antes de liberar la memoria nativa, eliminando el dangling pointer.
├── poc-cve-2025-14321.html # PoC funcional (código fuente legible)
└── README.md # Este archivo
Mozilla corrigió la vulnerabilidad en Firefox 146 agregando DetachData() en todos los destructores y teardown paths de RTCEncodedFrameBase.
Commit: https://hg-edge.mozilla.org/mozilla-central/rev/1051067f6e83
⚠ SOLO PARA PROPÓSITOS EDUCATIVOS Y DE INVESTIGACIÓN
Este PoC demuestra la existencia de la vulnerabilidad hasta el punto de corrupción de heap (crash). No implementa heap shaping, ASLR bypass, ni técnicas de hijack necesarias para ejecución de código. Su propósito es exclusivamente la verificación y comprensión del CVE en entornos controlados.