Overview

Low
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in WebRTC
DescriptionUse after free in WebRTC
ComponentWebRTC
Bug ClassUAF
Tracker497695401
Fix commitac1c38ac1687 (chromium/src) +2/-1
CISA KEVNot listed
CreditedGoogle
Disclosed2026-05-05

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
From ac1c38ac1687520c54e6f3634c7a97c5a2ba7714 Mon Sep 17 00:00:00 2001
From: Andrew Paseltiner <apaseltiner@chromium.org>
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 <apaseltiner@chromium.org>
Reviewed-by: Daniel Cheng <dcheng@chromium.org>
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 rj...@google.com

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