CVE-2026-5910
Overview
Background
- SEI (Supplemental Enhancement Information)
- optional H.264/H.265 bitstream messages whose
typeandpayload_sizeare each encoded as a run of0xffbytes plus a final byte, so an attacker-chosen stream can drive very large accumulated values. - `base::CheckedNumeric<int>`
- a wrapper type that tracks whether any arithmetic operation has overflowed, exposing
IsValid(),ValueOrDie(), andAssignIfValid()so callers can detect overflow instead of silently wrapping. - `IN_RANGE_OR_RETURN` / `SKIP_BITS_OR_RETURN`
- parser macros that validate a value against a range (or skip N bits) and return
kInvalidStreamon failure; the arguments are plainintexpressions evaluated before the macro runs. - Signed integer overflow
- in C++ the overflow of a signed
intis undefined behavior, so an unchecked accumulation or multiplication of parsed fields can produce a wrong or UB result used in later bounds math.
Root Cause Analysis
Several arithmetic expressions in the media H.264/H.265 parsers combined attacker-controlled bitstream fields into plain int values with no overflow protection. In ParseSEI (present in both h264_parser.cc and h265_parser.cc), the type and payload_size accumulators added 255 per 0xff byte and then computed payload_size * 8 into num_bits_remain, any of which could overflow a signed int. In the H.265 ParseSliceHeader, the summation of delta_poc_msb_cycle_lt[i] values, the num_entry_point_offsets upper-bound product (num_tile_columns_minus1 + 1) * pic_height_in_ctbs_y - 1, and the num_entry_point_offsets * (offset_len_minus1 + 1) bit-skip count could all overflow; the old slice_qp_delta validation also added 26 + init_qp_minus26 + slice_qp_delta which could overflow before the range check.
The fix routes each of these through base::CheckedNumeric, returns kInvalidStream on overflow, and refactors the slice_qp_delta check to compare the delta against pre-derived bounds rather than summing first; ParsePredWeightTable additionally range-checks delta_chroma_log2_weight_denom to [-7, 7] before adding it. This works because the dangerous values are now validated before being used in any subsequent size/bit calculation.
ints, permitting signed-integer overflow (undefined behavior) before the results were used in bounds/bit-skip math; the fix detects every such overflow with base::CheckedNumeric and bails out with kInvalidStream instead of continuing with a corrupted value.Attack Path
- Deliver a crafted stream
The attacker supplies an H.264 or H.265 bitstream (e.g. via a media element, WebCodecs, or a demuxed file) that Chrome’s
mediaparsers process. - Force an oversized accumulation
A long run of
0xffSEI bytes drivestype_checked/payload_size_checkedtowardINT_MAX, or a crafted slice header supplies largedelta_poc_msb_cycle_lt, tile, ornum_entry_point_offsetsvalues. - Trigger the overflowing arithmetic
Pre-patch,
payload_size * 8, the POC summation, the tile-count product, or theslice_qp_deltaaddition overflows a signedint, which is undefined behavior and yields a wrong value. - Corrupted value flows into bounds/bit math (inferred)
The wrong
num_bits_remainor skip count could mislead subsequentSKIP_BITS/ range validation; the diff itself only demonstrates the overflow, not a concrete out-of-bounds sink.
Impact Assessment
Media; no exploitation in the wild is reported. Any escalation beyond the overflow itself (memory corruption, information disclosure, or code execution) is not demonstrated by this diff and is at most (inferred).Changed Functions
| Function | Change | Notes |
|---|---|---|
whilemedia/parsers/h264_parser.cc |
modified | |
ifmedia/parsers/h265_parser.cc |
modified | |
whilemedia/parsers/h265_parser.cc |
modified |
Files Changed
media/parsers/h264_parser.ccmedia/parsers/h265_parser.cc
Audit Directions
- Variable-length field accumulationAudit every parser loop that adds a fixed increment per
0xff(or similar) escape byte, or sums repeated syntax elements, for unchecked signed accumulation. - Range-check-after-arithmeticFlag
IN_RANGE_OR_RETURN/SKIP_BITS_OR_RETURNcalls whose argument is itself anintsum or product of parsed fields; the arithmetic can overflow before the macro validates it, so derive bounds or usebase::CheckedNumericfirst. - Product-based size/limit derivationReview bitstream size and offset computations that multiply two parsed counts (tiles × rows, offsets × lengths); these are prime candidates for overflow and should be computed with checked arithmetic.
Patch
From 7cf2bae3b0688de45d5215e768ca987f2967fc46 Mon Sep 17 00:00:00 2001 From: Eugene Zemtsov <[email protected]> Date: Wed, 18 Feb 2026 14:30:58 -0800 Subject: [PATCH] media: Fix integer overflows in H264 and H265 parsers This change addresses multiple integer overflow issues in the H.264 and H.265 bitstream parsers. The following calculations are now protected or validated: - H.264/H.265 `ParseSEI`: The accumulation of SEI message `type` and `payload_size` is now protected using `base::CheckedNumeric`. - H.265 `ParseSliceHeader`: - The summation of `delta_poc_msb_cycle_lt` values is now protected using `base::CheckedNumeric`. - `slice_qp_delta` validation is refactored to check the delta against derived bounds instead of performing a potentially overflowing addition. - `num_entry_point_offsets` upper bound calculation and the subsequent bit skip calculation are now protected using `base::CheckedNumeric`. - H.265 `ParsePredWeightTable`: - `delta_chroma_log2_weight_denom` is now validated against constant bounds [-7, 7] before addition to prevent signed integer overflow. Bug: 485203821, 485115554, 485212874 Change-Id: Ifc8da5426b0d9f0e3bbfed30d175e62af46bca22 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7586436 Reviewed-by: Ted (Chromium) Meyer <[email protected]> Commit-Queue: Eugene Zemtsov <[email protected]> Cr-Commit-Position: refs/heads/main@{#1586702} --- diff --git a/media/parsers/h264_parser.cc b/media/parsers/h264_parser.cc index d0814220..03cafeb3 100644 --- a/media/parsers/h264_parser.cc +++ b/media/parsers/h264_parser.cc @@ -1542,22 +1542,43 @@ // the parsed SEI messages, so we have to set a limit here. constexpr int kMaxParsedSEIMessages = 64; do { - int type = 0; + base::CheckedNumeric<int> type_checked = 0; READ_BITS_OR_RETURN(8, &byte); while (byte == 0xff) { - type += 255; + type_checked += 255; READ_BITS_OR_RETURN(8, &byte); } - type += byte; + type_checked += byte; - int payload_size = 0; + if (!type_checked.IsValid()) { + DVLOG(1) << "SEI type overflow"; + return kInvalidStream; + } + int type = type_checked.ValueOrDie(); + + base::CheckedNumeric<int> payload_size_checked = 0; READ_BITS_OR_RETURN(8, &byte); while (byte == 0xff) { - payload_size += 255; + payload_size_checked += 255; READ_BITS_OR_RETURN(8, &byte); } - payload_size += byte; - int num_bits_remain = payload_size * 8; + payload_size_checked += byte; + + if (!payload_size_checked.IsValid()) { + DVLOG(1) << "SEI payload size overflow"; + return kInvalidStream; + } + + int payload_size = payload_size_checked.ValueOrDie(); + base::CheckedNumeric<int> num_bits_remain_checked = + payload_size_checked * 8; + + if (!num_bits_remain_checked.IsValid()) { + DVLOG(1) << "SEI payload bits overflow"; + return kInvalidStream; + } + + int num_bits_remain = num_bits_remain_checked.ValueOrDie(); DVLOG(4) << "Found SEI message type: " << type << " payload size: " << payload_size; diff --git a/media/parsers/h265_parser.cc b/media/parsers/h265_parser.cc index cd4800e9..06303f8 100644 --- a/media/parsers/h265_parser.cc +++ b/media/parsers/h265_parser.cc @@ -13,6 +13,7 @@ #include "base/bits.h" #include "base/logging.h" #include "base/notreached.h" +#include "base/numerics/checked_math.h" #include "base/numerics/safe_conversions.h" #include "media/base/decrypt_config.h" #include "media/base/video_codecs.h" @@ -1272,9 +1273,11 @@ std::pow(2, 32 - sps->log2_max_pic_order_cnt_lsb_minus4 - 4)); // Equation 7-52. if (i != 0 && i != shdr->num_long_term_sps) { - shdr->delta_poc_msb_cycle_lt[i] = - shdr->delta_poc_msb_cycle_lt[i] + - shdr->delta_poc_msb_cycle_lt[i - 1]; + base::CheckedNumeric<int> sum = shdr->delta_poc_msb_cycle_lt[i]; + sum += shdr->delta_poc_msb_cycle_lt[i - 1]; + if (!sum.AssignIfValid(&shdr->delta_poc_msb_cycle_lt[i])) { + return kInvalidStream; + } } } } @@ -1361,8 +1364,9 @@ IN_RANGE_OR_RETURN(5 - shdr->five_minus_max_num_merge_cand, 1, 5); } READ_SE_OR_RETURN(&shdr->slice_qp_delta); - IN_RANGE_OR_RETURN(26 + pps->init_qp_minus26 + shdr->slice_qp_delta, - -pps->qp_bd_offset_y, 51); + int base_qp = 26 + pps->init_qp_minus26; + IN_RANGE_OR_RETURN(shdr->slice_qp_delta, -pps->qp_bd_offset_y - base_qp, + 51 - base_qp); if (pps->pps_slice_chroma_qp_offsets_present_flag) { READ_SE_OR_RETURN(&shdr->slice_cb_qp_offset); @@ -1410,15 +1414,26 @@ (pps->num_tile_columns_minus1 + 1) * (pps->num_tile_rows_minus1 + 1) - 1); } else { // both are true - IN_RANGE_OR_RETURN( - num_entry_point_offsets, 0, - (pps->num_tile_columns_minus1 + 1) * sps->pic_height_in_ctbs_y - 1); + base::CheckedNumeric<int> limit = pps->num_tile_columns_minus1 + 1; + limit *= sps->pic_height_in_ctbs_y; + limit -= 1; + if (!limit.IsValid()) { + return kInvalidStream; + } + int limit_val = limit.ValueOrDie(); + IN_RANGE_OR_RETURN(num_entry_point_offsets, 0, limit_val); } if (num_entry_point_offsets > 0) { int offset_len_minus1; READ_UE_OR_RETURN(&offset_len_minus1); IN_RANGE_OR_RETURN(offset_len_minus1, 0, 31); - SKIP_BITS_OR_RETURN(num_entry_point_offsets * (offset_len_minus1 + 1)); + base::CheckedNumeric<int> bits_to_skip = offset_len_minus1 + 1; + bits_to_skip *= num_entry_point_offsets; + if (!bits_to_skip.IsValid()) { + return kInvalidStream; + } + int bits_to_skip_val = bits_to_skip.ValueOrDie(); + SKIP_BITS_OR_RETURN(bits_to_skip_val); } } @@ -2022,6 +2037,8 @@ IN_RANGE_OR_RETURN(pred_weight_table->luma_log2_weight_denom, 0, 7); if (sps.chroma_array_type) { READ_SE_OR_RETURN(&pred_weight_table->delta_chroma_log2_weight_denom); + IN_RANGE_OR_RETURN(pred_weight_table->delta_chroma_log2_weight_denom, -7, + 7); pred_weight_table->chroma_log2_weight_denom = pred_weight_table->delta_chroma_log2_weight_denom + pred_weight_table->luma_log2_weight_denom; @@ -2117,22 +2134,43 @@ // the parsed SEI messages, so we have to set a limit here. constexpr int kMaxParsedSEIMessages = 64; do { - int type = 0; + base::CheckedNumeric<int> type_checked = 0; READ_BITS_OR_RETURN(8, &byte); while (byte == 0xff) { - type += 255; + type_checked += 255; READ_BITS_OR_RETURN(8, &byte); } - type += byte; + type_checked += byte; - int payload_size = 0; + if (!type_checked.IsValid()) { + DVLOG(1) << "SEI type overflow"; + return kInvalidStream; + } + int type = type_checked.ValueOrDie(); + + base::CheckedNumeric<int> payload_size_checked = 0; READ_BITS_OR_RETURN(8, &byte); while (byte == 0xff) { - payload_size += 255; + payload_size_checked += 255; READ_BITS_OR_RETURN(8, &byte); } - payload_size += byte; - int num_bits_remain = payload_size * 8; + payload_size_checked += byte; + + if (!payload_size_checked.IsValid()) { + DVLOG(1) << "SEI payload size overflow"; + return kInvalidStream;
Original Bug Report
Integer Overflow in H265 Slice Header Parsing
Steps to reproduce the problem
- Build Chrome libfuzzer with below mentioned args
- execute the fuzzer with attached malformed H265 file
- Integer Overflow Crash Detected
Problem Description
An integer overflow exists in Chromium’s H.265 parser during slice header parsing. A crafted H.265 bitstream can trigger an overflow in arithmetic involving parsed slice parameters, resulting in crash
Vulnerable File Area:
media/parsers/h265_parser.cc
Function: media::H265Parser::ParseSliceHeader
Line: 1284
Build Args:
gn gen out/Media --args='
is_asan=true
is_debug=false
is_ubsan_security=true
is_component_build=false
proprietary_codecs=true
ffmpeg_branding="Chrome"
symbol_level=1
use_remoteexec=false
use_libfuzzer=true
angle_enable_metal=false
'
autoninja -C out/Media media_h265_parser_fuzzer
Execution Args:
./media_h265_parser_fuzzer delta_poc_overflow.h265
A poc file with Crafted slice header values cause overflow during parsing is attached below
Summary
Integer Overflow in H265 Slice Header Parsing
Custom Questions
Crash state:
INFO: Running with entropic power schedule (0xFF, 100).
INFO: Seed: 4195144660
INFO: Loaded 1 modules (2164628 inline 8-bit counters): 2164628 [0x5ba0205e9ac0, 0x5ba0207fa254),
INFO: Loaded 1 PC tables (2164628 PCs): 2164628 [0x5ba0207fa258,0x5ba022901b98),
./media_h265_parser_fuzzer: Running 1 inputs 1 time(s) each.
Running: /home/basha/Downloads/win_repro/delta_poc_overflow.h265
../../media/parsers/h265_parser.cc:1284:51: runtime error: signed integer overflow: 268435456 + 1879048192 cannot be represented in type 'int'
#0 0x5ba01760b60a in media::H265Parser::ParseSliceHeader(media::H265NALU const&, media::H265SliceHeader*, media::H265SliceHeader*) media/parsers/h265_parser.cc:1284:51
#1 0x5ba0133ac89d in LLVMFuzzerTestOneInput media/parsers/h265_parser_fuzzertest.cc:61:22
#2 0x5ba0133f027c in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long) third_party/libFuzzer/src/FuzzerLoop.cpp:619:13
#3 0x5ba0133c12b1 in fuzzer::RunOneTest(fuzzer::Fuzzer*, char const*, unsigned long) third_party/libFuzzer/src/FuzzerDriver.cpp:328:6
#4 0x5ba0133c9da0 in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) third_party/libFuzzer/src/FuzzerDriver.cpp:863:9
#5 0x5ba0133acfdd in main third_party/libFuzzer/src/FuzzerMain.cpp:20:10
#6 0x7c06d182a1c9 in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
#7 0x7c06d182a28a in __libc_start_main csu/../csu/libc-start.c:360:3
#8 0x5ba0132cfb29 in _start (/home/basha/Desktop/chromefuzz/chromium/src/out/UBSanMedia/media_h265_parser_fuzzer+0x7c11b29) (BuildId: 851ee95e8c3708a1)
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior ../../media/parsers/h265_parser.cc:1284:51
Executed /home/basha/Downloads/win_repro/delta_poc_overflow.h265 in 184 ms
***
*** NOTE: fuzzing was not performed, you have only
*** executed the target code on a fixed set of inputs.
***
Reporter credit:
Ameen Basha M K & Mohammed Yasar B
Additional Data
Category: Security
Chrome Channel: Not sure
Regression: N/A \