
Prova de conceito demonstrando uma vulnerabilidade de Use-After-Free no RTCEncodedFrameBase do Firefox por meio de WebRTC Encoded Transforms, possibilitando corrupção de heap e potencial RCE.
PoC para uma vulnerabilidade Use-After-Free (UAF) no componente RTCEncodedFrameBase do Firefox, explorável através da API WebRTC Encoded Transforms.
CVE-2025-14321: O destrutor de RTCEncodedFrameBase não chama DetachArrayBuffer() ao liberar a memória nativa do frame. Como resultado, os ArrayBuffers de JavaScript retêm ponteiros para memória já liberada (dangling pointers), permitindo leitura e escrita arbitrária sobre o heap do processo content do Firefox.
Este PoC foi testado no Mozilla Firefox 145.0.1 e Windows 10.
O Firefox expõe os frames de vídeo/áudio codificados como ArrayBuffers de JavaScript através de NewArrayBufferWithUserOwnedContents(). Quando o frame nativo (C++) é destruído, a memória backing do ArrayBuffer é liberada, mas o ArrayBuffer em JavaScript não é desvinculado (detach). Isso viola a invariante de ownership e gera um dangling pointer.
Código vulnerável (dom/media/webrtc/jsapi/RTCEncodedFrameBase.cpp):
// ❌ Destructor no desvincula el ArrayBuffer
RTCEncodedFrameBase::~RTCEncodedFrameBase() = default;
Código corrigido:
// ✅ Desvincula el ArrayBuffer antes de liberar memoria nativa
RTCEncodedFrameBase::~RTCEncodedFrameBase() {
DetachData();
}
new Uint8Array(buf) lê dados de memória realocada para outros objetos do heapnew Uint8Array(buf).fill(0x41) corrompe objetos adjacentes no heap (vtables, estruturas DOM, etc.)Essas primitivas são suficientes para demonstrar a vulnerabilidade. Com heap shaping adicional, poderiam escalar para execução 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 o Firefox após modificar as preferências.
O PoC tenta solicitar acesso à câmera via getUserMedia(). Isso não é obrigatório se as preferências de ICE estiverem configuradas corretamente. Sua função é promover o Firefox ao RFC IP handling mode 1, que garante a geração de candidatos ICE host em cenários onde as prefs por si só não são suficientes.
Na prática:
getUserMedia() falha silenciosamente (NotFoundError) sem mostrar prompt. ICE conecta mesmo assim graças a media.peerconnection.ice.loopback = true.Os Web Workers não funcionam a partir de file://. Servir por HTTP:
python3 -m http.server 8080
Abrir no Firefox vulnerável: http://localhost:8080/poc-cve-2025-14321.html
O PoC tenta solicitar acesso à câmera. Se houver câmera disponível e o usuário aceitar, Firefox promove a sessão para RFC IP handling mode 1. Se getUserMedia() falhar (VM sem câmera, permissão negada), o PoC continua com um canvas como fonte de frames — ICE conecta igualmente se media.peerconnection.ice.loopback = true estiver ativo.
Cria-se um <canvas> de 320×240 com animação a 30fps via captureStream(30). Isso gera frames de vídeo codificados de forma previsível. Uma vez que ICE conecta e a câmera foi usada, o PoC substitui o track da câmera pelo do canvas via replaceTrack() e libera a câmera.
Criam-se duas RTCPeerConnection locais (pc1 ↔ pc2) com negociação SDP padrão e trickle ICE. Os candidatos são trocados diretamente entre peers.
Atribui-se um RTCRtpScriptTransform ao sender de pc1 antes da negociação SDP. O worker intercepta cada frame codificado do pipeline de envio.
Para cada um dos primeiros 200 frames, o worker:
frame.data (ArrayBuffer que aponta para memória nativa)leaks[])new Uint8Array(buf).fill(0x41)Ao não reenviar, o GC destrói o wrapper C++ e libera a memória nativa. Mas o ArrayBuffer de JavaScript retém o ponteiro — dangling pointer criado.
A partir do frame 201, a cada 3 frames o worker:
0x41, o allocator reutilizou essa memória → UAF confirmado0x41 sobre o buffer (write primitive sobre heap alheio)Este ciclo de leitura/escrita sobre memória liberada causa corrupção progressiva do heap até que Firefox trava a aba (equivalente a SIGSEGV reportado no 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: Se houver câmera disponível e a permissão for concedida, o log mostrará
✓ getUserMedia concedido — ICE modo 1 activoe depois✓ Track reemplazado: cámara → canvasapós a conexão ICE. Em VMs sem câmera, o PoC usa canvas diretamente sem prompt.
O travamento da aba confirma corrupção de heap: a escrita de 0x41 sobre memória realocada para outros objetos (vtables, nós DOM) causa um acesso inválido à memória no processo content.
✓ Buffer[0] detached correctamente — fix activo
ArrayBuffer detached — versión parcheada ✓
O DetachData() no destrutor invalida o ArrayBuffer antes de liberar a memória nativa, eliminando o dangling pointer.
├── poc-cve-2025-14321.html # PoC funcional (código fuente legible)
└── README.md # Este archivo
A Mozilla corrigiu a vulnerabilidade no Firefox 146 adicionando DetachData() em todos os destrutores e teardown paths de RTCEncodedFrameBase.
⚠ APENAS PARA FINS EDUCATIVOS E DE PESQUISA
Este PoC demonstra a existência da vulnerabilidade até o ponto de corrupção de heap (crash). Não implementa heap shaping, bypass de ASLR, nem técnicas de hijack necessárias para execução de código. Seu propósito é exclusivamente a verificação e compreensão do CVE em ambientes controlados.