Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactOut of bounds read in WebRTC
DescriptionOut of bounds read in WebRTC
ComponentWebRTC
Bug ClassOOB
Tracker517727318
Fix commitb5cf1fa60711 (src) +14/-11
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-16

Changed Functions

FunctionChangeNotes
if
modules/desktop_capture/win/wgc_capture_session.cc
modified
for
modules/desktop_capture/win/wgc_capture_session.cc
modified

Files Changed

  • modules/desktop_capture/win/wgc_capture_session.cc
From b5cf1fa607113788cf894d5b6895c72fba1d9254 Mon Sep 17 00:00:00 2001
From: Alexander Cooper <[email protected]>
Date: Tue, 02 Jun 2026 15:12:16 -0700
Subject: [PATCH] [WGC] Fix frame size synchronization

Fixes a scenario where the mapped_texture_ and frame_pool_ sizes of the
WGC capturer could get out of sync and potentially bypass their
reconciliation on the following frame.

Fixed: chromium:517727318
Change-Id: Ie4e951bbdf41491200b7ba1289f85e4c02bc22d1
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/476860
Commit-Queue: Mark Foltz <[email protected]>
Auto-Submit: Alexander Cooper <[email protected]>
Reviewed-by: Mark Foltz <[email protected]>
Cr-Commit-Position: refs/heads/main@{#47898}
---

diff --git a/modules/desktop_capture/win/wgc_capture_session.cc b/modules/desktop_capture/win/wgc_capture_session.cc
index c7eaf54..9a887d7 100644
--- a/modules/desktop_capture/win/wgc_capture_session.cc
+++ b/modules/desktop_capture/win/wgc_capture_session.cc
@@ -564,14 +564,6 @@
     return hr;
   }
 
-  if (!mapped_texture_) {
-    hr = CreateMappedTexture(texture_2D);
-    if (FAILED(hr)) {
-      RecordGetFrameResult(GetFrameResult::kCreateMappedTextureFailed);
-      return hr;
-    }
-  }
-
   // We need to copy `texture_2D` into `mapped_texture_` as the latter has the
   // D3D11_CPU_ACCESS_READ flag set, which lets us access the image data.
   // Otherwise it would only be readable by the GPU.
@@ -588,17 +580,28 @@
   // If the size changed, we must resize `mapped_texture_` and `frame_pool_` to
   // fit the new size. This must be done before `CopySubresourceRegion` so that
   // the textures are the same size.
