Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free Cast
DescriptionUse after free Cast
ComponentChromium
Bug ClassUAF
Tracker514742747
Fix commit47ea02f4a2d3 (chromium/src) +26/-25
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-11

Changed Functions

FunctionChangeNotes
if
chrome/browser/media/cast_mirroring_service_host.cc
modified

Files Changed

  • chrome/browser/media/cast_mirroring_service_host.cc
From 47ea02f4a2d37a3b94775ba045e70ae54aac86a3 Mon Sep 17 00:00:00 2001
From: Sangwhan Moon <[email protected]>
Date: Fri, 22 May 2026 18:27:39 -0700
Subject: [PATCH] Access VideoCaptureHost receiver on I/O thread.

Addresses a threading violation in CastMirroringServiceHost.

Bug: 514742747
Change-Id: Ic3d94f4fc7e7f9a23540e5c7fe6cad3b15b656e9
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7861424
Auto-Submit: Sangwhan Moon <[email protected]>
Commit-Queue: Sangwhan Moon <[email protected]>
Reviewed-by: Alexander Cooper <[email protected]>
Reviewed-by: Mark Foltz <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1635340}
---

diff --git a/chrome/browser/media/cast_mirroring_service_host.cc b/chrome/browser/media/cast_mirroring_service_host.cc
index 0b48c9e..e365aa9 100644
--- a/chrome/browser/media/cast_mirroring_service_host.cc
+++ b/chrome/browser/media/cast_mirroring_service_host.cc
@@ -97,22 +97,28 @@
       std::move(receiver));
 }
 
