CVE-2026-14015
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifmodules/desktop_capture/cropping_window_capturer.cc |
modified |
Files Changed
modules/desktop_capture/cropping_window_capturer.cc
Patch
From 94ebfb0b0d864479db28b764274162773f93bb96 Mon Sep 17 00:00:00 2001
From: Alexander Cooper <alcooper@chromium.org>
Date: Thu, 28 May 2026 10:17:19 -0700
Subject: [PATCH] Fix race condition in CroppingWindowCapturer
https://webrtc-review.googlesource.com/c/src/+/467820 fixed an issue
where the captured window may have moved during capture, and thus the
frame would have the window at it's original location (e.g. when
CaptureFrame was called), but we cropped to it's new location (e.g. when
OnCaptureResult was called), thus potential leaking pixels. This
addresses the inverse of that. If we actually capture closer to when
`OnCaptureResult` is called and the window has moved, we could now be
capturing the old location where the window no longer is and is thus
leaking pixels. Since we cannot assert whether capture happened closer
to `CaptureFrame` or `OnCaptureResult`, we simply attempt to recapture
the frame if the window position has moved between these two calls.
Fixed: chromium:517207235
Change-Id: I98f41c709b0bcf0285e258e7421af3481692a1ba
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/476400
Reviewed-by: Mark Foltz <mfoltz@chromium.org>
Auto-Submit: Alexander Cooper <alcooper@chromium.org>
Commit-Queue: Alexander Cooper <alcooper@chromium.org>
Cr-Commit-Position: refs/heads/main@{#47851}
---
diff --git a/modules/desktop_capture/cropping_window_capturer.cc b/modules/desktop_capture/cropping_window_capturer.cc
index 3479044..1fb1928 100644
--- a/modules/desktop_capture/cropping_window_capturer.cc
+++ b/modules/desktop_capture/cropping_window_capturer.cc
@@ -96,6 +96,16 @@
return;
}
+ // We don't know when capture has occurred, so if the window moved we can't
+ // assert which pixels we should capture. Capture again and we'll return a
+ // frame once the window is stationary between the two calls.
+ DesktopRect current_window_rect = GetWindowRectInVirtualScreen();
+ if (!current_window_rect.equals(last_window_rect_)) {
+ RTC_LOG(LS_INFO) << "Window moved during capture";
+ window_capturer_->CaptureFrame();
+ return;
+ }
+
if (result != Result::SUCCESS) {
RTC_LOG(LS_WARNING) << "ScreenCapturer failed to capture a frame";
callback_->OnCaptureResult(result, nullptr);
Original Bug Report
Potential position TOCTOU in CroppingWindowCapturer allows cross-window pixel disclosure
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. 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) vulnerability exists in WebRTC’s CroppingWindowCapturer on Windows. During a window capture operation, the window coordinates are validated and cached before the full-screen capture occurs, but the post-capture verification fails to check if the window moved in the interim. If an attacker-controlled sharing window is moved between these steps, it can crop the screen using stale coordinates, potentially exposing pixels from underlying native windows.
Affected files:
third_party/webrtc/modules/desktop_capture/cropping_window_capturer.ccthird_party/webrtc/modules/desktop_capture/cropping_window_capturer_win.cc
Estimated timestamp from git blame: 2026-04-27
Bug Description and Root Cause
A potential position Time-of-Check to Time-of-Use (TOCTOU) vulnerability exists in the WebRTC CroppingWindowCapturer component on Windows.
During CaptureFrame() in third_party/webrtc/modules/desktop_capture/cropping_window_capturer.cc (line 48), the capturer queries and caches the crop-rectangle in last_window_rect_ before triggering the full-screen pixel capture:
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();
...
screen_capturer_->CaptureFrame();
} else {
window_capturer_->CaptureFrame();
}
}
However, during the post-capture callback OnCaptureResult() (line 90), the sanity check re-reads the live window position (via ShouldUseScreenCapturer()) and verifies its visibility and Z-order at that new location, but it never compares this new position against the cached last_window_rect_:
void CroppingWindowCapturer::OnCaptureResult(
DesktopCapturer::Result result,
std::unique_ptr<DesktopFrame> screen_frame) {
if (!ShouldUseScreenCapturer()) { // Re-reads live rect, updates window_region_rect_, runs IsTopWindow()
...
window_capturer_->CaptureFrame();
return;
}
...
std::unique_ptr<DesktopFrame> cropped_frame =
CreateCroppedDesktopFrame(std::move(screen_frame), last_window_rect_); // Crops full-screen using STALE last_window_rect_
...
}
Because ShouldUseScreenCapturer() overwrites the internal window_region_rect_ with the live coordinates but no logic verifies that last_window_rect_ still matches the updated window rect, a window translation (such as moving the sharing window via programmatic JavaScript APIs) that occurs between the caching step and the screen capture step will go completely undetected. The capturer then crops the screen frame using the stale coordinates, potentially leaking the pixels of whatever application window was previously covered by the sharing window.
Potential Attack Scenario / Trigger Steps
Note: The following are suggested/potential steps to trigger the vulnerability. Our tooling agent does not currently have the capability to run code or compile POCs to verify this behavior live.
- Setup: A victim native application window (e.g., containing sensitive personal or financial information) is positioned at screen region A.
- Targeting: An attacker-controlled web page opens a popup window and positions it at region A so that it completely covers the victim window and is topmost.
- Initiating Media Capture: The attacker triggers
getDisplayMedia({video: true})and induces the user to select the attacker’s popup window in the picker UI. - Timing the Race:
- At T0,
CaptureFrame()runs.ShouldUseScreenCapturer()checks the popup, storing coordinates A. - At T1,
last_window_rect_is cached as A. - Immediately after this (and before the pixel grab completes), the attacker’s page calls
popup.moveTo(B)to shift the popup to region B (an empty or non-sensitive desktop area). This exposes the victim window at region A. - At T3, the screen capturer performs the virtual screen pixel acquisition. Region A now contains the pixels of the exposed victim window.
- At T4,
OnCaptureResult()executes and callsShouldUseScreenCapturer(). It queries the popup’s live coordinates (now B), confirms that the popup is visible and topmost at B, and succeeds. - At T5,
CreateCroppedDesktopFramecrops the captured virtual screen using the stale coordinates A.
- At T0,
- Impact: The pixels of the victim window at region A are delivered to the attacker’s media stream, potentially disclosing cross-window pixel data without explicit user consent.
Suggested Fix
In CroppingWindowCapturer::OnCaptureResult(), after the second ShouldUseScreenCapturer() call succeeds, compare the newly-read window rect (converted to virtual screen coordinates) against last_window_rect_. If they differ (meaning the window moved during capture), discard the screen frame and fall back to capturing via the window capturer or return a temporary error:
if (!ShouldUseScreenCapturer()) {
RTC_LOG(LS_INFO) << "Window no longer on top when ScreenCapturer finishes";
window_capturer_->CaptureFrame();
return;
}
DesktopRect current_window_rect = GetWindowRectInVirtualScreen();
if (current_window_rect != last_window_rect_) {
RTC_LOG(LS_WARNING) << "Window moved during capture";
window_capturer_->CaptureFrame();
return;
}
Evaluated with Chrome root at commit: b1520ef4a76878853a31f0943b565e42060edec8
Results so far have been promising, but there can be wrong deductions. Feel free to adjust as follows:
- If you are familiar with the severity guidelines, you may adjust the severity.
- If this is a false positive, and there’s no work to be done, please close as WAI.
- If there is work to do here but not a vulnerability, please change the issue type to Task/Bug/FR.
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.