Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in WebRTC
DescriptionUse after free in WebRTC
ComponentWebRTC
Bug ClassUAF
Tracker440737137
Fix commit4e6f77f49842 (chromium/src) +112/-42
CISA KEVNot listed
Creditedsherkito
Disclosed2025-09-17

Changed Functions

FunctionChangeNotes
if
third_party/blink/renderer/modules/mediarecorder/media_recorder_handler.cc
modified
MODULES_EXPORT
third_party/blink/renderer/modules/mediarecorder/media_recorder_handler.h
modified
MediaStreamObserver
third_party/blink/renderer/modules/mediarecorder/media_recorder_handler.h
modified
MockWebMediaStreamObserver
third_party/blink/renderer/modules/mediastream/media_stream_track_impl_test.cc
modified

Files Changed

  • third_party/blink/public/platform/modules/mediastream/web_media_stream.h
  • third_party/blink/renderer/modules/mediarecorder/media_recorder_handler.cc
  • third_party/blink/renderer/modules/mediarecorder/media_recorder_handler.h
  • third_party/blink/renderer/modules/mediastream/media_stream_track_impl_test.cc
  • third_party/blink/renderer/modules/mediastream/web_media_player_ms.cc
From 4e6f77f4984209726c6d33475bc4afb13f0e2955 Mon Sep 17 00:00:00 2001
From: Guido Urdaneta <guidou@chromium.org>
Date: Thu, 04 Sep 2025 02:43:02 -0700
Subject: [PATCH] [MediaStream] Use weak ptrs to track observers in MediaStreamDescriptor

Prior to this CL, we were using raw pointers.
This was problematic for several reasons, including that one of the observer classes is GCed.
The GCed case (MediaRecorderHandler) is handled by using composition
instead of inheriting directly from the WebMediaStreamObserver interface.

Bug: 440737137
Change-Id: Ibebb8145b3d190b39f04424f4e14cb9020855038
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6912084
Reviewed-by: Tony Herre <toprice@chromium.org>
Commit-Queue: Guido Urdaneta <guidou@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1510795}
---

diff --git a/third_party/blink/public/platform/modules/mediastream/web_media_stream.h b/third_party/blink/public/platform/modules/mediastream/web_media_stream.h
index 72f417ee2..c65116e 100644
--- a/third_party/blink/public/platform/modules/mediastream/web_media_stream.h
+++ b/third_party/blink/public/platform/modules/mediastream/web_media_stream.h
@@ -25,6 +25,7 @@
 #ifndef THIRD_PARTY_BLINK_PUBLIC_PLATFORM_MODULES_MEDIASTREAM_WEB_MEDIA_STREAM_H_
 #define THIRD_PARTY_BLINK_PUBLIC_PLATFORM_MODULES_MEDIASTREAM_WEB_MEDIA_STREAM_H_
 
+#include "base/memory/weak_ptr.h"
 #include "third_party/blink/public/platform/web_common.h"
 #include "third_party/blink/public/platform/web_private_ptr.h"
 
@@ -84,8 +85,8 @@
   // the WebMediaStream. Observers cannot be null, cannot be added or removed
   // more than once, and cannot invoke AddObserver/RemoveObserver in their
   // TrackAdded/TrackRemoved callbacks.
-  void AddObserver(WebMediaStreamObserver*);
-  void RemoveObserver(WebMediaStreamObserver*);
+  void AddObserver(base::WeakPtr<WebMediaStreamObserver>);
+  void RemoveObserver(base::WeakPtr<WebMediaStreamObserver>);
 
 #if INSIDE_BLINK
   explicit WebMediaStream(MediaStreamDescriptor*);
diff --git a/third_party/blink/renderer/modules/mediarecorder/media_recorder_handler.cc b/third_party/blink/renderer/modules/mediarecorder/media_recorder_handler.cc
index 92359ac..a212a0e 100644
--- a/third_party/blink/renderer/modules/mediarecorder/media_recorder_handler.cc
+++ b/third_party/blink/renderer/modules/mediarecorder/media_recorder_handler.cc
@@ -281,7 +281,8 @@
     scoped_refptr<base::SingleThreadTaskRunner> main_thread_task_runner,
     KeyFrameRequestProcessor::Configuration key_frame_config)
     : key_frame_config_(key_frame_config),
