CVE-2026-9961
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ScopedWebrtcDebugLoggingcontent/browser/media/capture/desktop_capture_device.cc |
modified | |
ifcontent/browser/media/capture/desktop_capture_device.cc |
modified |
Files Changed
content/browser/media/capture/desktop_capture_device.cc
Patch
From 23bf8c3ee72ae504c10b85ab3de95fb0ca85548a Mon Sep 17 00:00:00 2001 From: Ilya Nikolaevskiy <[email protected]> Date: Wed, 22 Apr 2026 05:20:10 -0700 Subject: [PATCH] Add lock in ScopedWebrtcDebugLogging Fixed: 504710769 Change-Id: Ied8a200fcd41fe0f3a709e5bc98de6abf2ac2b4b Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7782067 Auto-Submit: Ilya Nikolaevskiy <[email protected]> Commit-Queue: Ilya Nikolaevskiy <[email protected]> Reviewed-by: Tove Petersson <[email protected]> Cr-Commit-Position: refs/heads/main@{#1618787} --- diff --git a/content/browser/media/capture/desktop_capture_device.cc b/content/browser/media/capture/desktop_capture_device.cc index 24eac68b..786839f 100644 --- a/content/browser/media/capture/desktop_capture_device.cc +++ b/content/browser/media/capture/desktop_capture_device.cc @@ -215,23 +215,41 @@ class ScopedWebrtcDebugLogging { public: explicit ScopedWebrtcDebugLogging(DesktopCaptureDevice::Client* client) { - g_client_ = client; + { + base::AutoLock auto_lock(GetClientLock()); + g_client_ = client; + } webrtc::InitDiagnosticLoggingDelegateFunction( &ScopedWebrtcDebugLogging::OnLog); } static void OnLog(const std::string& s) { - CHECK(g_client_); - g_client_->OnLog(s); + base::AutoLock auto_lock(GetClientLock()); + // g_client_ may be nullptr here because this is + // called via RTC_LOG macro by some background thread, + // which didn't set up ScopedWebrtcDebugLogging. + if (g_client_) { + g_client_->OnLog(s); + } } ~ScopedWebrtcDebugLogging() { - g_client_ = nullptr; + { + base::AutoLock auto_lock(GetClientLock()); + g_client_ = nullptr; + } webrtc::ResetDiagnosticLoggingDelegateFunction(); } private: static DesktopCaptureDevice::Client* g_client_; + + // Need to lock access to g_client_ because there might be some + // other thread already running capture and it may also invoke RTC_LOG macro. + static base::Lock& GetClientLock() { + static base::NoDestructor<base::Lock> lock; + return *lock; + } }; DesktopCaptureDevice::Client* ScopedWebrtcDebugLogging::g_client_ = nullptr;
Original Bug Report
Cross-thread UAF in DesktopCaptureDevice via ScopedWebrtcDebugLogging
Project Fortify, an experimental security project, has identified the following potential security issue. If you’re a feature owner CC-ed on this bug, please do your best to review these reports without the Chrome Security team. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.
Overview: A potential TOCTOU race condition in ScopedWebrtcDebugLogging allows a concurrent WebRTC capture thread to access a process-global raw pointer that has been freed. The global pointer is updated without synchronization during capture initialization, potentially leading to a use-after-free virtual call in the browser process.
Affected files:
content/browser/media/capture/desktop_capture_device.ccthird_party/webrtc_overrides/rtc_base/logging.cccontent/browser/renderer_host/media/in_process_video_capture_device_launcher.ccmedia/capture/video/video_capture_device_client.cc
Estimated timestamp from git blame: 2025-10-29
Summary
A potential Use-After-Free (UAF) vulnerability exists in the browser process due to unsynchronized access to a process-global raw pointer in ScopedWebrtcDebugLogging (located in content/browser/media/capture/desktop_capture_device.cc). When multiple desktop capture sessions are initiated, a race condition can allow a background WebRTC logging thread to perform a virtual call on a VideoCaptureDeviceClient object that has already been destroyed by the capture initialization thread.
Root Cause Analysis
The ScopedWebrtcDebugLogging class acts as an RAII helper to forward WebRTC logs to a capture client during device creation. It does this by setting a process-global static C++ raw pointer (g_client_) and registering a process-global logging delegate (webrtc::InitDiagnosticLoggingDelegateFunction).
// content/browser/media/capture/desktop_capture_device.cc
class ScopedWebrtcDebugLogging {
public:
explicit ScopedWebrtcDebugLogging(DesktopCaptureDevice::Client* client) {
g_client_ = client;
webrtc::InitDiagnosticLoggingDelegateFunction(&ScopedWebrtcDebugLogging::OnLog);
}
static void OnLog(const std::string& s) {
CHECK(g_client_);
g_client_->OnLog(s); // Potential UAF virtual call
}
~ScopedWebrtcDebugLogging() {
g_client_ = nullptr;
webrtc::ResetDiagnosticLoggingDelegateFunction();
}
private:
static DesktopCaptureDevice::Client* g_client_; // Not a raw_ptr<>
};
The implementation suffers from the following security issues:
- Lack of Synchronization:
g_client_is accessed and modified across multiple threads (theVideoCaptureThreaddoing the initialization, and background WebRTC threads emitting logs) without any locks. - No MiraclePtr Protection:
g_client_is a plain C++ raw pointer, so PartitionAlloc’s BackupRefPtr does not quarantine the memory if it becomes dangling.
Potential Exploitation Mechanics
Note: These are suggested steps to trigger the vulnerability. Our tooling has not automatically run code to verify this exact sequence.
- Session A: An attacker requests screen capture via
navigator.mediaDevices.getDisplayMedia(), and the user grants permission. A dedicated WebRTC background thread begins processing the capture and periodically emits diagnostic logs usingRTC_LOG. - Session B Request: The attacker initiates a second screen capture request.
- Initialization Begins: The user selects a target window. The browser process begins initializing Session B on the
VideoCaptureThread. Amedia::VideoCaptureDeviceClientobject is created and passed intoDesktopCaptureDevice::Create. - Globals Set: The
ScopedWebrtcDebugLoggingconstructor executes, setting the globalg_client_to point to Session B’s client and registering the logging delegate. - Concurrent Log: Concurrently, the background thread for Session A emits an
RTC_LOG. The log object’s destructor evaluates the global delegate and invokesScopedWebrtcDebugLogging::OnLog. - The Race Window: Inside
OnLog, the background thread executesCHECK(g_client_);. The compiler loads the non-volatileg_client_pointer into a CPU register. Immediately after this load, but before the virtual method callg_client_->OnLog(s), the thread is preempted by the OS scheduler. - Initialization Failure: Back on the
VideoCaptureThread, the attacker invalidates the chosen window (e.g., closing a popup via JS). Capture initialization fails,DesktopCaptureDevice::Createreturns null, andScopedWebrtcDebugLoggingis destroyed (nulling the globals). - Memory Freed: Because initialization failed, the
std::unique_ptr<media::VideoCaptureDeviceClient>is destroyed, and its memory is returned to PartitionAlloc. The memory is fully unprotected asg_client_was not abase::raw_ptr. - Reclamation: The attacker uses IPC heap spraying to reclaim the freed
VideoCaptureDeviceClientmemory block with forged data, placing a fake vtable pointer at the beginning of the object. - Use-After-Free: The background thread for Session A resumes execution. Using the dangling pointer cached in its CPU register, it executes the virtual method
g_client_->OnLog(s). This dereferences the attacker’s forged vtable, granting arbitrary Code Execution (RCE) and a full Sandbox Escape in the browser process.
Suggested Fix
Introduce a global base::Lock to synchronize all access to g_client_. The lock should be acquired during the construction and destruction of ScopedWebrtcDebugLogging, as well as within the OnLog callback, ensuring the pointer cannot be invalidated or freed while a log message is being dispatched.
Evaluated with Chrome root at commit: 7353d249d9cacf9c7218e1d7b8a39cf39c72d646
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.