CVE-2026-3061
Overview
Background
- `H265Parser`
- Chromium’s bitstream parser in
media/parsers/h265_parser.ccthat decodes HEVC NAL units (VPS, SPS, PPS, slice headers, SEI) into structs consumed by decoder and accelerator code. - `IN_RANGE_OR_RETURN`
- a parser macro that makes the current parse function return an error when a syntax element falls outside the given inclusive range, so the out-of-range value never reaches later code.
- VPS / slice header / SEI syntax elements
vps_max_layers_minus1andvps_max_layer_id(6-bit, video parameter set),slice_type(Exp-Golombue(v), slice header) andalpha_channel_use_idc(3-bit, alpha channel information SEI), each with a range the ITU-T H.265 specification restricts.- Fixed-width vs. `ue(v)` fields
- a 6-bit field can encode up to 63 and a 3-bit field up to 7, while a
ue(v)field is effectively unbounded, so a value can be well-formed in the bitstream yet outside the spec’s legal range.
Root Cause Analysis
The H.265 parser stored several syntax elements without enforcing the ranges the specification requires. In VPS parsing, vps_max_layers_minus1 and vps_max_layer_id were accepted up to 63, but the spec (7.4.3.1) limits both to 62. In slice-header parsing, slice_type was read with READ_UE_OR_RETURN and had no bound at all, though only 0–2 are valid (7.4.7.1). In the alpha-channel SEI path, alpha_channel_use_idc (a 3-bit field, 0–7) was not limited to 0–2 (F.7.4.3.1.1).
The patch tightens the two VPS checks from 0, 63 to 0, 62 and adds new IN_RANGE_OR_RETURN checks for slice_type and alpha_channel_use_idc right after each is read, so the parser bails out before the value is stored for later use. The diff does not show the code that consumes these values. The advisory’s out-of-bounds read most likely comes from one of them being used as an index or table selector sized to the spec range (inferred), but which field and which access caused it is not visible here. The @@ hunk headers carry no function names, so the enclosing functions are identified only from the fields they parse.
IN_RANGE_OR_RETURN.Attack Path
- Deliver crafted HEVC
The attacker provides an H.265 bitstream (e.g. in a media file or stream) whose VPS sets
vps_max_layers_minus1orvps_max_layer_idto 63, whose slice header carries aslice_typegreater than 2, or whose alpha channel SEI setsalpha_channel_use_idcabove 2. - Parser accepts the value
Before the fix,
H265Parserstored these values without error because they passed the old checks (or had none). - Downstream use of an out-of-range value Code that consumes the parsed structs uses the value under the spec-range assumption, for example as an array index or lookup selector, reading past the end of a structure or table (inferred; the consuming code is not in the diff).
- Information disclosure or crash The out-of-bounds read could leak adjacent memory into decoding state or crash the process (inferred).
Impact Assessment
H265Parser (renderer, GPU or a media utility process) is not shown in the diff. Reachability depends on H.265 support, which in Chrome varies by platform and by available hardware or OS decoders, so a stock build on a platform without HEVC support may never reach this parser; the diff does not establish the exact preconditions. The vendor rates it high severity, and no exploitation in the wild is reported; any escalation beyond an out-of-bounds read is (inferred).Changed Functions
| Function | Change | Notes |
|---|---|---|
formedia/parsers/h265_parser.cc |
modified | |
ifmedia/parsers/h265_parser.cc |
modified |
Files Changed
media/parsers/h265_parser.cc
Audit Directions
- Spec ranges narrower than bit widthGo through
h265_parser.cc,h264_parser.cc, and the AV1/VP9 header parsers for fixed-width fields stored with no check, or with a check matching only the bit width, where the spec defines a tighter range. - Unbounded `ue(v)` / `se(v)` valuesLook for every
READ_UE_OR_RETURN/READ_SE_OR_RETURNresult that is later used as an index, loop bound or enum without anIN_RANGE_OR_RETURNright after it. - Consumers trusting parser outputLook at hardware-accelerator glue (VAAPI, V4L2, D3D11, VideoToolbox delegates) that turns parsed fields into table indices or fixed-size array accesses, and add defensive bounds there rather than relying only on parser validation.
Patch
From 452f3fa28564cadf807215ce61293c6f20e8f2ef Mon Sep 17 00:00:00 2001
From: Eugene Zemtsov <eugene@chromium.org>
Date: Fri, 13 Feb 2026 14:00:18 -0800
Subject: [PATCH] media: Add missing range checks in H.265 parser
This CL adds missing range checks for the following H.265 syntax elements,
as required by the ITU-T H.265 specification:
- vps_max_layers_minus1: [0, 62] (7.4.3.1)
- vps_max_layer_id: [0, 62] (7.4.3.1)
- slice_type: [0, 2] (7.4.7.1)
- alpha_channel_use_idc: [0, 2] (F.7.4.3.1.1)
Bug: 482862710
Change-Id: I629d7fba4fbdcea889166ccf78aaee2af830a1d7
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7577373
Commit-Queue: Eugene Zemtsov <eugene@chromium.org>
Reviewed-by: Ted (Chromium) Meyer <tmathmeyer@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1584897}
---
diff --git a/media/parsers/h265_parser.cc b/media/parsers/h265_parser.cc
index c93b7b48..cd4800e9 100644
--- a/media/parsers/h265_parser.cc
+++ b/media/parsers/h265_parser.cc
@@ -326,7 +326,7 @@
READ_BOOL_OR_RETURN(&vps->vps_base_layer_internal_flag);
READ_BOOL_OR_RETURN(&vps->vps_base_layer_available_flag);
READ_BITS_OR_RETURN(6, &vps->vps_max_layers_minus1);
- IN_RANGE_OR_RETURN(vps->vps_max_layers_minus1, 0, 63);
+ IN_RANGE_OR_RETURN(vps->vps_max_layers_minus1, 0, 62);
READ_BITS_OR_RETURN(3, &vps->vps_max_sub_layers_minus1);
IN_RANGE_OR_RETURN(vps->vps_max_sub_layers_minus1, 0, kMaxSubLayers - 1);
READ_BOOL_OR_RETURN(&vps->vps_temporal_id_nesting_flag);
@@ -369,7 +369,7 @@
}
READ_BITS_OR_RETURN(6, &vps->vps_max_layer_id);
- IN_RANGE_OR_RETURN(vps->vps_max_layer_id, 0, 63);
+ IN_RANGE_OR_RETURN(vps->vps_max_layer_id, 0, 62);
READ_UE_OR_RETURN(&vps->vps_num_layer_sets_minus1);
IN_RANGE_OR_RETURN(vps->vps_num_layer_sets_minus1, 0, 1023);
for (int i = 1; i <= vps->vps_num_layer_sets_minus1; ++i) {
@@ -1186,6 +1186,7 @@
// slice_reserved_flag
SKIP_BITS_OR_RETURN(pps->num_extra_slice_header_bits);
READ_UE_OR_RETURN(&shdr->slice_type);
+ IN_RANGE_OR_RETURN(shdr->slice_type, 0, 2);
if ((shdr->irap_pic ||
sps->sps_max_dec_pic_buffering_minus1[clamped_temporal_id] == 0) &&
nalu.nuh_layer_id == 0) {
@@ -2151,6 +2152,7 @@
if (!info.alpha_channel_cancel_flag) {
READ_BITS_AND_MINUS_BITS_READ_OR_RETURN(
3, &info.alpha_channel_use_idc, &num_bits_remain);
+ IN_RANGE_OR_RETURN(info.alpha_channel_use_idc, 0, 2);
READ_BITS_AND_MINUS_BITS_READ_OR_RETURN(
3, &info.alpha_channel_bit_depth_minus8, &num_bits_remain);
READ_BITS_AND_MINUS_BITS_READ_OR_RETURN(
Original Bug Report
Missing range validation on second_chroma_qp_index_offset in H.264 PPS parser (h264_parser.cc:1151) allows out-of-spec values to reach kernel GPU drivers
Report description
Missing range validation on second_chroma_qp_index_offset in H.264 PPS parser (h264_parser.cc:1151) allows out-of-spec values to reach kernel GPU drivers
Bug location
Where do you want to report your vulnerability?
Chrome VRP – Report security issues affecting the Chrome browser. See program rules
Which URL (or repository) have you found the vulnerability in?
https://chromium.googlesource.com/chromium/src/+/main/media/parsers/h264_parser.cc
The problem
Please describe the technical details of the vulnerability
At h264_parser.cc:1151, second_chroma_qp_index_offset is read via READ_SE_OR_RETURN with no range check. The H.264 spec (ITU-T H.264 §7.4.2.2) requires this value to be in [-12, +12]. The identical sibling field chroma_qp_index_offset IS validated 33 lines earlier at line 1118:
// Line 1118: validated:
READ_SE_OR_RETURN(&pps->chroma_qp_index_offset);
IN_RANGE_OR_RETURN(pps->chroma_qp_index_offset, -12, 12);
// Line 1151: not validated:
READ_SE_OR_RETURN(&pps->second_chroma_qp_index_offset);
// Missing: IN_RANGE_OR_RETURN(pps->second_chroma_qp_index_offset, -12, 12);
READ_SE_OR_RETURN accepts exp-Golomb coded values up to approximately ±2³⁰. The unvalidated value is then copied directly into kernel GPU driver parameter buffers by all three hardware decoder backends, where int→int8 narrowing maps the full range to [-128, +127] — of which 231 values fall outside the spec-required [-12, +12]:
- D3D11 (
d3d11_h264_accelerator.cc:207):pic_param.second_chroma_qp_index_offset = pps->second_chroma_qp_index_offset→DXVA_PicParams_H264(CHAR) - VA-API (
h264_vaapi_video_decoder_delegate.cc:171): same pattern →VAPictureParameterBufferH264(signed char) - V4L2 (
v4l2_video_decoder_delegate_h264.cc:306): same pattern →v4l2_ctrl_h264_pps(__s8)
Source analysis of Intel’s open-source media-driver confirms the driver does NOT clamp the value. In decode_avc_basic_feature.cpp:243, the driver detects the violation and logs DECODE_WARNINGMESSAGE("Conflict with H264 Spec!") but does not correct the value — unlike other fields in the same function that are corrected after detection. The unclamped value is then written directly to GPU hardware command buffers (cmd.DW3.SecondChromaQpOffset = avcPicParams->second_chroma_qp_index_offset) across gen8/gen9/gen11/gen12.
Chrome’s built-in FFmpeg software decoder rejects the malformed stream, but the hardware decode path (default on all desktop platforms) accepts and processes it. Confirmed via chrome://media-internals on:
- Intel Arc 130V (Windows 11 / D3D11VideoDecoder)
- NVIDIA RTX 2070 Super (Windows 11 / D3D11VideoDecoder)
- Intel Alder Lake (ChromeOS / OOPVideoDecoder)
Fix: one line matching the existing pattern:
IN_RANGE_OR_RETURN(pps->second_chroma_qp_index_offset, -12, 12);
Reproduction: Open the attached off100_qp25_cabac.mp4 in Chrome on any desktop platform with hardware video decode enabled. Navigate to chrome://media-internals and observe that D3D11VideoDecoder (Windows) or VaapiVideoDecoder (Linux/ChromeOS) accepts and processes the stream. For comparison, note that Chrome’s built-in FFmpeg software decoder rejects the same file with an invalid data error. A second PoC with second_chroma_qp_index_offset = -128 (negative OOB index into the QPC table) was also tested and accepted by the hardware decoder.
Attached files:
off100_qp25_cabac.mp4 — Proof-of-concept H.264 High Profile MP4 with second_chroma_qp_index_offset set to 100 (spec requires [-12, +12]). Opens in Chrome via D3D11VideoDecoder/VaapiVideoDecoder hardware decode path. FFmpeg software decoder rejects it.
poc_chroma_qp_long_neg128.mp4 — 15-second variant with second_chroma_qp_index_offset set to -128 (negative OOB into QPC table). 450 frames with varying slice_qp_delta to hit different out-of-range qPi values. Also accepted by hardware decoder.
media-internals.txt — chrome://media-internals JSON log from Windows 11 / NVIDIA RTX 2070 Super showing FFmpegVideoDecoder rejection followed by successful D3D11VideoDecoder fallback and full stream processing.
poc_chroma_qp.py — Python script that generates the PoC MP4 from scratch. Self-contained, no external dependencies. Can be modified to produce variants with arbitrary second_chroma_qp_index_offset values.
poc_v2.py — Alternative PoC generator that patches a real ffmpeg-encoded H.264 stream, modifying only the second_chroma_qp_index_offset field in the existing PPS bitstream. Generates off100_qp25_cabac.mp4. Requires ffmpeg
Impact analysis
Chromium’s H.264 parser is the trust boundary between untrusted web content and kernel GPU drivers. Attacker-controlled out-of-spec values reach kernel drivers when a user visits a page containing a malicious <video> element. The Chrome GPU sandbox does not mitigate this — the GPU process is designed to make D3D11/VA-API/V4L2 calls, so the malformed parameters reach the kernel through the sandbox’s allowed syscall surface.
GPU drivers use this value to compute chroma QP (qPi = QPY + second_chroma_qp_index_offset) and index into a 52-entry QPC lookup table. With the PoC values (offset=100, slice_qp_delta=25), qPi reaches 151, overshooting the table by ~100 entries. With the negative variant (offset=-128), qPi goes deeply negative, indexing before the array start. If any driver fails to clamp before the lookup, this is a kernel out-of-bounds read.
CVE-2022-21813 and CVE-2022-21814 (NVIDIA) were this exact bug class — out-of-range video decoder parameters causing kernel OOB access. Chromium cannot delegate validation to downstream drivers across all vendors, architectures, and firmware versions. Intel’s open-source driver confirms this: it detects but does not correct the violation. ARM/Mali/Qualcomm V4L2 firmware-based decoders, where QPC table lookups occur in software rather than fixed-function silicon, represent the highest-risk targets.
No crashes were observed on tested hardware (Intel, NVIDIA), consistent with fixed-function decode units that truncate at the bitfield level. However, the parser must enforce spec compliance regardless of individual driver behavior — this is a defense-in-depth requirement at a critical trust boundary.
The cause
What version of Chrome have you found the security issue in?
144.0.7559.133 + stable (Windows 11, 64-bit)
Is the security issue related to a crash?
No, it is not related to a crash.
Choose the type of vulnerability
Memory Corruption (in a non-sandboxed process)
How would you like to be publicly acknowledged for your report?
Luke Francis