Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactOut of bounds read in UI
DescriptionOut of bounds read in UI
ComponentUI
Bug ClassOOB
Tracker496645393
Fix commit791187a955be (chromium/src) +8/-6
CISA KEVNot listed
CreditedGoogle
Disclosed2026-05-12

Files Changed

  • chrome/browser/ui/thumbnails/background_thumbnail_video_capturer.cc
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);
       },
Loading diff…

Original Bug Report

reported by vi...@google.com

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.cc
  • chrome/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.

  1. Compromise the GPU process.
  2. Wait for the browser process to request a background tab capture (e.g., user hovers over a background tab) and intercept the FrameSinkVideoConsumer::OnFrameCaptured callback.
  3. Send a message with info.coded_size set to a small dimension (e.g., 100x100) and a backing shared memory region of 40,000 bytes.
  4. Set content_rect to maliciously large dimensions, e.g., gfx::Rect(0, 0, 100, 10000).
  5. The browser process maps the memory, passes the size check (as coded_size matches the buffer), and creates an SkBitmap of size 100x10000.
  6. The browser routes the frame to a background thread for JPEG compression, where the encoder reads 9,900 rows 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.

View on issue tracker