High watch CVSS 9.6 chrome Logic Error 📄 Reporter bug report 🔧 Commit mapped

Overview

High
Severity
9.6
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInappropriate implementation in Media
DescriptionInappropriate implementation in Media
ComponentMedia
Sub-componentMedia
Bug ClassLogic Error
Action tierwatch — logic/policy bug — security-relevant, no memory-unsafety
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.
Tracker536439844
Fix commit8d7b91ee781c (chromium/src) +83/-5
CWECWE-20 (Improper input validation)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H
CISA KEVNot listed
CreditedGoogle
Reported2026-07-19 (per vendor advisory)
Fix landed2026-08-03 (public git)
Disclosed2026-08-18
Patch gap15 days — fix public in source before the advisory

Background

`VideoToolboxAV1Accelerator`
the macOS-only hardware AV1 decode path in media/gpu/mac/. It turns parsed AV1 sequence and frame headers into a CoreMedia CMFormatDescriptionRef (active_format_) and VideoToolbox session metadata, then submits frames to Apple’s VideoToolbox decoder.
`av1C` box
the AV1 Codec Configuration Box, built here by libgav1::ObuParser::GetAV1CodecConfigurationBox() from the stream’s sequence header OBU and embedded into the format description through CreateFormatExtensions. That function returns a nullptr buffer when it cannot build a box, for example when no sequence header is present.
Format change detection
the accelerator caches active_color_space_, active_profile_ and active_coded_size_, and rebuilds active_format_ only when one of these “key” values differs from the current picture’s values.

Root Cause Analysis

According to the commit message, VideoToolboxAV1Accelerator::ProcessFormat() had two flaws. First, it never checked the result of libgav1::ObuParser::GetAV1CodecConfigurationBox(). When the input had no valid sequence header OBU, the call returned a null av1c with av1c_size 0, which was still wrapped with UNSAFE_TODO(base::span(...)) and passed to CreateFormatExtensions. That built a malformed CoreMedia format description with an empty or null av1C. Second, the format-change key compared only color space, profile and current frame coded_size. A stream could switch bit depth (for example, 8-bit to 10-bit) or change the sequence’s max_frame_width/max_frame_height without forcing a new format description. VideoToolbox then kept decoding with a stale description that no longer matched the bitstream.

The fix returns false (logged through MEDIA_LOG) when av1c is null or shorter than 4 bytes. It also adds bit_depth and max_coded_size to the change key, tracked in the new members active_bit_depth_ and active_max_coded_size_, so the description is rebuilt whenever these parameters change.

Key insight
The accelerator trusted a parser helper’s output without checking it, and keyed its cached CoreMedia format on an incomplete set of stream parameters. Both let attacker-shaped AV1 data drive VideoToolbox with a format description that is invalid or does not match the stream. The fix validates the av1C buffer and makes bit depth and sequence max dimensions part of the format identity.

Attack Path

  1. Deliver AV1 content A web page supplies an attacker-crafted AV1 stream to a decoder that uses the VideoToolbox AV1 hardware path. This applies only on macOS hardware/OS versions where that path is chosen, through <video>/MSE or possibly WebCodecs (inferred).
  2. Invalid sequence data The stream causes GetAV1CodecConfigurationBox() to return null or a too-short box (the regression test uses 4 bytes with no sequence header OBU), and pre-patch code still builds a format description from the empty av1c_span.
  3. Parameter switch without reconfiguration Alternatively, the stream keeps frame width and height constant but changes color_config.bitdepth or the sequence max frame size. The stale active_format_ is reused, which the new BitDepthChangeTriggersFormatRegeneration test exercises.
  4. Mismatched decode VideoToolbox decodes frames whose real parameters differ from the format description and session metadata. This may cause memory-safety problems or data confusion inside the GPU-process / VideoToolbox decode pipeline (inferred; the patch shows no concrete memory sink).

Impact Assessment

The advisory classifies this as “Inappropriate implementation in Media” (a logic error). The patch shows that a malicious AV1 stream could drive the macOS VideoToolbox decoder with a malformed or stale CoreMedia format description. That could plausibly lead to decoder misbehaviour, crashes, or memory corruption in the GPU process or system decoder service (inferred). It needs only that a victim on macOS loads attacker-controlled AV1 media that is routed to hardware AV1 decode. The metadata gives no exploited-in-the-wild status or severity.

