Overview

Critical
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Chromoting
DescriptionUse after free in Chromoting
ComponentChromoting
Bug ClassUAF
Tracker522919313
Fix commit2c8e0329acc0 (chromium/src) +24/-6
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-30

Changed Functions

FunctionChangeNotes
if
remoting/host/win/desktop_event_handler.cc
modified

Files Changed

  • remoting/host/win/desktop_event_handler.cc
From 2c8e0329acc02ccd42b831afb1e25bdfa04601fd Mon Sep 17 00:00:00 2001
From: Yuwei Huang <[email protected]>
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 <[email protected]>
Reviewed-by: Joe Downing <[email protected]>
Commit-Queue: Yuwei Huang <[email protected]>
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
Loading diff…

Original Bug Report

reported by [email protected]

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.cc
  • remoting/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.

  1. Timer Setup: In Delegate::OnWorkerThreadStarted(), a base::RepeatingTimer is started on the worker thread at 100Hz to poll for cursor changes via CaptureCursorImage(). The callback is bound using base::Unretained(this). The Delegate also creates and owns a std::unique_ptr<webrtc::MouseCursorMonitor> webrtc_monitor_.
  2. Unsafe Teardown: When a remote session is disconnected, MouseCursorMonitorWin is destroyed on the caller sequence (typically the Network thread). Its destructor triggers DesktopEventHandler::~DesktopEventHandler, which calls DesktopEventHandler::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));
  }
}
  1. The Race Condition: The call to delegate_.reset() immediately destroys the Delegate object on the caller sequence. This destroys the webrtc_monitor_ (freeing its heap memory) and the base::RepeatingTimer.
  2. Use-After-Free: base::RepeatingTimer is 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 executing CaptureCursorImage(). The worker thread evaluates webrtc_monitor_->Capture(), dereferencing the just-freed unique_ptr memory, 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.)

  1. An attacker initiates an authenticated remote desktop connection to a Windows host.
  2. The connection is established, and the host spawns the WinEventWorkerThread and begins 100Hz cursor polling.
  3. The attacker sprays the heap with forged vtable pointers (e.g., using RDP channels or other manipulable input streams).
  4. The attacker abruptly drops the WebRTC connection or sends a Jingle session-terminate message.
  5. The host caller sequence executes delegate_.reset(), freeing the monitor.
  6. 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.

View on issue tracker