Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Media
DescriptionUse after free in Media
ComponentMedia
Bug ClassUAF
Tracker498285711
Fix commit055b8ce56f2e (chromium/src) +38/-5
CISA KEVNot listed
CreditedGoogle
Disclosed2026-04-28

Changed Functions

FunctionChangeNotes
if
media/filters/source_buffer_stream.cc
modified
TEST_F
media/filters/source_buffer_stream_unittest.cc
modified

Files Changed

  • media/filters/source_buffer_stream.cc
  • media/filters/source_buffer_stream_unittest.cc
From 055b8ce56f2e737e68730a9d3178ff4cb93fa7fb Mon Sep 17 00:00:00 2001
From: Dale Curtis <[email protected]>
Date: Wed, 08 Apr 2026 10:58:16 -0700
Subject: [PATCH] [MSE] Correct range_for_next_append_ during SourceBuffer GC

The range may become invalid during garbage collection.

R=tmathmeyer

Fixed: 498285711, 500387779
Change-Id: I2bc9150f69cf8daa92168d04022c6efd69f7ae6c
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7737855
Auto-Submit: Dale Curtis <[email protected]>
Reviewed-by: Ted (Chromium) Meyer <[email protected]>
Commit-Queue: Ted (Chromium) Meyer <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1611660}
---

diff --git a/media/filters/source_buffer_stream.cc b/media/filters/source_buffer_stream.cc
index 21486d00..e970821 100644
--- a/media/filters/source_buffer_stream.cc
+++ b/media/filters/source_buffer_stream.cc
@@ -1030,11 +1030,10 @@
 
     if (current_range->GetMemoryUsage() == 0) {
       CHECK_NE(current_range, selected_range_);
-      CHECK(range_for_next_append_ == ranges_.end() ||
-            range_for_next_append_->get() != current_range);
-
-      // Delete |current_range| by popping it out of |ranges_|.
-      reverse_direction ? ranges_.pop_back() : ranges_.pop_front();
+      auto range_to_delete =
+          reverse_direction ? std::prev(ranges_.end()) : ranges_.begin();
+      current_range = nullptr;
+      DeleteAndRemoveRange(&range_to_delete);
     }
 
     if (reverse_direction && new_range_for_append) {
diff --git a/media/filters/source_buffer_stream_unittest.cc b/media/filters/source_buffer_stream_unittest.cc
index b154eeb..7cccec0 100644
--- a/media/filters/source_buffer_stream_unittest.cc
+++ b/media/filters/source_buffer_stream_unittest.cc
@@ -5747,4 +5747,38 @@
   EXPECT_TRUE(IsRangeListSorted());
 }
 
+TEST_F(SourceBufferStreamTest, GarbageCollectionUpdatesRangeForNextAppend) {
+  // Set memory limit to 10 buffers.
+  SetMemoryLimit(10);
+
+  // 1. Append 10 buffers to create Range A [0, 90ms].
+  NewCodedFrameGroupAppend("0K 10K 20K 30K 40K 50K 60K 70K 80K 90K");
+
+  // 2. Append 10 buffers to create Range B [1000ms, 1090ms].
+  // This exceeds the memory limit and triggers GC, but Range A is kept because
+  // it was recently appended.
+  NewCodedFrameGroupAppend(
+      "1000K 1010K 1020K 1030K 1040K 1050K 1060K 1070K 1080K 1090K");
+
+  // 3. Start a new coded frame group that overlaps Range A.
+  // This sets range_for_next_append_ to Range A and
+  // last_appended_buffer_timestamp_ to kNoTimestamp.
+  stream_->OnStartOfCodedFrameGroup(base::Milliseconds(0));
+
+  // 4. Trigger Garbage Collection.
+  // We want to free enough data that Range A is deleted.
+  // Set memory limit very low so GC must evict something.
+  SetMemoryLimit(5);
+
+  // Garbage collect with media time at Range B (1000ms).
+  // This should evict Range A from the front since it is far behind media time.
+  EXPECT_TRUE(GarbageCollect(base::Milliseconds(1000), 0));
+
+  // 5. Append data. If the bug exists, range_for_next_append_ is dangling and
+  // dereferencing it will cause a UAF or hit a CHECK.
+  // With the fix, range_for_next_append_ is reset to ranges_.end() when
+  // Range A is deleted.
+  AppendBuffers("0K 10K");
+}
+
 }  // namespace media
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/media/filters/source_buffer_stream_unittest.cc b/media/filters/source_buffer_stream_unittest.cc
index b154eeb..7cccec0 100644
--- a/media/filters/source_buffer_stream_unittest.cc
+++ b/media/filters/source_buffer_stream_unittest.cc
@@ -5747,4 +5747,38 @@
   EXPECT_TRUE(IsRangeListSorted());
 }
 
