Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactOut of bounds read and write in Codecs
DescriptionOut of bounds read and write in Codecs
ComponentCodecs
Bug ClassOOB
Tracker520565945
Fix commitf77363617210 (chromium/src) +38/-16
CISA KEVNot listed
CreditedGoogle
Disclosed2026-07-08

Changed Functions

FunctionChangeNotes
if
media/gpu/av1_decoder.cc
modified

Files Changed

  • media/gpu/av1_decoder.cc
From f77363617210dd46fb5b7f765c4da1af718a7393 Mon Sep 17 00:00:00 2001
From: Sangwhan Moon <sxm@google.com>
Date: Wed, 10 Jun 2026 16:29:49 -0700
Subject: [PATCH] Refactor AV1 sequence header validation to fix hardware context invalidation

This change extracts sequence header comparison logic into a dedicated
helper method to improve readability. Additionally, it fixes an issue
where changes to structural parameters like 128x128 superblocks and film
grain configurations failed to trigger a hardware context
reconfiguration.

Bug: 520565945
Change-Id: I205443bb8f063ff4ee8637d7aa11f0bd79efe5e6
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7912486
Commit-Queue: James Zern <jzern@google.com>
Commit-Queue: Sangwhan Moon <sxm@google.com>
Auto-Submit: Sangwhan Moon <sxm@google.com>
Reviewed-by: James Zern <jzern@google.com>
Reviewed-by: Dale Curtis <dalecurtis@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1644968}
---

diff --git a/media/gpu/av1_decoder.cc b/media/gpu/av1_decoder.cc
index e82b9550..c355985 100644
--- a/media/gpu/av1_decoder.cc
+++ b/media/gpu/av1_decoder.cc
@@ -126,6 +126,18 @@
   return skhdr::ContentLightLevelInformation::MakeUint16(
       /*maxCLL=*/cll.max_cll, /*maxFALL=*/cll.max_fall);
 }
+bool RequiresHardwareContextReset(
+    const libgav1::ObuSequenceHeader& old_header,
+    const libgav1::ObuSequenceHeader& new_header) {
+  // Changes to these fields require re-allocating GPU memory pools and context.
+  return old_header.use_128x128_superblock !=
+             new_header.use_128x128_superblock ||
+         old_header.film_grain_params_present !=
+             new_header.film_grain_params_present ||
+         old_header.enable_cdef != new_header.enable_cdef ||
+         old_header.enable_restoration != new_header.enable_restoration;
+}
+
 }  // namespace
 
 scoped_refptr<AV1Picture> AV1Decoder::AV1Accelerator::CreateAV1PictureSecure(
@@ -301,23 +313,20 @@
           return kDecodeError;
         }
 
-        current_sequence_header_ = parser_->sequence_header();
+        const auto& new_sequence_header = parser_->sequence_header();
         VideoChromaSampling new_chroma_sampling =
-            GetAV1ChromaSampling(current_sequence_header_->color_config);
-        if (new_chroma_sampling != chroma_sampling_) {
-          chroma_sampling_ = new_chroma_sampling;
-        }
+            GetAV1ChromaSampling(new_sequence_header.color_config);
 
-        if (chroma_sampling_ != VideoChromaSampling::k420 &&
-            chroma_sampling_ != VideoChromaSampling::k444) {
+        if (new_chroma_sampling != VideoChromaSampling::k420 &&
+            new_chroma_sampling != VideoChromaSampling::k444) {
           DVLOG(1) << "Only YUV 4:2:0 and YUV 4:4:4 are supported";
           return kDecodeError;
         }
 
         const VideoCodecProfile new_profile =
-            AV1ProfileToVideoCodecProfile(current_sequence_header_->profile);
+            AV1ProfileToVideoCodecProfile(new_sequence_header.profile);
         const uint8_t new_bit_depth = base::checked_cast<uint8_t>(
-            current_sequence_header_->color_config.bitdepth);
+            new_sequence_header.color_config.bitdepth);
         if (!IsValidBitDepth(new_bit_depth, new_profile)) {
           DVLOG(1) << "Invalid bit depth="
                    << base::strict_cast<int>(new_bit_depth)
@@ -326,8 +335,8 @@
         }
 
         const gfx::Size new_frame_size(
-            base::strict_cast<int>(current_sequence_header_->max_frame_width),
-            base::strict_cast<int>(current_sequence_header_->max_frame_height));
+            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));
@@ -339,7 +348,7 @@
           new_visible_rect = gfx::Rect(new_frame_size);
         }
 
