CVE-2026-12014
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifchrome/browser/media/cast_mirroring_service_host.cc |
modified |
Files Changed
chrome/browser/media/cast_mirroring_service_host.cc
Patch
From 47ea02f4a2d37a3b94775ba045e70ae54aac86a3 Mon Sep 17 00:00:00 2001
From: Sangwhan Moon <sxm@chromium.org>
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 <sxm@chromium.org>
Commit-Queue: Sangwhan Moon <sxm@chromium.org>
Reviewed-by: Alexander Cooper <alcooper@chromium.org>
Reviewed-by: Mark Foltz <mfoltz@chromium.org>
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(
Original Bug Report
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.cccomponents/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:
- Initiate a mirroring session to ensure
video_capture_host_is populated. - Trigger a user-initiated pause or resume action (e.g., via the Global Media Controls).
- Simultaneously close the
VideoCaptureHostMojo pipe. This causes the IO thread to destroy theSelfOwnedReceiverand the underlyingSingleClientVideoCaptureHostobject. - 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. - The attacker then attempts to reclaim this memory on the IO thread (e.g., via heap spraying) before the
PauseVideoCaptureHostOnIOtask is executed. - Because the UI thread dereferences the pointer to pass it to
base::BindOnce, if the memory is reallocated before theraw_ptrwrapper in the task’sBindStateis constructed, MiraclePtr (BackupRefPtr) may protect the attacker’s new allocation instead of the original object. - 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.