针对 Firefox 中 RTCEncodedFrameBase 组件的释放后使用 (UAF) 漏洞的概念验证 (PoC),该漏洞可通过 WebRTC 编码转换 API 利用。
CVE-2025-14321: RTCEncodedFrameBase 的析构函数在释放帧的本机内存时未调用 DetachArrayBuffer()。因此,JavaScript 的 ArrayBuffer 仍保留指向已释放内存的指针(悬挂指针),允许对 Firefox 内容进程堆进行任意读写。
此 PoC 已在 Mozilla Firefox 145.0.1 和 Windows 10 上测试通过。
Firefox 通过 NewArrayBufferWithUserOwnedContents() 将编码后的视频/音频帧暴露为 JavaScript 的 ArrayBuffer。当本机 (C++) 帧被销毁时,ArrayBuffer 的后备内存被释放,但 JavaScript 中的 ArrayBuffer 未被分离(detach)。这违反了所有权不变性,导致悬挂指针。
漏洞代码 (dom/media/webrtc/jsapi/RTCEncodedFrameBase.cpp):
// ❌ 析构函数未分离 ArrayBuffer
RTCEncodedFrameBase::~RTCEncodedFrameBase() = default;
修补后的代码:
// ✅ 在释放本机内存前分离 ArrayBuffer
RTCEncodedFrameBase::~RTCEncodedFrameBase() {
DetachData();
}
new Uint8Array(buf) 读取已重新分配给堆上其他对象的内存数据new Uint8Array(buf).fill(0x41) 破坏堆中相邻的对象(虚函数表、DOM 结构等)这些基本操作足以证明漏洞存在。结合进一步的堆布局 (heap shaping),可能升级为远程代码执行 (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
⚠ 修改偏好设置后重启 Firefox。
PoC 尝试通过 getUserMedia() 请求摄像头访问。如果 ICE 偏好设置正确配置,这不是必需的。其作用是促使 Firefox 进入 RFC IP 处理模式 1,从而在偏好设置本身不足以生成 ICE 主机候选者的情况下保证生成。
实际上:
getUserMedia() 静默失败(NotFoundError),不弹出提示。由于 media.peerconnection.ice.loopback = true,ICE 仍能连接。Web Worker 不支持 file:// 协议。需通过 HTTP 提供:
python3 -m http.server 8080
在受影响的 Firefox 中打开:http://localhost:8080/poc-cve-2025-14321.html
PoC 尝试请求摄像头访问。如果有摄像头且用户允许,Firefox 将会话提升至 RFC IP 处理模式 1。如果 getUserMedia() 失败(无摄像头的虚拟机、权限被拒绝),PoC 继续使用画布作为帧源——若 media.peerconnection.ice.loopback = true 处于活动状态,ICE 仍能连接。
创建一个 320×240 的 <canvas>,通过 captureStream(30) 以 30fps 生成动画。这会产生可预测的编码视频帧。一旦 ICE 连接且已使用摄像头,PoC 通过 replaceTrack() 用画布轨道替换摄像头轨道,并释放摄像头。
创建两个本地 RTCPeerConnection(pc1 ↔ pc2),使用标准 SDP 协商和 trickle ICE。候选者直接在两个对等端之间交换。
在 SDP 协商 之前,为 pc1 的发送方分配一个 RTCRtpScriptTransform。该 worker 拦截发送管道中的每个编码帧。
对于前 200 个帧中的每一个,worker 执行以下操作:
frame.data(指向本机内存的 ArrayBuffer)leaks[]) 中new Uint8Array(buf).fill(0x41)由于不转发,垃圾回收器销毁 C++ 包装器并释放本机内存。但 JavaScript 的 ArrayBuffer 仍保留指针——创建了悬挂指针。
从第 201 帧开始,每 3 帧 worker 执行以下操作:
0x41 不同,则分配器已重用该内存 → UAF 确认0x41 重新写入缓冲区(对他人堆的写入原语)这种对已释放内存的读写循环会逐步破坏堆,直到 Firefox 标签页崩溃(等同于公告中报告的 SIGSEGV)。
[0.4s] ✓ 初始轨道:画布(回退)
[0.4s] ✓ ICE 连接已建立!
[0.5s] ▶▶▶ 收到第一个编码帧!◀◀◀
[0.5s] 泄漏 #0: 1219B → 41414141414141414141414141414141
[2.7s] 泄漏 #49: 1497B → 41414141414141414141414141414141
[9.8s] 泄漏 #199: 1870B → 41414141414141414141414141414141
[9.9s] 保留了 200 个 ArrayBuffer → 本机内存已释放
[9.9s] → 悬挂指针已激活,等待重用…
[ 标签页崩溃 — 堆损坏 ]
注意: 如果有摄像头且授予权限,日志将显示
✓ getUserMedia 已授予 — ICE 模式 1 已激活,然后✓ 轨道已替换:摄像头 → 画布在 ICE 连接之后。在无摄像头的虚拟机中,PoC 直接使用画布,不弹出提示。
标签页崩溃确认堆损坏:在已重新分配给其他对象(虚函数表、DOM 节点)的内存上写入 0x41 会导致内容进程中的无效内存访问。
✓ Buffer[0] 已正确分离 — 修复已生效
ArrayBuffer 已分离 — 已修补版本 ✓
析构函数中的 DetachData() 在释放本机内存之前使 ArrayBuffer 失效,从而消除了悬挂指针。
├── poc-cve-2025-14321.html # 功能性 PoC(可读源代码)
└── README.md # 本文件
Mozilla 在 Firefox 146 中通过在 RTCEncodedFrameBase 的所有析构函数和拆卸路径中添加 DetachData() 修复了此漏洞。
提交:https://hg-edge.mozilla.org/mozilla-central/rev/1051067f6e83
⚠ 仅用于教育和研究目的
此 PoC 仅证明漏洞存在直至堆损坏(崩溃)。它不实现堆布局、ASLR 绕过或执行代码所需的劫持技术。其目的仅在于在受控环境中验证和理解 CVE。