CVE-2026-8541
Overview
Files Changed
chrome/browser/ui/thumbnails/background_thumbnail_video_capturer.cc
Patch
From 791187a955bee94e1f65b53deb98a842a7a2e8c8 Mon Sep 17 00:00:00 2001
From: Stephen Nusko <nuskos@chromium.org>
Date: Wed, 01 Apr 2026 06:56:31 -0700
Subject: [PATCH] Add size check before installing pixels in BackgroundThumbnailVideoCapturer.
This change pre-calculates the SkImageInfo and row bytes, and adds a
CHECK to verify that the provided memory mapping is large enough to hold
the bitmap data before calling SkBitmap::installPixels.
See (internal-only): go/code-terracotta-review-explainer
This is a phase 1 quick fix.
Bug: 496645393
Change-Id: I4bae557699e67ee1656b6d8c079542ff4dd8afe1
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7711554
Auto-Submit: Stephen Nusko <nuskos@chromium.org>
Reviewed-by: Dana Fried <dfried@chromium.org>
Commit-Queue: Dana Fried <dfried@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1608491}
---
diff --git a/chrome/browser/ui/thumbnails/background_thumbnail_video_capturer.cc b/chrome/browser/ui/thumbnails/background_thumbnail_video_capturer.cc
index 7dfe004..2bc9af6 100644
--- a/chrome/browser/ui/thumbnails/background_thumbnail_video_capturer.cc
+++ b/chrome/browser/ui/thumbnails/background_thumbnail_video_capturer.cc
@@ -168,14 +168,16 @@
effective_content_rect.Inset(scroll_insets);
const gfx::Size bitmap_size(content_rect.right(), content_rect.bottom());
+ const SkImageInfo image_info = SkImageInfo::MakeN32(
+ bitmap_size.width(), bitmap_size.height(), kPremul_SkAlphaType,
+ info->color_space.ToSkColorSpace());
+ const size_t row_bytes =
+ media::VideoFrame::RowBytes(media::VideoFrame::Plane::kARGB,
+ info->pixel_format, info->coded_size.width());
+ CHECK_GE(mapping.size(), image_info.computeByteSize(row_bytes));
SkBitmap frame;
frame.installPixels(
- SkImageInfo::MakeN32(bitmap_size.width(), bitmap_size.height(),
- kPremul_SkAlphaType,
- info->color_space.ToSkColorSpace()),
- pixels,
- media::VideoFrame::RowBytes(media::VideoFrame::Plane::kARGB,
- info->pixel_format, info->coded_size.width()),
+ image_info, pixels, row_bytes,
[](void* addr, void* context) {
delete static_cast<FramePinner*>(context);
},
Original Bug Report
Potential OOB read in browser process via unvalidated content_rect in VideoConsumer
Project Fortify, an experimental security project, has identified the following potential security issue.
Overview: A compromised GPU process can trigger an out-of-bounds read in the browser process by supplying an unvalidated content_rect during frame capture. The browser uses these dimensions to construct an SkBitmap without checking if it exceeds the shared memory buffer size, leading to an OOB read during JPEG encoding or painting. This could result in a browser crash or potential information disclosure.
Affected files:
chrome/browser/ui/thumbnails/background_thumbnail_video_capturer.ccchrome/browser/ui/thumbnails/thumbnail_image.cc
Estimated timestamp from git blame: 2026-02-27
Summary
There is a potential out-of-bounds (OOB) read vulnerability in the browser process when handling video frames captured from a compromised GPU process. Implementations of viz::mojom::FrameSinkVideoConsumer in the browser process, specifically BackgroundThumbnailVideoCapturer and BrowserLiveBackgroundController::VideoConsumer, fail to adequately validate the content_rect parameter before using it to size an SkBitmap backed by shared memory.
Vulnerability Details
When the GPU process sends a captured frame via the FrameSinkVideoConsumer::OnFrameCaptured Mojo IPC, it provides a shared memory handle (data), frame metadata (info), and a content_rect.
In BackgroundThumbnailVideoCapturer::OnFrameCaptured (and similarly in BrowserLiveBackgroundController), the code validates that the shared memory mapping size is large enough to hold a frame of info->coded_size. However, it completely ignores info->coded_size when constructing the resulting image. Instead, it derives the SkBitmap’s width and height directly from the unvalidated content_rect boundaries (content_rect.right() and content_rect.bottom()).
// chrome/browser/ui/thumbnails/background_thumbnail_video_capturer.cc
const gfx::Size bitmap_size(content_rect.right(), content_rect.bottom());
SkBitmap frame;
frame.installPixels(
SkImageInfo::MakeN32(bitmap_size.width(), bitmap_size.height(), ...),
pixels,
media::VideoFrame::RowBytes(..., info->coded_size.width()),
...);
SkBitmap::installPixels accepts a raw void* for the memory buffer but does not take a buffer_size parameter. It inherently assumes the provided buffer is large enough for the requested height and row bytes. Because the content_rect height can arbitrarily exceed the coded_size height, this creates an SkBitmap that extends far beyond the bounds of the mapped shared memory.
When this SkBitmap is later passed to ThumbnailImage::CompressBitmap and encoded via gfx::JPEGCodec::Encode, the JPEG encoder iterates over the massive height, reading out of bounds into adjacent browser process memory.
Impact
An attacker compromising the GPU process could exploit this to read browser process memory. The OOB data is compressed into a JPEG and displayed in the browser UI (e.g., Tab Hover Cards). Because the compromised GPU process handles compositing the final screen, it can read the rendered pixels and extract the leaked browser memory. Additionally, if the OOB read hits an unmapped guard page, it will cause an immediate browser process crash (Denial of Service).
Suggested Reproduction Steps
Note: These are potential steps to trigger the issue, as the Fortify LLM agent cannot actively run code to provide a working PoC.
- Compromise the GPU process.
- Wait for the browser process to request a background tab capture (e.g., user hovers over a background tab) and intercept the
FrameSinkVideoConsumer::OnFrameCapturedcallback. - Send a message with
info.coded_sizeset to a small dimension (e.g.,100x100) and a backing shared memory region of40,000bytes. - Set
content_rectto maliciously large dimensions, e.g.,gfx::Rect(0, 0, 100, 10000). - The browser process maps the memory, passes the size check (as
coded_sizematches the buffer), and creates anSkBitmapof size100x10000. - The browser routes the frame to a background thread for JPEG compression, where the encoder reads
9,900rows out of bounds.
Suggested Fix
Ensure that the dimensions derived from content_rect do not result in a byte size requirement that exceeds the mapped shared memory.
A safe pattern for this already exists in DevToolsEyeDropper::OnFrameCaptured (chrome/browser/devtools/devtools_eye_dropper.cc), which uses computeByteSize to explicitly validate the buffer size against the SkImageInfo before calling installPixels:
auto image_info = SkImageInfo::MakeN32(...);
const size_t row_bytes = media::VideoFrame::RowBytes(...);
// Ensure the unbounded pointer points to enough memory.
CHECK_GE(mapping.size(), image_info.computeByteSize(row_bytes));
frame.installPixels(image_info, ...);
This check should be applied to BackgroundThumbnailVideoCapturer, BrowserLiveBackgroundController::VideoConsumer, and any other implementers of FrameSinkVideoConsumer that manually construct SkBitmaps from content_rect.
Evaluated with Chrome root at commit: bb48272cafb7e24c93f55ef40da398cd206ee651
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. Please feel free to reach out to me if you have concerns or feedback.