CVE-2026-8548
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
formedia/gpu/windows/d3d12_video_encode_av1_delegate.cc |
modified | |
formedia/gpu/windows/d3d12_video_encode_h264_delegate.cc |
modified | |
formedia/gpu/windows/d3d12_video_encode_h265_delegate.cc |
modified |
Files Changed
media/gpu/windows/d3d12_video_encode_av1_delegate.ccmedia/gpu/windows/d3d12_video_encode_h264_delegate.ccmedia/gpu/windows/d3d12_video_encode_h265_delegate.cc
Patch
From b4b39ce12995f6ca6e5580e3e503a36594cd8e8c Mon Sep 17 00:00:00 2001
From: Eugene Zemtsov <eugene@chromium.org>
Date: Tue, 31 Mar 2026 15:53:34 -0700
Subject: [PATCH] media: Validate reference buffer indices in D3D12 encoding
This change ensures all manual reference and update buffer indices
fall within supported bounds before proceeding with encoding.
Bug: 497821764
Change-Id: I0f269b50b7518830e1b3fa7dba43ce97f3ca2fd2
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7712138
Reviewed-by: Qiu, Jianlin <jianlin.qiu@intel.com>
Commit-Queue: Eugene Zemtsov <eugene@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1608151}
---
diff --git a/media/gpu/windows/d3d12_video_encode_av1_delegate.cc b/media/gpu/windows/d3d12_video_encode_av1_delegate.cc
index 9c2d0e3..05dae4f31 100644
--- a/media/gpu/windows/d3d12_video_encode_av1_delegate.cc
+++ b/media/gpu/windows/d3d12_video_encode_av1_delegate.cc
@@ -1002,6 +1002,20 @@
UINT input_frame_subresource,
const VideoEncoder::EncodeOptions& options,
const gfx::ColorSpace& input_color_space) {
+ for (uint8_t ref_idx : options.reference_buffers) {
+ if (ref_idx >= GetMaxNumOfManualRefBuffers()) {
+ return {EncoderStatus::Codes::kBadReferenceBuffer,
+ "Manual reference buffer index exceeds that is supported by "
+ "encoder"};
+ }
+ }
+
+ if (options.update_buffer.has_value() &&
+ options.update_buffer.value() >= GetMaxNumOfManualRefBuffers()) {
+ return {EncoderStatus::Codes::kBadReferenceBuffer,
+ "Update buffer index exceeds that is supported by encoder"};
+ }
+
input_arguments_.SequenceControlDesc.Flags =
D3D12_VIDEO_ENCODER_SEQUENCE_CONTROL_FLAG_NONE;
input_arguments_.SequenceControlDesc.RateControl =
diff --git a/media/gpu/windows/d3d12_video_encode_h264_delegate.cc b/media/gpu/windows/d3d12_video_encode_h264_delegate.cc
index df6703f..b28c640 100644
--- a/media/gpu/windows/d3d12_video_encode_h264_delegate.cc
+++ b/media/gpu/windows/d3d12_video_encode_h264_delegate.cc
@@ -394,6 +394,18 @@
destroy_buffer = 1;
}
} else {
+ for (uint8_t ref_idx : options.reference_buffers) {
+ if (ref_idx >= GetMaxNumOfManualRefBuffers()) {
+ return {EncoderStatus::Codes::kBadReferenceBuffer,
+ "Manual reference buffer index exceeds that is supported by "
+ "encoder"};
+ }
+ }
+ if (options.reference_buffers.size() > list0_reference_frames_.size()) {
+ return {EncoderStatus::Codes::kBadReferenceBuffer,
+ "Number of manual reference buffers exceeds that is supported by "
+ "encoder"};
+ }
reference_buffers = options.reference_buffers;
update_buffer = options.update_buffer;
}
diff --git a/media/gpu/windows/d3d12_video_encode_h265_delegate.cc b/media/gpu/windows/d3d12_video_encode_h265_delegate.cc
index 3592d00..5365aaf0 100644
--- a/media/gpu/windows/d3d12_video_encode_h265_delegate.cc
+++ b/media/gpu/windows/d3d12_video_encode_h265_delegate.cc
@@ -319,6 +319,18 @@
destroy_buffer = 1;
}
} else {
+ for (uint8_t ref_idx : options.reference_buffers) {
+ if (ref_idx >= GetMaxNumOfManualRefBuffers()) {
+ return {EncoderStatus::Codes::kBadReferenceBuffer,
+ "Manual reference buffer index exceeds that is supported by "
+ "encoder"};
+ }
+ }
+ if (options.reference_buffers.size() > list0_reference_frames_.size()) {
+ return {EncoderStatus::Codes::kBadReferenceBuffer,
+ "Number of manual reference buffers exceeds that is supported by "
+ "encoder"};
+ }
reference_buffers = options.reference_buffers;
update_buffer = options.update_buffer;
}
@@ -376,7 +388,6 @@
pic_params_.pList0ReferenceFrames = nullptr;
} else {
pic_params_.FrameType = D3D12_VIDEO_ENCODER_FRAME_TYPE_HEVC_P_FRAME;
- CHECK_LE(reference_buffers.size(), list0_reference_frames_.size());
for (size_t i = 0; i < reference_buffers.size(); i++) {
std::optional<uint32_t> descriptor_index =
reference_frame_manager_.GetReferenceFrameId(reference_buffers[i]);
Original Bug Report
Potential Renderer-to-GPU sandbox escape via OOB access in D3D12 AV1 encode driver
Project Fortify, an experimental security project, has identified the following potential security issue.
Overview: The D3D12 AV1 video encoding delegate fails to validate reference buffer indices provided by an untrusted renderer before passing them to the hardware driver. By supplying out-of-bounds indices, a compromised renderer can trigger an out-of-bounds array access within the D3D12 GPU driver. This driver-level memory corruption could potentially lead to a sandbox escape from the Renderer to the GPU process.
Affected files:
media/gpu/windows/d3d12_video_encode_av1_delegate.ccmedia/gpu/windows/d3d12_video_encode_delegate.cc
Estimated timestamp from git blame: 2025-12-17
Summary
A vulnerability exists in the D3D12 AV1 video encoding implementation in Chrome (media/gpu/windows/d3d12_video_encode_av1_delegate.cc). When manual reference buffer control is enabled, the delegate processes reference indices supplied by the renderer via Mojo (VideoEncoder::EncodeOptions::reference_buffers). While Chrome correctly validates the number of reference buffers, it completely fails to validate the values of the indices themselves before passing them to the underlying Independent Hardware Vendor (IHV) D3D12 driver.
According to the D3D12 AV1 specification, reference indices must strictly point to valid Decoded Picture Buffer (DPB) slots, which range from 0 to 7. Because the values from the untrusted renderer (which can be up to 255) are copied directly into the driver’s command struct without bounds checking, an attacker can reliably trigger an out-of-bounds array access within the third-party GPU driver.
Technical Details
- Mojo IPC: A compromised renderer acquires a
media.mojom.VideoEncodeAcceleratorinterface. It callsEncode(), passing amedia.mojom.VideoEncodeOptionsstruct. The renderer populates thereference_buffersarray with an out-of-bounds index (e.g.,[200]) and crucially leaves the optionalupdate_bufferfield empty. - Size Validation Passes: In the GPU process,
D3D12VideoEncodeDelegate::Encode(media/gpu/windows/d3d12_video_encode_delegate.cc:276) correctly checks that the size of thereference_buffersarray does not exceed the maximum allowed (typically 8 for AV1). This check passes since the array size is only 1. - Missing Value Validation: The execution flow reaches
D3D12VideoEncodeAV1Delegate::HandleManualReferences(media/gpu/windows/d3d12_video_encode_av1_delegate.cc:1449). This function copies the unvalidated index (200) directly into thepicture_params_struct, which is mapped directly to the D3D12 API’sD3D12_VIDEO_ENCODER_AV1_PICTURE_CONTROL_CODEC_DATA.// Set primary reference frame to the first reference buffer index. picture_params_.PrimaryRefFrame = options.reference_buffers[0]; // ... we pad the remaining ReferenceIndices[] with `options.reference_buffers[0]` ... for (size_t i = 0; i < 7ull; i++) { base::span(picture_params_.ReferenceIndices)[i] = options.reference_buffers.size() > i ? options.reference_buffers[i] : options.reference_buffers[0]; } - Driver Invocation: The
picture_params_struct is subsequently passed to the D3D12 driver viaID3D12VideoEncodeCommandList::EncodeFrame. The driver expects these indices to be within[0, 7]. By providing200, the driver performs an out-of-bounds read/write on its internal DPB tracking arrays. - Bypassing Chrome Safety Checks: Later in Chrome’s execution flow,
D3D12VideoEncodeAV1Delegate::RefreshDPBAndDescriptors()is called. Because the attacker deliberately omittedoptions.update_buffer,picture_params_.RefreshFrameFlagswas evaluated to0in step 3.This early return elegantly bypasses avoid D3D12VideoEncodeAV1Delegate::RefreshDPBAndDescriptors() { // ... if (picture_params_.RefreshFrameFlags == 0) { return; // Early exit cleanly bypasses the CHECK below. } uint8_t refreshed_dpb_idx = std::countr_zero((picture_params_.RefreshFrameFlags)); CHECK_LT(refreshed_dpb_idx, max_num_ref_frames_); // ... }CHECK_LTassertion that would have otherwise safely crashed the GPU process on invalid inputs, ensuring the driver-level exploit executes cleanly.
Potential Impact
The impact depends on the specific hardware driver implementation (e.g., Intel, AMD, NVIDIA). At minimum, this results in a GPU process crash. However, memory corruption at the driver level could potentially be exploited to achieve arbitrary code execution within the highly privileged GPU process, representing a full sandbox escape from the Renderer.
(Note: These are potential steps based on source code analysis; our tooling has not executed a live proof-of-concept.)
Suggested Fix
In D3D12VideoEncodeAV1Delegate::HandleManualReferences (or centrally in D3D12VideoEncodeDelegate::Encode), validate that every element within the options.reference_buffers array is strictly less than the maximum allowed number of reference frames (GetMaxNumOfManualRefBuffers()).
// In D3D12VideoEncodeAV1Delegate::HandleManualReferences:
for (uint8_t ref_idx : options.reference_buffers) {
if (ref_idx >= GetMaxNumOfManualRefBuffers()) {
// Return an error or crash cleanly.
return;
}
}
(Note: The H.265 delegate already performs a similar validation check via reference_frame_manager_.GetReferenceFrameId(reference_buffers[i]).)
Evaluated with Chrome root at commit: a9cbf6e8b275fe4147435aa905f3b7f5a656f5f0
Results from so far have been promising, but there can be wrong deductions. If this proves to be a false positive, please close as WAI; 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.