CVE-2026-76035
Overview
Background
- `VideoToolboxAV1Accelerator`
- the macOS-only hardware AV1 decode path in
media/gpu/mac/. It turns parsed AV1 sequence and frame headers into a CoreMediaCMFormatDescriptionRef(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 throughCreateFormatExtensions. That function returns anullptrbuffer 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_andactive_coded_size_, and rebuildsactive_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.
av1C buffer and makes bit depth and sequence max dimensions part of the format identity.Attack Path
- 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). - 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 emptyav1c_span. - Parameter switch without reconfiguration
Alternatively, the stream keeps frame width and height constant but changes
color_config.bitdepthor the sequence max frame size. The staleactive_format_is reused, which the newBitDepthChangeTriggersFormatRegenerationtest exercises. - 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
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifmedia/gpu/mac/video_toolbox_av1_accelerator.cc |
modified | |
TEST_Fmedia/gpu/mac/video_toolbox_av1_accelerator_unittest.cc |
modified |
Files Changed
media/gpu/mac/video_toolbox_av1_accelerator.ccmedia/gpu/mac/video_toolbox_av1_accelerator.hmedia/gpu/mac/video_toolbox_av1_accelerator_unittest.cc
Audit Directions
- Unchecked parser helper outputsLook in the other VideoToolbox accelerators (H.264, HEVC, VP9) and the other platform decoders for codec-config builders (
avcC/hvcC/vpcCequivalents) whose null or short output is wrapped in a span and passed toCreateFormatExtensionsor CoreMedia APIs without validation. - Incomplete reconfiguration keysCheck 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 buffersAudit
UNSAFE_TODO(base::span(ptr, size))construction in media code whereptr/sizecome from a third-party library, and make sure null or zero-length results are handled before use.
Patch
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
Regression Test / PoC
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
Original Bug Report
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.ccmedia/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:
- Unchecked Codec Configuration Box (
av1C) Retrieval: Format regeneration does not validate the output oflibgav1::ObuParser::GetAV1CodecConfigurationBox(). If format regeneration is triggered on a frame lacking an in-band Sequence Header,libgav1returns anullptrwith a size of0. The accelerator blindly wraps this in aspan, creating a 0-byteav1Cextension atom that is passed to Apple CoreMedia’sVTDecompressionSessionCreate. This violates the AV1-ISOBMFF specification (requiring >= 4 bytes) and hands a 0-byte bounds-bearing configuration structure to the driver. - 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 viaVTDecompressionSessionDecodeFrame, 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
- 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 intotemporal_unit_data_.ProcessFormatexecutes, branches into format regeneration, and extracts a validav1Cvialibgav1::ObuParser::GetAV1CodecConfigurationBox(media/gpu/mac/video_toolbox_av1_accelerator.cc:204).active_coded_size_is saved as{2048, 2048}. A validVTDecompressionSessionis created.
- 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::DecodeInternalprocesses this without returningkConfigChange.ProcessFormatcomputes the new per-framecoded_sizeas{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,libgav1returnskStatusBitstreamErrorand outputsnullptrand*av1c_size = 0(third_party/libgav1/src/src/obu_parser.cc:3017-3019).- At
video_toolbox_av1_accelerator.cc:206,av1c_spanis 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-byteNSDataobject for theav1Cextension atom.CMVideoFormatDescriptionCreatesuccessfully builds a malformed format description, which is passed toVTDecompressionSessionCreatein Apple’s driver, risking an OOB memory fetch during parser initialization.
Case B: Stale av1C Reuse via Missing Parameter Keys
- Temporal Unit 1 (TU1): The attacker serves
[OBU_SEQUENCE_HEADER bitdepth=8, max_frame=128x128]+[OBU_FRAME key_frame 128x128].ProcessFormatinitializes a format description for 8-bit AV1 video.session_metadata_.bit_depthis set to8.- A VideoToolbox session configured with an 8-bit output pixel format is initialized.
- 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::DecodeInternalparses the new sequence header and registers the bitdepth change, safely returningAcceleratedVideoDecoder::kConfigChange(media/gpu/av1_decoder.cc:385).VideoToolboxVideoDecoder::DecodereceiveskConfigChange, issues acontinue, and resumes without resetting the accelerator.ProcessFormatis 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_spaceis identical (bitdepth is not mapped intoVideoColorSpace).profileis identical.coded_size(the 128x128 per-frame size) is identical.- Because
bitdepthandmax_frameare structurally omitted from this condition, the block is skipped.active_format_is not reset. VideoToolboxDecompressionSessionManager::Processcompares theCMFormatDescriptionRefpointer (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
- Case A: Add a boundary check immediately after
GetAV1CodecConfigurationBox()inProcessFormatto ensureav1cis not null andav1c_size >= 4. If the stream lacks a sequence header (causingnullptr), the decoder should either fetch the cached header or cleanly abort decoding. - Case B: Expand the change-detection validation in
ProcessFormatto evaluatesequence_header.color_config.bitdepth != session_metadata_.bit_depthand 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:
- Data Passthrough Validation: We checked
media/gpu/av1_decoder.cc:627-646. InAV1Decoder::DecodeAndOutputPicture,*decoder_buffer_is passed as the final argument (data) toaccelerator_->SubmitDecode(). The parameter isscoped_refptr<media::DecoderBuffer>, which resolves viastd::ranges::contiguous_rangemapping directly tobase::span<const uint8_t> data. The bytes passed are identical to the chunk delivered by the renderer. - kConfigChange Handling: We verified
media/gpu/mac/video_toolbox_video_decoder.cc:260-300.VideoToolboxVideoDecoder::Decodesafely identifiesAcceleratedVideoDecoder::kConfigChange(line 273), and subsequently triggers acontinue;(line 274). This loops without resetting or alteringaccelerator_, proving the premise of Case B. - Manager Pointer Comparison: We analyzed
VideoToolboxDecompressionSessionManager::Process(media/gpu/mac/video_toolbox_decompression_session_manager.mm:154-170). The logic explicitly gatesdecompression_session_->CanAcceptFormat(format)behind the pointer comparisonformat != 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>orWebCodecsAPIs. 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.