Medium watch CVSS 7.5 chrome Input Validation 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
7.5
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactImproper input validation in Media
DescriptionImproper input validation in Media
ComponentMedia
Sub-componentD3D12 VEA
Bug ClassInput Validation
Action tierwatch — ub-class only — no demonstrated corruption path
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.
Tracker532917452
Fix commit76e56a93297a (chromium/src) +11/-1
CWECWE-20 (Improper input validation)
CVSS vectorCVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:H
CISA KEVNot listed
CreditedGoogle
Reported2026-07-09 (per vendor advisory)
Fix landed2026-07-10 (public git)
Disclosed2026-08-25
Patch gap46 days — fix public in source before the advisory

Background

D3D12 hardware video encoding
Chrome’s GPU-process video encode accelerator (VEA) uses the D3D12 Video Encode API, with per-codec delegates like D3D12VideoEncodeH264Delegate and D3D12VideoEncodeH265Delegate.
`max_num_ref_frames_`
The delegate’s chosen maximum number of reference frames, used to size/manage the reference (DPB) and the manual reference buffer budget.
Driver capability fields
MaxDPBCapacity, MaxLongTermReferences, and MaxL0ReferencesForP are driver-reported limits describing how many references (and specifically L0 references for P-frames) the hardware supports.
Manual references
Manually managed reference pictures whose budget is max_num_ref_frames_ - 1; exceeding the driver’s L0-for-P limit means requesting more references than the driver actually allows.

Root Cause Analysis

When configuring encoding, the H.264 delegate set max_num_ref_frames_ from min(MaxDPBCapacity, MaxLongTermReferences + 1) and the H.265 delegate set it directly to MaxDPBCapacity, in both cases ignoring the driver’s MaxL0ReferencesForP limit on P-frame L0 references. If a driver reports an L0-for-P limit smaller than these values, the delegate would size its (manual) reference usage above what the driver permits, violating the invariant that the number of manual references must not exceed the driver’s MaxL0ReferencesForP.

The fix adds max_num_ref_frames_ = min(max_num_ref_frames_, MaxL0ReferencesForP + 1) for H.264 and max_num_ref_frames_ = min(MaxDPBCapacity, MaxL0ReferencesForP) for H.265, clamping the reference count to the driver’s actual capability so the manual-reference budget stays within bounds. The added unit-test driver mocks set MaxL0ReferencesForP = 3 (below the other reported limits) to exercise the new clamp.

Key insight
The core mistake was deriving the reference-frame count from DPB/long-term limits while omitting the driver’s MaxL0ReferencesForP bound; the fix clamps max_num_ref_frames_ against MaxL0ReferencesForP so the manual reference buffer never exceeds what the driver supports.

Attack Path

  1. Reach the encoder Content that drives hardware H.264/H.265 encoding (e.g. via WebCodecs/WebRTC) causes the D3D12 VEA delegate to initialize in the GPU process.
  2. Driver reports a small L0 limit On a driver where MaxL0ReferencesForP is smaller than MaxDPBCapacity (and MaxLongTermReferences + 1 for H.264), the unclamped computation picks a max_num_ref_frames_ above that limit.
  3. Over-budget reference usage The delegate manages more manual references than the driver allows, producing an out-of-bounds / over-budget reference-buffer condition (inferred); the diff shows only the missing bound, not the concrete sink.
  4. Memory corruption or failure The excess reference count leads to buffer mismanagement in the encode path (inferred), consistent with an improper-input-validation issue in Media.

Impact Assessment

The bug is an improper-input-validation flaw in the GPU-process D3D12 video-encode delegates where the reference-frame count could exceed the driver’s MaxL0ReferencesForP, leading to over-budget reference-buffer handling; preconditions include hardware H.264/H.265 encoding on a driver whose L0-for-P limit is below the other reported capacities. The advisory reports no exploited-in-the-wild status, and any memory-corruption or code-execution consequence is (inferred) rather than demonstrated by the patch.

