CVE-2026-3061
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
formedia/parsers/h265_parser.cc |
modified | |
ifmedia/parsers/h265_parser.cc |
modified |
Files Changed
media/parsers/h265_parser.cc
Patch
From 452f3fa28564cadf807215ce61293c6f20e8f2ef Mon Sep 17 00:00:00 2001 From: Eugene Zemtsov <[email protected]> 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 <[email protected]> Reviewed-by: Ted (Chromium) Meyer <[email protected]> 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