Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in SurfaceCapture
DescriptionUse after free in SurfaceCapture
ComponentSurfaceCapture
Bug ClassUAF
Tracker504710769
Fix commit23bf8c3ee72a (chromium/src) +22/-4
CISA KEVNot listed
CreditedGoogle
Disclosed2026-05-27

Changed Functions

FunctionChangeNotes
ScopedWebrtcDebugLogging
content/browser/media/capture/desktop_capture_device.cc
modified
if
content/browser/media/capture/desktop_capture_device.cc
modified

Files Changed

  • content/browser/media/capture/desktop_capture_device.cc
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;
Loading diff…

Original Bug Report

reported by [email protected]

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.cc
  • third_party/webrtc_overrides/rtc_base/logging.cc
  • content/browser/renderer_host/media/in_process_video_capture_device_launcher.cc
  • media/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:

  1. Lack of Synchronization: g_client_ is accessed and modified across multiple threads (the VideoCaptureThread doing the initialization, and background WebRTC threads emitting logs) without any locks.
  2. 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.

  1. 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 using RTC_LOG.
  2. Session B Request: The attacker initiates a second screen capture request.
  3. Initialization Begins: The user selects a target window. The browser process begins initializing Session B on the VideoCaptureThread. A media::VideoCaptureDeviceClient object is created and passed into DesktopCaptureDevice::Create.
  4. Globals Set: The ScopedWebrtcDebugLogging constructor executes, setting the global g_client_ to point to Session B’s client and registering the logging delegate.
  5. Concurrent Log: Concurrently, the background thread for Session A emits an RTC_LOG. The log object’s destructor evaluates the global delegate and invokes ScopedWebrtcDebugLogging::OnLog.
  6. The Race Window: Inside OnLog, the background thread executes CHECK(g_client_);. The compiler loads the non-volatile g_client_ pointer into a CPU register. Immediately after this load, but before the virtual method call g_client_->OnLog(s), the thread is preempted by the OS scheduler.
  7. Initialization Failure: Back on the VideoCaptureThread, the attacker invalidates the chosen window (e.g., closing a popup via JS). Capture initialization fails, DesktopCaptureDevice::Create returns null, and ScopedWebrtcDebugLogging is destroyed (nulling the globals).
  8. 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 as g_client_ was not a base::raw_ptr.
  9. Reclamation: The attacker uses IPC heap spraying to reclaim the freed VideoCaptureDeviceClient memory block with forged data, placing a fake vtable pointer at the beginning of the object.
  10. 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.

View on issue tracker