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
Tracker500028989
Fix commit94ad12b2d1da (chromium/src) +55/-0
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-02

Changed Functions

FunctionChangeNotes
TEST_F
media/gpu/h264_decoder_unittest.cc
modified
if
media/parsers/h264_parser.cc
modified

Files Changed

  • media/gpu/h264_decoder_unittest.cc
  • media/parsers/h264_parser.cc
From 94ad12b2d1dadb6f4728d4864b2828f4e015267f Mon Sep 17 00:00:00 2001
From: Ted Meyer <[email protected]>
Date: Wed, 08 Apr 2026 13:23:34 -0700
Subject: [PATCH] Harden h264 parser to prevent sending OOB data

Address missing h264 spec section 7.4.3 - the first_mb_in_slice should
always be in range [0, PicSizeInMbs - 1]. This change just hardens the
decoders to prevent an out-of-bounds value making it to the hardware
driver, in case those drivers don't check things themselves.

Fixed: 500028989
Change-Id: I624243d798b2eee4d6de6070502ff96f0c284bb1
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7737743
Auto-Submit: Ted (Chromium) Meyer <[email protected]>
Commit-Queue: Ted (Chromium) Meyer <[email protected]>
Reviewed-by: Eugene Zemtsov <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1611752}
---

diff --git a/media/gpu/h264_decoder_unittest.cc b/media/gpu/h264_decoder_unittest.cc
index a4aa9d3..83e640c 100644
--- a/media/gpu/h264_decoder_unittest.cc
+++ b/media/gpu/h264_decoder_unittest.cc
@@ -815,6 +815,43 @@
   ASSERT_TRUE(decoder_->Flush());
 }
 