Files Changed

  • media/gpu/windows/d3d12_video_encode_h265_delegate_unittest.cc
  • media/gpu/windows/d3d12_video_encode_h265_delegate.cc
  • media/gpu/windows/d3d12_video_encode_h264_delegate.cc
  • media/gpu/windows/d3d12_video_encode_h264_delegate_unittest.cc

Audit Directions

  • Missing driver-limit clamps
    Audit other codec delegates and D3D12/media capability paths that compute buffer sizes or counts from a subset of driver-reported limits, ensuring every relevant limit (DPB, long-term, L0/L1 references) participates in the bound.
  • `min()` chains dropping a term
    Look for std::min capability computations where a newly relevant hardware field was not added, leaving a dimension unbounded on adversarial or unusual drivers.
  • Reference/DPB budget invariants
    Review manual-reference and DPB management for the assumption that the chosen count fits the hardware’s actual per-frame reference limits before allocation or indexing.
From 76e56a93297aff4d06e8fd4b9cb2f8df1f61b7d3 Mon Sep 17 00:00:00 2001
From: Qiu Jianlin <jianlin.qiu@intel.com>
Date: Fri, 10 Jul 2026 16:30:13 -0700
Subject: [PATCH] [D3D12 VEA] Bound max manual reference to driver L0 ref limit.

Make sure maximum allowed manual references do not exceed L0 p frame
references allowed by driver.

Bug: 532917452
Change-Id: I7afd6efeac27398643b2c0d53111aac17d676304
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8074938
Auto-Submit: Qiu, Jianlin <jianlin.qiu@intel.com>
Commit-Queue: Eugene Zemtsov <eugene@chromium.org>
Reviewed-by: Eugene Zemtsov <eugene@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1660596}
---
diff --git a/media/gpu/windows/d3d12_video_encode_h264_delegate.cc b/media/gpu/windows/d3d12_video_encode_h264_delegate.cc
index 2f554e89..bab79517 100644
--- a/media/gpu/windows/d3d12_video_encode_h264_delegate.cc
+++ b/media/gpu/windows/d3d12_video_encode_h264_delegate.cc
@@ -657,6 +657,12 @@
     max_num_ref_frames_ = std::min<uint32_t>(
         picture_control_support_h264.MaxDPBCapacity,
         picture_control_support_h264.MaxLongTermReferences + 1);
+    // We never see driver with MaxL0ReferenceForP < MaxLongTermReferences,
+    // but bound to it in case it happens. Manual reference buffer is with
+    // budget (max_num_ref_frames_ -1).
+    max_num_ref_frames_ = std::min<uint32_t>(
+        max_num_ref_frames_,
+        picture_control_support_h264.MaxL0ReferencesForP + 1);
   }
 
   if ((config.bitrate.mode() == Bitrate::Mode::kConstant ||
diff --git a/media/gpu/windows/d3d12_video_encode_h265_delegate.cc b/media/gpu/windows/d3d12_video_encode_h265_delegate.cc
index 6a410b7..f99a2bd8 100644
--- a/media/gpu/windows/d3d12_video_encode_h265_delegate.cc
+++ b/media/gpu/windows/d3d12_video_encode_h265_delegate.cc
@@ -513,7 +513,9 @@
                   max_num_ref_frames_)};
     }
   } else {
-    max_num_ref_frames_ = picture_control_support_h265.MaxDPBCapacity;
+    max_num_ref_frames_ =
+        std::min<uint32_t>(picture_control_support_h265.MaxDPBCapacity,
+                           picture_control_support_h265.MaxL0ReferencesForP);
   }
 
   if ((config.bitrate.mode() == Bitrate::Mode::kConstant ||
diff --git a/media/gpu/windows/d3d12_video_encode_h264_delegate_unittest.cc b/media/gpu/windows/d3d12_video_encode_h264_delegate_unittest.cc
index fd032dc..3ae0cd8 100644
--- a/media/gpu/windows/d3d12_video_encode_h264_delegate_unittest.cc
+++ b/media/gpu/windows/d3d12_video_encode_h264_delegate_unittest.cc
@@ -73,6 +73,7 @@
           }
           picture_control->PictureSupport.pH264Support->MaxLongTermReferences =
               15;
+          picture_control->PictureSupport.pH264Support->MaxL0ReferencesForP = 3;
           picture_control->PictureSupport.pH264Support->MaxDPBCapacity = 16;
           return S_OK;
         });