-      main_thread_task_runner_(std::move(main_thread_task_runner)) {}
+      main_thread_task_runner_(std::move(main_thread_task_runner)),
+      media_stream_observer_(std::make_unique<MediaStreamObserver>(this)) {}
 
 bool MediaRecorderHandler::CanSupportMimeType(const String& type,
                                               const String& web_codecs) {
@@ -560,7 +561,7 @@
   DCHECK(!muxer_adapter_);
 
   DCHECK(!is_media_stream_observer_);
-  media_stream_->AddObserver(this);
+  media_stream_->AddObserver(media_stream_observer_->AsWeakPtr());
   is_media_stream_observer_ = true;
 
   timeslice_ = timeslice == std::numeric_limits<int>::max()
@@ -715,7 +716,7 @@
 
   // Unregister from media stream notifications.
   if (media_stream_ && is_media_stream_observer_) {
-    media_stream_->RemoveObserver(this);
+    media_stream_->RemoveObserver(media_stream_observer_->AsWeakPtr());
   }
   is_media_stream_observer_ = false;
 
diff --git a/third_party/blink/renderer/modules/mediarecorder/media_recorder_handler.h b/third_party/blink/renderer/modules/mediarecorder/media_recorder_handler.h
index c221640b..30fb95e 100644
--- a/third_party/blink/renderer/modules/mediarecorder/media_recorder_handler.h
+++ b/third_party/blink/renderer/modules/mediarecorder/media_recorder_handler.h
@@ -10,6 +10,7 @@
 #include <string_view>
 
 #include "base/feature_list.h"
+#include "base/memory/weak_ptr.h"
 #include "base/task/single_thread_task_runner.h"
 #include "base/threading/thread_checker.h"
 #include "base/time/time.h"
@@ -22,6 +23,7 @@
 #include "third_party/blink/renderer/modules/mediarecorder/video_track_recorder.h"
 #include "third_party/blink/renderer/modules/modules_export.h"
 #include "third_party/blink/renderer/platform/heap/collection_support/heap_vector.h"
+#include "third_party/blink/renderer/platform/heap/persistent.h"
 #include "third_party/blink/renderer/platform/heap/weak_cell.h"
 #include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
@@ -67,8 +69,7 @@
 class MODULES_EXPORT MediaRecorderHandler final
     : public GarbageCollected<MediaRecorderHandler>,
       public VideoTrackRecorder::CallbackInterface,
-      public AudioTrackRecorder::CallbackInterface,
-      public WebMediaStreamObserver {
+      public AudioTrackRecorder::CallbackInterface {
  public:
   MediaRecorderHandler(
       scoped_refptr<base::SingleThreadTaskRunner> main_thread_task_runner,
@@ -118,10 +119,34 @@
  private:
   friend class MediaRecorderHandlerFixture;
   friend class MediaRecorderHandlerPassthroughTest;
+  friend class MediaStreamObserver;
 
-  // WebMediaStreamObserver overrides.
-  void TrackAdded(const WebString& track_id) override;
-  void TrackRemoved(const WebString& track_id) override;
+  class MediaStreamObserver : public WebMediaStreamObserver {
+   public:
+    explicit MediaStreamObserver(MediaRecorderHandler* handler)
+        : media_recorder_handler_(handler) {}
+
+    // WebMediaStreamObserver overrides.
+    void TrackAdded(const WebString& track_id) override {
+      CHECK(media_recorder_handler_);
+      media_recorder_handler_->TrackAdded(track_id);
+    }
+    void TrackRemoved(const WebString& track_id) override {
+      CHECK(media_recorder_handler_);
+      media_recorder_handler_->TrackRemoved(track_id);
+    }
+
+    base::WeakPtr<WebMediaStreamObserver> AsWeakPtr() {
+      return weak_factory_.GetWeakPtr();
+    }
+
+   private:
+    WeakPersistent<MediaRecorderHandler> media_recorder_handler_;
+    base::WeakPtrFactory<WebMediaStreamObserver> weak_factory_{this};
+  };
+
+  void TrackAdded(const WebString& track_id);
+  void TrackRemoved(const WebString& track_id);
 
   // VideoTrackRecorder::CallbackInterface overrides.
   void OnEncodedVideo(
@@ -242,6 +267,8 @@
   bool has_codec_description_changed_error_printed_ = false;
 #endif
 
+  std::unique_ptr<MediaStreamObserver> media_stream_observer_;
+
   // For invalidation of in-flight callbacks back to ourselves. Need to track
   // each callback interface specifically as there seem to be no automatic
   // coercion.
diff --git a/third_party/blink/renderer/modules/mediastream/media_stream_track_impl_test.cc b/third_party/blink/renderer/modules/mediastream/media_stream_track_impl_test.cc
index aa2e4a02..65a52d8 100644
--- a/third_party/blink/renderer/modules/mediastream/media_stream_track_impl_test.cc
+++ b/third_party/blink/renderer/modules/mediastream/media_stream_track_impl_test.cc
@@ -7,6 +7,7 @@
 #include <tuple>
 
 #include "base/feature_list.h"
+#include "base/memory/weak_ptr.h"
 #include "base/run_loop.h"
 #include "base/test/gmock_callback_support.h"
 #include "media/base/video_frame.h"
@@ -91,6 +92,12 @@
 class MockWebMediaStreamObserver : public WebMediaStreamObserver {
  public:
   MOCK_METHOD(void, EnabledStateChangedForWebRtcAudio, (bool));
+  base::WeakPtr<WebMediaStreamObserver> AsWeakPtr() {
+    return weak_ptr_factory_.GetWeakPtr();
+  }
+
+ private:
+  base::WeakPtrFactory<MockWebMediaStreamObserver> weak_ptr_factory_{this};
 };
 
 MediaStreamComponent* MakeMockVideoComponent() {
@@ -344,7 +351,7 @@
       MediaStream::Create(v8_scope.GetExecutionContext(), audio_tracks);
   auto* descriptor = media_stream->Descriptor();
   descriptor->SetActive(true);
-  descriptor->AddObserver(&observer);
+  descriptor->AddObserver(observer.AsWeakPtr());
 
   // let video = document.createElement('video');
   // video.srcObject = media_stream;
@@ -379,7 +386,7 @@
       MediaStream::Create(v8_scope.GetExecutionContext(), audio_tracks);
   auto* descriptor = media_stream->Descriptor();
   descriptor->SetActive(true);
-  descriptor->AddObserver(&observer);
+  descriptor->AddObserver(observer.AsWeakPtr());
 
   // let video = document.createElement('video');
   // video.srcObject = media_stream;
diff --git a/third_party/blink/renderer/modules/mediastream/web_media_player_ms.cc b/third_party/blink/renderer/modules/mediastream/web_media_player_ms.cc
index 870b673..9ebf604 100644
--- a/third_party/blink/renderer/modules/mediastream/web_media_player_ms.cc
+++ b/third_party/blink/renderer/modules/mediastream/web_media_player_ms.cc
@@ -408,7 +408,7 @@
       String::Format("%s() [delegate_id=%d]", __func__, delegate_id_));
 
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/third_party/blink/renderer/modules/mediastream/media_stream_track_impl_test.cc b/third_party/blink/renderer/modules/mediastream/media_stream_track_impl_test.cc
index aa2e4a02..65a52d8 100644
--- a/third_party/blink/renderer/modules/mediastream/media_stream_track_impl_test.cc
+++ b/third_party/blink/renderer/modules/mediastream/media_stream_track_impl_test.cc
@@ -7,6 +7,7 @@
 #include <tuple>
 
 #include "base/feature_list.h"
+#include "base/memory/weak_ptr.h"
 #include "base/run_loop.h"
 #include "base/test/gmock_callback_support.h"
 #include "media/base/video_frame.h"
@@ -91,6 +92,12 @@
 class MockWebMediaStreamObserver : public WebMediaStreamObserver {
  public:
   MOCK_METHOD(void, EnabledStateChangedForWebRtcAudio, (bool));
+  base::WeakPtr<WebMediaStreamObserver> AsWeakPtr() {
+    return weak_ptr_factory_.GetWeakPtr();
+  }
+
+ private:
+  base::WeakPtrFactory<MockWebMediaStreamObserver> weak_ptr_factory_{this};
 };
 
 MediaStreamComponent* MakeMockVideoComponent() {
@@ -344,7 +351,7 @@
       MediaStream::Create(v8_scope.GetExecutionContext(), audio_tracks);
   auto* descriptor = media_stream->Descriptor();
   descriptor->SetActive(true);
-  descriptor->AddObserver(&observer);
+  descriptor->AddObserver(observer.AsWeakPtr());
 
   // let video = document.createElement('video');
   // video.srcObject = media_stream;
@@ -379,7 +386,7 @@
       MediaStream::Create(v8_scope.GetExecutionContext(), audio_tracks);
   auto* descriptor = media_stream->Descriptor();
   descriptor->SetActive(true);
-  descriptor->AddObserver(&observer);
+  descriptor->AddObserver(observer.AsWeakPtr());
 
   // let video = document.createElement('video');
   // video.srcObject = media_stream;
Loading diff…

Original Bug Report

reported by ss...@gmail.com

Use-After-Free in MediaStreamDescriptor

Steps to reproduce the problem

  1. chrome.exe –js-flags="–stress-incremental-marking –expose-gc" –no-sandbox

Problem Description

void MediaStreamDescriptor::AddComponent(MediaStreamComponent* component) {
  '''
  '''
  // Iterate over a copy of |observers_| to avoid re-entrancy issues.
  Vector<WebMediaStreamObserver*> observers = observers_;
  for (auto*& observer : observers)
    observer->TrackAdded(WebString(component->Id()));
}


A vulnerability occurs when the garbage collector runs while maintaining GC objects on the stack using a Vector.\

SpaceSplitString::Data* SpaceSplitString::Data::Create(
    const AtomicString& string) {
  auto result = SharedDataMap().insert(string, nullptr);
  SpaceSplitString::Data* data = result.stored_value->value;
  if (result.is_new_entry) {
    data = MakeGarbageCollected<SpaceSplitString::Data>(string);
    result.stored_value->value = data;
  }
  return data;
}
```\
As shown in the asan.log file, the MakeGarbageCollected function can be called from the SpaceSplitString function and several other functions within the TrackAdded function.\
```c++
void CppHeap::ReportBufferedAllocationSizeIfPossible() {
    '''
    '''
    used_size_.fetch_add(static_cast<size_t>(bytes_to_report),
                         std::memory_order_relaxed);
    allocated_size_ += bytes_to_report;

    if (v8_flags.incremental_marking) {
      if (allocated_size_ > allocated_size_limit_for_check_) {
        Heap* heap = isolate_->heap();
        heap->StartIncrementalMarkingIfAllocationLimitIsReached(
            heap->main_thread_local_heap(),
            heap->GCFlagsForIncrementalMarking(),
            kGCCallbackScheduleIdleGarbageCollection);
        if (heap->incremental_marking()->IsMajorMarking()) {
          if (heap->AllocationLimitOvershotByLargeMargin()) {
            heap->FinalizeIncrementalMarkingAtomically(
                i::GarbageCollectionReason::kExternalFinalize);
          } else {
            heap->incremental_marking()->AdvanceOnAllocation();
          }
        }
        allocated_size_limit_for_check_ =
            allocated_size_ + kIncrementalMarkingCheckInterval;
      }
    }
  }
}
```\
If there is a lot of memory allocated so far inside the MakeGarbageCollected function, you can call FinalizeIncrementalMarkingAtomically through the ReportBufferedAllocationSizeIfPossible function to execute Full GarbageCollect.\
The probability of the conditions for executing the above GarbageCollect is very low and can change a lot depending on the version, environment, etc., so the probability can be adjusted by taking actions such as setting the ArrayBuffer allocation size and loop count to different values.

# Summary
Use-After-Free in MediaStreamDescriptor

# Custom Questions
#### Type of crash: 
Renderer

#### Crash state: 
Crash referencing vftable

#### Reporter credit: 
sherkito

# Additional Data
Category: Security \
Chrome Channel: Not sure \
Regression: N/A \
View on issue tracker