-        const auto& cc = current_sequence_header_->color_config;
+        const auto& cc = new_sequence_header.color_config;
         const VideoColorSpace header_color_space =
             VideoColorSpace(cc.color_primary, cc.transfer_characteristics,
                             cc.matrix_coefficients,
@@ -361,23 +370,36 @@
                                   new_color_space != picture_color_space_;
         }
 
+        const bool structural_change =
+            current_sequence_header_ &&
+            RequiresHardwareContextReset(*current_sequence_header_,
+                                         new_sequence_header);
+
+        current_sequence_header_ = new_sequence_header;
         ClearReferenceFrames();
-        // Issues kConfigChange only if either the dimensions, profile or bit
-        // depth is changed.
+
+        // Issue kConfigChange if the sequence header changed significantly,
+        // OR if the visible rect/color space changed.
         if (frame_size_ != new_frame_size ||
             visible_rect_ != new_visible_rect || profile_ != new_profile ||
-            bit_depth_ != new_bit_depth || is_color_space_change) {
-          DVLOG(1) << "New profile: " << GetProfileName(new_profile)
+            bit_depth_ != new_bit_depth ||
+            chroma_sampling_ != new_chroma_sampling || is_color_space_change ||
+            structural_change) {
+          DVLOG(1) << "Configuration changed. New profile: "
+                   << GetProfileName(new_profile)
                    << ", new resolution: " << new_frame_size.ToString()
                    << ", new visible rect: " << new_visible_rect.ToString()
                    << ", new bit depth: "
                    << base::strict_cast<int>(new_bit_depth)
                    << ", new color space: " << new_color_space.ToString();
+
           frame_size_ = new_frame_size;
           visible_rect_ = new_visible_rect;
           profile_ = new_profile;
           bit_depth_ = new_bit_depth;
           picture_color_space_ = new_color_space;
+          chroma_sampling_ = new_chroma_sampling;
+
           std::move(clear_current_frame).Cancel();
           return kConfigChange;
         }
Loading diff…

Original Bug Report

reported by vm...@google.com

Potential AV1Decoder kConfigChange bypass when non-dimensional sequence-header fields flip

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: The AV1 decoder in Chromium does not trigger a configuration change (kConfigChange) when certain sequence-level parameters change mid-stream without a dimension or profile change. This allows updated stream-level properties to be submitted to an existing hardware decode context. This state inconsistency can lead to frame pool exhaustion on the Chromium side, or potential driver-side out-of-bounds reads/writes if the platform’s GPU driver fails to dynamically reallocate internal scratch buffers.

Affected files:

  • media/gpu/av1_decoder.cc
  • media/gpu/vaapi/av1_vaapi_video_decoder_delegate.cc
  • media/gpu/windows/d3d11_av1_accelerator.cc
  • media/gpu/v4l2/v4l2_video_decoder_delegate_av1.cc

Estimated timestamp from git blame: 2020-11-14

Description

Inside AV1Decoder::DecodeInternal() (in media/gpu/av1_decoder.cc), a new ObuSequenceHeader is saved unconditionally when libgav1 reports a sequence header change (parser_->sequence_header_changed() is true). However, a configuration change (kConfigChange) is only returned to the calling client if a subset of parameters change (such as dimensions, profile, color space, or bit depth):