diff --git a/media/gpu/windows/d3d12_video_encode_h265_delegate_unittest.cc b/media/gpu/windows/d3d12_video_encode_h265_delegate_unittest.cc
index 21b2d3e..054c4b80 100644
--- a/media/gpu/windows/d3d12_video_encode_h265_delegate_unittest.cc
+++ b/media/gpu/windows/d3d12_video_encode_h265_delegate_unittest.cc
@@ -70,6 +70,7 @@
           }
           picture_control->PictureSupport.pHEVCSupport->MaxLongTermReferences =
               1;
+          picture_control->PictureSupport.pHEVCSupport->MaxL0ReferencesForP = 3;
           picture_control->PictureSupport.pHEVCSupport->MaxDPBCapacity = 16;
           return S_OK;
         });
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/media/gpu/windows/d3d12_video_encode_h264_delegate_unittest.cc b/media/gpu/windows/d3d12_video_encode_h264_delegate_unittest.cc
index fd032dc..3ae0cd8 100644
--- a/media/gpu/windows/d3d12_video_encode_h264_delegate_unittest.cc
+++ b/media/gpu/windows/d3d12_video_encode_h264_delegate_unittest.cc
@@ -73,6 +73,7 @@
           }
           picture_control->PictureSupport.pH264Support->MaxLongTermReferences =
               15;
+          picture_control->PictureSupport.pH264Support->MaxL0ReferencesForP = 3;
           picture_control->PictureSupport.pH264Support->MaxDPBCapacity = 16;
           return S_OK;
         });
diff --git a/media/gpu/windows/d3d12_video_encode_h265_delegate_unittest.cc b/media/gpu/windows/d3d12_video_encode_h265_delegate_unittest.cc
index 21b2d3e..054c4b80 100644
--- a/media/gpu/windows/d3d12_video_encode_h265_delegate_unittest.cc
+++ b/media/gpu/windows/d3d12_video_encode_h265_delegate_unittest.cc
@@ -70,6 +70,7 @@
           }
           picture_control->PictureSupport.pHEVCSupport->MaxLongTermReferences =
               1;
+          picture_control->PictureSupport.pHEVCSupport->MaxL0ReferencesForP = 3;
           picture_control->PictureSupport.pHEVCSupport->MaxDPBCapacity = 16;
           return S_OK;
         });
Loading diff…

Original Bug Report

reported by vm...@google.com

Potential GPU process OOB access via unvalidated D3D12 video encode active reference counts

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 D3D12 H264 and HEVC video encode delegates fail to clamp the number of active L0 reference frames against the driver-advertised MaxL0ReferencesForP capability. A compromised renderer could supply an excessive number of reference buffers, resulting in a potential out-of-bounds read/write inside the sandboxed GPU process via the vendor User Mode Driver. These are potential steps and findings as our tooling does not currently have the capability to run code to confirm.

Affected files:

  • media/gpu/windows/d3d12_video_encode_h264_delegate.cc
  • media/gpu/windows/d3d12_video_encode_h265_delegate.cc

Estimated timestamp from git blame: 2025-08-26

Root Cause

