CVE-2026-13787
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifremoting/host/win/desktop_event_handler.cc |
modified |
Files Changed
remoting/host/win/desktop_event_handler.cc
Patch
From 2c8e0329acc02ccd42b831afb1e25bdfa04601fd Mon Sep 17 00:00:00 2001
From: Yuwei Huang <yuweih@chromium.org>
Date: Fri, 12 Jun 2026 12:31:21 -0700
Subject: [PATCH] remoting: Destroy DesktopEventHandler::Delegate on the worker thread
Destroying the delegate on the caller thread while it is active on the
worker thread causes sequence safety issues. This change defers
destruction to the worker thread when the event handler is stopped.
Bug: 522919313
Change-Id: Id5ca8d77c8f139cb549ce04d207110aa8395f24e
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7927860
Auto-Submit: Yuwei Huang <yuweih@chromium.org>
Reviewed-by: Joe Downing <joedow@chromium.org>
Commit-Queue: Yuwei Huang <yuweih@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1646172}
---
diff --git a/remoting/host/win/desktop_event_handler.cc b/remoting/host/win/desktop_event_handler.cc
index 959820ca..2dbe82a 100644
--- a/remoting/host/win/desktop_event_handler.cc
+++ b/remoting/host/win/desktop_event_handler.cc
@@ -103,6 +103,7 @@
// This is set to null after Stop() is called, i.e., DesktopEventHandler has
// been destroyed.
std::unique_ptr<Delegate> delegate_ GUARDED_BY(delegate_lock_);
+ bool stopping_ GUARDED_BY(delegate_lock_) = false;
// Fields below are only initialized after the worker thread has started.
std::unique_ptr<webrtc::Desktop> desktop_;
@@ -159,11 +160,21 @@
void DesktopEventHandler::Core::Stop() {
DCHECK_CALLED_ON_VALID_SEQUENCE(caller_sequence_checker_);
- base::AutoLock lock(delegate_lock_);
- delegate_.reset();
- if (worker_task_runner_) {
- worker_task_runner_->PostTask(
- FROM_HERE, base::BindOnce(&Core::DestroyWorkerThread, this));
+ // Extract the delegate under the lock but destroy it outside the lock block
+ // to avoid deadlock and minimize lock contention.
+ std::unique_ptr<Delegate> delegate_to_destroy;
+ scoped_refptr<base::SequencedTaskRunner> task_runner;
+ {
+ base::AutoLock lock(delegate_lock_);
+ stopping_ = true;
+ task_runner = worker_task_runner_;
+ if (!task_runner) {
+ delegate_to_destroy = std::move(delegate_);
+ }
+ }
+ if (task_runner) {
+ task_runner->PostTask(FROM_HERE,
+ base::BindOnce(&Core::DestroyWorkerThread, this));
}
}
@@ -244,18 +255,25 @@
void DesktopEventHandler::Core::DestroyWorkerThread() {
DCHECK_CALLED_ON_VALID_SEQUENCE(worker_sequence_checker_);
+ // Extract the delegate under the lock but destroy it outside the lock block
+ // to avoid deadlock and minimize lock contention.
+ std::unique_ptr<Delegate> delegate_to_destroy;
+ scoped_refptr<base::SequencedTaskRunner> task_runner_to_release;
{
base::AutoLock lock(delegate_lock_);
if (delegate_) {
delegate_->OnWorkerThreadStopping();
+ if (stopping_) {
+ delegate_to_destroy = std::move(delegate_);
+ }
}
+ task_runner_to_release = std::move(worker_task_runner_);
}
check_input_desktop_timer_.Stop();
if (win_event_hook_) {
UnhookWinEvent(win_event_hook_);
win_event_hook_ = nullptr;
}
- worker_task_runner_ = nullptr;
}
// DesktopEventHandler implementation
Original Bug Report
Potential Use-After-Free in MouseCursorMonitorWin via unsafe cross-thread destruction
Flapjack, 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 Use-After-Free vulnerability exists in the Chrome Remote Desktop Host on Windows (remoting_desktop.exe). An unsafe cross-thread destruction of MouseCursorMonitorWin::Delegate during session teardown creates a race condition with a high-frequency polling timer, potentially leading to arbitrary code execution as SYSTEM.
Affected files:
remoting/host/win/desktop_event_handler.ccremoting/host/win/mouse_cursor_monitor_win.cc
Estimated timestamp from git blame: 2025-11-18
Summary
A potential Use-After-Free (UAF) vulnerability exists in the Windows host component of Chrome Remote Desktop (remoting_desktop.exe). The issue occurs due to the improper destruction of the MouseCursorMonitorWin::Delegate object across different threads during session teardown. This leads to a race condition with a 100Hz polling timer, causing a worker thread to dereference a freed std::unique_ptr object.
Vulnerability Details
In MouseCursorMonitorWin, cursor capturing is handled by a nested Delegate class running on a dedicated worker thread managed by DesktopEventHandler.
- Timer Setup: In
Delegate::OnWorkerThreadStarted(), abase::RepeatingTimeris started on the worker thread at 100Hz to poll for cursor changes viaCaptureCursorImage(). The callback is bound usingbase::Unretained(this). TheDelegatealso creates and owns astd::unique_ptr<webrtc::MouseCursorMonitor> webrtc_monitor_. - Unsafe Teardown: When a remote session is disconnected,
MouseCursorMonitorWinis destroyed on the caller sequence (typically the Network thread). Its destructor triggersDesktopEventHandler::~DesktopEventHandler, which callsDesktopEventHandler::Core::Stop():
void DesktopEventHandler::Core::Stop() {
DCHECK_CALLED_ON_VALID_SEQUENCE(caller_sequence_checker_);
base::AutoLock lock(delegate_lock_);
delegate_.reset(); // Unsafe cross-thread destruction
if (worker_task_runner_) {
worker_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&Core::DestroyWorkerThread, this));
}
}
- The Race Condition: The call to
delegate_.reset()immediately destroys theDelegateobject on the caller sequence. This destroys thewebrtc_monitor_(freeing its heap memory) and thebase::RepeatingTimer. - Use-After-Free:
base::RepeatingTimeris sequence-affine. Destroying it on a sequence different from the one it was started on is unsafe and fails to safely cancel pending or currently executing tasks on the worker thread. Because the timer fires every 10ms, there is a high probability that the worker thread is simultaneously executingCaptureCursorImage(). The worker thread evaluateswebrtc_monitor_->Capture(), dereferencing the just-freedunique_ptrmemory, resulting in a Use-After-Free.
Impact
In Chrome Remote Desktop on Windows, remoting_desktop.exe typically runs with SYSTEM privileges. Because the freed memory belongs to a std::unique_ptr, it is not protected by MiraclePtr (BRP). Furthermore, the UAF occurs on a virtual method call (Capture()). This provides an ideal primitive for an attacker to hijack control flow by reallocating the memory and forging the vtable, potentially leading to local privilege escalation and arbitrary code execution as SYSTEM.
Potential Attacker Steps
(Note: These are suggested steps based on code analysis; our tooling cannot run live code to verify.)
- An attacker initiates an authenticated remote desktop connection to a Windows host.
- The connection is established, and the host spawns the
WinEventWorkerThreadand begins 100Hz cursor polling. - The attacker sprays the heap with forged vtable pointers (e.g., using RDP channels or other manipulable input streams).
- The attacker abruptly drops the WebRTC connection or sends a Jingle
session-terminatemessage. - The host caller sequence executes
delegate_.reset(), freeing the monitor. - If timed correctly, the worker thread simultaneously executes
CaptureCursorImage()and dereferences the attacker-controlled memory, hijacking control flow.
Suggested Fix
The Delegate and its members must be destroyed on the sequence where they are used. DesktopEventHandler::Core::Stop() should not call delegate_.reset() directly on the caller thread.
Instead, the destruction of the delegate should be posted to the worker thread as part of DestroyWorkerThread, or Stop() should be refactored to cleanly signal the worker thread to stop the timer and delete the delegate before the thread is joined or dropped.
Evaluated with Chrome root at commit: 2155cb00003ec35716a76ed3246eae995f87b7ff
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.