// media/gpu/av1_decoder.cc
if (parser_->sequence_header_changed()) {
  ...
  current_sequence_header_ = parser_->sequence_header();
  ...
  ClearReferenceFrames();
  // Issues kConfigChange only if either the dimensions, profile or bit
  // depth is changed.
  if (frame_size_ != new_frame_size ||
      visible_rect_ != new_visible_rect || profile_ != new_profile ||
      bit_depth_ != new_bit_depth || is_color_space_change) {
    ...
    return kConfigChange;
  }
}

If an attacker crafts an AV1 bitstream where a subsequent sequence header keeps the maximum frame size, profile, and bit depth identical but flips other structural properties—such as use_128x128_superblock, film_grain_params_present, enable_cdef, or enable_restoration—the parser’s ParametersChanged() evaluates to true, but kConfigChange is not returned.

Potential Consequences

1. Chromium-Side Buffer Pool Exhaustion (Denial of Service)

When film_grain_params_present is toggled mid-stream from false to true, the decoder requires twice as many surfaces because it must allocate separate display and reconstruction surfaces for film grain synthesis. However, since no kConfigChange is triggered, the buffer pool size is never renegotiated with the client decoder, and the decoder continues to use the existing smaller pool. This leads to surface allocation failures (CreateSurface returning nullptr), resulting in kRanOutOfSurfaces state and a complete playback stall/crash.

2. Driver-Side Memory Corruption (Out-of-Bounds Read/Write)

Under-the-hood GPU drivers (such as Intel iHD, Mesa, or vendor D3D11/V4L2 drivers) allocate internal scratch, line, and auxiliary buffers (such as CDEF scratch or loop-restoration line buffers) once during hardware decoding context creation based on the initial sequence parameters. When parameters like the superblock size flip mid-context (e.g., from 64x64 to 128x128) without context recreation, the driver may attempt to execute operations using the outdated, smaller buffer sizes. Depending on the platform’s graphics driver implementation and validation rigor, this state mismatch could lead to driver-side heap out-of-bounds reads/writes in the GPU process.

Potential Reproduction Steps (Suggested)

(Note: These are suggested steps. Our analysis is based on static review of the codebase, and we have not run these steps programmatically.)

  1. Serve a video containing an AV1 stream with two consecutive sequence headers:
    • Sequence Header A: max_frame_width=256, max_frame_height=256, use_128x128_superblock=false, film_grain_params_present=false, enable_cdef=false.
    • Sequence Header B: max_frame_width=256, max_frame_height=256, use_128x128_superblock=true, film_grain_params_present=true, enable_cdef=true.
  2. Observe that upon transitioning from Header A to Header B, AV1Decoder::DecodeInternal() does not return kConfigChange because the dimensions and profile are identical.
  3. The updated parameters (superblock size, film grain, and CDEF flags) are transmitted directly to the active hardware decoder context via the accelerator delegates (e.g., SubmitDecode).
  4. Monitor the GPU process for a playback stall (due to surface pool exhaustion) or potential memory corruption inside the platform-specific GPU driver.

Suggested Fix

Update the gating check for returning kConfigChange in media/gpu/av1_decoder.cc to also include any sequence header fields that alter allocation bounds, or trigger a full context recreation whenever any parameter in ParametersChanged() is modified:

// Suggested modification
if (frame_size_ != new_frame_size ||
    visible_rect_ != new_visible_rect || profile_ != new_profile ||
    bit_depth_ != new_bit_depth || is_color_space_change ||
    current_sequence_header_->use_128x128_superblock != old_sequence_header.use_128x128_superblock ||
    current_sequence_header_->film_grain_params_present != old_sequence_header.film_grain_params_present) {
  ...
  return kConfigChange;
}

Evaluated with Chrome root at commit: e9507a33bb4148ee071aaaf8a7e9ad68770359bf


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