Changed Functions

FunctionChangeNotes
if
media/gpu/mac/video_toolbox_av1_accelerator.cc
modified
TEST_F
media/gpu/mac/video_toolbox_av1_accelerator_unittest.cc
modified

Files Changed

  • media/gpu/mac/video_toolbox_av1_accelerator.cc
  • media/gpu/mac/video_toolbox_av1_accelerator.h
  • media/gpu/mac/video_toolbox_av1_accelerator_unittest.cc

Audit Directions

  • Unchecked parser helper outputs
    Look in the other VideoToolbox accelerators (H.264, HEVC, VP9) and the other platform decoders for codec-config builders (avcC/hvcC/vpcC equivalents) whose null or short output is wrapped in a span and passed to CreateFormatExtensions or CoreMedia APIs without validation.
  • Incomplete reconfiguration keys
    Check every cached “active format/config” comparison in hardware decoders and encoders to confirm it includes all parameters that change buffer layout (bit depth, chroma subsampling, max dimensions, profile), and that a mid-stream change cannot reuse a stale configuration.
  • `UNSAFE_TODO` spans over external buffers
    Audit UNSAFE_TODO(base::span(ptr, size)) construction in media code where ptr/size come from a third-party library, and make sure null or zero-length results are handled before use.
From 8d7b91ee781c387a36d9fe0472f3210c3da6a3f7 Mon Sep 17 00:00:00 2001
From: Eugene Zemtsov <[email protected]>
Date: Mon, 03 Aug 2026 10:38:41 -0700
Subject: [PATCH] media: Validate av1C box and track bit depth & frame size in VT AV1 accelerator

VideoToolboxAV1Accelerator::ProcessFormat() previously failed to
validate the output of
libgav1::ObuParser::GetAV1CodecConfigurationBox(), allowing empty/null
av1C spans to construct malformed CoreMedia format descriptions.
Additionally, bit depth and sequence max frame dimensions were omitted
from format change detection keys, preventing format description updates
on stream parameter transitions. transitions.

Bug: 536439844
Change-Id: I6d3ff4086a4e8ac9470ca0816344dd6e00490b5e
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8179644
Commit-Queue: Eugene Zemtsov <[email protected]>
Reviewed-by: Dale Curtis <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1672762}
---
diff --git a/media/gpu/mac/video_toolbox_av1_accelerator.cc b/media/gpu/mac/video_toolbox_av1_accelerator.cc
index 62dd23e..689306a 100644
--- a/media/gpu/mac/video_toolbox_av1_accelerator.cc
+++ b/media/gpu/mac/video_toolbox_av1_accelerator.cc
@@ -188,14 +188,18 @@
       break;
   }
 
-  // TODO(crbug.com/40936765): Should this be the current frame size, or the
-  // sequence max frame size?
   gfx::Size coded_size(base::strict_cast<int>(pic.frame_header.width),
                        base::strict_cast<int>(pic.frame_header.height));
