Medium watch CVSS 8.8 chrome Input Validation 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
8.8
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactImproper input validation in Media
DescriptionImproper input validation in Media
ComponentMedia
Bug ClassInput Validation
Action tierwatch — ub-class only — no demonstrated corruption path
Computed by PatchGap's deterministic exploitability ruler from the fixing commit. patch-now = RCE-class & attacker-reachable in a default config, or exploited in the wild; watch = real security signal below that bar; noise = no security relevance.
Tracker536428842
Fix commit8f06b14492a6 (chromium/src) +9/-0
CWECWE-20 (Improper input validation)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
CISA KEVNot listed
CreditedGoogle
Reported2026-07-19 (per vendor advisory)
Fix landed2026-07-21 (public git)
Disclosed2026-08-25
Patch gap35 days — fix public in source before the advisory

Background

`AV1Decoder`
the AV1 bitstream parser and state machine in media/gpu/av1_decoder.cc, which drives platform hardware-accelerated decoders (VA-API, V4L2, D3D11 and so on) on behalf of web media playback.
`new_frame_size`
the coded or maximum frame size taken from the AV1 sequence and frame headers; AV1 encodes each dimension with 16 bits, so values up to 65536 pixels are syntactically legal.
`media::limits`
Chromium’s global media bounds: kMaxDimension (per-axis limit, 32k per the commit message) and kMaxCanvas (maximum total pixel area) that the rest of the media pipeline assumes no frame exceeds.
`kDecodeError`
the decoder result that aborts decoding of the stream.

Root Cause Analysis

In the frame-size handling block of media/gpu/av1_decoder.cc (the enclosing function name is not shown in the hunk), the decoder accepted any non-empty new_frame_size that the bitstream supplied. The only check after it was that the render or visible rectangle fits inside the frame size. AV1 allows dimensions up to 64k, but Chromium’s media stack enforces limits::kMaxDimension (32k) and limits::kMaxCanvas, and the code after this point (surface allocation, buffer-size arithmetic, VideoFrame creation) is written on the assumption that those limits hold.

The patch adds a check that rejects a frame size whose width or height exceeds kMaxDimension, or whose checked area exceeds kMaxCanvas, and returns kDecodeError. An area overflow is treated as INT_MAX, so it also fails. The check runs immediately after the existing DCHECK(!new_frame_size.IsEmpty()) and before the visible-rect containment check, so an oversized stream is rejected before any resize or allocation logic sees it. This tightens the input validation and brings the hardware AV1 path in line with the media-wide limits.

Key insight
The AV1 hardware decoder trusted bitstream-declared frame dimensions that are legal in AV1 but larger than Chromium’s media::limits. The fix validates width, height and checked area against kMaxDimension and kMaxCanvas, and fails decoding when they are exceeded.

Attack Path

  1. Serve a crafted AV1 stream a web page supplies AV1 content through a <video> element, MSE or WebCodecs, with a sequence or frame header that declares a dimension above 32768, or a total area above kMaxCanvas.
  2. Reach hardware decode on a platform and GPU with accelerated AV1 decoding, the stream is parsed by AV1Decoder in the GPU or media process (software decode paths are not touched by this patch).
  3. Bypass size limits before the fix, the oversized new_frame_size passed the only checks (non-empty, contains the visible rect) and decoding continued with dimensions outside media::limits.
  4. Downstream mis-sizing later code that relies on those limits could compute wrong buffer sizes or allocate inconsistent surfaces (inferred; the specific sink is not shown in the patch).

Impact Assessment

The advisory describes this as improper input validation in Media and gives no in-the-wild status. A remote page can reach it with crafted AV1 media, but only where hardware AV1 decoding is used, so the GPU or media process is affected and the reachable platforms depend on the decoder hardware. Memory corruption or code execution in that process through downstream size arithmetic is (inferred), not demonstrated by the patch.

Files Changed

  • media/gpu/av1_decoder.cc

Audit Directions

  • Codec-legal versus pipeline-legal dimensions
    check the other media/gpu accelerated decoders (H.264, HEVC, VP9, VP8) and the WebCodecs config paths for frame-size acceptance that relies only on the codec’s own bitfield range rather than media::limits.
  • Area arithmetic
    look for width * height or gfx::Size::GetArea() uses on untrusted sizes that are not checked; prefer GetCheckedArea() with a fail-closed fallback.
  • Mid-stream resolution changes
    audit where a decoder re-reads sizes on keyframes or sequence-header changes, and confirm each such point repeats the bounds validation and does not rely on an earlier, initial config check.
From 8f06b14492a6591362290aa978f5f61484b26464 Mon Sep 17 00:00:00 2001
From: Ted Meyer <[email protected]>
Date: Mon, 20 Jul 2026 17:18:39 -0700
Subject: [PATCH] Bounds check av1 decoder

