Medium watch CVSS 8.8 chrome UAF 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
8.8
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Cast
DescriptionUse after free in Cast
ComponentCast
Sub-componentCast Streaming
Bug ClassUAF
Action tierwatch — corruption primitive but gated (uncommon) — not default-reachable
Computed by PatchGap's deterministic exploitability ruler from the fixing commit. patch-now = RCE-class & attacker-reachable in a default config, or exploited in the wild; watch = real security signal below that bar; noise = no security relevance.
Tracker423387026
Fix commite67cd45088c4 (chromium/src) +15/-8
CWECWE-416 (Use-after-free)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
CISA KEVNot listed
CreditedFayez
Reported2025-06-09 (per vendor advisory)
Fix landed2025-06-12 (public git)
Disclosed2025-08-05
Patch gap54 days — fix public in source before the advisory

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::OnceCallback type used to deliver a capture error message from AudioCapturingCallback::OnCaptureError back 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.

Key insight
A locally-allocated string was passed as a 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

  1. Start a mirroring session A Cast/mirroring streaming session runs with an OpenscreenSessionHost and an audio AudioCapturingCallback whose error_callback_ is bound with base::BindPostTaskToCurrentDefault.
  2. Trigger an audio capture error An audio capture error drives AudioCapturingCallback::OnCaptureError, which builds a local error_message string and passes it into the string_view-typed error callback.
  3. Defer past the string's lifetime Because the callback is posted to a task runner, the local error_message is destroyed before the task runs, leaving the bound std::string_view pointing at freed stack/heap storage.
  4. Deferred read of freed memory When the posted task later invokes ReportAndLogError, it reads the dangling string_view (via logger_.LogError and histogram logging), a use-after-free. Any escalation of this freed-buffer read into a stronger primitive is (inferred).

Impact Assessment

An attacker or condition able to induce an audio capture error during a mirroring session triggers a use-after-free read of a freed message buffer in the Cast/mirroring service, per the advisory’s “Use after free in Cast” classification. The precondition is an active mirroring session that produces a capture error causing the deferred error callback to fire after the local string is destroyed. The advisory states no exploited-in-the-wild status, and any escalation beyond the dangling read to memory corruption or code execution is (inferred).

Changed Functions

FunctionChangeNotes
if
components/mirroring/service/openscreen_session_host.cc
modified

Files Changed

  • components/mirroring/service/openscreen_session_host.cc
  • components/mirroring/service/openscreen_session_host.h

Audit Directions

  • `string_view`/span arguments into deferred callbacks
    Search for std::string_view (or other non-owning views/spans) passed into callbacks wrapped by base::BindPostTaskToCurrentDefault, base::BindOnce/PostTask, where the backing buffer is a local; these are classic deferred-use dangling references.
  • Local backing storage bound into async work
    Audit 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 runners
    Review method signatures that take string_view but are invoked via callbacks/tasks; prefer owning parameters when the call can be deferred across the caller’s stack frame.
From e67cd45088c4d1eb50ac0f4a66b428e320531b97 Mon Sep 17 00:00:00 2001
From: Jordan Bayles <jophba@chromium.org>
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 <jophba@chromium.org>
Reviewed-by: Muyao Xu <muyaoxu@google.com>
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
Loading diff…

Original Bug Report

reported by fa...@microsoft.com

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

View on issue tracker