CVE-2026-8016
Overview
Background
- `RTCPeerConnectionHandler`
- the Blink-renderer object that bridges a JavaScript
RTCPeerConnectionto the underlying WebRTC implementation, holding aframe_pointer to its owning document frame. - `CloseAndUnregister`
- the teardown method on
RTCPeerConnectionHandlerthat detaches the handler when the peer connection is closed/unregistered, previously clearingclient_but notframe_. - `PeerConnectionTracker`
- a Blink object that collects and forwards per-connection stats reports, reaching into the handler’s frame via
pc_handler_->frame()during asynchronous tasks. - `raw_ptr`
- Chromium’s checked raw-pointer wrapper used for
frame_; the commit notes it would ideally be aWeakPersistentbut layering prevents it here.
Root Cause Analysis
After a peer connection is closed, RTCPeerConnectionHandler::CloseAndUnregister cleared client_ and set is_unregistered_ = true but left frame_ pointing at a frame that could subsequently be torn down, leaving a dangling raw pointer. Asynchronous stats-processing in PeerConnectionTracker still dereferenced pc_handler_->frame() (the pre-patch guard only checked !pc_handler_), so a stats task that ran after the frame was gone would use the stale frame_.
The patch sets frame_ = nullptr during unregistration so the pointer no longer outlives the frame, and adds !pc_handler_->frame() to the early-return condition so the tracker bails out instead of dereferencing a null/stale frame. Together these enforce the invariant that frame_ is only dereferenced while the handler is still registered and attached to a live frame.
frame_ back-pointer, leaving a dangling pointer that later async stats work could use; the fix nulls frame_ on unregister and makes the consumer re-check frame() before dereferencing it.Attack Path
- Establish a peer connection
Script creates an
RTCPeerConnection, instantiating anRTCPeerConnectionHandlerbound to the document’s frame viaframe_. - Trigger unregistration
The connection is closed, invoking
CloseAndUnregister, which (pre-patch) leftframe_set while the frame itself could be navigated away or destroyed. - Race an async stats task
A queued stats report in
PeerConnectionTrackerruns asynchronously and dereferencespc_handler_->frame(), which the old!pc_handler_check did not guard. (inferred: precise timing/navigation needed to free the frame) - Use-after-free / null deref
The stale
frame_is converted viaTo<WebLocalFrameImpl>(*pc_handler_->frame()), yielding a use-after-free or null dereference in the renderer. (inferred beyond the UAF the advisory names)
Impact Assessment
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifthird_party/blink/renderer/modules/peerconnection/peer_connection_tracker.cc |
modified |
Files Changed
third_party/blink/renderer/modules/peerconnection/peer_connection_tracker.ccthird_party/blink/renderer/modules/peerconnection/rtc_peer_connection_handler.cc
Audit Directions
- Back-pointers not cleared on teardownAudit teardown/unregister methods that null some owned pointers (e.g.,
client_) but leave sibling back-pointers likeframe_live; each surviving pointer is a dangling-UAF candidate. - Async consumers of handler stateReview callbacks/tasks in
PeerConnectionTrackerand similar trackers that dereferencepc_handler_->frame()or other handler fields, and confirm they null-check every accessor after the object may have been unregistered. - `raw_ptr` where `WeakPersistent` is wantedFlag
raw_ptrfields pointing at GC-managed/frame-lifetime objects (as the commit flags for a followup); these lack automatic clearing and need manual nulling on every teardown path.
Patch
From ac1c38ac1687520c54e6f3634c7a97c5a2ba7714 Mon Sep 17 00:00:00 2001 From: Andrew Paseltiner <[email protected]> Date: Tue, 31 Mar 2026 05:34:05 -0700 Subject: [PATCH] [RTC] Clear frame field in RTCPeerConnectionHandler::CloseAndUnregister To ensure that it isn't accessed after the connection is unregistered. Also add a null check for the frame in PeerConnectionTracker when processing stats reports to avoid potential null dereferences during asynchronous tasks. Ideally we would replace raw_ptr with WeakPersistent, but it is complicated by the layering requirements in this directory. We will investigate making that change in a followup. Fixed: 497695401 Change-Id: Ic7ab9b7d9d3f0a35692205c7e064143d75b12e58 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7711324 Commit-Queue: Andrew Paseltiner <[email protected]> Reviewed-by: Daniel Cheng <[email protected]> Cr-Commit-Position: refs/heads/main@{#1607798} --- diff --git a/third_party/blink/renderer/modules/peerconnection/peer_connection_tracker.cc b/third_party/blink/renderer/modules/peerconnection/peer_connection_tracker.cc index 85a26b88..c9191f7 100644 --- a/third_party/blink/renderer/modules/peerconnection/peer_connection_tracker.cc +++ b/third_party/blink/renderer/modules/peerconnection/peer_connection_tracker.cc @@ -369,7 +369,7 @@ base::ListValue result_list; - if (!pc_handler_) { + if (!pc_handler_ || !pc_handler_->frame()) { return result_list; } auto* local_frame = To<WebLocalFrameImpl>(*pc_handler_->frame()).GetFrame(); diff --git a/third_party/blink/renderer/modules/peerconnection/rtc_peer_connection_handler.cc b/third_party/blink/renderer/modules/peerconnection/rtc_peer_connection_handler.cc index f79506bb..388c2572 100644 --- a/third_party/blink/renderer/modules/peerconnection/rtc_peer_connection_handler.cc +++ b/third_party/blink/renderer/modules/peerconnection/rtc_peer_connection_handler.cc @@ -830,6 +830,7 @@ // Clear the pointer to client_ so that it does not interfere with // garbage collection. client_ = nullptr; + frame_ = nullptr; is_unregistered_ = true; // Reset the `PeerConnectionDependencyFactory` so we don't prevent it from
Original Bug Report
Potential UAF in RTCPeerConnectionHandler via dangling WebLocalFrame pointer
Project Fortify, an experimental security project, has identified the following potential security issue.
Overview: A Use-After-Free (UAF) vulnerability exists in RTCPeerConnectionHandler because it retains a raw_ptr to an Oilpan-managed WebLocalFrameImpl after the frame is removed. This pointer can be dereferenced during asynchronous WebRTC statistics collection (e.g., via chrome://webrtc-internals), leading to a virtual method call on freed memory.
Affected files:
third_party/blink/renderer/modules/peerconnection/rtc_peer_connection_handler.hthird_party/blink/renderer/modules/peerconnection/peer_connection_tracker.ccthird_party/blink/renderer/modules/peerconnection/rtc_peer_connection_handler.cc
Estimated timestamp from git blame: 2024-11-13
Description
A Use-After-Free (UAF) vulnerability exists in RTCPeerConnectionHandler because it maintains a raw_ptr<WebLocalFrame> frame_ that is not cleared when the associated WebLocalFrame is destroyed and garbage-collected.
RTCPeerConnectionHandler is owned by an RTCPeerConnection object. While the handler’s lifecycle is typically tied to the frame it resides in, an attacker can keep the RTCPeerConnection (and thus its handler) alive by maintaining a JavaScript reference to it in a parent window after the associated iframe has been removed.
When the iframe is removed, RTCPeerConnection::ContextDestroyed() is called, which triggers RTCPeerConnectionHandler::CloseAndUnregister(). However, CloseAndUnregister() fails to clear the frame_ member. Since WebLocalFrame (specifically its implementation WebLocalFrameImpl) is an Oilpan-managed object, it will eventually be garbage-collected after the iframe is removed. Because raw_ptr does not provide BackupRefPtr (MiraclePtr) protection for Oilpan-managed memory, the pointer remains dangling.
The vulnerability is triggered when asynchronous WebRTC statistics collection occurs. This is commonly performed by chrome://webrtc-internals, which polls for statistics every second. The polling mechanism (PeerConnectionTracker::GetStandardStats()) creates an InternalStandardStatsObserver that holds a WeakPtr to the RTCPeerConnectionHandler.
If the statistics are delivered after the frame has been garbage-collected but while the handler is still alive, the observer’s WeakPtr check passes. It then calls pc_handler_->frame() and dereferences the dangling pointer in InternalStandardStatsObserver::ReportToList (at peer_connection_tracker.cc:375).
Impact
This is a Use-After-Free on the Oilpan heap. The call to To<WebLocalFrameImpl>(*pc_handler_->frame()) invokes IsA<WebLocalFrameImpl>, which triggers a virtual function call (IsWebLocalFrame()) on the freed object. This can potentially be exploited for Remote Code Execution (RCE) in the renderer process by using Oilpan heap spraying to control the memory of the freed WebLocalFrameImpl object and hijacking the virtual method call.
Potential steps an attacker would follow to trigger the vulnerability
Note: These are theoretical steps, as our tooling agent cannot yet run code.
- Prerequisite: The attacker convinces the user to open
chrome://webrtc-internalsin one tab (or leverages an existing debugging session). This triggers periodic 1-second polling for WebRTC statistics across all renderer processes viaPeerConnectionTrackerHost::GetStandardStats(). - In a separate tab, the user loads a malicious HTML page controlled by the attacker.
- The malicious page creates a child
<iframe>and injects JavaScript execution into it. - Inside the iframe, the attacker’s script creates a new
RTCPeerConnectionobject. - The
RTCPeerConnectionHandlerstores araw_ptr<WebLocalFrame> frame_pointing to the iframe’sWebLocalFrameImpl. - To prevent the
RTCPeerConnectionfrom being destroyed when the iframe is removed, the attacker’s script escapes a reference to it into the parent window’s global scope (e.g.,window.parent.pc = pc;). - The 1-second timer from
chrome://webrtc-internalsfires, callingPeerConnectionTracker::GetStandardStats(). This iterates over active peer connections and creates an asynchronousInternalStandardStatsObserver, passing it to the native WebRTC engine to gather stats on a background thread. - The Race Condition: While the WebRTC thread is asynchronously gathering stats, the attacker’s script in the parent window removes the iframe from the DOM (
iframe.remove()). - Removing the iframe calls
RTCPeerConnectionHandler::CloseAndUnregister(). This unregisters the handler but fails to clear theframe_member. - The attacker forces a Blink/V8 garbage collection using
window.gc(). Since the iframe’s execution context was destroyed, theWebLocalFrameImplobject loses its strong references and is reclaimed by the Oilpan garbage collector. Theframe_member is now a dangling pointer. - The WebRTC signaling thread finishes stats collection and calls
InternalStandardStatsObserver::OnStatsDelivered(), which posts a task to the main thread to executeReportToList(). - On the main thread,
ReportToList()checks if theWeakPtrto theRTCPeerConnectionHandleris valid. Because the parent window kept the JS object alive, the check passes. ReportToList()executesauto* local_frame = To<WebLocalFrameImpl>(*pc_handler_->frame()).GetFrame();. This returns the danglingraw_ptrpointing to the freedWebLocalFrameImplobject.- The custom Blink cast helper
To<WebLocalFrameImpl>invokes a virtual method call (IsWebLocalFrame()) on the freed object. If the attacker sprayed the Oilpan heap prior to this step, they hijack the vtable, leading to RCE.
Proposed Fix
There are two primary ways to fix this vulnerability:
- Clear the pointer on close: Explicitly set
frame_ = nullptr;withinRTCPeerConnectionHandler::CloseAndUnregister(). This ensures the dangling pointer is neutralized when the context is destroyed. - Use Oilpan Smart Pointers (Preferred): Change the
raw_ptr<WebLocalFrame> frame_member inRTCPeerConnectionHandlerto aWeakMember<WebLocalFrame>. This will automatically clear the pointer when theWebLocalFrameImplis garbage-collected by Oilpan, preventing the UAF entirely. IfRTCPeerConnectionHandleris not currently an Oilpan-managed object, it may need to be made one, or it could hold the frame as aWeakPersistent<WebLocalFrame>(thoughWeakPersistentis generally discouraged in favor of making the owner garbage-collected).
Evaluated with Chrome root at commit: 876d480da1f794d87813cfa2e6ff4fcf9771e939
Results so far have been promising, but there can be wrong deductions. If this proves to be a false positive, please close as WAI; data from false positives will be used to improve accuracy over time. And please feel free to reach out to me directly if you have concerns or feedback on the project.