We have a limit of 32k pixels for any given dimension, but av1 allows
64k. We should fail to decode if it's too big.

Fixed: 536428842
Change-Id: Iedefeef7fac680674820553ceab52bf5f31ff435
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8127838
Reviewed-by: Syed AbuTalib <[email protected]>
Auto-Submit: Ted (Chromium) Meyer <[email protected]>
Commit-Queue: Syed AbuTalib <[email protected]>
Commit-Queue: Ted (Chromium) Meyer <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1665064}
---
diff --git a/media/gpu/av1_decoder.cc b/media/gpu/av1_decoder.cc
index f3b862ed..c84473a 100644
--- a/media/gpu/av1_decoder.cc
+++ b/media/gpu/av1_decoder.cc
@@ -354,7 +354,16 @@
         gfx::Rect new_visible_rect(
             base::strict_cast<int>(current_frame_header_->width),
             base::strict_cast<int>(current_frame_header_->height));
+
         DCHECK(!new_frame_size.IsEmpty());
+        if (new_frame_size.width() > limits::kMaxDimension ||
+            new_frame_size.height() > limits::kMaxDimension ||
+            new_frame_size.GetCheckedArea().ValueOrDefault(
+                std::numeric_limits<int>::max()) > limits::kMaxCanvas) {
+          DVLOG(1) << "AV1 max_frame_size " << new_frame_size.ToString()
+                   << " exceeds media::limits";
+          return kDecodeError;
+        }
         if (!gfx::Rect(new_frame_size).Contains(new_visible_rect)) {
           DVLOG(1) << "Render size exceeds picture size. render size: "
                    << new_visible_rect.ToString()
Loading diff…

Original Bug Report

reported by [email protected]

Potential Unbounded AV1 max_frame_width/height Reaches GPU Drivers (Missing kMaxDimension Check)

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: Chromium’s AV1Decoder fails to validate libgav1-parsed max_frame_width and max_frame_height (up to 65536) against limits::kMaxDimension during mid-stream resolution changes. These unvalidated dimensions reach hardware decode drivers (via vaCreateContext and ID3D11VideoDevice::CreateVideoDecoder / DXVA_PicParams_AV1) in the sandboxed GPU or OOP-VD processes. This may trigger a vendor driver-side integer overflow resulting in an undersized allocation and subsequent out-of-bounds write.

Affected files:

  • media/gpu/av1_decoder.cc
  • media/gpu/windows/d3d11_av1_accelerator.cc
  • media/gpu/windows/d3d11_decoder_configurator.cc
  • media/gpu/windows/d3d11_video_decoder.cc
  • media/gpu/vaapi/vaapi_video_decoder.cc

Estimated timestamp from git blame: 2020-11-14

1. Summary of the Issue (Meant for Human Triage)

A potential vulnerability exists in Chromium’s AV1 video decoder implementation where the hardware-decode picture size is derived from attacker-controlled sequence headers without validation against Chromium’s platform limits. Specifically, media::AV1Decoder sets the frame_size_ property directly from the sequence header’s max_frame_width and max_frame_height fields as parsed by the libgav1 library. The AV1 specification permits these fields to be as large as 65536x65536.

During a mid-stream resolution change (kConfigChange), AV1Decoder fails to restrict these parsed dimensions against standard Chromium video bounds, such as media::limits::kMaxDimension (32767) or media::limits::kMaxCanvas (2^29 - 1). The unvalidated dimensions are subsequently passed directly to low-level platform APIs in sandboxed hardware-decode processes:

  • On Windows (sandboxed GPU process): Passed to ID3D11VideoDevice::CreateVideoDecoder() and ID3D12VideoDevice::CreateVideoDecoderHeap(). Furthermore, the D3D11 accelerator delegate verbatim copies the unbounded sizes into the DXVA_PicParams_AV1 struct.
  • On Linux/ChromeOS (sandboxed OOP-VD utility process): Passed to vaCreateContext() and vaCreateSurfaces().

Because these parameters reach proprietary vendor kernel-mode and user-mode drivers without prior bounding, they present a risk of driver-side integer overflows (e.g., during buffer capacity calculations like width * height * bytes_per_pixel wrapping 32-bit limits). This can lead to undersized memory allocations and consequent out-of-bounds (OOB) writes within the driver. The issue does not apply to Android, as Android platforms utilize MediaCodecVideoDecoder rather than media::AV1Decoder.

2. Proof-of-Concept & Detailed Execution Flow

The following sequence demonstrates the execution flow from the bitstream parsing stage down to the proprietary graphics driver sinks.

(Note: These are potential steps tracing the exact code paths, as our static analysis tooling does not execute dynamic PoCs.)

Potential Attacker Steps to Trigger:

  1. Decoder Setup: An attacker uses the WebCodecs API (e.g., VideoDecoder.configure()) to initialize hardware-accelerated decoding with valid, small initial dimensions (e.g., 128x128). This passes the initial VideoDecoderConfig::IsValidConfig() check.
  2. Payload Delivery: The attacker queues a maliciously crafted AV1 Temporal Unit containing a Sequence Header OBU and a Frame Header OBU.
  3. Sequence Header Forgery: The Sequence Header is configured with frame_width_bits_minus_1 = 15, frame_height_bits_minus_1 = 15, max_frame_width_minus_1 = 65535, and max_frame_height_minus_1 = 65535. Additionally, the attacker sets seq_level_idx[0] = 31 to evade level conformance checks.
  4. Frame Header Restraint: The Frame Header OBU is configured with frame_size_override_flag = 1 and actual frame dimensions of 128x128 to pass frame bounding checks.

Detailed Execution Flow:

Step 1: Parser Level (libgav1) At third_party/libgav1/src/src/obu_parser.cc:485-492, libgav1 parses the sequence header OBU:

OBU_READ_LITERAL_OR_FAIL(4);
sequence_header.frame_width_bits = 1 + scratch;              // ∈ [1, 16]
OBU_READ_LITERAL_OR_FAIL(4);
sequence_header.frame_height_bits = 1 + scratch;             // ∈ [1, 16]
OBU_READ_LITERAL_OR_FAIL(sequence_header.frame_width_bits);
sequence_header.max_frame_width = static_cast<int32_t>(1 + scratch);   // ∈ [1, 65536]
OBU_READ_LITERAL_OR_FAIL(sequence_header.frame_height_bits);
sequence_header.max_frame_height = static_cast<int32_t>(1 + scratch);  // ∈ [1, 65536]

To bypass CheckLevelConformance (obu_parser.cc:99-183), the attacker’s seq_level_idx >= 20 (e.g., 31) triggers an early return of kStatusOk (obu_parser.cc:136). Frame-specific conformance checks (:636-640 and :688) pass because the actual frame size remains 128x128.

Step 2: AV1Decoder Resolution Transition In media/gpu/av1_decoder.cc:337-349, AV1Decoder::DecodeInternal() processes the new sequence header:

const gfx::Size new_frame_size(
    base::strict_cast<int>(new_sequence_header.max_frame_width),
    base::strict_cast<int>(new_sequence_header.max_frame_height));
gfx::Rect new_visible_rect(
    base::strict_cast<int>(current_frame_header_->width),
    base::strict_cast<int>(current_frame_header_->height));
DCHECK(!new_frame_size.IsEmpty());
if (!gfx::Rect(new_frame_size).Contains(new_visible_rect)) {
  ...
}

No check is performed against media::limits::kMaxDimension. Since a 65536x65536 rectangle logically contains a 128x128 rectangle, the code continues. At av1_decoder.cc:396, frame_size_ is updated:

frame_size_ = new_frame_size;

And the function returns kConfigChange (:404).

Step 3a: Sink - D3D11/D3D12 (Windows GPU Process) In media/gpu/windows/d3d11_video_decoder.cc:667-690, the config’s coded size is blindly updated using the new, unchecked dimensions (config_.set_coded_size(new_coded_size)). This triggers D3D11DecoderConfigurator::SetUpDecoderDescriptor (media/gpu/windows/d3d11_decoder_configurator.cc:218-225):

void D3D11DecoderConfigurator::SetUpDecoderDescriptor(const gfx::Size& coded_size) {
  decoder_desc_ = {};
  decoder_desc_.Guid = decoder_guid_;
  decoder_desc_.SampleWidth = coded_size.width();     // 65536
  decoder_desc_.SampleHeight = coded_size.height();   // 65536
  decoder_desc_.OutputFormat = dxgi_format_;
}

This descriptor is dispatched to the underlying driver via CreateVideoDecoder() (media/gpu/windows/d3d11_video_decoder_wrapper.cc:381). Simultaneously, D3D11AV1Accelerator::FillPicParams verbatim copies the fields into DXVA_PicParams_AV1 (media/gpu/windows/d3d11_av1_accelerator.cc:174-177):

pp->width = frame_header.width;
pp->height = frame_header.height;
pp->max_width = seq_header.max_frame_width;    // Verbatim copy: 65536
pp->max_height = seq_header.max_frame_height;  // Verbatim copy: 65536

These are directly submitted to the vendor’s User-Mode Driver (UMD) via ID3D11VideoContext::SubmitDecoderBuffers(), presenting a significant risk of internal driver allocations undersizing their buffers.

Step 3b: Sink - VA-API (Linux/ChromeOS OOP-VD process) In media/gpu/vaapi/vaapi_video_decoder.cc:698, decoder_->GetPicSize() is retrieved. At vaapi_video_decoder.cc:795, the wrapper creates a new driver context:

if (!vaapi_wrapper_->CreateContext(decoder_pic_size)) { ... }

This triggers vaCreateContext in media/gpu/vaapi/vaapi_wrapper.cc:2560-2563:

VAStatus va_res = vaCreateContext(
    va_display_, va_config_id_, picture_size.width(), picture_size.height(), // 65536, 65536
    flag, empty_va_surfaces_ids_pointer, empty_va_surfaces_ids_size,
    &va_context_id_);

Suggested Fix

In media/gpu/av1_decoder.cc::DecodeInternal(), prior to verifying the container boundaries, explicitly enforce media::limits::kMaxDimension limits against the parsed sequence headers.

const gfx::Size new_frame_size(
    base::strict_cast<int>(new_sequence_header.max_frame_width),
    base::strict_cast<int>(new_sequence_header.max_frame_height));

if (new_frame_size.width() > media::limits::kMaxDimension ||
    new_frame_size.height() > media::limits::kMaxDimension) {
  return kDecodeError;
}

3. Technical Verification Details (Automated Audit Logs)

Prior Critic Verdict and Reasoning

*   Severity: High (S1)
*   Brief Notes / Reasoning:
    The report accurately identifies that media::AV1Decoder::GetPicSize() can return dimensions up to 65536x65536 because libgav1::ObuSequenceHeader::max_frame_width and max_frame_height are uncontrolled by the Chromium wrapper and lack validation against media::limits::kMaxDimension (32767). The IsValidConfig() check in MojoVideoDecoderService is bypassed since the malicious sequence header triggers an internal kConfigChange that applies the new unvalidated size directly.
    
    An attacker can trigger this via a malicious AV1 bitstream (e.g., <video> or WebCodecs). The unvalidated size reaches multiple HW-decode APIs: vaCreateContext() on Linux/CrOS, and ID3D11VideoDevice::CreateVideoDecoder() on Windows.
    
    Crucially, on Windows, the D3D11 accelerator delegate (d3d11_av1_accelerator.cc) copies seq_header.max_frame_width verbatim into the DXVA_PicParams_AV1 struct passed to the driver.
    
    The report rates this as Medium (S2) arguing that driver-side consequences (OOB vs clean fail) are unconfirmed. However, the KB explicitly categorizes this as High (S1): '- Bitstream field passed unvalidated to a HW-decode driver struct: a [media parser] field read ... with no IN_RANGE_OR_RETURN, copied verbatim by an accelerator delegate (*_vaapi_video_decoder_delegate.cc, d3d11_*_accelerator.cc) into VAPictureParameterBuffer* / DXVA_PicParams_* ... Do NOT reject for "no ASAN report" — the corruption is driver-side. PoC = the bad value at the delegate copy step.'
    
    The vulnerability runs in the sandboxed GPU process (Windows) and sandboxed OOP-VD process (Linux/CrOS). It does not reach Android's unsandboxed GPU process, as Android uses MediaCodecVideoDecoder rather than media::AV1Decoder, precluding a Critical (S0) rating. Consequently, severity is High (S1).

Audit Logs & Code Reachability Proofs

  1. libgav1 Array Checks: The validator confirmed via codebase_investigator that kLevelInfo is an array of size 20 in obu_parser.cc. Supplying a seq_level_idx ≥ 20 successfully causes CheckLevelConformance to abort processing immediately and return kStatusOk without bounds validation (obu_parser.cc:136).
  2. Absence of Limit Clamping: The validator confirmed via codebase_investigator that AV1Decoder::DecodeInternal() does not invoke limits::kMaxDimension (media/base/limits.h).
  3. Config Validation Skip on Windows: Code traces from d3d11_video_decoder.cc:660-720 verify that config_.set_coded_size(new_coded_size) runs inside the kConfigChange handler block with no invocation of VideoDecoderConfig::IsValidConfig(). The bad state proceeds directly into ResetD3DVideoDecoder(), demonstrating an irrefutable path to descriptor poisoning.
  4. No Downstream Clamps in VA-API: The validator checked media/gpu/vaapi/vaapi_wrapper.cc:2543-2578 and confirmed that picture_size.width() and picture_size.height() are passed without secondary validation or clamping to vaCreateContext().
  5. Platform Exclusion: Investigation into media/gpu/args.gni (use_av1_hw_decoder) and media/gpu/BUILD.gn demonstrates Android uses MediaCodecVideoDecoder and therefore excludes media::AV1Decoder. The issue’s ceiling correctly falls to the Sandboxed GPU process boundaries (S1/High).

Evaluated with Chrome root at commit: b96d2ec58f4f5f92b540a723966b199d6e9951b4


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