CVE-2026-10981
Overview
Files Changed
media/gpu/vp9_decoder.cc
Patch
From 8def0f2575cda293ba16480642205be522150517 Mon Sep 17 00:00:00 2001
From: Ted Meyer <tmathmeyer@chromium.org>
Date: Tue, 26 May 2026 10:22:02 -0700
Subject: [PATCH] clamp renderable frame size for VP9-kSVC
`new_pic_size` is potentially larger than the actual gpu-allocated
buffers, which have a maximum size of `frame_width` x `frame_height`.
The renderable width and height should be clamped to this.
Fixed: 513762354
Change-Id: Ie5d99c7b86ae4e41bd4e817bcc7821025af33c50
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7861639
Reviewed-by: Dale Curtis <dalecurtis@chromium.org>
Reviewed-by: James Zern <jzern@google.com>
Commit-Queue: Dale Curtis <dalecurtis@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1636285}
---
diff --git a/media/gpu/vp9_decoder.cc b/media/gpu/vp9_decoder.cc
index 55ba85da..912c1c9 100644
--- a/media/gpu/vp9_decoder.cc
+++ b/media/gpu/vp9_decoder.cc
@@ -250,12 +250,18 @@
gfx::Size new_pic_size = curr_frame_size_;
gfx::Rect new_render_rect(curr_frame_hdr_->render_width,
curr_frame_hdr_->render_height);
- // For safety, check the validity of render size or leave it as pic size.
- if (!gfx::Rect(new_pic_size).Contains(new_render_rect)) {
- DVLOG(1) << "Render size exceeds picture size. render size: "
+ const gfx::Rect frame_size(curr_frame_hdr_->frame_width,
+ curr_frame_hdr_->frame_height);
+ if (!frame_size.Contains(new_render_rect)) {
+ // For safety, check the validity of render size or leave it as the actual
+ // per-frame decoded size. In the k-SVC path |curr_frame_size_| is the
+ // *maximum* layer size (Vp9Parser::ParseSVCFrame stamps allocate_size
+ // onto every layer), so clamping against it would allow render_rect to
+ // extend into the undecoded region of the surface (b/149727823).
+ DVLOG(1) << "Render size exceeds frame size. render size: "
<< new_render_rect.ToString()
- << ", picture size: " << new_pic_size.ToString();
- new_render_rect = gfx::Rect(new_pic_size);
+ << ", frame size: " << frame_size.ToString();
+ new_render_rect = frame_size;
}
VideoCodecProfile new_profile =
VP9ProfileToVideoCodecProfile(curr_frame_hdr_->profile);
Original Bug Report
VP9 k-SVC: Potential GPU information leak via improper render_rect clamping
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: In VP9 k-SVC hardware decoding, the GPU process incorrectly uses the maximum spatial layer resolution to clamp the bitstream-provided render size for every layer in a frame. This allows a compromised renderer to craft a stream that outputs a VideoFrame exposing undecoded regions of a GPU surface, potentially leaking stale data from other origins.
Affected files:
media/gpu/vp9_decoder.ccmedia/parsers/vp9_parser.ccmedia/parsers/vp9_uncompressed_header_parser.ccmedia/gpu/vaapi/vp9_vaapi_video_decoder_delegate.ccmedia/gpu/vaapi/vaapi_video_decoder.cc
Estimated timestamp from git blame: 2019-07-19
Summary
A potential security vulnerability exists in Chromium’s VP9 k-SVC (Spatial Scalability) hardware decoding path, primarily affecting platforms using the VA-API (e.g., ChromeOS on x86). The issue involves a logic error in how the render_rect (which determines the visible_rect of an output VideoFrame) is validated against the actual frame dimensions during SVC decoding. This could allow a compromised renderer to leak uninitialized or stale GPU memory from the GPU process.
Root Cause Analysis
In media/parsers/vp9_parser.cc, the function Vp9Parser::ParseSVCFrame() handles superframes containing multiple spatial layers. It iterates through all sub-frames to find the maximum frame_width and frame_height among all layers and then assigns this maximum size to the allocate_size property of every layer in the SVC frame:
// media/parsers/vp9_parser.cc
for (const auto& frame_info : frames) {
// ... (pre-parses headers to find max size)
max_frame_size.SetToMax(gfx::Size(curr_frame_header_.frame_width,
curr_frame_header_.frame_height));
}
for (auto& frame_info : frames) {
frame_info.allocate_size = max_frame_size;
}
When VP9Decoder::Decode() (in media/gpu/vp9_decoder.cc) processes a sub-frame, it uses this allocate_size (stored in curr_frame_size_) to clamp the bitstream-provided render_width and render_height:
// media/gpu/vp9_decoder.cc
gfx::Size new_pic_size = curr_frame_size_; // The maximum size across all SVC layers
gfx::Rect new_render_rect(curr_frame_hdr_->render_width, curr_frame_hdr_->render_height);
// Safety clamp: incorrectly compares against max layer size instead of current layer size
if (!gfx::Rect(new_pic_size).Contains(new_render_rect)) {
new_render_rect = gfx::Rect(new_pic_size);
}
Because new_pic_size represents the maximum layer size rather than the current layer’s resolution, a small layer (e.g., 64x64) can specify a large render_rect (e.g., 4096x4096) that bypasses the clamp check as long as a 4096x4096 layer exists elsewhere in the same SVC superframe.
Potential Impact
The hardware decoder only writes pixel data to the actual resolution of the layer (e.g., 64x64). However, the resulting VideoFrame is returned to the renderer with an oversized visible_rect (e.g., 4096x4096).
A compromised renderer can read the content of the undecoded regions using APIs like WebCodecs or WebGL. Since GPU surfaces are frequently recycled in the GPU process, these regions may contain stale contents from surfaces previously used by other origins, leading to a cross-origin information leak. This is particularly relevant on Android and ChromeOS where the GPU process may have different sandboxing or memory management characteristics.
Suggested Potential Reproduction Steps
- From a compromised renderer, initialize a
media.mojom.VideoDecoderfor VP9. - Craft a VP9 k-SVC buffer containing two spatial layers:
- Layer 0: Small resolution (64x64), but with bitstream
render_width/render_heightset to 4096. - Layer 1: High resolution (4096x4096).
- Layer 0: Small resolution (64x64), but with bitstream
- Submit the buffer via Mojo for decoding.
- The GPU process decodes Layer 0 but outputs a
VideoFramewith avisible_rectof 4096x4096. - The renderer receives the frame and reads the pixels outside the 64x64 top-left corner to observe stale GPU memory.
Recommended Fix
The render_rect should be clamped against the actual frame_width and frame_height of the layer currently being decoded, rather than the allocate_size which reflects the maximum across the SVC group. In media/gpu/vp9_decoder.cc, use curr_frame_hdr_->frame_width and curr_frame_hdr_->frame_height for the safety clamp check.
Evaluated with Chrome root at commit: 1a8d40fc44df2088d5945c0bf53584038aa1614a
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.