Under Direct3D 12, the D3D12_VIDEO_ENCODER_CODEC_PICTURE_CONTROL_SUPPORT_H264 and _HEVC structures expose driver-defined capability boundaries, including MaxL0ReferencesForP (which specifies the maximum number of L0 reference frames allowed for P-frames).

However, Chrome’s D3D12 video encode delegates completely ignore MaxL0ReferencesForP. Instead, the delegates derive the maximum number of manual reference buffers solely using MaxDPBCapacity and MaxLongTermReferences:

  • H264 Delegate (media/gpu/windows/d3d12_video_encode_h264_delegate.cc:657-659):
    max_num_ref_frames_ = std::min<uint32_t>(
        picture_control_support_h264.MaxDPBCapacity,
        picture_control_support_h264.MaxLongTermReferences + 1);
    
  • HEVC Delegate (media/gpu/windows/d3d12_video_encode_h265_delegate.cc:515-517):
    max_num_ref_frames_ = picture_control_support_h265.MaxDPBCapacity;
    

Because of this, GetMaxNumOfManualRefBuffers() returns a bound that is not constrained by MaxL0ReferencesForP. When the compromised renderer issues an Encode() request, options.reference_buffers.size() is validated only against GetMaxNumOfManualRefBuffers().

This un-clamped list is then written straight to the D3D12 picture parameters sent to the vendor User Mode Driver (UMD):

  • H264 Sink (media/gpu/windows/d3d12_video_encode_h264_delegate.cc:500-501):
    pic_params_.List0ReferenceFramesCount = reference_buffers.size();
    pic_params_.pList0ReferenceFrames     = list0_reference_frames_.data();
    
  • HEVC Sink (media/gpu/windows/d3d12_video_encode_h265_delegate.cc:394-395):
    pic_params_.List0ReferenceFramesCount = reference_buffers.size();
    pic_params_.pList0ReferenceFrames     = list0_reference_frames_.data();
    

This structural data is ultimately submitted to the UMD via ID3D12VideoEncodeCommandList::EncodeFrame.

Potential Attack Path

Note: These steps are logical predictions based on static code analysis; our automated tools do not have the capability to execute code or verify runtime behavior against specific hardware.

  1. A compromised renderer initiates a video encoding session via Mojo VideoEncodeAccelerator::Initialize with manual_reference_buffer_control=true using an H.264 or HEVC profile.
  2. The GPU process initializes the corresponding delegate. The driver-reported capabilities are queried, but MaxL0ReferencesForP is ignored during bounds determination.
  3. The renderer calls Mojo Encode with an active reference list (options.reference_buffers) containing more elements than MaxL0ReferencesForP (e.g. 4 references on a driver supporting at most 1).
  4. The GPU-side validation accepts the request since the count is smaller than GetMaxNumOfManualRefBuffers().
  5. The delegate sets pic_params_.List0ReferenceFramesCount to the oversized count and submits the command buffer to the driver.
  6. If the vendor UMD sizes its internal structures or loop limits based on its advertised MaxL0ReferencesForP capability and trusts the API caller not to exceed it, an out-of-bounds read/write could occur inside the GPU process memory space.

Suggested Fix

We recommend reading and enforcing MaxL0ReferencesForP during initialization and validation:

  1. In D3D12VideoEncodeH264Delegate::InitializeVideoEncoder, clamp max_num_ref_frames_ or limit the allowable manual reference buffer count returned by GetMaxNumOfManualRefBuffers() for P-frames to also consider MaxL0ReferencesForP.
  2. In D3D12VideoEncodeH265Delegate::InitializeVideoEncoder, ensure the maximum manual reference buffer size is clamped by MaxL0ReferencesForP.
  3. Ensure that D3D12VideoEncodeDelegate::Encode() rejects any encode options where reference_buffers.size() is larger than the driver’s reported MaxL0ReferencesForP limit when encoding a non-keyframe (P-frame).

Evaluated with Chrome root at commit: 84065d9121f6e48f67755f0ae963cc09617e5c85


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