Medium chrome Logic Error 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactImproper input validation in Media
DescriptionImproper input validation in Media
ComponentMedia
Bug ClassLogic Error
Tracker536428842
Fix commit8f06b14492a6 (chromium/src) +9/-0
CISA KEVNot listed
CreditedGoogle
Disclosed2026-08-25

Files Changed

  • media/gpu/av1_decoder.cc
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