Medium chrome Logic Error 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactNumeric truncation error in Media
DescriptionNumeric truncation error in Media
ComponentMedia
Bug ClassLogic Error
Tracker536423794
Fix commit71c52c59bfb6 (chromium/src) +17/-2
CISA KEVNot listed
CreditedGoogle
Disclosed2026-09-08

Changed Functions

FunctionChangeNotes
for
media/gpu/v4l2/v4l2_video_decoder_delegate_h265.cc
modified

Files Changed

  • media/gpu/v4l2/v4l2_video_decoder_delegate_h265.cc
From 71c52c59bfb6daa32a033e4658ebfd45cbcb0855 Mon Sep 17 00:00:00 2001
From: Hirokazu Honda <hiroh@chromium.org>
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 <nhebert@chromium.org>
Auto-Submit: Hirokazu Honda <hiroh@chromium.org>
Reviewed-by: Nathan Hebert <nhebert@chromium.org>
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]);
       }
     }
   }
Loading diff…

Original Bug Report

reported by aw...@chromium.org

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.cc
  • media/parsers/h265_parser.h
  • media/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

  1. Attacker Bitstream Construction: An attacker serves a malicious Annex-B HEVC stream (via an HTML5 <video>, MSE, or WebCodecs). They construct an SPS defining pic_width_in_luma_samples = 8192 and a minimum CTB size of 16 (yielding 512 CTBs horizontally). They then construct a PPS referencing this SPS with tiles_enabled_flag = 1, uniform_spacing_flag = 0, and exactly two tile columns (num_tile_columns_minus1 = 1).
  2. Parser Read: media/parsers/h265_parser.cc parses the bitstream. It computes sps->pic_width_in_ctbs_y = 512.
  3. Tile Width Validation: At media/parsers/h265_parser.cc:974-981, the parser reads the explicit tile width provided by the attacker:
    for (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;
    }
    
    The attacker encodes the first tile width as 510. The IN_RANGE_OR_RETURN validates that 510 is between 0 and 510 (pic_width_in_ctbs_y - 1 - 1). The parser securely writes 510 and 0 to its std::array<int, 19> column_width_minus1.
  4. Delegate Narrowing Cast: In the sandboxed kHardwareVideoDecoding utility process on a ChromeOS ARM device, V4L2VideoDecoderDelegateH265::SubmitFrameMetadata copies the parser state to the kernel UAPI struct (v4l2_ctrl_hevc_pps):
    // 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];
    }
    
    The array v4l2_pps.column_width_minus1 is defined in linux/v4l2-controls.h as __u8 column_width_minus1[20]. Assigning the int value 510 to this __u8 array silently truncates it to 254 (510 % 256).
  5. Ioctl Submission: The delegate sends the truncated controls to the driver via VIDIOC_S_EXT_CTRLS at media/gpu/v4l2/v4l2_video_decoder_delegate_h265.cc:503.
  6. 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 to pic_width_in_ctbs_y), but it is not aware of the Linux kernel UAPI __u8 restriction.
  • VA-API Sibling Delegate: Checked at media/gpu/vaapi/h265_vaapi_video_decoder_delegate.cc:156-178. VA-API uses uint16_t for column_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. The static_assert validates array element count equality, but fails to check the width or type compatibility of the elements, allowing the int to __u8 narrowing 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 in v4l2_video_decoder_delegate_h265.cc indicating active production code for CrOS overlays. kUseOutOfProcessVideoDecoding is generally enabled by default on ChromeOS via media/base/media_switches.cc:1438, placing this within the kHardwareVideoDecoding sandboxed 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.

View on issue tracker