-  if (SizeHasChanged(new_size, size_)) {
+  const bool needs_resize = SizeHasChanged(new_size, size_);
+
+  if (!mapped_texture_ || needs_resize) {
     hr = CreateMappedTexture(texture_2D, new_size.Width, new_size.Height);
     if (FAILED(hr)) {
-      RecordGetFrameResult(GetFrameResult::kResizeMappedTextureFailed);
+      RecordGetFrameResult(GetFrameResult::kCreateMappedTextureFailed);
       return hr;
     }
+  }
 
+  if (needs_resize) {
     hr = frame_pool_->Recreate(direct3d_device_.Get(), kPixelFormat,
                                num_buffers(), new_size);
     if (FAILED(hr)) {
       RecordGetFrameResult(GetFrameResult::kRecreateFramePoolFailed);
+      // On failure, `frame_pool_` remains at `size_`. Reset `mapped_texture_`
+      // because `mapped_texture_` and `frame_pool_` are now at inconsistent
+      // sizes. Clearing it forces a consistent recreation on the next frame.
+      // Note that it's not sufficient to simply leave `mapped_texture_` at it's
+      // current size, because if the window were to be resized back to `size_`,
+      // then we would erroneously think we don't need to re-create the texture.
+      mapped_texture_.Reset();
       return hr;
     }
   }
@@ -713,7 +716,7 @@
   const int width_in_bytes =
       current_frame->size().width() * DesktopFrame::kBytesPerPixel;
   RTC_DCHECK_GE(current_frame->stride(), width_in_bytes);
-  RTC_DCHECK_GE(map_info.RowPitch, width_in_bytes);
+  RTC_CHECK_GE(map_info.RowPitch, width_in_bytes);
   const int middle_pixel_offset =
       (image_width / 2) * DesktopFrame::kBytesPerPixel;
   for (int i = 0; i < image_height; i++) {
Loading diff…

Original Bug Report

reported by [email protected]

Potential out-of-bounds read in WgcCaptureSession::ProcessFrame due to state desynchronization

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 state desynchronization issue in WgcCaptureSession::ProcessFrame can lead to an out-of-bounds read of mapped Direct3D 11 staging memory. If a resize occurs and recreating the frame pool fails, the staging texture is left resized to a smaller dimension while the tracking state retains the old larger size. Subsequent frame processing at the original size bypasses reallocation and reads past the boundaries of the mapped texture.

Affected files:

  • third_party/webrtc/modules/desktop_capture/win/wgc_capture_session.cc

Estimated timestamp from git blame: 2022-03-09

Summary of Root Cause

In WgcCaptureSession::ProcessFrame (located in third_party/webrtc/modules/desktop_capture/win/wgc_capture_session.cc), the capture session’s size-tracking member size_ is synchronized non-atomically when a captured window size changes.

When a resize to a smaller size is detected in ProcessFrame:

  1. CreateMappedTexture is invoked (line 592), which successfully reallocates mapped_texture_ to the new smaller size (e.g., $A imes A$).
  2. frame_pool_->Recreate is then called (line 598). If this allocation fails (for example, due to transient GPU memory pressure), the function returns early on line 602.
  3. Because of the early return, the session’s overall size-tracking variable size_ is not updated to the new size and remains at the old larger size (e.g., $S imes S$).

If the captured window is then resized back to the larger size $S$:

  1. SizeHasChanged(S, S) evaluates to false because both the incoming frame size and the stale tracking member size_ are $S$. The resize block is completely bypassed.
  2. The copy dimensions image_height and image_width are computed as min(size_, new_size) = S (lines 615-616).
  3. The code maps mapped_texture_ (which is still sized $A imes A$) and enters a loop copying $S$ rows of $S imes 4$ bytes using memcpy (lines 719-730).
  4. In production builds, RTC_DCHECK_GE checks are disabled. This results in an out-of-bounds memory read from the mapped staging texture buffer.

Potential Steps to Trigger

Note: These are suggested/potential steps modeled from static analysis; our tooling agent does not currently have the capability to run code or compile a working proof-of-concept.

  1. A web page requests screen or window capture using getDisplayMedia, and the user selects a window of size $S$ (e.g., $1000 \times 1000$).
  2. The attacker triggers transient GPU memory pressure (e.g., by spawning multiple heavy WebGL/WebGPU contexts or massive resource allocations) to prepare for an allocation failure.
  3. The captured window is resized to a smaller dimension $A$ (e.g., $500 \times 500$).
  4. ProcessFrame enters the resize block, reallocates mapped_texture_ to $A$, but fails to recreate the frame pool due to GPU memory pressure, returning early.
  5. The captured window is resized back to the larger size $S$.
  6. On the next frame capture, the code bypasses resizing because size_ was never updated from $S$. It attempts to copy a larger frame region out of the smaller staging texture, resulting in an out-of-bounds read of adjacent mapped memory in the browser process.

Suggested Fix

Ensure that if any part of the resizing sequence fails, the session either rolls back the staging texture, marks the session as permanently failed, or updates size_ defensively. For instance, in wgc_capture_session.cc:

    hr = frame_pool_->Recreate(direct3d_device_.Get(), kPixelFormat,
                               num_buffers(), new_size);
    if (FAILED(hr)) {
      RecordGetFrameResult(GetFrameResult::kRecreateFramePoolFailed);
      // Ensure the session size is synchronized or invalidated on failure
      size_ = new_size; 
      return hr;
    }

Evaluated with Chrome root at commit: 5133b93d189b383c37805b1cf3a9d2dbfe8d7379


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.

View on issue tracker