CVE-2025-8292
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
forthird_party/blink/renderer/modules/mediastream/media_stream_track_impl.cc |
modified | |
MODULES_EXPORTthird_party/blink/renderer/modules/mediastream/media_stream_track_impl.h |
modified | |
MakeLocalMediaStreamAudioSourcethird_party/blink/renderer/modules/mediastream/media_stream_track_impl_test.cc |
modified | |
TEST_Fthird_party/blink/renderer/modules/mediastream/media_stream_track_impl_test.cc |
modified |
Files Changed
third_party/blink/renderer/modules/mediastream/media_stream_track_impl.ccthird_party/blink/renderer/modules/mediastream/media_stream_track_impl.hthird_party/blink/renderer/modules/mediastream/media_stream_track_impl_test.cc
Patch
From 90d99dcd83af2f13e0f917b5a9dc59eeee0204d0 Mon Sep 17 00:00:00 2001 From: Evan Liu <[email protected]> Date: Mon, 14 Jul 2025 12:21:45 -0700 Subject: [PATCH] Fix potential UAF in MediaStreamTrackImpl This CL fixes a potential UAF vulnerability in MediaStreamTrackImpl where pointers to the SpeechRecognitionMediaStreamAudioSinks that are owned by the MediaStreamTrackImpl could potentially be accessed after the sinks are destroyed. Fixed: 426054987 Change-Id: I453160a8eed7926e2cc3500260de04d2722c98e1 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6712212 Commit-Queue: Evan Liu <[email protected]> Reviewed-by: Mark Foltz <[email protected]> Cr-Commit-Position: refs/heads/main@{#1486476} --- diff --git a/third_party/blink/renderer/modules/mediastream/media_stream_track_impl.cc b/third_party/blink/renderer/modules/mediastream/media_stream_track_impl.cc index 2131a1b1..320d73e80 100644 --- a/third_party/blink/renderer/modules/mediastream/media_stream_track_impl.cc +++ b/third_party/blink/renderer/modules/mediastream/media_stream_track_impl.cc @@ -810,6 +810,19 @@ return capture_handle; } +void MediaStreamTrackImpl::Dispose() { + // `MediaStreamTrackImpl` and the `SpeechRecognitionMediaStreamAudioSink` + // which it owns may be destroyed before the `MediaStreamAudioTrack`. Remove + // the sinks before destroying them to prevent `MediaStreamAudioTrack` from + // using them after destruction. + if (MediaStreamAudioTrack* audio_track = + MediaStreamAudioTrack::From(Component())) { + for (SpeechRecognitionMediaStreamAudioSink* sink : registered_sinks_) { + audio_track->RemoveSink(sink); + } + } +} + ScriptPromise<IDLUndefined> MediaStreamTrackImpl::applyConstraints( ScriptState* script_state, const MediaTrackConstraints* constraints) { diff --git a/third_party/blink/renderer/modules/mediastream/media_stream_track_impl.h b/third_party/blink/renderer/modules/mediastream/media_stream_track_impl.h index a7ec455..aa392255 100644 --- a/third_party/blink/renderer/modules/mediastream/media_stream_track_impl.h +++ b/third_party/blink/renderer/modules/mediastream/media_stream_track_impl.h @@ -57,6 +57,8 @@ // Primary implementation of the MediaStreamTrack interface and idl type. class MODULES_EXPORT MediaStreamTrackImpl : public MediaStreamTrack, public MediaStreamSource::Observer { + USING_PRE_FINALIZER(MediaStreamTrackImpl, Dispose); + public: // Create a MediaStreamTrackImpl of the appropriate type for the display // surface type. @@ -169,6 +171,8 @@ friend class CanvasCaptureMediaStreamTrack; friend class InternalsMediaStream; + void Dispose(); + // MediaStreamTrack void applyConstraints(ScriptPromiseResolver<IDLUndefined>*, const MediaTrackConstraints*) override; 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 55c7b9c0..f9675051 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 @@ -20,6 +20,7 @@ #include "third_party/blink/renderer/bindings/core/v8/script_promise_tester.h" #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_testing.h" #include "third_party/blink/renderer/bindings/modules/v8/v8_constrain_long_range.h" +#include "third_party/blink/renderer/bindings/modules/v8/v8_media_stream_track_state.h" #include "third_party/blink/renderer/bindings/modules/v8/v8_media_track_constraints.h" #include "third_party/blink/renderer/bindings/modules/v8/v8_union_constrainlongrange_long.h" #include "third_party/blink/renderer/core/frame/frame_test_helpers.h" @@ -28,14 +29,15 @@ #include "third_party/blink/renderer/core/streams/readable_stream.h" #include "third_party/blink/renderer/core/streams/readable_stream_default_reader.h" #include "third_party/blink/renderer/modules/mediastream/apply_constraints_processor.h" -#include "third_party/blink/renderer/modules/mediastream/local_media_stream_audio_source.h" #include "third_party/blink/renderer/modules/mediastream/media_constraints.h" #include "third_party/blink/renderer/modules/mediastream/media_constraints_impl.h" #include "third_party/blink/renderer/modules/mediastream/media_stream.h" #include "third_party/blink/renderer/modules/mediastream/media_stream_constraints_util_video_content.h" +#include "third_party/blink/renderer/modules/mediastream/media_stream_track.h" #include "third_party/blink/renderer/modules/mediastream/media_stream_video_track.h" #include "third_party/blink/renderer/modules/mediastream/mock_media_stream_video_sink.h" #include "third_party/blink/renderer/modules/mediastream/mock_media_stream_video_source.h" +#include "third_party/blink/renderer/modules/mediastream/speech_recognition_media_stream_audio_sink.h" #include "third_party/blink/renderer/modules/peerconnection/mock_peer_connection_dependency_factory.h" #include "third_party/blink/renderer/platform/bindings/exception_state.h" #include "third_party/blink/renderer/platform/mediastream/media_stream_audio_source.h" @@ -90,20 +92,6 @@ MOCK_METHOD(void, EnabledStateChangedForWebRtcAudio, (bool)); }; -std::unique_ptr<blink::LocalMediaStreamAudioSource> -MakeLocalMediaStreamAudioSource() { - blink::MediaStreamDevice device; - device.type = blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE; - return std::make_unique<blink::LocalMediaStreamAudioSource>( - /*blink::WebLocalFrame=*/nullptr, device, - /*requested_buffer_size=*/nullptr, - /*disable_local_echo=*/false, - MediaStreamAudioProcessingLayout::MakeForUnprocessedLocalSourceForTests( - /*platform_aec=*/false, /*available_platform_effects=*/0), - blink::WebPlatformMediaStreamSource::ConstraintsRepeatingCallback(), - blink::scheduler::GetSingleThreadTaskRunnerForTesting()); -} - MediaStreamComponent* MakeMockVideoComponent() { std::unique_ptr<MockMediaStreamVideoSource> platform_source = MakeMockMediaStreamVideoSource(); @@ -121,7 +109,10 @@ MediaStreamComponent* MakeMockAudioComponent() { MediaStreamSource* source = MakeGarbageCollected<MediaStreamSource>( "id", MediaStreamSource::StreamType::kTypeAudio, "name", - /*remote=*/false, MakeLocalMediaStreamAudioSource()); + /*remote=*/false, + std::make_unique<MediaStreamAudioSource>( + scheduler::GetSingleThreadTaskRunnerForTesting(), + true /* is_local_source */)); auto platform_track = std::make_unique<MediaStreamAudioTrack>(true /* is_local_track */); return MakeGarbageCollected<MediaStreamComponentImpl>( @@ -284,7 +275,10 @@ MediaStreamComponent* MakeMockWebRtcAudioComponent() { auto* source = MakeGarbageCollected<MediaStreamSource>( "id", MediaStreamSource::StreamType::kTypeAudio, "name", - /*remote=*/true, MakeLocalMediaStreamAudioSource()); + /*remote=*/true, + std::make_unique<MediaStreamAudioSource>( + scheduler::GetSingleThreadTaskRunnerForTesting(), + false /* is_local_source */)); scoped_refptr<webrtc::AudioTrackInterface> remote_track( blink::MockWebRtcAudioTrack::Create("track_id").get()); @@ -298,7 +292,10 @@ MediaStreamComponent* MakeMockAudioComponent() { MediaStreamSource* source = MakeGarbageCollected<MediaStreamSource>( "id", MediaStreamSource::StreamType::kTypeAudio, "name", - /*remote=*/false, MakeLocalMediaStreamAudioSource()); + /*remote=*/false, + std::make_unique<MediaStreamAudioSource>( + scheduler::GetSingleThreadTaskRunnerForTesting(), + true /* is_local_source */)); auto platform_track = std::make_unique<MediaStreamAudioTrack>(true /* is_local_track */); return MakeGarbageCollected<MediaStreamComponentImpl>( @@ -861,4 +858,51 @@ EXPECT_EQ(video_track->min_frame_rate(), kMinFrameRate); } +TEST_F(MediaStreamTrackImplTest, StopAudioTrackAfterSinkDestroyed) { + V8TestingScope v8_scope; + + // 1. Create the underlying platform track and its component. + // Keep this component alive with a Persistent handle to control its + // lifetime, ensuring it outlives the temporary track and sink created below. + Persistent<MediaStreamComponent> component = MakeMockAudioComponent(); + MediaStreamAudioSource* source = + MediaStreamAudioSource::From(component->Source()); + + MediaStreamAudioTrack* platform_track = + MediaStreamAudioTrack::From(component.Get()); + ASSERT_TRUE(source); + ASSERT_TRUE(platform_track); + + // 2. Start the platform track by connecting it to the source. After this, the + // track is "live" and can accept sinks. + source->ConnectToInitializedTrack(component.Get()); + + // 3. Create a temporary MediaStreamTrackImpl wrapper and a sink in a + // separate scope. This wrapper will "own" the sink via a strong GC ref. + { + MediaStreamTrack* track1 = MakeGarbageCollected<MediaStreamTrackImpl>( + v8_scope.GetExecutionContext(), component.Get()); + auto* sink = MakeGarbageCollected<SpeechRecognitionMediaStreamAudioSink>( + v8_scope.GetExecutionContext(), base::DoNothing()); + + // 4. Register the sink with the wrapper and add its raw pointer to the + // platform track's sink list. + track1->RegisterSink(sink); + platform_track->AddSink(sink); + } + + // 5. Force garbage collection. This destroys `track_with_sink` and `sink`. + // If the fix is present, `track_with_sink->Dispose()` is called, removing + // the sink from `platform_track`. + WebHeap::CollectAllGarbageForTesting(); + + // 5. Now, destroy the component that owns the platform track by clearing the + // persistent handle and running GC again. The component's pre-finalizer, + // Dispose(), will call `platform_track->StopAndNotify()`. + component.Clear(); + WebHeap::CollectAllGarbageForTesting(); + + // The test passes if it doesn't crash. +}
Regression Test / PoC
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 55c7b9c0..f9675051 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
@@ -20,6 +20,7 @@
#include "third_party/blink/renderer/bindings/core/v8/script_promise_tester.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_testing.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_constrain_long_range.h"
+#include "third_party/blink/renderer/bindings/modules/v8/v8_media_stream_track_state.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_media_track_constraints.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_union_constrainlongrange_long.h"
#include "third_party/blink/renderer/core/frame/frame_test_helpers.h"
@@ -28,14 +29,15 @@
#include "third_party/blink/renderer/core/streams/readable_stream.h"
#include "third_party/blink/renderer/core/streams/readable_stream_default_reader.h"
#include "third_party/blink/renderer/modules/mediastream/apply_constraints_processor.h"
-#include "third_party/blink/renderer/modules/mediastream/local_media_stream_audio_source.h"
#include "third_party/blink/renderer/modules/mediastream/media_constraints.h"
#include "third_party/blink/renderer/modules/mediastream/media_constraints_impl.h"
#include "third_party/blink/renderer/modules/mediastream/media_stream.h"
#include "third_party/blink/renderer/modules/mediastream/media_stream_constraints_util_video_content.h"
+#include "third_party/blink/renderer/modules/mediastream/media_stream_track.h"
#include "third_party/blink/renderer/modules/mediastream/media_stream_video_track.h"
#include "third_party/blink/renderer/modules/mediastream/mock_media_stream_video_sink.h"
#include "third_party/blink/renderer/modules/mediastream/mock_media_stream_video_source.h"
+#include "third_party/blink/renderer/modules/mediastream/speech_recognition_media_stream_audio_sink.h"
#include "third_party/blink/renderer/modules/peerconnection/mock_peer_connection_dependency_factory.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/mediastream/media_stream_audio_source.h"
@@ -90,20 +92,6 @@
MOCK_METHOD(void, EnabledStateChangedForWebRtcAudio, (bool));
};
-std::unique_ptr<blink::LocalMediaStreamAudioSource>
-MakeLocalMediaStreamAudioSource() {
- blink::MediaStreamDevice device;
- device.type = blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE;
- return std::make_unique<blink::LocalMediaStreamAudioSource>(
- /*blink::WebLocalFrame=*/nullptr, device,
- /*requested_buffer_size=*/nullptr,
- /*disable_local_echo=*/false,
- MediaStreamAudioProcessingLayout::MakeForUnprocessedLocalSourceForTests(
- /*platform_aec=*/false, /*available_platform_effects=*/0),
- blink::WebPlatformMediaStreamSource::ConstraintsRepeatingCallback(),
- blink::scheduler::GetSingleThreadTaskRunnerForTesting());
-}
-
MediaStreamComponent* MakeMockVideoComponent() {
std::unique_ptr<MockMediaStreamVideoSource> platform_source =
MakeMockMediaStreamVideoSource();
@@ -121,7 +109,10 @@
MediaStreamComponent* MakeMockAudioComponent() {
MediaStreamSource* source = MakeGarbageCollected<MediaStreamSource>(
"id", MediaStreamSource::StreamType::kTypeAudio, "name",
- /*remote=*/false, MakeLocalMediaStreamAudioSource());
+ /*remote=*/false,
+ std::make_unique<MediaStreamAudioSource>(
+ scheduler::GetSingleThreadTaskRunnerForTesting(),
+ true /* is_local_source */));
auto platform_track =
std::make_unique<MediaStreamAudioTrack>(true /* is_local_track */);
return MakeGarbageCollected<MediaStreamComponentImpl>(
@@ -284,7 +275,10 @@
MediaStreamComponent* MakeMockWebRtcAudioComponent() {
auto* source = MakeGarbageCollected<MediaStreamSource>(
"id", MediaStreamSource::StreamType::kTypeAudio, "name",
- /*remote=*/true, MakeLocalMediaStreamAudioSource());
+ /*remote=*/true,
+ std::make_unique<MediaStreamAudioSource>(
+ scheduler::GetSingleThreadTaskRunnerForTesting(),
+ false /* is_local_source */));
scoped_refptr<webrtc::AudioTrackInterface> remote_track(
blink::MockWebRtcAudioTrack::Create("track_id").get());
@@ -298,7 +292,10 @@
MediaStreamComponent* MakeMockAudioComponent() {
MediaStreamSource* source = MakeGarbageCollected<MediaStreamSource>(
"id", MediaStreamSource::StreamType::kTypeAudio, "name",
- /*remote=*/false, MakeLocalMediaStreamAudioSource());
+ /*remote=*/false,
+ std::make_unique<MediaStreamAudioSource>(
+ scheduler::GetSingleThreadTaskRunnerForTesting(),
+ true /* is_local_source */));
auto platform_track =
std::make_unique<MediaStreamAudioTrack>(true /* is_local_track */);
return MakeGarbageCollected<MediaStreamComponentImpl>(
@@ -861,4 +858,51 @@
EXPECT_EQ(video_track->min_frame_rate(), kMinFrameRate);
}
+TEST_F(MediaStreamTrackImplTest, StopAudioTrackAfterSinkDestroyed) {
+ V8TestingScope v8_scope;
+
+ // 1. Create the underlying platform track and its component.
+ // Keep this component alive with a Persistent handle to control its
+ // lifetime, ensuring it outlives the temporary track and sink created below.
+ Persistent<MediaStreamComponent> component = MakeMockAudioComponent();
+ MediaStreamAudioSource* source =
+ MediaStreamAudioSource::From(component->Source());
+
+ MediaStreamAudioTrack* platform_track =
+ MediaStreamAudioTrack::From(component.Get());
+ ASSERT_TRUE(source);
+ ASSERT_TRUE(platform_track);
+
+ // 2. Start the platform track by connecting it to the source. After this, the
+ // track is "live" and can accept sinks.
+ source->ConnectToInitializedTrack(component.Get());
+
+ // 3. Create a temporary MediaStreamTrackImpl wrapper and a sink in a
+ // separate scope. This wrapper will "own" the sink via a strong GC ref.
+ {
+ MediaStreamTrack* track1 = MakeGarbageCollected<MediaStreamTrackImpl>(
+ v8_scope.GetExecutionContext(), component.Get());
+ auto* sink = MakeGarbageCollected<SpeechRecognitionMediaStreamAudioSink>(
+ v8_scope.GetExecutionContext(), base::DoNothing());
+
+ // 4. Register the sink with the wrapper and add its raw pointer to the
+ // platform track's sink list.
+ track1->RegisterSink(sink);
+ platform_track->AddSink(sink);
+ }
+
+ // 5. Force garbage collection. This destroys `track_with_sink` and `sink`.
+ // If the fix is present, `track_with_sink->Dispose()` is called, removing
+ // the sink from `platform_track`.
+ WebHeap::CollectAllGarbageForTesting();
+
+ // 5. Now, destroy the component that owns the platform track by clearing the
+ // persistent handle and running GC again. The component's pre-finalizer,
+ // Dispose(), will call `platform_track->StopAndNotify()`.
+ component.Clear();
+ WebHeap::CollectAllGarbageForTesting();
+
+ // The test passes if it doesn't crash.
+}
+
} // namespace blink
Original Bug Report
use-after-poison in blink::MediaStreamAudioTrack::StopAndNotify(class base::OnceCallback<(void)>)
Security Bug
Important: Please do not change the component of this bug manually.
Please READ THIS FAQ before filing a bug: https://chromium.googlesource.com/chromium/src/+/HEAD/docs/security/faq.md
Please see the following link for instructions on filing security bugs: https://www.chromium.org/Home/chromium-security/reporting-security-bugs
Reports may be eligible for reward payments under the Chrome VRP: https://g.co/chrome/vrp
NOTE: Security bugs are normally made public once a fix has been widely deployed.
VULNERABILITY DETAILS AddressSanitizer: use-after-poison in blink::MediaStreamAudioTrack::StopAndNotify(class base::OnceCallback<(void)>)
VERSION
asan-win32-release_x64-1476007
Reproduction Steps
- python -m http.server 8000
- Run:
chrome --autoplay-policy=no-user-gesture-required --js-flags="-expose-gc" --no-sandbox --user-data-dir=test --enable-logging=stderr poc.html
Type of crash
Tab
Root Cause Analysis (RCA)
Coming soon.