+  gfx::Size max_coded_size(
+      base::strict_cast<int>(sequence_header.max_frame_width),
+      base::strict_cast<int>(sequence_header.max_frame_height));
+
+  int bit_depth = sequence_header.color_config.bitdepth;
 
   // If the parameters have changed, generate a new format.
   if (color_space != active_color_space_ || profile != active_profile_ ||
-      coded_size != active_coded_size_) {
+      coded_size != active_coded_size_ || bit_depth != active_bit_depth_ ||
+      max_coded_size != active_max_coded_size_) {
     active_format_.reset();
 
     // Generate the av1c.
@@ -203,13 +207,17 @@
     std::unique_ptr<uint8_t[]> av1c =
         libgav1::ObuParser::GetAV1CodecConfigurationBox(
             data.data(), data.size(), &av1c_size);
+    if (!av1c || av1c_size < 4) {
+      MEDIA_LOG(ERROR, media_log_.get())
+          << "Failed to extract valid AV1CodecConfigurationBox";
+      return false;
+    }
     auto av1c_span =
         UNSAFE_TODO(base::span<const uint8_t>(av1c.get(), av1c_size));
 
     // Build a format configuration with AV1 extensions.
     base::apple::ScopedCFTypeRef<CFDictionaryRef> format_config =
-        CreateFormatExtensions(kCMVideoCodecType_AV1, profile,
-                               sequence_header.color_config.bitdepth,
+        CreateFormatExtensions(kCMVideoCodecType_AV1, profile, bit_depth,
                                color_space, av1c_span);
     if (!format_config) {
       MEDIA_LOG(ERROR, media_log_.get())
@@ -235,6 +243,8 @@
     active_color_space_ = color_space;
     active_profile_ = profile;
     active_coded_size_ = coded_size;
+    active_max_coded_size_ = max_coded_size;
+    active_bit_depth_ = bit_depth;
 
     // Update session configuration.
     session_metadata_ = VideoToolboxDecompressionSessionMetadata{
diff --git a/media/gpu/mac/video_toolbox_av1_accelerator.h b/media/gpu/mac/video_toolbox_av1_accelerator.h
index 2f38bdb6..2f6b183 100644
--- a/media/gpu/mac/video_toolbox_av1_accelerator.h
+++ b/media/gpu/mac/video_toolbox_av1_accelerator.h
@@ -66,6 +66,8 @@
   VideoColorSpace active_color_space_;
   VideoCodecProfile active_profile_ = VIDEO_CODEC_PROFILE_UNKNOWN;
   gfx::Size active_coded_size_;
+  gfx::Size active_max_coded_size_;
+  int active_bit_depth_ = 0;
 
   base::apple::ScopedCFTypeRef<CMFormatDescriptionRef> active_format_;
   VideoToolboxDecompressionSessionMetadata session_metadata_;
diff --git a/media/gpu/mac/video_toolbox_av1_accelerator_unittest.cc b/media/gpu/mac/video_toolbox_av1_accelerator_unittest.cc
index 0679898..afef753 100644
--- a/media/gpu/mac/video_toolbox_av1_accelerator_unittest.cc
+++ b/media/gpu/mac/video_toolbox_av1_accelerator_unittest.cc
@@ -133,4 +133,70 @@
   EXPECT_THAT(data2, ElementsAreArray(show_existing_frame_data));
 }
 
+TEST_F(VideoToolboxAV1AcceleratorTest, InvalidAV1CBox) {
+  // Data without sequence header OBU will fail to yield valid av1C.
+  constexpr uint8_t invalid_data[] = {0x01, 0x02, 0x03, 0x04};
+
+  libgav1::ObuSequenceHeader sequence_header = {};
+  sequence_header.profile = libgav1::kProfile0;
+  sequence_header.color_config.bitdepth = 8;
+
+  const AV1ReferenceFrameVector ref_frames;
+  const libgav1::Vector<libgav1::TileBuffer> tile_buffers;
+
+  scoped_refptr<AV1Picture> pic = accelerator_->CreateAV1Picture(false);
+  pic->frame_header.width = 320;
+  pic->frame_header.height = 240;
+
+  accelerator_->SetStream(base::span(invalid_data), nullptr);
+  EXPECT_EQ(accelerator_->SubmitDecode(*pic, sequence_header, ref_frames,
+                                       tile_buffers, base::span(invalid_data)),
+            AV1Decoder::AV1Accelerator::Status::kFail);
+}
+
+TEST_F(VideoToolboxAV1AcceleratorTest,
+       BitDepthChangeTriggersFormatRegeneration) {
+  constexpr uint8_t frame_data[] = {0x0a, 0x0b, 0x00, 0x00, 0x00, 0x04, 0x3c,
+                                    0xff, 0xbc, 0xfb, 0xf9, 0x80, 0x40};
+
+  const AV1ReferenceFrameVector ref_frames;
+  const libgav1::Vector<libgav1::TileBuffer> tile_buffers;
+
+  scoped_refptr<AV1Picture> pic = accelerator_->CreateAV1Picture(false);
+  pic->frame_header.width = 320;
+  pic->frame_header.height = 240;
+  pic->set_visible_rect(gfx::Rect(320, 240));
+
+  VideoToolboxDecompressionSessionMetadata metadata_8bit;
+  VideoToolboxDecompressionSessionMetadata metadata_10bit;
+
+  // First decode: 8-bit.
+  libgav1::ObuSequenceHeader sequence_header_8bit = {};
+  sequence_header_8bit.profile = libgav1::kProfile0;
+  sequence_header_8bit.color_config.bitdepth = 8;
+
+  EXPECT_CALL(*this, OnDecode(_, _, _)).WillOnce(SaveArg<1>(&metadata_8bit));
+  EXPECT_CALL(*this, OnOutput(_));
+  accelerator_->SetStream(base::span(frame_data), nullptr);
+  EXPECT_EQ(accelerator_->SubmitDecode(*pic, sequence_header_8bit, ref_frames,
+                                       tile_buffers, base::span(frame_data)),
+            AV1Decoder::AV1Accelerator::Status::kOk);
+  accelerator_->OutputPicture(*pic);
+  EXPECT_EQ(metadata_8bit.bit_depth, 8);
+
+  // Second decode: Bit depth changes to 10-bit with frame dimensions unchanged.
+  libgav1::ObuSequenceHeader sequence_header_10bit = {};
+  sequence_header_10bit.profile = libgav1::kProfile0;
+  sequence_header_10bit.color_config.bitdepth = 10;
+
+  EXPECT_CALL(*this, OnDecode(_, _, _)).WillOnce(SaveArg<1>(&metadata_10bit));
+  EXPECT_CALL(*this, OnOutput(_));
+  accelerator_->SetStream(base::span(frame_data), nullptr);
+  EXPECT_EQ(accelerator_->SubmitDecode(*pic, sequence_header_10bit, ref_frames,
+                                       tile_buffers, base::span(frame_data)),
+            AV1Decoder::AV1Accelerator::Status::kOk);
+  accelerator_->OutputPicture(*pic);
+  EXPECT_EQ(metadata_10bit.bit_depth, 10);
+}
+
 }  // namespace media
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/media/gpu/mac/video_toolbox_av1_accelerator_unittest.cc b/media/gpu/mac/video_toolbox_av1_accelerator_unittest.cc
index 0679898..afef753 100644
--- a/media/gpu/mac/video_toolbox_av1_accelerator_unittest.cc
+++ b/media/gpu/mac/video_toolbox_av1_accelerator_unittest.cc
@@ -133,4 +133,70 @@
   EXPECT_THAT(data2, ElementsAreArray(show_existing_frame_data));
 }
 
+TEST_F(VideoToolboxAV1AcceleratorTest, InvalidAV1CBox) {
+  // Data without sequence header OBU will fail to yield valid av1C.
+  constexpr uint8_t invalid_data[] = {0x01, 0x02, 0x03, 0x04};
+
+  libgav1::ObuSequenceHeader sequence_header = {};
+  sequence_header.profile = libgav1::kProfile0;
+  sequence_header.color_config.bitdepth = 8;
+
+  const AV1ReferenceFrameVector ref_frames;
+  const libgav1::Vector<libgav1::TileBuffer> tile_buffers;
+
+  scoped_refptr<AV1Picture> pic = accelerator_->CreateAV1Picture(false);
+  pic->frame_header.width = 320;
+  pic->frame_header.height = 240;
+
+  accelerator_->SetStream(base::span(invalid_data), nullptr);
+  EXPECT_EQ(accelerator_->SubmitDecode(*pic, sequence_header, ref_frames,
+                                       tile_buffers, base::span(invalid_data)),
+            AV1Decoder::AV1Accelerator::Status::kFail);
+}
+
+TEST_F(VideoToolboxAV1AcceleratorTest,
+       BitDepthChangeTriggersFormatRegeneration) {
+  constexpr uint8_t frame_data[] = {0x0a, 0x0b, 0x00, 0x00, 0x00, 0x04, 0x3c,
+                                    0xff, 0xbc, 0xfb, 0xf9, 0x80, 0x40};
+
+  const AV1ReferenceFrameVector ref_frames;
+  const libgav1::Vector<libgav1::TileBuffer> tile_buffers;
+
+  scoped_refptr<AV1Picture> pic = accelerator_->CreateAV1Picture(false);
+  pic->frame_header.width = 320;
+  pic->frame_header.height = 240;
+  pic->set_visible_rect(gfx::Rect(320, 240));
+
+  VideoToolboxDecompressionSessionMetadata metadata_8bit;
+  VideoToolboxDecompressionSessionMetadata metadata_10bit;
+
+  // First decode: 8-bit.
+  libgav1::ObuSequenceHeader sequence_header_8bit = {};
+  sequence_header_8bit.profile = libgav1::kProfile0;
+  sequence_header_8bit.color_config.bitdepth = 8;
+
+  EXPECT_CALL(*this, OnDecode(_, _, _)).WillOnce(SaveArg<1>(&metadata_8bit));
+  EXPECT_CALL(*this, OnOutput(_));
+  accelerator_->SetStream(base::span(frame_data), nullptr);
+  EXPECT_EQ(accelerator_->SubmitDecode(*pic, sequence_header_8bit, ref_frames,
+                                       tile_buffers, base::span(frame_data)),
+            AV1Decoder::AV1Accelerator::Status::kOk);
+  accelerator_->OutputPicture(*pic);
+  EXPECT_EQ(metadata_8bit.bit_depth, 8);
+
+  // Second decode: Bit depth changes to 10-bit with frame dimensions unchanged.
+  libgav1::ObuSequenceHeader sequence_header_10bit = {};
+  sequence_header_10bit.profile = libgav1::kProfile0;
+  sequence_header_10bit.color_config.bitdepth = 10;
+
+  EXPECT_CALL(*this, OnDecode(_, _, _)).WillOnce(SaveArg<1>(&metadata_10bit));
+  EXPECT_CALL(*this, OnOutput(_));
+  accelerator_->SetStream(base::span(frame_data), nullptr);
+  EXPECT_EQ(accelerator_->SubmitDecode(*pic, sequence_header_10bit, ref_frames,
+                                       tile_buffers, base::span(frame_data)),
+            AV1Decoder::AV1Accelerator::Status::kOk);
+  accelerator_->OutputPicture(*pic);
+  EXPECT_EQ(metadata_10bit.bit_depth, 10);
+}
+
 }  // namespace media
