CVE-2026-17848
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
formedia/gpu/vaapi/h265_vaapi_video_decoder_delegate.cc |
modified |
Files Changed
media/gpu/vaapi/h265_vaapi_video_decoder_delegate.cc
Patch
From 1e35b040e9dd3c35f57d1205c84d8caaa942dc7d Mon Sep 17 00:00:00 2001 From: Ted Meyer <[email protected]> Date: Fri, 12 Jun 2026 13:19:30 -0700 Subject: [PATCH] Guard int assignments to smaller types in H265 The VAAPI headers just seem to use the wrong integer size (uint8) for an h265 field that can be 16 bits wide in high-bit-depth content. The change causes decoding to fail in out-of-bounds scenarios. We decay the field type from the vaapi headers in case they get updated someday. Not much can be done about testing here, it's just an integer size mismatch. I did verify that the videos with high luma/chroma values are blocked from being played without impacting otherwise valid video. Fixed: 518284253 Change-Id: Ib1ffa26d4df1eebac222ca1ed4eab8f0c2b6143c Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7923576 Reviewed-by: Nathan Hebert <[email protected]> Reviewed-by: Dale Curtis <[email protected]> Commit-Queue: Ted (Chromium) Meyer <[email protected]> Cr-Commit-Position: refs/heads/main@{#1646207} --- diff --git a/media/gpu/vaapi/h265_vaapi_video_decoder_delegate.cc b/media/gpu/vaapi/h265_vaapi_video_decoder_delegate.cc index 944bbcf0..fd26e13 100644 --- a/media/gpu/vaapi/h265_vaapi_video_decoder_delegate.cc +++ b/media/gpu/vaapi/h265_vaapi_video_decoder_delegate.cc @@ -7,6 +7,7 @@ #include "base/compiler_specific.h" #include "base/containers/span.h" #include "base/memory/scoped_refptr.h" +#include "base/numerics/safe_conversions.h" #include "build/build_config.h" #include "media/base/cdm_context.h" #include "media/gpu/macros.h" @@ -365,6 +366,15 @@ #define SHDR_TO_SP_LSF(a) slice_param_.LongSliceFlags.fields.a = slice_hdr->a #define SHDR_TO_SP_LSF2(a, b) \ slice_param_.LongSliceFlags.fields.a = slice_hdr->b +#define CHECKED_SHDR_TO_SP2(SRC, DEST, ERR) \ + do { \ + using dest_type = std::decay_t<decltype(slice_param_.DEST)>; \ + if (!base::IsValueInRangeForNumericType<dest_type>(slice_hdr->SRC)) { \ + return (ERR); \ + } \ + SHDR_TO_SP2(SRC, DEST); \ + } while (0) + SHDR_TO_SP(slice_segment_address); const auto ref_pic_list0_size = ref_pic_list0.size(); const auto ref_pic_list1_size = ref_pic_list1.size(); @@ -435,40 +445,69 @@ SHDR_TO_SP2(pred_weight_table.delta_chroma_log2_weight_denom, delta_chroma_log2_weight_denom); for (int i = 0; i < kMaxRefIdxActive; ++i) { + // VAAPI headers use the wrong bit-depth for a few fields: + // field | required | actual + // VASliceParametBufferHEVC.luma_offset_l0 | uint16 | uint8 + // VASliceParametBufferHEVC.luma_offset_l1 | uint16 | uint8 + // VASliceParametBufferHEVC.ChromaOffsetL0 | uint16 | uint8 + // VASliceParametBufferHEVC.ChromaOffsetL1 | uint16 | uint8 + // Likely due to spec changes adding support for high-bit-depth content + // that was just never added to VAAPI. We have to verify that these values + // are within acceptable ranges for the vaapi driver, otherwise playback + // isn't possible. This likely means that some high-bit-depth content that + // is valid just can't be played through VAAPI. + UNSAFE_TODO(SHDR_TO_SP2(pred_weight_table.delta_luma_weight_l0[i], delta_luma_weight_l0[i])); - UNSAFE_TODO( - SHDR_TO_SP2(pred_weight_table.luma_offset_l0[i], luma_offset_l0[i])); + + UNSAFE_TODO(CHECKED_SHDR_TO_SP2(pred_weight_table.luma_offset_l0[i], + luma_offset_l0[i], DecodeStatus::kFail)); if (slice_hdr->IsBSlice()) { UNSAFE_TODO(SHDR_TO_SP2(pred_weight_table.delta_luma_weight_l1[i], delta_luma_weight_l1[i])); - UNSAFE_TODO( - SHDR_TO_SP2(pred_weight_table.luma_offset_l1[i], luma_offset_l1[i])); + UNSAFE_TODO(CHECKED_SHDR_TO_SP2(pred_weight_table.luma_offset_l1[i], + luma_offset_l1[i], DecodeStatus::kFail)); } for (int j = 0; j < 2; ++j) { - UNSAFE_TODO(SHDR_TO_SP2(pred_weight_table.delta_chroma_weight_l0[i][j], - delta_chroma_weight_l0[i][j])); + UNSAFE_TODO(CHECKED_SHDR_TO_SP2( + pred_weight_table.delta_chroma_weight_l0[i][j], + delta_chroma_weight_l0[i][j], DecodeStatus::kFail)); int chroma_weight_l0 = (1 << slice_hdr->pred_weight_table.chroma_log2_weight_denom) + slice_hdr->pred_weight_table.delta_chroma_weight_l0[i][j]; - UNSAFE_TODO(slice_param_.ChromaOffsetL0[i][j]) = + int chroma_offset_l0 = Clip3(-sps->wp_offset_half_range_c, sps->wp_offset_half_range_c - 1, (sps->wp_offset_half_range_c + slice_hdr->pred_weight_table.delta_chroma_offset_l0[i][j] - ((sps->wp_offset_half_range_c * chroma_weight_l0) >> slice_hdr->pred_weight_table.chroma_log2_weight_denom))); + using chroma_offset_l0_type = + std::decay_t<decltype(slice_param_.ChromaOffsetL0[i][j])>; + if (!base::IsValueInRangeForNumericType<chroma_offset_l0_type>( + chroma_offset_l0)) { + return DecodeStatus::kFail; + } + UNSAFE_TODO(slice_param_.ChromaOffsetL0[i][j]) = chroma_offset_l0; if (slice_hdr->IsBSlice()) { - UNSAFE_TODO(SHDR_TO_SP2(pred_weight_table.delta_chroma_weight_l1[i][j], - delta_chroma_weight_l1[i][j])); + UNSAFE_TODO(CHECKED_SHDR_TO_SP2( + pred_weight_table.delta_chroma_weight_l1[i][j], + delta_chroma_weight_l1[i][j], DecodeStatus::kFail)); int chroma_weight_l1 = (1 << slice_hdr->pred_weight_table.chroma_log2_weight_denom) + slice_hdr->pred_weight_table.delta_chroma_weight_l1[i][j]; - UNSAFE_TODO(slice_param_.ChromaOffsetL1[i][j]) = + int chroma_offset_l1 = Clip3(-sps->wp_offset_half_range_c, sps->wp_offset_half_range_c - 1, (sps->wp_offset_half_range_c + slice_hdr->pred_weight_table.delta_chroma_offset_l1[i][j] - ((sps->wp_offset_half_range_c * chroma_weight_l1) >> slice_hdr->pred_weight_table.chroma_log2_weight_denom))); + using chroma_offset_l1_type = + std::decay_t<decltype(slice_param_.ChromaOffsetL1[i][j])>; + if (!base::IsValueInRangeForNumericType<chroma_offset_l1_type>( + chroma_offset_l1)) { + return DecodeStatus::kFail; + } + UNSAFE_TODO(slice_param_.ChromaOffsetL1[i][j]) = chroma_offset_l1; } } }
Original Bug Report
[482862710 same] Kernel GPU Corruption: H.265 luma_offset int→int8_t Truncation
Steps to reproduce the problem
- Open Chrome 148+ on Linux with Intel/AMD GPU (VA-API capable)
- Serve the PoC files via HTTP or
file://protocol - Load
test.html?file=poc_luma_200.mp4 - Observe:
chrome://media-internalsshows VaapiVideoDecoder selected- Full Chrome window exhibits visual flickering/corruption
Chrome Launch Flags
google-chrome-stable \
--enable-features=VaapiVideoDecodeLinuxGL,VaapiVideoDecoder,PlatformHEVCDecoderSupport \
--autoplay-policy=no-user-gesture-required \
"file:///path/to/test.html?file=poc_luma_200.mp4"
Note: On Chrome 148 Linux, PlatformHEVCDecoderSupport is enabled by default.
Problem Description
Affected Versions
- Chrome 148.0.7778.178 (confirmed)
- All Chrome/Chromium versions with HEVC VA-API hardware decode enabled
- Platforms: Linux (VA-API), ChromeOS, potentially Windows (D3D11)
Summary
The H.265 VA-API video decoder delegate copies parsed luma_offset_l0[i] and
luma_offset_l1[i] values (type int, range ±511 with 10-bit high-precision
offsets) into the VA-API VASliceParameterBufferHEVC struct where the destination
field is int8_t (range ±128). This silent integer narrowing sends incorrect
weighted prediction offset values to the GPU hardware decoder.
Root Cause
File: media/gpu/vaapi/h265_vaapi_video_decoder_delegate.cc
Line: 441, 446
#define SHDR_TO_SP2(a, b) slice_param_.b = slice_hdr->a
// Line 441 — luma_offset_l0: int → int8_t
SHDR_TO_SP2(pred_weight_table.luma_offset_l0[i], luma_offset_l0[i]);
// Line 446 — luma_offset_l1: int → int8_t
SHDR_TO_SP2(pred_weight_table.luma_offset_l1[i], luma_offset_l1[i]);
The parser (h265_parser.cc:2101) correctly validates the range per the HEVC spec:
- With
high_precision_offsets_enabled_flag=1and 10-bit depth: range [-512, 511] - Parser stores in
inttype — no truncation
But the VA-API backend blindly copies to int8_t without checking if the value
fits, causing undefined behavior (signed integer overflow in C++).
Impact
Immediate (Confirmed)
- GPU memory corruption manifesting as full-process visual artifacts
- Incorrect weighted prediction values sent to hardware decoder
- The corruption extends beyond the video surface to the compositor
Summary
[482862710 same] Kernel GPU Corruption: H.265 luma_offset int→int8_t Truncation
Additional Data
Category: Security
Chrome Channel: Stable
Regression: N/A \