
PoC for a Use-After-Free (UAF) vulnerability in the RTCEncodedFrameBase component of Firefox, exploitable via the WebRTC Encoded Transforms API.
CVE-2025-14321: The destructor of RTCEncodedFrameBase does not call DetachArrayBuffer() when releasing the native memory of the frame. As a result, JavaScript ArrayBuffers retain pointers to already freed memory (dangling pointers), allowing arbitrary read and write on the heap of the Firefox content process.
This PoC has been tested on Mozilla Firefox 145.0.1 and Windows 10.
Firefox exposes encoded video/audio frames as JavaScript ArrayBuffers via NewArrayBufferWithUserOwnedContents(). When the native (C++) frame is destroyed, the backing memory of the ArrayBuffer is freed, but the JavaScript ArrayBuffer is not detached. This violates the ownership invariant and creates a dangling pointer.
Vulnerable code (dom/media/webrtc/jsapi/RTCEncodedFrameBase.cpp):
// ❌ Destructor does not detach the ArrayBuffer
RTCEncodedFrameBase::~RTCEncodedFrameBase() = default;
Patched code:
// ✅ Detaches the ArrayBuffer before freeing native memory
RTCEncodedFrameBase::~RTCEncodedFrameBase() {
DetachData();
}
new Uint8Array(buf) reads data from memory reallocated to other heap objectsnew Uint8Array(buf).fill(0x41) corrupts adjacent heap objects (vtables, DOM structures, etc.)These primitives are sufficient to demonstrate the vulnerability. With additional heap shaping, they could be escalated to remote code execution (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
⚠ Restart Firefox after modifying preferences.
The PoC attempts to request camera access via getUserMedia(). This is not mandatory if ICE preferences are correctly set. Its purpose is to promote Firefox to RFC IP handling mode 1, which guarantees generation of ICE host candidates in scenarios where the preferences alone are not sufficient.
In practice:
getUserMedia() fails silently (NotFoundError) without showing a prompt. ICE still connects thanks to media.peerconnection.ice.loopback = true.Web Workers do not work from file://. Serve via HTTP:
python3 -m http.server 8080
Open in vulnerable Firefox: http://localhost:8080/poc-cve-2025-14321.html
The PoC attempts to request camera access. If a camera is available and the user accepts, Firefox promotes the session to RFC IP handling mode 1. If getUserMedia() fails (VM without camera, permission denied), the PoC continues with a canvas as frame source — ICE still connects if media.peerconnection.ice.loopback = true is active.
A <canvas> of 320×240 with animation at 30fps is created via captureStream(30). This generates predictably encoded video frames. Once ICE connects and the camera was used, the PoC replaces the camera track with the canvas track via replaceTrack() and releases the camera.
Two local RTCPeerConnection objects are created (pc1 ↔ pc2) with standard SDP negotiation and trickle ICE. Candidates are exchanged directly between peers.
An RTCRtpScriptTransform is assigned to the sender of pc1 before SDP negotiation. The worker intercepts each encoded frame from the send pipeline.
For each of the first 200 frames, the worker:
frame.data (ArrayBuffer pointing to native memory)leaks[])new Uint8Array(buf).fill(0x41)By not forwarding, the GC destroys the C++ wrapper and frees the native memory. But the JavaScript ArrayBuffer retains the pointer — dangling pointer created.
Starting from frame 201, every 3 frames the worker:
0x41, the allocator reused that memory → UAF confirmed0x41 over the buffer (write primitive on foreign heap)This read/write cycle on freed memory causes progressive heap corruption until Firefox crashes the tab (equivalent to SIGSEGV reported in the advisory).
[0.4s] ✓ Initial track: canvas (fallback)
[0.4s] ✓ ICE CONNECTION ESTABLISHED!
[0.5s] ▶▶▶ FIRST ENCODED FRAME RECEIVED! ◀◀◀
[0.5s] Leak #0: 1219B → 41414141414141414141414141414141
[2.7s] Leak #49: 1497B → 41414141414141414141414141414141
[9.8s] Leak #199: 1870B → 41414141414141414141414141414141
[9.9s] 200 ArrayBuffers retained → native memory already freed
[9.9s] → Dangling pointers active, waiting for reuse…
[ TAB CRASH — heap corruption]
Note: If a camera is available and permission is granted, the log will show
✓ getUserMedia granted — ICE mode 1 activeand later✓ Track replaced: camera → canvasafter ICE connection. On VMs without camera, the PoC uses canvas directly without prompt.
The tab crash confirms heap corruption: writing 0x41 over memory reallocated to other objects (vtables, DOM nodes) causes an invalid memory access in the content process.
✓ Buffer[0] detached correctly — fix active
ArrayBuffer detached — patched version ✓
The DetachData() in the destructor invalidates the ArrayBuffer before freeing native memory, eliminating the dangling pointer.
├── poc-cve-2025-14321.html # Functional PoC (readable source code)
└── README.md # This file
Mozilla fixed the vulnerability in Firefox 146 by adding DetachData() in all destructors and teardown paths of RTCEncodedFrameBase.
Commit: https://hg-edge.mozilla.org/mozilla-central/rev/1051067f6e83
⚠ FOR EDUCATIONAL AND RESEARCH PURPOSES ONLY
This PoC demonstrates the existence of the vulnerability up to the point of heap corruption (crash). It does not implement heap shaping, ASLR bypass, or hijack techniques necessary for code execution. Its purpose is exclusively verification and understanding of the CVE in controlled environments.