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 <evliu@google.com>
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 <evliu@google.com>
Reviewed-by: Mark Foltz <mfoltz@chromium.org>
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.