CVE-2026-12461
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifmodules/desktop_capture/win/wgc_capture_session.cc |
modified | |
formodules/desktop_capture/win/wgc_capture_session.cc |
modified |
Files Changed
modules/desktop_capture/win/wgc_capture_session.cc
Patch
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++) {
Original Bug Report
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:
CreateMappedTextureis invoked (line 592), which successfully reallocatesmapped_texture_to the new smaller size (e.g., $A imes A$).frame_pool_->Recreateis then called (line 598). If this allocation fails (for example, due to transient GPU memory pressure), the function returns early on line 602.- 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$:
SizeHasChanged(S, S)evaluates tofalsebecause both the incoming frame size and the stale tracking membersize_are $S$. The resize block is completely bypassed.- The copy dimensions
image_heightandimage_widthare computed asmin(size_, new_size) = S(lines 615-616). - The code maps
mapped_texture_(which is still sized $A imes A$) and enters a loop copying $S$ rows of $S imes 4$ bytes usingmemcpy(lines 719-730). - In production builds,
RTC_DCHECK_GEchecks 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.
- A web page requests screen or window capture using
getDisplayMedia, and the user selects a window of size $S$ (e.g., $1000 \times 1000$). - 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.
- The captured window is resized to a smaller dimension $A$ (e.g., $500 \times 500$).
ProcessFrameenters the resize block, reallocatesmapped_texture_to $A$, but fails to recreate the frame pool due to GPU memory pressure, returning early.- The captured window is resized back to the larger size $S$.
- 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.