Loading diff…

Original Bug Report

reported by [email protected]

Potential VT driver OOB via unvalidated AV1 av1C generation and flawed change-detection logic

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 macOS VideoToolbox AV1 accelerator fails to validate the size of generated av1C atoms and omits bit depth/max frame dimensions from its format change-detection logic. An attacker can feed malicious streams to trigger a 0-byte configuration record or reuse a stale 8-bit session for 10-bit decoding. This can result in potential driver-side out-of-bounds memory corruption within the sandboxed GPU process.

Affected files:

  • media/gpu/mac/video_toolbox_av1_accelerator.cc
  • media/gpu/mac/vt_config_util.mm

Estimated timestamp from git blame: 2023-11-14

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

A security logic vulnerability exists in the macOS AV1 hardware decoder path (VideoToolboxAV1Accelerator::ProcessFormat). An attacker-controlled server can deliver a maliciously crafted AV1 bitstream to force Apple’s VideoToolbox graphics driver into processing out-of-spec or inconsistent stream structures, potentially causing out-of-bounds memory operations within the sandboxed GPU process.

The vulnerability stems from two independent flaws in the format regeneration logic:

  1. Unchecked Codec Configuration Box (av1C) Retrieval: Format regeneration does not validate the output of libgav1::ObuParser::GetAV1CodecConfigurationBox(). If format regeneration is triggered on a frame lacking an in-band Sequence Header, libgav1 returns a nullptr with a size of 0. The accelerator blindly wraps this in a span, creating a 0-byte av1C extension atom that is passed to Apple CoreMedia’s VTDecompressionSessionCreate. This violates the AV1-ISOBMFF specification (requiring >= 4 bytes) and hands a 0-byte bounds-bearing configuration structure to the driver.
  2. Insufficient Format Change-Detection Keys: The accelerator tracks format changes by checking only {color_space, profile, per-frame coded_size}. It completely omits the stream’s bit depth and sequence-level maximum frame dimensions (max_frame_width, max_frame_height). If a stream transitions from 8-bit to 10-bit, or drastically increases its maximum sequence frame size while holding the per-frame size constant, the format regeneration block is skipped. A stale 8-bit configuration is then used to decode 10-bit inputs via VTDecompressionSessionDecodeFrame, forcing the driver to decode into undersized/incorrectly formatted memory surfaces.

