CVE-2026-87529
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
formedia/gpu/v4l2/v4l2_video_decoder_delegate_h265.cc |
modified |
Files Changed
media/gpu/v4l2/v4l2_video_decoder_delegate_h265.cc
Patch
From 71c52c59bfb6daa32a033e4658ebfd45cbcb0855 Mon Sep 17 00:00:00 2001 From: Hirokazu Honda <[email protected]> Date: Fri, 07 Aug 2026 09:26:39 -0700 Subject: [PATCH] media/gpu/v4l2: Validate H.265 tile dimensions range for V4L2 PPS In V4L2VideoDecoderDelegateH265::SubmitFrameMetadata(), column_width_minus1 and row_height_minus1 from the parsed H.265 PPS are copied into v4l2_pps. The V4L2 HEVC UAPI stores these fields as __u8 arrays, whereas the parsed H.265 PPS fields use larger integer types. Add bounds checks with base::IsValueInRangeForNumericType<__u8>() and use base::checked_cast<__u8>() to safely convert the values and prevent integer overflow/truncation. Bug: 536423794 Test: v4l2_video_decode_accelerator_tests Change-Id: Ia46d684549da7571c96bda9243826370e1096ea7 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8223082 Commit-Queue: Nathan Hebert <[email protected]> Auto-Submit: Hirokazu Honda <[email protected]> Reviewed-by: Nathan Hebert <[email protected]> Cr-Commit-Position: refs/heads/main@{#1675750} --- diff --git a/media/gpu/v4l2/v4l2_video_decoder_delegate_h265.cc b/media/gpu/v4l2/v4l2_video_decoder_delegate_h265.cc index 1c152cc0..9e8e0505 100644 --- a/media/gpu/v4l2/v4l2_video_decoder_delegate_h265.cc +++ b/media/gpu/v4l2/v4l2_video_decoder_delegate_h265.cc @@ -295,8 +295,15 @@ std::remove_reference_t<decltype(pps->column_width_minus1)>>, "column_width_minus1 arrays must be same size"); for (int i = 0; i <= pps->num_tile_columns_minus1; ++i) { + if (!base::IsValueInRangeForNumericType<__u8>( + pps->column_width_minus1[i])) { + DVLOG(1) << "column_width_minus1[" << i + << "]=" << pps->column_width_minus1[i] + << " exceeds V4L2 UAPI __u8 range"; + return Status::kFail; + } UNSAFE_TODO(v4l2_pps.column_width_minus1[i]) = - pps->column_width_minus1[i]; + base::checked_cast<__u8>(pps->column_width_minus1[i]); } static_assert( @@ -305,7 +312,15 @@ std::remove_reference_t<decltype(pps->row_height_minus1)>>, "row_height_minus1 arrays must be same size"); for (int i = 0; i <= pps->num_tile_rows_minus1; ++i) { - UNSAFE_TODO(v4l2_pps.row_height_minus1[i]) = pps->row_height_minus1[i]; + if (!base::IsValueInRangeForNumericType<__u8>( + pps->row_height_minus1[i])) { + DVLOG(1) << "row_height_minus1[" << i + << "]=" << pps->row_height_minus1[i] + << " exceeds V4L2 UAPI __u8 range"; + return Status::kFail; + } + UNSAFE_TODO(v4l2_pps.row_height_minus1[i]) = + base::checked_cast<__u8>(pps->row_height_minus1[i]); } } }
Original Bug Report
Potential V4L2 HEVC delegate narrowing truncation of tile dimensions before VIDIOC_S_EXT_CTRLS
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 V4L2 HEVC accelerator delegate silently truncates PPS tile size parameters from int to __u8 when copying metadata to the V4L2 control structure. This causes inconsistent tile geometries to be sent to kernel drivers via VIDIOC_S_EXT_CTRLS, risking driver-side and GPU-memory corruption.
Affected files:
media/gpu/v4l2/v4l2_video_decoder_delegate_h265.ccmedia/parsers/h265_parser.hmedia/parsers/h265_parser.cc
Estimated timestamp from git blame: 2023-04-18
1. Summary of the Issue (Meant for Human Triage)
A potential narrowing truncation vulnerability exists in the Chromium V4L2 stateless HEVC video decoder delegate (V4L2VideoDecoderDelegateH265). The H.265 parser correctly parses Picture Parameter Set (PPS) explicit tile dimensions (column_width_minus1 and row_height_minus1) into std::array<int, N>. These values are bounded by the Sequence Parameter Set (SPS) dimensions, meaning they can legitimately reach values up to 1054 for large (e.g., 8K) resolutions with 16x16 Coding Tree Blocks (CTBs).
However, when the V4L2 delegate copies this metadata to the Linux kernel UAPI structure v4l2_ctrl_hevc_pps to configure the hardware decoder, the destination fields are typed as __u8[]. Because the copy uses a direct assignment without a range check, any tile width or height value ≥ 256 is silently truncated to its lowest 8 bits. The overall picture dimensions sent in the paired SPS control (v4l2_ctrl_hevc_sps) are typed as __u16 and are not truncated.
Consequently, an attacker-controlled web video stream can cause the V4L2 driver to receive an internally inconsistent state: an SPS describing a wide picture, paired with a PPS describing tile boundaries that sum to a fraction of that width. The VIDIOC_S_EXT_CTRLS ioctl succeeds because the kernel’s generic validation (std_validate_compound()) does not cross-check the sum of the tile widths against the SPS width. This mismatch propagates to the hardware driver (e.g., MediaTek mtk-vcodec, Hantro G2, or Rockchip), which calculates tile boundaries based on the truncated data. When the hardware CABAC engine subsequently decodes slice data that was validly encoded against the un-truncated widths, it will desynchronize and read/write out of bounds within the driver-allocated, GPU-resident scratch buffers.
2. Proof-of-Concept & Detailed Execution Flow
(Note: These are potential execution steps deduced from static analysis; our tooling agent does not have the ability to run code natively to demonstrate a live exploit.)
Potential Attacker Steps & Code Flow
- Attacker Bitstream Construction: An attacker serves a malicious Annex-B HEVC stream (via an HTML5
<video>, MSE, or WebCodecs). They construct an SPS definingpic_width_in_luma_samples = 8192and a minimum CTB size of 16 (yielding 512 CTBs horizontally). They then construct a PPS referencing this SPS withtiles_enabled_flag = 1,uniform_spacing_flag = 0, and exactly two tile columns (num_tile_columns_minus1 = 1). - Parser Read:
media/parsers/h265_parser.ccparses the bitstream. It computessps->pic_width_in_ctbs_y = 512. - Tile Width Validation: At
media/parsers/h265_parser.cc:974-981, the parser reads the explicit tile width provided by the attacker:The attacker encodes the first tile width asfor (int i = 0; i < pps->num_tile_columns_minus1; ++i) { READ_UE_OR_RETURN(&pps->column_width_minus1[i]); IN_RANGE_OR_RETURN( pps->column_width_minus1[i], 0, pps->column_width_minus1[pps->num_tile_columns_minus1] - 1); pps->column_width_minus1[pps->num_tile_columns_minus1] -= pps->column_width_minus1[i] + 1; }510. TheIN_RANGE_OR_RETURNvalidates that510is between0and510(pic_width_in_ctbs_y - 1 - 1). The parser securely writes510and0to itsstd::array<int, 19> column_width_minus1. - Delegate Narrowing Cast: In the sandboxed
kHardwareVideoDecodingutility process on a ChromeOS ARM device,V4L2VideoDecoderDelegateH265::SubmitFrameMetadatacopies the parser state to the kernel UAPI struct (v4l2_ctrl_hevc_pps):The array// media/gpu/v4l2/v4l2_video_decoder_delegate_h265.cc:297-300 for (int i = 0; i <= pps->num_tile_columns_minus1; ++i) { UNSAFE_TODO(v4l2_pps.column_width_minus1[i]) = pps->column_width_minus1[i]; }v4l2_pps.column_width_minus1is defined inlinux/v4l2-controls.has__u8 column_width_minus1[20]. Assigning theintvalue510to this__u8array silently truncates it to254(510 % 256). - Ioctl Submission: The delegate sends the truncated controls to the driver via
VIDIOC_S_EXT_CTRLSatmedia/gpu/v4l2/v4l2_video_decoder_delegate_h265.cc:503. - Driver Desync & Memory Corruption: The V4L2 driver uses the truncated tile geometry (summing to 256 CTBs) to allocate and program hardware tile-boundary registers. When the V4L2 delegate subsequently pushes the slice data (encoded correctly against the 511-CTB tile), the CABAC engine desynchronizes from the hardware boundaries, resulting in out-of-bounds reads and writes in the GPU-mapped scratch buffers.
Suggested Fix
To fix this vulnerability, V4L2VideoDecoderDelegateH265::SubmitFrameMetadata should explicitly validate that the parsed column_width_minus1 and row_height_minus1 values can safely fit within the V4L2 UAPI 8-bit types. If they exceed std::numeric_limits<uint8_t>::max(), the delegate should safely abort the decode process by returning a kDecodeError status (or false), preventing the truncated data from ever reaching VIDIOC_S_EXT_CTRLS.
3. Technical Verification Details (Automated Audit Logs - Reviewers may skip this section)
> The vulnerability accurately identifies a narrowing truncation from int to __u8 in V4L2VideoDecoderDelegateH265::SubmitFrameMetadata. The parser bounds column_width_minus1 by pic_width_in_ctbs_y (up to 1056), but the V4L2 struct stores it as __u8, silently truncating values ≥256. This leads to inconsistent tile geometry being passed to the hardware driver via ioctl. Medium (S2) is correct per severity guidelines: it represents a GPU validating-layer gap where an unchecked value reaches the driver, but the consequence is driver-specific and GPU-memory-resident (no host-memory write primitive shown). Furthermore, the affected code runs on ChromeOS/Linux, typically in the sandboxed OOP-VD utility process, confirming the one-step discount from the GPU process tier.
Prior Validator Critic Notes
- H.265 Parser Bounds: Checked at
media/parsers/h265_parser.cc:976-978. The parser correctly checks bounds according to the H.265 spec (relative topic_width_in_ctbs_y), but it is not aware of the Linux kernel UAPI__u8restriction. - VA-API Sibling Delegate: Checked at
media/gpu/vaapi/h265_vaapi_video_decoder_delegate.cc:156-178. VA-API usesuint16_tforcolumn_width_minus1[], so truncation does not occur on that platform, confirming this is a V4L2-specific issue. - Static Assertions: Checked in
media/gpu/v4l2/v4l2_video_decoder_delegate_h265.cc:292-296. Thestatic_assertvalidates array element count equality, but fails to check the width or type compatibility of the elements, allowing theintto__u8narrowing conversion to compile without error under current warnings/flags. - Device Resolution Configuration Checks: The resolution changes during mid-stream decoding run through
V4L2StatelessVideoDecoderBackend::ApplyResolution()but do not trigger secondary validation of the tile widths against the original configuration capabilities.
Code Flow Map
media/parsers/h265_parser.h:309:std::array<int, kMaxNumTileColumnWidth> column_width_minus1 = {};media/gpu/v4l2/v4l2_video_decoder_delegate_h265.cc:298-299:UNSAFE_TODO(v4l2_pps.column_width_minus1[i]) = pps->column_width_minus1[i];linux/v4l2-controls.h:2199:__u8 column_width_minus1[20];media/gpu/v4l2/v4l2_video_decoder_delegate_h265.cc:503:if (device_->Ioctl(VIDIOC_S_EXT_CTRLS, &ext_ctrls) != 0) {
Environmental Assumptions
- Target platform: ChromeOS ARM with V4L2 stateless HEVC enabled.
- Build targets:
#if BUILDFLAG(IS_CHROMEOS)enabled inv4l2_video_decoder_delegate_h265.ccindicating active production code for CrOS overlays.kUseOutOfProcessVideoDecodingis generally enabled by default on ChromeOS viamedia/base/media_switches.cc:1438, placing this within thekHardwareVideoDecodingsandboxed utility process.
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.