Low patch-now CVSS 8.8 chrome UAF 📄 Reporter bug report 🔧 Commit mapped

Overview

Low
Severity
8.8
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in WebRTC
DescriptionUse after free in WebRTC
ComponentWebRTC
Sub-componentRTC
Bug ClassUAF
Action tierpatch-now — corruption primitive, web-reachable in default config (unverified)
Computed by PatchGap's deterministic exploitability ruler from the fixing commit. patch-now = RCE-class & attacker-reachable in a default config, or exploited in the wild; watch = real security signal below that bar; noise = no security relevance.
Tracker497695401
Fix commitac1c38ac1687 (chromium/src) +2/-1
CWECWE-416 (Use-after-free)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
CISA KEVNot listed
CreditedGoogle
Reported2026-03-30 (per vendor advisory)
Fix landed2026-03-31 (public git)
Disclosed2026-05-05
Patch gap35 days — fix public in source before the advisory

Background

`RTCPeerConnectionHandler`
the Blink-renderer object that bridges a JavaScript RTCPeerConnection to the underlying WebRTC implementation, holding a frame_ pointer to its owning document frame.
`CloseAndUnregister`
the teardown method on RTCPeerConnectionHandler that detaches the handler when the peer connection is closed/unregistered, previously clearing client_ but not frame_.
`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 a WeakPersistent but 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.

Key insight
The handler cleared its client reference on unregister but forgot to clear its 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

  1. Establish a peer connection Script creates an RTCPeerConnection, instantiating an RTCPeerConnectionHandler bound to the document’s frame via frame_.
  2. Trigger unregistration The connection is closed, invoking CloseAndUnregister, which (pre-patch) left frame_ set while the frame itself could be navigated away or destroyed.
  3. Race an async stats task A queued stats report in PeerConnectionTracker runs asynchronously and dereferences pc_handler_->frame(), which the old !pc_handler_ check did not guard. (inferred: precise timing/navigation needed to free the frame)
  4. Use-after-free / null deref The stale frame_ is converted via To<WebLocalFrameImpl>(*pc_handler_->frame()), yielding a use-after-free or null dereference in the renderer. (inferred beyond the UAF the advisory names)

Impact Assessment

An attacker-controlled page could trigger a use-after-free of the frame pointer within the renderer process during asynchronous WebRTC stats processing. The vendor advisory rates this low severity; any escalation beyond the renderer-side UAF (e.g., control-flow hijack) is (inferred) and not demonstrated by the diff. Preconditions include an active peer connection whose handler is unregistered while a stats task is still in flight.

Changed Functions

FunctionChangeNotes
if
third_party/blink/renderer/modules/peerconnection/peer_connection_tracker.cc
modified

Files Changed

  • third_party/blink/renderer/modules/peerconnection/peer_connection_tracker.cc
  • third_party/blink/renderer/modules/peerconnection/rtc_peer_connection_handler.cc

Audit Directions

  • Back-pointers not cleared on teardown
    Audit teardown/unregister methods that null some owned pointers (e.g., client_) but leave sibling back-pointers like frame_ live; each surviving pointer is a dangling-UAF candidate.
  • Async consumers of handler state
    Review callbacks/tasks in PeerConnectionTracker and similar trackers that dereference pc_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 wanted
    Flag raw_ptr fields 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.
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
Loading diff…

Original Bug Report

reported by [email protected]

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.h
  • third_party/blink/renderer/modules/peerconnection/peer_connection_tracker.cc
  • third_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.

  1. Prerequisite: The attacker convinces the user to open chrome://webrtc-internals in one tab (or leverages an existing debugging session). This triggers periodic 1-second polling for WebRTC statistics across all renderer processes via PeerConnectionTrackerHost::GetStandardStats().
  2. In a separate tab, the user loads a malicious HTML page controlled by the attacker.
  3. The malicious page creates a child <iframe> and injects JavaScript execution into it.
  4. Inside the iframe, the attacker’s script creates a new RTCPeerConnection object.
  5. The RTCPeerConnectionHandler stores a raw_ptr<WebLocalFrame> frame_ pointing to the iframe’s WebLocalFrameImpl.
  6. To prevent the RTCPeerConnection from 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;).
  7. The 1-second timer from chrome://webrtc-internals fires, calling PeerConnectionTracker::GetStandardStats(). This iterates over active peer connections and creates an asynchronous InternalStandardStatsObserver, passing it to the native WebRTC engine to gather stats on a background thread.
  8. 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()).
  9. Removing the iframe calls RTCPeerConnectionHandler::CloseAndUnregister(). This unregisters the handler but fails to clear the frame_ member.
  10. The attacker forces a Blink/V8 garbage collection using window.gc(). Since the iframe’s execution context was destroyed, the WebLocalFrameImpl object loses its strong references and is reclaimed by the Oilpan garbage collector. The frame_ member is now a dangling pointer.
  11. The WebRTC signaling thread finishes stats collection and calls InternalStandardStatsObserver::OnStatsDelivered(), which posts a task to the main thread to execute ReportToList().
  12. On the main thread, ReportToList() checks if the WeakPtr to the RTCPeerConnectionHandler is valid. Because the parent window kept the JS object alive, the check passes.
  13. ReportToList() executes auto* local_frame = To<WebLocalFrameImpl>(*pc_handler_->frame()).GetFrame();. This returns the dangling raw_ptr pointing to the freed WebLocalFrameImpl object.
  14. 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:

  1. Clear the pointer on close: Explicitly set frame_ = nullptr; within RTCPeerConnectionHandler::CloseAndUnregister(). This ensures the dangling pointer is neutralized when the context is destroyed.
  2. Use Oilpan Smart Pointers (Preferred): Change the raw_ptr<WebLocalFrame> frame_ member in RTCPeerConnectionHandler to a WeakMember<WebLocalFrame>. This will automatically clear the pointer when the WebLocalFrameImpl is garbage-collected by Oilpan, preventing the UAF entirely. If RTCPeerConnectionHandler is not currently an Oilpan-managed object, it may need to be made one, or it could hold the frame as a WeakPersistent<WebLocalFrame> (though WeakPersistent is 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.

View on issue tracker