Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactRace in WebRTC
DescriptionRace in WebRTC
ComponentWebRTC
Bug ClassRace
Tracker504557432
Fix commit22f071a33eb4 (src) +11/-3
CISA KEVNot listed
CreditedGoogle
Disclosed2026-05-27

Changed Functions

FunctionChangeNotes
if
modules/desktop_capture/cropping_window_capturer.cc
modified

Files Changed

  • modules/desktop_capture/cropping_window_capturer.cc
  • modules/desktop_capture/cropping_window_capturer.h
From 22f071a33eb46cbabcbf05961127dde7a2e3d908 Mon Sep 17 00:00:00 2001
From: mark a. foltz <mfoltz@chromium.org>
Date: Mon, 27 Apr 2026 11:55:24 -0700
Subject: [PATCH] [desktop capture] Fix TOCTOU race condition in CroppingWindowCapturer.

Crop the frame using the window rectangle captured when the capture
was initiated, rather than re-querying the rectangle after the capture
finishes.

Bug: chromium:504557432
Change-Id: I459d528ac343163fde9a9dbf6c40a542c1a04c61
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/467820
Reviewed-by: Stefan Holmer <stefan@webrtc.org>
Reviewed-by: Alexander Cooper <alcooper@chromium.org>
Commit-Queue: Mark Foltz <mfoltz@chromium.org>
Cr-Commit-Position: refs/heads/main@{#47575}
---

diff --git a/modules/desktop_capture/cropping_window_capturer.cc b/modules/desktop_capture/cropping_window_capturer.cc
index b0cbc70..3479044 100644
--- a/modules/desktop_capture/cropping_window_capturer.cc
+++ b/modules/desktop_capture/cropping_window_capturer.cc
@@ -47,6 +47,9 @@
 
 void CroppingWindowCapturer::CaptureFrame() {
   if (ShouldUseScreenCapturer()) {
+    // We record the window position here at capture time; it may differ at
+    // frame delivery time.
+    last_window_rect_ = GetWindowRectInVirtualScreen();
     if (!screen_capturer_) {
       screen_capturer_ = DesktopCapturer::CreateRawScreenCapturer(options_);
       if (excluded_window_) {
@@ -74,6 +77,7 @@
 bool CroppingWindowCapturer::SelectSource(SourceId id) {
   if (window_capturer_->SelectSource(id)) {
     selected_window_ = id;
+    last_window_rect_ = {};
     return true;
   }
   return false;
@@ -98,15 +102,14 @@
     return;
   }
 
-  DesktopRect window_rect = GetWindowRectInVirtualScreen();
-  if (window_rect.is_empty()) {
+  if (last_window_rect_.is_empty()) {
     RTC_LOG(LS_WARNING) << "Window rect is empty";
     callback_->OnCaptureResult(Result::ERROR_TEMPORARY, nullptr);
     return;
   }
 
   std::unique_ptr<DesktopFrame> cropped_frame =
-      CreateCroppedDesktopFrame(std::move(screen_frame), window_rect);
+      CreateCroppedDesktopFrame(std::move(screen_frame), last_window_rect_);
 
   if (!cropped_frame) {
     RTC_LOG(LS_WARNING) << "Window is outside of the captured display";
diff --git a/modules/desktop_capture/cropping_window_capturer.h b/modules/desktop_capture/cropping_window_capturer.h
index 5647803..fe95fc4 100644
--- a/modules/desktop_capture/cropping_window_capturer.h
+++ b/modules/desktop_capture/cropping_window_capturer.h
@@ -77,6 +77,11 @@
   std::unique_ptr<DesktopCapturer> screen_capturer_;
   SourceId selected_window_;
   WindowId excluded_window_;
+
+  // The window rectangle in the virtual screen, relative to the top-left corner
+  // of the virtual screen. This is the rectangle used to crop the frame.
+  // It is updated in CaptureFrame() and used in OnCaptureResult().
+  DesktopRect last_window_rect_;
 };
 
 }  // namespace webrtc
Loading diff…

Original Bug Report

reported by vm...@google.com

Potential arbitrary screen pixel leak via TOCTOU in CroppingWindowCapturerWin

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 time-of-check to time-of-use (TOCTOU) race condition exists in the Windows desktop capturer when falling back to the cropping window capturer. By rapidly moving a shared popup window during capture, a malicious web page could potentially cause the capturer to crop a stale full-screen frame using new window coordinates. This could allow an attacker to read arbitrary screen pixels outside the shared window, bypassing intended sharing boundaries.

Affected files:

  • third_party/webrtc/modules/desktop_capture/cropping_window_capturer_win.cc
  • third_party/webrtc/modules/desktop_capture/cropping_window_capturer.cc

Estimated timestamp from git blame: 2025-06-11

Description

A potential Time-of-Check to Time-of-Use (TOCTOU) vulnerability exists in CroppingWindowCapturerWin within the WebRTC desktop capture module on Windows. When Windows Graphics Capture (WGC) is unavailable or disabled, Chrome falls back to CroppingWindowCapturer. This capturer optimizes window capture by taking a full-screen snapshot and cropping it to the target window’s boundaries.

The vulnerability occurs because the target window’s coordinates are re-queried after the full-screen capture has completed, but these new coordinates are then used to crop the old full-screen image. If the window is moved during the screen capture process, pixels from the window’s new location are extracted from the stale full-screen snapshot, potentially leaking the contents of other applications or the underlying desktop.

Root Cause Analysis

In third_party/webrtc/modules/desktop_capture/cropping_window_capturer.cc, the CaptureFrame() method first calls ShouldUseScreenCapturer() to verify the window is on top, and then calls screen_capturer_->CaptureFrame() to perform a synchronous OS-level full-screen capture.

Once the full-screen capture finishes, CroppingWindowCapturer::OnCaptureResult() executes:

void CroppingWindowCapturer::OnCaptureResult(...) {
  if (!ShouldUseScreenCapturer()) {
    // ... 
    return;
  }
  ...
  DesktopRect window_rect = GetWindowRectInVirtualScreen();
  ...
  std::unique_ptr<DesktopFrame> cropped_frame =
      CreateCroppedDesktopFrame(std::move(screen_frame), window_rect);
  ...
}

The second call to ShouldUseScreenCapturer() invokes the Windows-specific implementation in cropping_window_capturer_win.cc. This function calls the Windows OS API GetWindowRect() and updates the member variable window_region_rect_ with the live, current position of the window:

bool CroppingWindowCapturerWin::ShouldUseScreenCapturer() {
  ...
  if (!GetWindowRect(selected, &window_region_rect_)) {
    return false;
  }
  ...
}

Because the full-screen capture takes time, a race window exists. If the shared window is moved during this delay, the second ShouldUseScreenCapturer() call updates window_region_rect_ to the new coordinates. GetWindowRectInVirtualScreen() then retrieves these new coordinates, and CreateCroppedDesktopFrame blindly crops the stale screen_frame using them.

Potential Exploitation Steps

Note: These are suggested steps; our tooling agent does not have the ability to run active code to provide a working proof-of-concept.

  1. A malicious site opens a popup window and prompts the user to share it via navigator.mediaDevices.getDisplayMedia().
  2. The user grants permission, expecting only the popup window’s contents to be shared.
  3. The malicious site executes a high-frequency JavaScript loop calling window.moveTo(Target_X, Target_Y), rapidly moving the popup between its original location and an area of the screen the attacker wishes to read (e.g., overlapping another application).
  4. A race condition occurs:
    • The browser captures the full desktop while the popup is at its original position.
    • The window.moveTo() IPC moves the window to the target coordinates.
    • The browser re-queries the window bounds, getting the target coordinates.
    • The browser crops the full desktop image using the target coordinates.
  5. The resulting video frame delivered to the malicious site contains the desktop pixels at the target coordinates, successfully bypassing cross-application and cross-origin boundaries.

Suggested Fix

To resolve this issue, the cropping logic should use the window coordinates that were valid at the exact time the screen capture was initiated, rather than re-querying live coordinates afterward.

  1. Modify CroppingWindowCapturer to cache the window_rect (or window_region_rect_) immediately before calling screen_capturer_->CaptureFrame().
  2. Pass this cached rectangle explicitly to CreateCroppedDesktopFrame inside OnCaptureResult, ignoring any subsequent positional changes of the window.
  3. Ensure that the second call to ShouldUseScreenCapturer() does not implicitly mutate state that dictates the cropping boundaries for the current frame.

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