CVE-2025-8578
Overview
Background
- `OpenscreenSessionHost`
- the Cast/mirroring service class that runs a streaming session and reports errors via
ReportAndLogError. - `std::string_view`
- a non-owning view over character data; it is only valid while the underlying buffer it points at remains alive.
- `base::BindPostTaskToCurrentDefault`
- a helper that wraps a callback so it is invoked later on the current task runner, meaning the callback’s arguments must outlive the original stack frame.
- `ErrorCallback`
- the
base::OnceCallbacktype used to deliver a capture error message fromAudioCapturingCallback::OnCaptureErrorback to the session host.
Root Cause Analysis
OpenscreenSessionHost::ReportAndLogError took its message parameter as a std::string_view, and this method is used with base::BindPostTaskToCurrentDefault, so the call is deferred onto a task runner rather than run synchronously. In AudioCapturingCallback::OnCaptureError, a std::string error_message was built as a local variable and passed into error_callback_, which was typed ErrorCallback = base::OnceCallback<void(std::string_view)>; the string_view bound into the posted task referenced that local string. When the local error_message went out of scope before the posted task executed, the string_view dangled, and reading it during the deferred ReportAndLogError was a use-after-free.
The fix changes both the ErrorCallback type and ReportAndLogError’s signature to take an owning std::string, so the message’s characters are moved/owned by the callback and remain valid until the task runs. As the commit message notes, because the message is generated specifically for this callback, taking ownership avoids the dangling view without adding superfluous copies.
std::string_view into a callback deferred via base::BindPostTaskToCurrentDefault, so the view outlived the string; the fix makes the callback and ReportAndLogError own a std::string so the buffer’s lifetime covers the deferred execution.Attack Path
- Start a mirroring session
A Cast/mirroring streaming session runs with an
OpenscreenSessionHostand an audioAudioCapturingCallbackwhoseerror_callback_is bound withbase::BindPostTaskToCurrentDefault. - Trigger an audio capture error
An audio capture error drives
AudioCapturingCallback::OnCaptureError, which builds a localerror_messagestring and passes it into thestring_view-typed error callback. - Defer past the string's lifetime
Because the callback is posted to a task runner, the local
error_messageis destroyed before the task runs, leaving the boundstd::string_viewpointing at freed stack/heap storage. - Deferred read of freed memory
When the posted task later invokes
ReportAndLogError, it reads the danglingstring_view(vialogger_.LogErrorand histogram logging), a use-after-free. Any escalation of this freed-buffer read into a stronger primitive is(inferred).
Impact Assessment
(inferred).Changed Functions
| Function | Change | Notes |
|---|---|---|
ifcomponents/mirroring/service/openscreen_session_host.cc |
modified |
Files Changed
components/mirroring/service/openscreen_session_host.cccomponents/mirroring/service/openscreen_session_host.h
Audit Directions
- `string_view`/span arguments into deferred callbacksSearch for
std::string_view(or other non-owning views/spans) passed into callbacks wrapped bybase::BindPostTaskToCurrentDefault,base::BindOnce/PostTask, where the backing buffer is a local; these are classic deferred-use dangling references. - Local backing storage bound into async workAudit functions that construct a local string/buffer and hand a view of it to code that may run later, ensuring ownership is transferred (move a
std::string, copy a span’s data). - View-typed public APIs used with task runnersReview method signatures that take
string_viewbut are invoked via callbacks/tasks; prefer owning parameters when the call can be deferred across the caller’s stack frame.
Patch
From e67cd45088c4d1eb50ac0f4a66b428e320531b97 Mon Sep 17 00:00:00 2001 From: Jordan Bayles <[email protected]> Date: Thu, 12 Jun 2025 15:17:38 -0700 Subject: [PATCH] [Cast Streaming] Fix UaF caused by binding a view object This patch fixes a use-after-free issue in the OpenscreenSessionHost::ReportAndLogError method caused by using a locally allocated string with a function that takes a string_view and is wrapped in base::BindPostTaskToCurrentDefault. The solution is to change the callback and function signatures to own the std::string instance. Since this instance is an error message generated for this callback specifically, this should not result in any superfluous copies. Bug: 423387026 Change-Id: Ib2e897a65c26b7780171470bad20d56782a81d7e Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6635890 Commit-Queue: Jordan Bayles <[email protected]> Reviewed-by: Muyao Xu <[email protected]> Cr-Commit-Position: refs/heads/main@{#1473218} --- diff --git a/components/mirroring/service/openscreen_session_host.cc b/components/mirroring/service/openscreen_session_host.cc index c9775e33..7277c5b 100644 --- a/components/mirroring/service/openscreen_session_host.cc +++ b/components/mirroring/service/openscreen_session_host.cc @@ -193,7 +193,10 @@ using AudioDataCallback = base::RepeatingCallback<void(std::unique_ptr<media::AudioBus> audio_bus, base::TimeTicks recorded_time)>; - using ErrorCallback = base::OnceCallback<void(std::string_view)>; + + // NOTE: the caller is expected to take ownership of the error message, since + // we cannot otherwise make any guarantees about its lifetime. + using ErrorCallback = base::OnceCallback<void(std::string)>; AudioCapturingCallback(AudioDataCallback audio_data_callback, ErrorCallback error_callback, mojo::Remote<mojom::SessionObserver>& observer) @@ -232,11 +235,11 @@ void OnCaptureError(media::AudioCapturerSource::ErrorCode code, const std::string& message) override { - std::string error_message = base::StrCat( - {"AudioCaptureError occurred, code: ", - base::NumberToString(static_cast<int>(code)), ", message: ", message}); - if (!error_callback_.is_null()) { - std::move(error_callback_).Run(error_message); + if (error_callback_) { + std::move(error_callback_) + .Run(base::StrCat({"AudioCaptureError occurred, code: ", + base::NumberToString(static_cast<int>(code)), + ", message: ", message})); } } @@ -736,7 +739,7 @@ } void OpenscreenSessionHost::ReportAndLogError(SessionError error, - std::string_view message) { + std::string message) { base::UmaHistogramEnumeration("MediaRouter.MirroringService.SessionError", error); logger_.LogError(error, message); diff --git a/components/mirroring/service/openscreen_session_host.h b/components/mirroring/service/openscreen_session_host.h index 7c093ad..f1caa444 100644 --- a/components/mirroring/service/openscreen_session_host.h +++ b/components/mirroring/service/openscreen_session_host.h @@ -150,7 +150,11 @@ void OnAsyncInitialized(const SupportedProfiles& profiles); // Notify `observer_` that error occurred and close the session. - void ReportAndLogError(mojom::SessionError error, std::string_view message); + // + // NOTE: since this method is used with base::Callback, it takes ownership + // of the `message` to avoid lifetime issues, especially when posted to a task + // runner. + void ReportAndLogError(mojom::SessionError error, std::string message); // Stops the current streaming session. If not called from StopSession(), a // new streaming session will start later after exchanging OFFER/ANSWER
Original Bug Report
Likely UAF in OpenscreenSessionHost::ReportAndLogError
VULNERABILITY DETAILS
The issue is reported via a GWP-ASan cab.
Allocation stack trace size: 27 Deallocation stack trace size: 23 Allocation address: 6ec00e5efa0 Allocation size: 60 Error type: Use After Free Region start: 6ec00003000 Region size: 1041000 Allocator Type: Malloc GWP-ASan Mode: Classic Allocation Thread ID: 5288
Allocation Stack Trace:
0 0x7fff548de0f5 [msedge!base::debug::CollectStackTrace at base/debug/stack_trace_win.cc:326] 1 0x7fff548de0b5 [msedge!gwp_asan::internal::AllocationInfo::GetStackTrace at components/gwp_asan/common/allocation_info.cc:27] 2 0x7fff53cba3b9 [msedge!gwp_asan::internal::GuardedPageAllocator::Allocate at components/gwp_asan/client/guarded_page_allocator.cc:324] 3 0x7fff51de7326 [msedge!gwp_asan::internal::`anonymous namespace’::AllocFn at components/gwp_asan/client/sampling_malloc_shims.cc:56] 4 0x7fff52ff1fdf [msedge!malloc at base/allocator/partition_allocator/src/partition_alloc/shim/allocator_shim_override_ucrt_symbols_win.h:94] 5 0x7fff55d50817 [msedge!operator new at vctools/crt/vcstartup/src/heap/new_scalar.cpp:36] 6 0x7fff548c2ed9 [msedge!base::StrCat at base/strings/strcat.cc:15] 7 0x7fff5e6e26bc [msedge!mirroring::OpenscreenSessionHost::AudioCapturingCallback::OnCaptureError at components/mirroring/service/openscreen_session_host.cc:237] 8 0x7fff56a3b988 [msedge!media::AudioInputDevice::DetectedDeadInputStream at media/audio/audio_input_device.cc:378] 9 0x7fff5486b125 [msedge!base::RepeatingCallback<void ()>::Run at base/functional/callback.h:345] 10 0x7fff56a38f20 [msedge!media::AliveChecker::CheckIfAlive at media/audio/alive_checker.cc:143] 11 0x7fff54c8f898 [msedge!base::RepeatingTimer::RunUserTask at base/timer/timer.cc:215] 12 0x7fff52171c4f [msedge!base::sequence_manager::internal::ThreadControllerWithMessagePumpImpl::DoWork at base/task/sequence_manager/thread_controller_with_message_pump_impl.cc:332] 13 0x7fff52fc358f [msedge!base::MessagePumpDefault::Run at base/message_loop/message_pump_default.cc:43] 14 0x7fff53d1418e [msedge!base::sequence_manager::internal::ThreadControllerWithMessagePumpImpl::Run at base/task/sequence_manager/thread_controller_with_message_pump_impl.cc:634] 15 0x7fff53d12a5f [msedge!base::RunLoop::Run at base/run_loop.cc:139] 16 0x7fff56156e81 [msedge!content::UtilityMain at content/utility/utility_main.cc:626] 17 0x7fff51edb917 [msedge!content::RunOtherNamedProcessTypeMain at content/app/content_main_runner_impl.cc:791] 18 0x7fff51eda690 [msedge!content::ContentMainRunnerImpl::Run at content/app/content_main_runner_impl.cc:1171] 19 0x7fff51ed9cfa [msedge!content::RunContentProcess at content/app/content_main.cc:419] 20 0x7fff51ed8fda [msedge!content::ContentMain at content/app/content_main.cc:432] 21 0x7fff51ed8969 [msedge!ChromeMain at chrome/app/chrome_main.cc:340] 22 0x7ff635e17ce6 [msedge_exe!MainDllLoader::Launch at chrome/app/main_dll_loader_win.cc:241] 23 0x7ff635e15597 [msedge_exe!wWinMain at chrome/app/chrome_exe_main_win.cc:804] 24 0x7ff635ef94c2 [msedge_exe!__scrt_common_main_seh at vctools/crt/vcstartup/src/startup/exe_common.inl:288] 25 0x7fffd8eb259d [kernel32!BaseThreadInitThunk at clientcore/base/win32/client/thread.c:75] 26 0x7fffda7eaf58 [ntdll!RtlUserThreadStart at minkernel/ntdll/rtlstrt.c:1166]
Deallocation Thread ID: 5288
Deallocation Stack Trace:
0 0x7fff548de0f5 [msedge!base::debug::CollectStackTrace at base/debug/stack_trace_win.cc:326] 1 0x7fff548de0b5 [msedge!gwp_asan::internal::AllocationInfo::GetStackTrace at components/gwp_asan/common/allocation_info.cc:27] 2 0x7fff53d54451 [msedge!gwp_asan::internal::GuardedPageAllocator::Deallocate at components/gwp_asan/client/guarded_page_allocator.cc:364] 3 0x7fff5e6e272a [msedge!mirroring::OpenscreenSessionHost::AudioCapturingCallback::OnCaptureError at components/mirroring/service/openscreen_session_host.cc:243] 4 0x7fff56a3b988 [msedge!media::AudioInputDevice::DetectedDeadInputStream at media/audio/audio_input_device.cc:378] 5 0x7fff5486b125 [msedge!base::RepeatingCallback<void ()>::Run at base/functional/callback.h:345] 6 0x7fff56a38f20 [msedge!media::AliveChecker::CheckIfAlive at media/audio/alive_checker.cc:143] 7 0x7fff54c8f898 [msedge!base::RepeatingTimer::RunUserTask at base/timer/timer.cc:215] 8 0x7fff52171c4f [msedge!base::sequence_manager::internal::ThreadControllerWithMessagePumpImpl::DoWork at base/task/sequence_manager/thread_controller_with_message_pump_impl.cc:332] 9 0x7fff52fc358f [msedge!base::MessagePumpDefault::Run at base/message_loop/message_pump_default.cc:43] 10 0x7fff53d1418e [msedge!base::sequence_manager::internal::ThreadControllerWithMessagePumpImpl::Run at base/task/sequence_manager/thread_controller_with_message_pump_impl.cc:634] 11 0x7fff53d12a5f [msedge!base::RunLoop::Run at base/run_loop.cc:139] 12 0x7fff56156e81 [msedge!content::UtilityMain at content/utility/utility_main.cc:626] 13 0x7fff51edb917 [msedge!content::RunOtherNamedProcessTypeMain at content/app/content_main_runner_impl.cc:791] 14 0x7fff51eda690 [msedge!content::ContentMainRunnerImpl::Run at content/app/content_main_runner_impl.cc:1171] 15 0x7fff51ed9cfa [msedge!content::RunContentProcess at content/app/content_main.cc:419] 16 0x7fff51ed8fda [msedge!content::ContentMain at content/app/content_main.cc:432] 17 0x7fff51ed8969 [msedge!ChromeMain at chrome/app/chrome_main.cc:340] 18 0x7ff635e17ce6 [msedge_exe!MainDllLoader::Launch at chrome/app/main_dll_loader_win.cc:241] 19 0x7ff635e15597 [msedge_exe!wWinMain at chrome/app/chrome_exe_main_win.cc:804] 20 0x7ff635ef94c2 [msedge_exe!__scrt_common_main_seh at vctools/crt/vcstartup/src/startup/exe_common.inl:288] 21 0x7fffd8eb259d [kernel32!BaseThreadInitThunk at clientcore/base/win32/client/thread.c:75] 22 0x7fffda7eaf58 [ntdll!RtlUserThreadStart at minkernel/ntdll/rtlstrt.c:1166]
Posted from:
msedge!mirroring::OpenscreenSessionHost::StartCapturingAudio+0x7e (00007fff5e6de894) [D:\a\_work\e\src\components\mirroring\service\openscreen_session_host.cc @ 1126] task backtrace: [1] msedge!media::AliveChecker::Start+0x85 (00007fff56a38de1) [D:\a_work\e\src\media\audio\alive_checker.cc @ 97]
[2] msedge!media::AliveChecker::Start+0x85 (00007fff56a38de1) [D:\a\_work\e\src\media\audio\alive_checker.cc @ 97] [3] msedge!media::AliveChecker::Start+0x85 (00007fff56a38de1) [D:\a_work\e\src\media\audio\alive_checker.cc @ 97]
[4] msedge!mojo::core::ipcz_driver::MojoTrap::TrapEventHandler+0x6ae (00007fff`548a172e) [D:\a_work\e\src\mojo\core\ipcz_driver\mojo_trap.cc @ 400]
@$task_trace()
The task was posted from msedge!mirroring::OpenscreenSessionHost::StartCapturingAudio
audio_capturing_callback_ = std::make_unique<AudioCapturingCallback>( base::BindPostTaskToCurrentDefault(base::BindRepeating( &AudioRtpStream::InsertAudio, audio_stream_->AsWeakPtr())), base::BindPostTaskToCurrentDefault(base::BindOnce( &OpenscreenSessionHost::ReportAndLogError, weak_factory_.GetWeakPtr(), SessionError::AUDIO_CAPTURE_ERROR)), observer_);
Based on the allocation and deallocation stack the error_message is destructed which is being passed as a string_view to .
void OnCaptureError(media::AudioCapturerSource::ErrorCode code, const std::string& message) override { std::string error_message = base::StrCat( {“AudioCaptureError occurred, code: “, base::NumberToString(static_cast<int>(code)), “, message: “, message}); if (!error_callback_.is_null()) { std::move(error_callback_).Run(error_message); } }
The error_message is passed as a string_view to ReportAndLogError as message which is already destructed.
0:000> dq 0x000006ec00e5efa0 ——–> message
000006ec00e5efa0 ???????????????? ???????????????? 000006ec00e5efb0 ???????????????? ????????????????
000006ec00e5efc0 ???????????????? ???????????????? 000006ec00e5efd0 ???????????????? ????????????????
000006ec00e5efe0 ???????????????? ???????????????? 000006ec00e5eff0 ???????????????? ????????????????
000006ec00e5f000 ???????????????? ???????????????? 000006ec00e5f010 ???????????????? ????????????????
0:000> dq 0x000006ec022f1900 —–> OpenscreenSessionHost
000006ec022f1900 00007fff5fad7df0 00007fff5fad7e10 000006ec022f1910 00007fff5fad7e30 1264a8c000000002
000006ec022f1920 00000000007fff55 0000000400000000 000006ec022f1930 0032746f6f74686b 0000000000000000
000006ec022f1940 0700000000000000 000006ec0220a670 000006ec022f1950 0000000000000024 8000000000000028
000006ec022f1960 312d7265646e6573 0000003130343335 000006ec022f1970 0d00000000000000 00000000ffffff00
void OpenscreenSessionHost::ReportAndLogError(SessionError error, std::string_view message) { base::UmaHistogramEnumeration(“MediaRouter.MirroringService.SessionError”, error); logger_.LogError(error, message);
This could be caused if the error_callback_ was posted on a different thread due to the usage of BindPostTaskToCurrentDefault
VERSION Chrome Version: M137 Operating System: Windows
REPRODUCTION CASE This is a GWP-ASan bug. Attached the details above.
FOR CRASHES, PLEASE INCLUDE THE FOLLOWING ADDITIONAL INFORMATION Type of crash: [tab, browser, etc.] Crash State: [see link above: stack trace with symbols, registers, exception record] Client ID (if relevant): [see link above]
CREDIT INFORMATION Externally reported security bugs may appear in Chrome release notes. If this bug is included, how would you like to be credited? Reporter credit: Fayez