+TEST_F(SourceBufferStreamTest, GarbageCollectionUpdatesRangeForNextAppend) {
+  // Set memory limit to 10 buffers.
+  SetMemoryLimit(10);
+
+  // 1. Append 10 buffers to create Range A [0, 90ms].
+  NewCodedFrameGroupAppend("0K 10K 20K 30K 40K 50K 60K 70K 80K 90K");
+
+  // 2. Append 10 buffers to create Range B [1000ms, 1090ms].
+  // This exceeds the memory limit and triggers GC, but Range A is kept because
+  // it was recently appended.
+  NewCodedFrameGroupAppend(
+      "1000K 1010K 1020K 1030K 1040K 1050K 1060K 1070K 1080K 1090K");
+
+  // 3. Start a new coded frame group that overlaps Range A.
+  // This sets range_for_next_append_ to Range A and
+  // last_appended_buffer_timestamp_ to kNoTimestamp.
+  stream_->OnStartOfCodedFrameGroup(base::Milliseconds(0));
+
+  // 4. Trigger Garbage Collection.
+  // We want to free enough data that Range A is deleted.
+  // Set memory limit very low so GC must evict something.
+  SetMemoryLimit(5);
+
+  // Garbage collect with media time at Range B (1000ms).
+  // This should evict Range A from the front since it is far behind media time.
+  EXPECT_TRUE(GarbageCollect(base::Milliseconds(1000), 0));
+
+  // 5. Append data. If the bug exists, range_for_next_append_ is dangling and
+  // dereferencing it will cause a UAF or hit a CHECK.
+  // With the fix, range_for_next_append_ is reset to ranges_.end() when
+  // Range A is deleted.
+  AppendBuffers("0K 10K");
+}
+
 }  // namespace media
Loading diff…

Original Bug Report

reported by [email protected]

UAF in SourceBufferStream::FreeBuffers via dangling range_for_next_append_ iterator

Project Fortify, an experimental security project, has identified the following potential security issue.

Overview: A Use-After-Free (UAF) vulnerability exists in SourceBufferStream because the range_for_next_append_ iterator can become dangling when its range is removed during garbage collection. A safety check fails to reset the iterator when last_appended_buffer_timestamp_ is kNoTimestamp, allowing an attacker to dereference the dangling iterator and potentially achieve arbitrary code execution.

Affected files:

  • media/filters/source_buffer_stream.cc
  • media/filters/source_buffer_stream.h

Estimated timestamp from git blame: 2024-04-03

Description

A potential Use-After-Free (UAF) vulnerability exists in media::SourceBufferStream within the Chromium Renderer process. The issue stems from the management of the range_for_next_append_ iterator, which points to a SourceBufferRange within a std::list. Under specific conditions, this iterator can become dangling when the range it points to is removed from the list during garbage collection.

Vulnerability Details

SourceBufferStream maintains range_for_next_append_ (a std::list<std::unique_ptr<SourceBufferRange>>::iterator) to track where the next media append should occur. The FreeBuffers method is responsible for removing ranges to satisfy memory limits. It includes a check to reset the iterator if the range being deleted is the one containing the last appended buffer:

// media/filters/source_buffer_stream.cc
    // Check to see if we've just deleted the GOP that was last appended.
    base::TimeDelta end_timestamp = buffers.back()->timestamp();
    if (end_timestamp == last_appended_buffer_timestamp_) {
      // ...
      range_for_next_append_ = ranges_.end();
    } else {
      bytes_freed += bytes_deleted;
    }

    if (current_range->GetMemoryUsage() == 0) {
      DCHECK_NE(current_range, selected_range_);
      DCHECK(range_for_next_append_ == ranges_.end() ||
             range_for_next_append_->get() != current_range);

      // Delete |current_range| by popping it out of |ranges_|.
      reverse_direction ? ranges_.pop_back() : ranges_.pop_front();
    }

If last_appended_buffer_timestamp_ is kNoTimestamp (which can occur if a new coded frame group is started but no buffers have yet been appended to the stream), the condition end_timestamp == last_appended_buffer_timestamp_ will never be true. In this case, range_for_next_append_ is not reset. When the range is popped from the list, the iterator becomes dangling. Note that the DCHECK verifying the iterator’s safety is compiled out in Release builds.

Potential Reachability

This vulnerable state is reachable by performing the following steps through the Media Source Extensions (MSE) API:

  1. Append Muxed Media: Append an initialization segment and a media segment containing multiplexed audio and video frames to create disjoint buffered ranges (e.g., [0-5s] and [10-15s]).
  2. Reset Parser: Call SourceBuffer.abort() to reset the parser state and end the current coded frame group.
  3. Trigger New Coded Frame Group (Video-Only): Append a new media segment that contains only video frames. The FrameProcessor detects the new coded frame group and broadcasts NotifyStartOfCodedFrameGroup to all track buffers.
  4. Audio Stream State Update: For the audio track, SourceBufferStream::OnStartOfCodedFrameGroup sets range_for_next_append_ to an existing overlapping range and calls ResetLastAppendedState(), which sets last_appended_buffer_timestamp_ to kNoTimestamp.
  5. Trigger Garbage Collection: Append a large amount of video data to exceed the SourceBuffer’s memory limit. This triggers ChunkDemuxerStream::EvictCodedFrames() and subsequently SourceBufferStream::FreeBuffers() for the audio stream.
  6. Bypass Safety Check: FreeBuffers() deletes the range pointed to by range_for_next_append_. Because last_appended_buffer_timestamp_ is kNoTimestamp, the protective check is bypassed, and the range is popped while leaving range_for_next_append_ dangling.
  7. Trigger UAF: Append a new media segment containing audio frames without calling abort(). SourceBufferStream::Append() is called, which dereferences the dangling range_for_next_append_ iterator to call AppendBuffersToEnd.

Impact

Since std::list iterators in libc++ store raw pointers to nodes, this UAF is not mitigated by MiraclePtr (BRP). An attacker can use heap spraying to control the freed list node’s contents, allowing them to forge the this pointer for the AppendBuffersToEnd call. Inside AppendBuffersToEnd, operations on member variables (like std::deque::push_back and std::map::insert) can be manipulated to achieve a powerful arbitrary memory write primitive. This could bypass Control-Flow Integrity (CFI) protections and potentially lead to Remote Code Execution (RCE) within the sandboxed Renderer process.

Ensure that FreeBuffers correctly resets range_for_next_append_ whenever the range it points to is removed, regardless of the value of last_appended_buffer_timestamp_. A safer approach would be to check if range_for_next_append_->get() == current_range and reset it explicitly before the range is popped, similar to the logic used in SourceBufferStream::DeleteAndRemoveRange.

Evaluated with Chrome root at commit: ff3d2b74fa39431785bd60e51463b08fcc71ee33


Results from 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