-void PauseVideoCaptureHostOnIO(media::mojom::VideoCaptureHost* host,
-                               base::UnguessableToken device_id,
-                               base::OnceClosure on_paused_callback) {
+void PauseVideoCaptureHostOnIO(
+    mojo::SelfOwnedReceiverRef<media::mojom::VideoCaptureHost> host,
+    base::UnguessableToken device_id,
+    base::OnceClosure on_paused_callback) {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
-  host->Pause(device_id);
-  std::move(on_paused_callback).Run();
+  if (host) {
+    host->impl()->Pause(device_id);
+    std::move(on_paused_callback).Run();
+  }
 }
 
-void ResumeVideoCaptureHostOnIO(media::mojom::VideoCaptureHost* host,
-                                base::UnguessableToken device_id,
-                                base::UnguessableToken session_id,
-                                media::VideoCaptureParams params,
-                                base::OnceClosure on_resumed_callback) {
+void ResumeVideoCaptureHostOnIO(
+    mojo::SelfOwnedReceiverRef<media::mojom::VideoCaptureHost> host,
+    base::UnguessableToken device_id,
+    base::UnguessableToken session_id,
+    media::VideoCaptureParams params,
+    base::OnceClosure on_resumed_callback) {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
-  host->Resume(device_id, session_id, params);
-  std::move(on_resumed_callback).Run();
+  if (host) {
+    host->impl()->Resume(device_id, session_id, params);
+    std::move(on_resumed_callback).Run();
+  }
 }
 
 blink::mojom::MediaStreamType ConvertVideoStreamType(
@@ -611,23 +617,18 @@
 
 void CastMirroringServiceHost::Pause(base::OnceClosure on_paused_callback) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  if (video_capture_host_) {
-    content::GetIOThreadTaskRunner({})->PostTask(
-        FROM_HERE,
-        base::BindOnce(&PauseVideoCaptureHostOnIO, video_capture_host_->impl(),
-                       ignored_token_, std::move(on_paused_callback)));
-  }
+  content::GetIOThreadTaskRunner({})->PostTask(
+      FROM_HERE, base::BindOnce(&PauseVideoCaptureHostOnIO, video_capture_host_,
+                                ignored_token_, std::move(on_paused_callback)));
 }
 
 void CastMirroringServiceHost::Resume(base::OnceClosure on_resumed_callback) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  if (video_capture_host_) {
-    content::GetIOThreadTaskRunner({})->PostTask(
-        FROM_HERE,
-        base::BindOnce(&ResumeVideoCaptureHostOnIO, video_capture_host_->impl(),
-                       ignored_token_, ignored_token_, ignored_params_,
-                       std::move(on_resumed_callback)));
-  }
+  content::GetIOThreadTaskRunner({})->PostTask(
+      FROM_HERE,
+      base::BindOnce(&ResumeVideoCaptureHostOnIO, video_capture_host_,
+                     ignored_token_, ignored_token_, ignored_params_,
+                     std::move(on_resumed_callback)));
 }
 
 void CastMirroringServiceHost::GetMirroringStats(
Loading diff…

Original Bug Report

reported by [email protected]

Potential Cross-thread Use-After-Free in CastMirroringServiceHost::Pause and Resume

Project Fortify, an experimental security project, has identified the following potential security issue. If you’re a feature owner CC-ed on this bug, please do your best to review these reports. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.

Overview: A potential race condition exists where an IO-thread-bound WeakPtr is incorrectly dereferenced on the UI thread in CastMirroringServiceHost. This violation of the base::WeakPtr threading contract can lead to a Use-After-Free (UAF) in the Browser process. A compromised Mirroring Service utility process could potentially exploit this to achieve arbitrary code execution and a sandbox escape.

Affected files:

  • chrome/browser/media/cast_mirroring_service_host.cc
  • components/mirroring/browser/single_client_video_capture_host.cc

Estimated timestamp from git blame: 2023-04-01

Summary

A potential cross-thread Use-After-Free (UAF) vulnerability has been identified in CastMirroringServiceHost. The class incorrectly dereferences a base::WeakPtr (aliased as mojo::SelfOwnedReceiverRef) on the UI thread, despite the pointer being bound to the IO thread sequence. This threading violation, combined with the way tasks are posted back to the IO thread, creates a window for a racy deletion and subsequent memory corruption.

Technical Details

In chrome/browser/media/cast_mirroring_service_host.cc, video_capture_host_ is a mojo::SelfOwnedReceiverRef<media::mojom::VideoCaptureHost>, which is internally a base::WeakPtr to a mojo::internal::SelfOwnedReceiver. This receiver is constructed on the IO thread via CreateVideoCaptureHostOnIO, making its WeakPtrFactory bound to the IO thread sequence.

However, both CastMirroringServiceHost::Pause() and CastMirroringServiceHost::Resume() check and dereference this pointer on the UI thread:

void CastMirroringServiceHost::Pause(base::OnceClosure on_paused_callback) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (video_capture_host_) { // Racy check on UI thread
    content::GetIOThreadTaskRunner({})->PostTask(
        FROM_HERE,
        base::BindOnce(&PauseVideoCaptureHostOnIO, video_capture_host_->impl(),
                       ignored_token_, std::move(on_paused_callback)));
  }
}

Potential Exploit Path

A compromised Mirroring Service utility process could potentially trigger this UAF through the following steps:

  1. Initiate a mirroring session to ensure video_capture_host_ is populated.
  2. Trigger a user-initiated pause or resume action (e.g., via the Global Media Controls).
  3. Simultaneously close the VideoCaptureHost Mojo pipe. This causes the IO thread to destroy the SelfOwnedReceiver and the underlying SingleClientVideoCaptureHost object.
  4. If the UI thread’s video_capture_host_ check occurs while the IO thread is mid-destruction, it may retrieve a raw pointer (impl()) to the freed memory.
  5. The attacker then attempts to reclaim this memory on the IO thread (e.g., via heap spraying) before the PauseVideoCaptureHostOnIO task is executed.
  6. Because the UI thread dereferences the pointer to pass it to base::BindOnce, if the memory is reallocated before the raw_ptr wrapper in the task’s BindState is constructed, MiraclePtr (BackupRefPtr) may protect the attacker’s new allocation instead of the original object.
  7. When the task runs on the IO thread, the virtual call host->Pause() would then be dispatched through an attacker-controlled vtable, potentially leading to arbitrary code execution in the Browser process.

Note: These are potential steps based on code analysis; a functional proof-of-concept has not been verified.

Suggested Fix

Ensure that the video_capture_host_ WeakPtr is only accessed on the IO thread. Instead of dereferencing it on the UI thread to retrieve the impl() pointer, the CastMirroringServiceHost::Pause and Resume methods should post a task to the IO thread that takes the WeakPtr itself as an argument. The IO thread task can then safely check the WeakPtr’s validity and perform the necessary operations on the implementation.

Evaluated with Chrome root at commit: 29093e11cf509e3593f6229e4b1b075cca356049


Results so far have been promising, but there can be wrong deductions. Feel free to adjust as follows:

  • If you are familiar with the severity guidelines, you may adjust the severity.
  • If this is a false positive, and there’s no work to be done, please close as WAI.
  • If there is work to do here but not a vulnerability, please change the issue type to Task/Bug/FR.

Data from false positives will be used to improve accuracy over time. And please feel free to reach out to me directly if you have concerns or feedback on the project.

View on issue tracker