Note: These steps represent potential exploitation paths based on code analysis; our tooling agent does not run or execute code to empirically demonstrate the Apple VideoToolbox internal driver panic.


2. Proof-of-Concept & Detailed Execution Flow

The following outlines the potential steps an attacker could take using a webpage with a <video> tag or WebCodecs configured for hardware AV1 decode on macOS (M3+ Apple Silicon).

Case A: 0-byte av1C Driver Submission via Reference-Frame Scaling

  1. Temporal Unit 1 (TU1): The attacker serves a chunk containing [OBU_SEQUENCE_HEADER max_frame=4096x4096] and [OBU_FRAME key_frame width=2048 height=2048].
    • AV1Decoder::SetStream() copies this into temporal_unit_data_.
    • ProcessFormat executes, branches into format regeneration, and extracts a valid av1C via libgav1::ObuParser::GetAV1CodecConfigurationBox (media/gpu/mac/video_toolbox_av1_accelerator.cc:204).
    • active_coded_size_ is saved as {2048, 2048}. A valid VTDecompressionSession is created.
  2. Temporal Unit 2 (TU2): The attacker serves a chunk containing no Sequence Header OBU, just an [OBU_FRAME inter_frame frame_size_override_flag=1 width=1024 height=1024].
    • AV1Decoder::DecodeInternal processes this without returning kConfigChange.
    • ProcessFormat computes the new per-frame coded_size as {1024, 1024}.
    • At video_toolbox_av1_accelerator.cc:197, coded_size != active_coded_size_, so the regeneration branch is entered. active_format_.reset() executes.
    • libgav1::ObuParser::GetAV1CodecConfigurationBox() runs on the TU2 bytes. Finding no sequence header, libgav1 returns kStatusBitstreamError and outputs nullptr and *av1c_size = 0 (third_party/libgav1/src/src/obu_parser.cc:3017-3019).
    • At video_toolbox_av1_accelerator.cc:206, av1c_span is constructed with {nullptr, 0}. No null-check or size boundary validation is performed.
    • CreateFormatExtensions (vt_config_util.mm:241) receives the span. [NSData dataWithBytes:nullptr length:0] creates a 0-byte NSData object for the av1C extension atom.
    • CMVideoFormatDescriptionCreate successfully builds a malformed format description, which is passed to VTDecompressionSessionCreate in Apple’s driver, risking an OOB memory fetch during parser initialization.

