CVE-2026-5908
Overview
Background
- `base::CheckedNumeric<int>`
- a wrapper type that performs arithmetic while tracking overflow, exposing
IsValid(),ValueOrDie(), andAssignIfValid()so callers can detect and reject out-of-range results. - SEI messages
- Supplemental Enhancement Information units whose
typeandpayload_sizeare encoded as a run of0xffbytes plus a final byte, i.e. accumulated as255 * n + last, parsed byParseSEIin bothh264_parser.ccandh265_parser.cc. - H.265 slice-header fields
delta_poc_msb_cycle_lt[],slice_qp_delta,num_entry_point_offsets, and entry-pointoffset_len_minus1, all read from the bitstream and combined arithmetically inParseSliceHeader.- `IN_RANGE_OR_RETURN` / `SKIP_BITS_OR_RETURN`
- macros that respectively enforce an inclusive range (returning failure otherwise) and advance the bitstream by a bit count; both can be fed overflowing expressions if the inputs are not pre-checked.
Root Cause Analysis
Several arithmetic combinations of attacker-controlled bitstream values in the H.264/H.265 parsers were performed with plain int, so a crafted stream could drive them to signed-integer overflow (undefined behavior) before any bounds check saw a sane value. In ParseSEI, the accumulators type += 255 / payload_size += 255 (over a long 0xff run) and num_bits_remain = payload_size * 8 could overflow int; the invariant that these counters stay representable was unenforced. In H.265 ParseSliceHeader, delta_poc_msb_cycle_lt[i] + delta_poc_msb_cycle_lt[i-1] (Equation 7-52), the num_entry_point_offsets upper-bound (num_tile_columns_minus1 + 1) * pic_height_in_ctbs_y - 1, and the skip count num_entry_point_offsets * (offset_len_minus1 + 1) could each overflow; additionally slice_qp_delta was validated by computing the potentially overflowing 26 + init_qp_minus26 + slice_qp_delta inside IN_RANGE_OR_RETURN. In ParsePredWeightTable, delta_chroma_log2_weight_denom was added to luma_log2_weight_denom without first bounding it.
The fix wraps each accumulation/product in base::CheckedNumeric<int> and returns kInvalidStream on !IsValid()/failed AssignIfValid, refactors the slice_qp_delta check to bound the raw delta ([-qp_bd_offset_y - base_qp, 51 - base_qp]) so no overflowing sum is formed, and adds IN_RANGE_OR_RETURN(delta_chroma_log2_weight_denom, -7, 7) before the addition — each guard runs before its value is used.
int, permitting signed-overflow UB before validation; the fix routes those computations through base::CheckedNumeric<int> (and reorders the slice_qp_delta/delta_chroma_log2_weight_denom checks to bound raw inputs) so overflow is detected and the stream rejected.Attack Path
- Craft a malformed H.264/H.265 stream
The attacker builds NAL units with, for example, an SEI message using a long run of
0xffbytes sotype/payload_sizeaccumulate pastINT_MAX, or an H.265 slice header with hostilenum_tile_columns_minus1,pic_height_in_ctbs_y,offset_len_minus1, ordelta_poc_msb_cycle_ltvalues. - Victim decodes the media
The stream reaches
ParseSEI/ParseSliceHeadervia a<video>element, MSE, or a media file in the media/GPU parsing path. - Signed-integer overflow occurs (pre-patch)
The plain-
intaccumulations or products overflow, which is undefined behavior and can yield a wrong (e.g. small or negative)num_bits_remain,limit, or skip count. - Overflowed value used downstream
A corrupted size/skip/limit could feed
SKIP_BITS_OR_RETURNor subsequent bounds logic with an implausible value, causing mis-parsing. (inferred — the patch demonstrates the overflow but no specific OOB read/write, memory-corruption primitive, or escalation)
Impact Assessment
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
- Unchecked accumulators over `0xff` runsAudit any parser loop of the form
x += 255; ... x += lastfor missing overflow protection, across all SEI/NAL/extension-data readers. - Products of two parsed quantitiesReview multiplications like
count * size,a * b - 1, orn * (len + 1)built from bitstream fields; these should usebase::CheckedNumericbefore feeding range checks or skip/allocation sinks. - Validation that computes the sum it is checkingPrefer bounding the raw delta (as the
slice_qp_delta/delta_chroma_log2_weight_denomfixes do) rather than evaluatingbase + deltainsideIN_RANGE_OR_RETURN, where the intermediate can overflow before the check.
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 SEI 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
A signed integer overflow occurs in Chromium’s H.265 parser while processing SEI (Supplemental Enhancement Information) data. The overflow happens during multiplication of a large parsed value, which can lead to crash
Vulnerable File Details
media/parsers/h265_parser.cc
Function: media::H265Parser::ParseSEI
Line: 2144
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 sei_overflow.h265
Attached a poc file, The file contains a crafted SEI payload with a large parsed field value designed to overflow during size calculation.
Summary
Integer Overflow in H265 SEI Parsing
Custom Questions
Crash state:
INFO: Running with entropic power schedule (0xFF, 100).
INFO: Seed: 4166765090
INFO: Loaded 1 modules (2164628 inline 8-bit counters): 2164628 [0x630b9b2a4ac0, 0x630b9b4b5254),
INFO: Loaded 1 PC tables (2164628 PCs): 2164628 [0x630b9b4b5258,0x630b9d5bcb98),
./media_h265_parser_fuzzer: Running 1 inputs 1 time(s) each.
Running: /home/basha/Downloads/win_repro/sei_overflow.h265
../../media/parsers/h265_parser.cc:2144:40: runtime error: signed integer overflow: 268435456 * 8 cannot be represented in type 'int'
#0 0x630b922e623c in media::H265Parser::ParseSEI(media::H265SEI*) media/parsers/h265_parser.cc:2144:40
#1 0x630b8e0677b7 in LLVMFuzzerTestOneInput media/parsers/h265_parser_fuzzertest.cc:43:22
#2 0x630b8e0ab27c in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long) third_party/libFuzzer/src/FuzzerLoop.cpp:619:13
#3 0x630b8e07c2b1 in fuzzer::RunOneTest(fuzzer::Fuzzer*, char const*, unsigned long) third_party/libFuzzer/src/FuzzerDriver.cpp:328:6
#4 0x630b8e084da0 in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) third_party/libFuzzer/src/FuzzerDriver.cpp:863:9
#5 0x630b8e067fdd in main third_party/libFuzzer/src/FuzzerMain.cpp:20:10
#6 0x7ef19742a1c9 in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
#7 0x7ef19742a28a in __libc_start_main csu/../csu/libc-start.c:360:3
#8 0x630b8df8ab29 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:2144:40
Executed /home/basha/Downloads/win_repro/sei_overflow.h265 in 275 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 \