CVE-2026-113257
Overview
Files Changed
chrome/browser/media/router/providers/cast/mirroring_activity.ccchrome/browser/media/mirroring_service_host.hchrome/browser/media/cast_mirroring_service_host.ccchrome/browser/media/router/providers/cast/mirroring_activity.hchrome/browser/media/router/providers/cast/mirroring_activity_unittest.cc
Original Bug Report
Cross-thread UAF in browser process during Cast Mirroring (MirroringActivity)
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 without the security team.
Overview: A race condition allows the browser IO thread to access a RenderFrameHostImpl bare pointer while it is concurrently destroyed on the UI thread. This cross-thread Use-After-Free (UAF) can be exploited by a compromised Mirroring Service utility process to achieve Remote Code Execution (RCE) and a sandbox escape.
Affected files:
chrome/browser/media/cast_mirroring_service_host.ccchrome/browser/media/router/providers/cast/mirroring_activity.ccchrome/browser/media/mirroring_service_host.h
Estimated timestamp from git blame: 2024-09-04
Summary
A cross-thread Use-After-Free (UAF) vulnerability exists in the Chrome browser process within the Cast mirroring implementation. The vulnerability is caused by MirroringActivity::OnSourceChanged, a Mojo IPC handler bound on the browser IO thread, which synchronously calls CastMirroringServiceHost::GetTabSourceId. This call chain improperly accesses UI-thread-only data structures (WebContents, RenderFrameHostManager) from the IO thread, obtaining a bare pointer to a RenderFrameHostImpl that can be concurrently destroyed on the UI thread during a navigation commit.
Technical Details
In chrome/browser/media/router/providers/cast/mirroring_activity.cc, the OnSourceChanged() method is executed on the browser IO thread (as verified by its DCHECK_CALLED_ON_VALID_SEQUENCE(io_sequence_checker_)). It synchronously calls host_->GetTabSourceId().
CastMirroringServiceHost::GetTabSourceId() is implemented as follows:
std::optional<content::FrameTreeNodeId> CastMirroringServiceHost::GetTabSourceId() const {
if (web_contents()) {
return web_contents()->GetPrimaryMainFrame()->GetFrameTreeNodeId();
}
return std::nullopt;
}
This chain of calls is inherently thread-unsafe. It accesses WebContents and RenderFrameHostManager structures which are strictly managed on the UI thread. Specifically, GetPrimaryMainFrame() eventually returns a bare RenderFrameHostImpl* pointer. Because the IO thread holds this bare pointer as a stack temporary, it is not protected by MiraclePtr (BackupRefPtr’s reference count is not incremented).
Simultaneously, on the UI thread, a cross-document navigation commit can trigger RenderFrameHostManager::SetRenderFrameHost(). This method std::moves the current render_frame_host_ unique_ptr out of the manager. The old frame is passed to RenderFrameHostManager::UnloadOldFrame(). If the renderer process is not live (e.g., if it crashed), the RenderFrameHostImpl is immediately and synchronously destroyed at the end of the scope in UnloadOldFrame.
Because the IO thread holds a bare pointer, there is a race condition. If the IO thread obtains the pointer before the UI thread swaps and destroys it, but attempts to execute the subsequent virtual call (GetFrameTreeNodeId()) after the memory has been freed and the BRP quarantine lifted, a UAF occurs. An attacker who has compromised the Mirroring Service utility process can use heap spraying to reclaim the freed memory, leading to arbitrary control-flow hijacking in the browser process via a fake vtable.
Suggested Steps to Trigger
Note: These are suggested steps for how an attacker might trigger the vulnerability; our tooling agent does not have the ability to run or verify this code directly.
- Initiate tab mirroring of an attacker-controlled page.
- Compromise the Mirroring Service utility process (via a separate vulnerability).
- From the compromised utility process, spam
OnSourceChanged()via theSessionObserverMojo pipe. - Concurrently, from the attacker-controlled page being mirrored, trigger a cross-document navigation.
- To maximize the race window, intentionally crash the renderer process just before the navigation commit. This causes immediate synchronous deletion of the old
RenderFrameHostImplinUnloadOldFrame. - The compromised utility process sprays the browser process heap with fake
RenderFrameHostImplobjects containing forged vtables. - The IO thread wins the race, obtaining the bare pointer, suffering a context switch, and then attempting to call the virtual
GetFrameTreeNodeId()on the reclaimed memory, achieving Remote Code Execution (RCE).
Suggested Fix
CastMirroringServiceHost should not synchronously access WebContents or RenderFrameHost from the IO thread. Given that GetTabSourceId is documented in the interface as not requiring the UI thread, the implementation should either:
- Cache the
FrameTreeNodeIdon the IO thread when the mirroring session starts, updating it safely via tasks posted from the UI thread when the source changes. - Change the interface design to post a task to the UI thread to fetch the ID asynchronously.
Evaluated with Chrome root at commit: ff3d2b74fa39431785bd60e51463b08fcc71ee33
Results so far have been promising, but there can be wrong deductions. If this proves to be a false positive, please close as WAI; 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.