Medium chrome Logic Error 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInsufficient validation of untrusted input in Codecs
DescriptionInsufficient validation of untrusted input in Codecs
ComponentCodecs
Bug ClassLogic Error
Tracker518284253
Fix commit1e35b040e9dd (chromium/src) +49/-10
CISA KEVNot listed
CreditedAmeen Basha M K
Disclosed2026-07-29

Changed Functions

FunctionChangeNotes
for
media/gpu/vaapi/h265_vaapi_video_decoder_delegate.cc
modified

Files Changed

  • media/gpu/vaapi/h265_vaapi_video_decoder_delegate.cc
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;
       }
     }
   }
Loading diff…

Original Bug Report

reported by [email protected]

[482862710 same] Kernel GPU Corruption: H.265 luma_offset int→int8_t Truncation

Steps to reproduce the problem

  1. Open Chrome 148+ on Linux with Intel/AMD GPU (VA-API capable)
  2. Serve the PoC files via HTTP or file:// protocol
  3. Load test.html?file=poc_luma_200.mp4
  4. Observe:
    • chrome://media-internals shows 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=1 and 10-bit depth: range [-512, 511]
  • Parser stores in int type — 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 \

View on issue tracker