Case B: Stale av1C Reuse via Missing Parameter Keys

  1. Temporal Unit 1 (TU1): The attacker serves [OBU_SEQUENCE_HEADER bitdepth=8, max_frame=128x128] + [OBU_FRAME key_frame 128x128].
    • ProcessFormat initializes a format description for 8-bit AV1 video. session_metadata_.bit_depth is set to 8.
    • A VideoToolbox session configured with an 8-bit output pixel format is initialized.
  2. Temporal Unit 2 (TU2): The attacker serves [OBU_SEQUENCE_HEADER bitdepth=10, max_frame=8192x8192] + [OBU_FRAME frame_size_override_flag=1 width=128 height=128].
    • AV1Decoder::DecodeInternal parses the new sequence header and registers the bitdepth change, safely returning AcceleratedVideoDecoder::kConfigChange (media/gpu/av1_decoder.cc:385).
    • VideoToolboxVideoDecoder::Decode receives kConfigChange, issues a continue, and resumes without resetting the accelerator.
    • ProcessFormat is invoked. It checks if the parameters changed (video_toolbox_av1_accelerator.cc:197):
      if (color_space != active_color_space_ || profile != active_profile_ || coded_size != active_coded_size_)
      
    • color_space is identical (bitdepth is not mapped into VideoColorSpace). profile is identical. coded_size (the 128x128 per-frame size) is identical.
    • Because bitdepth and max_frame are structurally omitted from this condition, the block is skipped. active_format_ is not reset.
    • VideoToolboxDecompressionSessionManager::Process compares the CMFormatDescriptionRef pointer (format != active_format_.get()). Since they match, it bypasses format evaluation.
    • The macOS VideoToolbox HW-accelerated driver (VTDecompressionSessionDecodeFrame) begins decoding a 10-bit frame with an 8192x8192 sequence maximum into an 8-bit driver-allocated context, forcing an out-of-bounds driver memory write.