+// POC: Unvalidated first_mb_in_slice in subsequent slices reaches the
+// accelerator.
+//
+// H264Decoder::PreprocessCurrentSlice() only validates first_mb_in_slice==0
+// when IsNewPrimaryCodedPicture() returns true. For the second (and later)
+// slice of a multi-slice picture, IsNewPrimaryCodedPicture() returns false
+// and the value is forwarded to accelerator_->SubmitSlice() with no upper
+// bound check. The H.264 spec (7.4.3) requires first_mb_in_slice <
+// PicSizeInMbs; the H.265 parser enforces the equivalent condition.
+//
+// This test feeds a hand-crafted Annex-B stream:
+//   SPS  : 320x240 (PicSizeInMbs = 20*15 = 300)
+//   PPS
+//   IDR slice 0 : first_mb_in_slice = 0      (passes the new-picture check)
+//   IDR slice 1 : first_mb_in_slice = 65535  (same frame_num/idr_pic_id ->
+//                                             IsNewPrimaryCodedPicture()=false)
+TEST_F(H264DecoderTest, UnvalidatedFirstMbInSliceReachesAccelerator) {
+  static constexpr uint8_t kStream[] = {
+      0x00, 0x00, 0x00, 0x01, 0x67, 0x42, 0x00, 0x1e, 0xda, 0x05, 0x07,
+      0xe4, 0x00, 0x00, 0x00, 0x01, 0x68, 0xce, 0x38, 0x80, 0x00, 0x00,
+      0x00, 0x01, 0x65, 0x88, 0x84, 0xd5, 0x55, 0x40, 0x00, 0x00, 0x00,
+      0x01, 0x65, 0x00, 0x00, 0x80, 0x00, 0x08, 0x84, 0xd5, 0x55, 0x40,
+  };
+
+  decoder_->SetStream(0, DecoderBuffer::CopyFrom(base::span(kStream)));
+
+  // First Decode() processes the SPS and reports a config change.
+  ASSERT_EQ(AcceleratedVideoDecoder::kConfigChange, decoder_->Decode());
+  EXPECT_EQ(gfx::Size(320, 240), decoder_->GetPicSize());
+
+  EXPECT_CALL(*accelerator_, SubmitSlice(_, _, _, _, _, _, _, _))
+      .WillRepeatedly(Return(H264Decoder::H264Accelerator::Status::kOk));
+
+  // Second Decode() processes PPS + both IDR slices of the same picture.
+  ASSERT_EQ(AcceleratedVideoDecoder::kDecodeError, decoder_->Decode());
+}
+
 TEST_F(H264DecoderTest, ModifyReferencePicList_CompactionTime) {
   // Seed the decoder DPB and `curr_pic_` with a long term ref pic.
   scoped_refptr<H264Picture> ref_pic = base::MakeRefCounted<H264Picture>();
diff --git a/media/parsers/h264_parser.cc b/media/parsers/h264_parser.cc
index 8ae6bb03..c4efb1f 100644
--- a/media/parsers/h264_parser.cc
+++ b/media/parsers/h264_parser.cc
@@ -1414,11 +1414,29 @@
   if (!sps->frame_mbs_only_flag) {
     READ_BOOL_OR_RETURN(&shdr->field_pic_flag);
     if (shdr->field_pic_flag) {
+      // Note that per-spec, the field_pic_flag should be used as a denominator
+      // when calculating frame_height while checking pic_size_in_mbs below.
+      // If interlaced streams ever become supported, additional arithmetic will
+      // need to be added to the calculation of `frame_height_in_mbs`.
       DVLOG(1) << "Interlaced streams not supported";
       return kUnsupportedStream;
     }
   }
 
+  // H.264 spec 7.4.3: first_mb_in_slice shall be in [0, PicSizeInMbs - 1].
+  // Without this check the value flows unvalidated into
+  // VASliceParameterBufferH264.first_mb_in_slice and is used by the VA-API
+  // driver as a write offset into the decode surface.
+  {
+    const int frame_height_in_mbs = (2 - sps->frame_mbs_only_flag) *
+                                    (sps->pic_height_in_map_units_minus1 + 1);
+    base::CheckedNumeric<int> pic_size = sps->pic_width_in_mbs_minus1 + 1;
+    pic_size *= frame_height_in_mbs;
+    TRUE_OR_RETURN(pic_size.IsValid());
+    const int pic_size_in_mbs = pic_size.ValueOrDie();
+    IN_RANGE_OR_RETURN(shdr->first_mb_in_slice, 0, pic_size_in_mbs - 1);
+  }
+
   if (shdr->idr_pic_flag) {
     READ_UE_OR_RETURN(&shdr->idr_pic_id);
     IN_RANGE_OR_RETURN(shdr->idr_pic_id, 0, 65535);
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/media/gpu/h264_decoder_unittest.cc b/media/gpu/h264_decoder_unittest.cc
index a4aa9d3..83e640c 100644
--- a/media/gpu/h264_decoder_unittest.cc
+++ b/media/gpu/h264_decoder_unittest.cc
@@ -815,6 +815,43 @@
   ASSERT_TRUE(decoder_->Flush());
 }
 
+// POC: Unvalidated first_mb_in_slice in subsequent slices reaches the
+// accelerator.
+//
+// H264Decoder::PreprocessCurrentSlice() only validates first_mb_in_slice==0
+// when IsNewPrimaryCodedPicture() returns true. For the second (and later)
+// slice of a multi-slice picture, IsNewPrimaryCodedPicture() returns false
+// and the value is forwarded to accelerator_->SubmitSlice() with no upper
+// bound check. The H.264 spec (7.4.3) requires first_mb_in_slice <
+// PicSizeInMbs; the H.265 parser enforces the equivalent condition.
+//
+// This test feeds a hand-crafted Annex-B stream:
+//   SPS  : 320x240 (PicSizeInMbs = 20*15 = 300)
+//   PPS
+//   IDR slice 0 : first_mb_in_slice = 0      (passes the new-picture check)
+//   IDR slice 1 : first_mb_in_slice = 65535  (same frame_num/idr_pic_id ->
+//                                             IsNewPrimaryCodedPicture()=false)
+TEST_F(H264DecoderTest, UnvalidatedFirstMbInSliceReachesAccelerator) {
+  static constexpr uint8_t kStream[] = {
+      0x00, 0x00, 0x00, 0x01, 0x67, 0x42, 0x00, 0x1e, 0xda, 0x05, 0x07,
+      0xe4, 0x00, 0x00, 0x00, 0x01, 0x68, 0xce, 0x38, 0x80, 0x00, 0x00,
+      0x00, 0x01, 0x65, 0x88, 0x84, 0xd5, 0x55, 0x40, 0x00, 0x00, 0x00,
+      0x01, 0x65, 0x00, 0x00, 0x80, 0x00, 0x08, 0x84, 0xd5, 0x55, 0x40,
+  };
+
+  decoder_->SetStream(0, DecoderBuffer::CopyFrom(base::span(kStream)));
+
+  // First Decode() processes the SPS and reports a config change.
+  ASSERT_EQ(AcceleratedVideoDecoder::kConfigChange, decoder_->Decode());
+  EXPECT_EQ(gfx::Size(320, 240), decoder_->GetPicSize());
+
+  EXPECT_CALL(*accelerator_, SubmitSlice(_, _, _, _, _, _, _, _))
+      .WillRepeatedly(Return(H264Decoder::H264Accelerator::Status::kOk));
+
+  // Second Decode() processes PPS + both IDR slices of the same picture.
+  ASSERT_EQ(AcceleratedVideoDecoder::kDecodeError, decoder_->Decode());
+}
+
 TEST_F(H264DecoderTest, ModifyReferencePicList_CompactionTime) {
   // Seed the decoder DPB and `curr_pic_` with a long term ref pic.
   scoped_refptr<H264Picture> ref_pic = base::MakeRefCounted<H264Picture>();
Loading diff…

Original Bug Report

reported by [email protected]

Potential OOB write in GPU process via unvalidated H.264 first_mb_in_slice in VA-API

Project Fortify, an experimental security project, has identified the following potential security issue. If you’re a feature owner CC-ed on this bug, please do your best to review these reports without the security team.

Overview: Chromium’s H.264 parser and decoder fail to validate the first_mb_in_slice field for non-initial slices of a picture. When hardware acceleration via VA-API is used, this unvalidated value is passed to the GPU driver. This can potentially lead to an out-of-bounds DMA write in the GPU or Utility process.

Affected files:

  • media/gpu/h264_decoder.cc
  • media/parsers/h264_parser.cc
  • media/gpu/vaapi/h264_vaapi_video_decoder_delegate.cc

Estimated timestamp from git blame: 2018-07-17

Description

A potential high-severity memory corruption vulnerability exists in Chromium’s hardware-accelerated H.264 video decoding pipeline. The first_mb_in_slice field within H.264 slice headers is not correctly validated against the picture’s dimensions for multi-slice frames. When processing subsequent slices in a picture, an arbitrarily large value can be passed directly to the VA-API driver, which may result in an out-of-bounds (OOB) memory write by the hardware decoder.

Root Cause Analysis

  1. Lack of bounds check in parsing: In media/parsers/h264_parser.cc, H264Parser::ParseSliceHeader reads the first_mb_in_slice field from the bitstream using READ_UE_OR_RETURN. However, it never validates that this value is within the bounds of the total macroblocks in the picture defined by the Sequence Parameter Set (SPS).
  2. Validation bypass in decoding: In media/gpu/h264_decoder.cc, H264Decoder::PreprocessCurrentSlice contains a validation check (slice_hdr->first_mb_in_slice != 0). However, this check is nested inside an if (IsNewPrimaryCodedPicture(...)) block. For the second and subsequent slices of a multi-slice picture, this condition evaluates to false, causing the decoder to skip the check entirely.
  3. Propagation to Hardware: The unvalidated value is passed to the VA-API delegate in media/gpu/vaapi/h264_vaapi_video_decoder_delegate.cc. The SubmitSlice function copies the value directly into the VASliceParameterBufferH264 struct (SHDRToSP(first_mb_in_slice)).
  4. OOB Write: This struct is submitted to the underlying VA-API driver (e.g., Intel iHD or Mesa). Hardware drivers typically use first_mb_in_slice to compute the memory offset within the destination GPU surface for the decoded macroblock data. An abnormally large value can cause the GPU DMA to write out-of-bounds, corrupting the address space of the GPU process (Linux Desktop) or the sandboxed Utility process (ChromeOS OOP-VD).

Suggested Attacker Steps

Note: These are suggested/potential steps, as our tooling agent doesn’t yet have the ability to run code to verify a working exploit.

  1. Craft a malicious H.264 video stream.
  2. Define a Sequence Parameter Set (SPS) with a small resolution (e.g., 320x240, yielding 300 macroblocks).
  3. Within an IDR frame, provide a valid first slice with first_mb_in_slice = 0.
  4. Provide a second slice for the same frame with a malformed, massive first_mb_in_slice value (e.g., 65535).
  5. Embed this video stream into a web page using an HTML5 <video> tag or the WebCodecs API.
  6. When a victim views the page on a VA-API enabled system, the unvalidated field will potentially trigger an out-of-bounds write during hardware decoding.

Suggested Fix

Add a bounds check to ensure first_mb_in_slice is strictly less than the total number of macroblocks in the picture. This can be done in H264Decoder::ProcessCurrentSlice (or PreprocessCurrentSlice outside the IsNewPrimaryCodedPicture block) since the SPS and picture dimensions are known at that stage.

For example, calculate the picture size in macroblocks (based on sps->pic_width_in_mbs_minus1 and sps->pic_height_in_map_units_minus1) and add a check:

if (slice_hdr->first_mb_in_slice >= PicSizeInMbs) {
    return H264Accelerator::Status::kFail;
}

Evaluated with Chrome root at commit: f200f57a19490707ff8bc7aa5de3cbc443a3afad


Results so far have been promising, but there can be wrong deductions. If this proves to be a false positive, please close as WAI; data from false positives will be used to improve accuracy over time. And please feel free to reach out to me directly if you have concerns or feedback on the project.

View on issue tracker