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 <eugene@chromium.org>
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 <tmathmeyer@chromium.org>
Commit-Queue: Eugene Zemtsov <eugene@chromium.org>
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 \