Suggested Fix

  1. Case A: Add a boundary check immediately after GetAV1CodecConfigurationBox() in ProcessFormat to ensure av1c is not null and av1c_size >= 4. If the stream lacks a sequence header (causing nullptr), the decoder should either fetch the cached header or cleanly abort decoding.
  2. Case B: Expand the change-detection validation in ProcessFormat to evaluate sequence_header.color_config.bitdepth != session_metadata_.bit_depth and include a comparison against the maximum sequence dimensions.

3. Technical Verification Details (Automated Audit Logs - Reviewers may skip this section)

> Verbatim Critic Verdict: The vulnerability report is accurate and verified in the codebase. In media/gpu/mac/video_toolbox_av1_accelerator.cc, ProcessFormat fails to validate the return value of libgav1::ObuParser::GetAV1CodecConfigurationBox(). If a frame chunk lacking a sequence header triggers a format regeneration (e.g., due to a per-frame resolution change), it returns nullptr and size 0. This directly leads to a 0-byte av1C atom being passed to VTDecompressionSessionCreate, violating the AV1-ISOBMFF §2.3 spec (which requires >= 4 bytes). Furthermore, the change-detection key at line 197 omits bitdepth and max_frame, meaning an in-band sequence header change from 8-bit to 10-bit will not trigger a format regeneration, feeding a 10-bit stream to an 8-bit VTDecompressionSessionDecodeFrame. > > Severity Justification: High (S1). > 1. Ceiling (S1): The vulnerability exists in media/gpu/mac/, which is macOS-specific and compile-time excluded on Android. Therefore, the GPU process is sandboxed on the target platform. Memory corruption in a sandboxed GPU process reachable from web content is capped at High (S1); the Android-unsandboxed Critical (S0) rationale does not apply. > 2. Modifier (S2 -> S1): The original report conservatively rated this as Medium (S2) because the closed-source nature of Apple’s VideoToolbox prevented empirical demonstration of an OOB write. However, per the explicit instruction: ‘Bitstream field passed unvalidated to a HW-decode driver struct… Do NOT reject for no ASAN report — the corruption is driver-side. PoC = the bad value at the delegate copy step.’ Because the spec-invalid values observably reach the driver call (VTDecompressionSessionCreate and VTDecompressionSessionDecodeFrame), we assess it at the full S1 severity.

Codebase Investigator Verifications:

  1. Data Passthrough Validation: We checked media/gpu/av1_decoder.cc:627-646. In AV1Decoder::DecodeAndOutputPicture, *decoder_buffer_ is passed as the final argument (data) to accelerator_->SubmitDecode(). The parameter is scoped_refptr<media::DecoderBuffer>, which resolves via std::ranges::contiguous_range mapping directly to base::span<const uint8_t> data. The bytes passed are identical to the chunk delivered by the renderer.
  2. kConfigChange Handling: We verified media/gpu/mac/video_toolbox_video_decoder.cc:260-300. VideoToolboxVideoDecoder::Decode safely identifies AcceleratedVideoDecoder::kConfigChange (line 273), and subsequently triggers a continue; (line 274). This loops without resetting or altering accelerator_, proving the premise of Case B.
  3. Manager Pointer Comparison: We analyzed VideoToolboxDecompressionSessionManager::Process (media/gpu/mac/video_toolbox_decompression_session_manager.mm:154-170). The logic explicitly gates decompression_session_->CanAcceptFormat(format) behind the pointer comparison format != active_format_.get(). If the format description reference matches, validation is bypassed.

Environmental & Feature Checks:

  • The feature requires VTIsHardwareDecodeSupported(kCMVideoCodecType_AV1), natively available on Apple Silicon M3+ / A17 Pro+ hardware.
  • The vulnerability triggers purely through A-SERVER logic via the <video> or WebCodecs APIs. No user interaction or prior renderer compromise is required.

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