CVE-2026-9110
Overview
Background
- `DesktopDragDropClientWin`
- the Windows Aura drag-and-drop client; one instance exists per desktop window tree host, and
StartDragAndDropruns a nested (blocking) OLE drag loop. - `drag_drop_in_progress_`
- the removed per-instance member that tracked whether this client was currently running a drag.
- `g_is_dragging`
- the new file-scoped
static/global flag that tracks whether anyDesktopDragDropClientWinis running a drag process-wide. - `base::AutoReset`
- an RAII helper that sets a variable to a value and restores the previous value when it goes out of scope, used here to set and clear
g_is_draggingaround the drag loop.
Root Cause Analysis
The in-progress state was tracked per instance via drag_drop_in_progress_, so each window’s DesktopDragDropClientWin only knew about its own drag; the CHECK(!drag_drop_in_progress_) at the top of StartDragAndDrop and the IsDragDropInProgress() query could not see a drag already running in a different window. This allowed multiple concurrent drags originating from different windows — the “inappropriate implementation” the fix targets.
The patch replaces the per-instance member with a single file-scoped g_is_dragging, guards the drag loop with base::AutoReset<bool> drag_scoper(&g_is_dragging, true) so the flag is set for the entire nested drag and reliably cleared on every exit path (removing the old conditional if (alive) drag_drop_in_progress_ = false; cleanup), and routes the entry CHECK, destructor cancel, and IsDragDropInProgress() through the global. It also hardens DragCancel with an if (drag_source_) null check so cancellation during teardown does not dereference an unset drag_source_.
static so at most one drag runs at a time.Attack Path
- Start a first drag
Initiate a drag-and-drop from one browser/desktop window, entering
DesktopDragDropClientWin::StartDragAndDropand its nested OLE drag loop. - Start a second drag from another window
Because the old
drag_drop_in_progress_flag was per instance, a different window’s client sees its own flag as false and passesCHECK(!drag_drop_in_progress_), beginning a concurrent drag. - Overlapping drag state
Two drags run simultaneously across windows, an unintended UI state the implementation was meant to forbid; the diff does not demonstrate a specific memory-corruption sink from this, so any such consequence is
(inferred). - Fix enforcement
With
g_is_draggingglobal and set viaAutoReset, the second window’sCHECK(!g_is_dragging)now fails fast andIsDragDropInProgress()reports the true process-wide state.
Impact Assessment
DragCancel null hardening, so any escalation to memory corruption or code execution is (inferred) and not demonstrated. No exploited-in-the-wild status is asserted beyond the advisory.Changed Functions
| Function | Change | Notes |
|---|---|---|
ifui/views/widget/desktop_aura/desktop_drag_drop_client_win.cc |
modified |
Files Changed
ui/views/widget/desktop_aura/desktop_drag_drop_client_win.ccui/views/widget/desktop_aura/desktop_drag_drop_client_win.h
Audit Directions
- Per-instance flags that should be globalAudit singleton-like UI operations (modal loops, drags, nested message pumps) where a “busy”/“in progress” guard is stored per object but the resource is effectively process-wide.
- RAII vs conditional cleanupReview flags cleared by hand-written conditional code (
if (alive) flag = false;) on functions with multiple exit paths; preferbase::AutoReset/scope guards that cannot be skipped. - Unchecked COM/handle dereference on teardownFlag cancel/cleanup methods (like
DragCancel) that dereference members which may be unset when invoked from destructors or early-exit paths.
Patch
From 16dcbc3d1476a35a652d71c034c851fa6da109d0 Mon Sep 17 00:00:00 2001
From: David Bienvenu <davidbienvenu@chromium.org>
Date: Wed, 13 May 2026 07:44:42 -0700
Subject: [PATCH] win: Use static to keep track of in progress drags.
Prevent multiple drags from different windows.
Bug: 503551154
Change-Id: I7bb052b88a7eb4fe06a827a55bbb05db2391e8f2
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7841115
Reviewed-by: Keren Zhu <kerenzhu@chromium.org>
Commit-Queue: David Bienvenu <davidbienvenu@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1629961}
---
diff --git a/ui/views/widget/desktop_aura/desktop_drag_drop_client_win.cc b/ui/views/widget/desktop_aura/desktop_drag_drop_client_win.cc
index b096048e..6d31bee 100644
--- a/ui/views/widget/desktop_aura/desktop_drag_drop_client_win.cc
+++ b/ui/views/widget/desktop_aura/desktop_drag_drop_client_win.cc
@@ -6,6 +6,7 @@
#include <memory>
+#include "base/auto_reset.h"
#include "base/notimplemented.h"
#include "base/scoped_observation.h"
#include "base/threading/hang_watcher.h"
@@ -47,19 +48,21 @@
scoped_observation_;
};
+bool g_is_dragging = false;
+
} // namespace
DesktopDragDropClientWin::DesktopDragDropClientWin(
aura::Window* root_window,
HWND window,
DesktopWindowTreeHostWin* desktop_host)
- : drag_drop_in_progress_(false), desktop_host_(desktop_host) {
+ : desktop_host_(desktop_host) {
drop_target_ = new DesktopDropTargetWin(root_window);
drop_target_->Init(window);
}
DesktopDragDropClientWin::~DesktopDragDropClientWin() {
- if (drag_drop_in_progress_) {
+ if (g_is_dragging) {
DragCancel();
}
}
@@ -71,7 +74,7 @@
const gfx::Point& screen_location,
int allowed_operations,
ui::mojom::DragEventSource source) {
- CHECK(!drag_drop_in_progress_);
+ CHECK(!g_is_dragging);
gfx::Point touch_screen_point;
if (source == ui::mojom::DragEventSource::kTouch) {
source_window->GetHost()->ConvertDIPToPixels(&touch_screen_point);
@@ -97,7 +100,6 @@
SourceWindowObserver source_window_observer(source_window);
base::WeakPtr<DesktopDragDropClientWin> alive(weak_factory_.GetWeakPtr());
- drag_drop_in_progress_ = true;
drag_source_ = ui::DragSourceWin::Create();
Microsoft::WRL::ComPtr<ui::DragSourceWin> drag_source_copy = drag_source_;
drag_source_copy->set_data(data.get());
@@ -105,6 +107,7 @@
true);
DWORD effect;
+ base::AutoReset<bool> drag_scoper(&g_is_dragging, true);
// Never consider the current scope as hung. The hang watching deadline (if
// any) is not valid since the user can take unbounded time to complete the
@@ -128,10 +131,6 @@
}
drag_source_copy->set_data(nullptr);
- if (alive) {
- drag_drop_in_progress_ = false;
- }
-
if (result != DRAGDROP_S_DROP) {
effect = DROPEFFECT_NONE;
}
@@ -141,11 +140,13 @@
}
void DesktopDragDropClientWin::DragCancel() {
- drag_source_->CancelDrag();
+ if (drag_source_) {
+ drag_source_->CancelDrag();
+ }
}
bool DesktopDragDropClientWin::IsDragDropInProgress() {
- return drag_drop_in_progress_;
+ return g_is_dragging;
}
void DesktopDragDropClientWin::AddObserver(
diff --git a/ui/views/widget/desktop_aura/desktop_drag_drop_client_win.h b/ui/views/widget/desktop_aura/desktop_drag_drop_client_win.h
index 69bf4e0..374f7ba 100644
--- a/ui/views/widget/desktop_aura/desktop_drag_drop_client_win.h
+++ b/ui/views/widget/desktop_aura/desktop_drag_drop_client_win.h
@@ -61,8 +61,6 @@
}
private:
- bool drag_drop_in_progress_;
-
Microsoft::WRL::ComPtr<ui::DragSourceWin> drag_source_;
scoped_refptr<DesktopDropTargetWin> drop_target_;
Original Bug Report
Potential Drag-and-Drop Reentrancy Protection Bypass via Multiple Windows
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 without the Chrome Security team. Please see go/chrome-ai-generated-security-bugs-faq for more information.
Overview: The reentrancy protection in DesktopDragDropClientWin is incomplete because it relies on a per-instance flag, allowing multiple windows to bypass the check. An attacker can use a second window to trigger a nested drag operation, manipulate the OS cursor, and perform unauthorized drops. This allows a compromised renderer to drop arbitrary payloads at sensitive locations, potentially leading to UXSS or RCE.
Affected files:
ui/views/widget/desktop_aura/desktop_drag_drop_client_win.ccui/views/widget/desktop_aura/desktop_drag_drop_client_win.hui/views/widget/desktop_aura/desktop_window_tree_host_win.cccontent/browser/web_contents/web_contents_view_aura.cc
Estimated timestamp from git blame: 2026-04-03
Vulnerability Description
A potential vulnerability exists in the Windows drag-and-drop implementation where the protection against reentrancy can be bypassed using multiple browser windows.
While DesktopDragDropClientWin::StartDragAndDrop attempts to prevent nested drag loops using CHECK(!drag_drop_in_progress_), drag_drop_in_progress_ is an instance variable. Because DesktopWindowTreeHostWin creates a unique DesktopDragDropClientWin instance for each window, the check fails to prevent a second window on the same UI thread from initiating a drag operation while the first is still active.
Furthermore, WebContentsViewAura::StartDragging instantiates a ScopedAllowApplicationTasksInNativeNestedLoop. This allows Mojo IPCs (such as new StartDragging requests) to be handled while the UI thread is blocked inside the system-modal ::DoDragDrop loop.
Potential Exploit Scenario
While we do not have a working Proof of Concept, the following sequence demonstrates how a compromised renderer could exploit this:
- Setup: A compromised renderer opens two windows: a small foreground popup (W1) and a large background window (W2) whose bounds encompass an attacker-chosen sensitive drop target (
P_victim). - Initial Drag: The user is tricked into initiating a touch-based drag on W1.
aura::Env::GetInstance()->SetTouchDown(true)is called. - W1 Loop: W1’s renderer sends a
StartDraggingIPC. The browser callsDesktopDragDropClientWin::StartDragAndDrop. W1 passes theCHECK(!drag_drop_in_progress_), sets its flag, and enters the blocking::DoDragDroploop. - Cursor Shift: While the browser UI thread is blocked in
DoDragDrop, W1’s renderer uses JavaScript (window.moveTo()) to move W1 off-screen. The physical touch point (and OS cursor) is now hovering over W2. - Malicious Nested Drag: W2’s renderer sends a second
StartDraggingIPC, specifyingkTouchas the source andP_victimas the location. - Reentrancy: Because of the
ScopedAllowApplicationTasksInNativeNestedLoopapplied during W1’s drag, the UI thread pulls W2’s IPC and executes it. - Bypass: W2 calls
DesktopDragDropClientWin::StartDragAndDrop. Because W2 is a different instance than W1, itsdrag_drop_in_progress_flag is false, bypassing theCHECK. - Cursor Hijacking: W2 verifies the touch state (which is still true from step 2) and that the cursor is over itself (true from step 4). It then calls
StartTouchDrag(P_victim). This method invokesui::SendMouseEvent, which uses the Windows::SendInputAPI to forcibly teleport the OS cursor toP_victimand simulate aLEFTDOWNevent. - Artificial Release: W2 sets its flag and calls
::DoDragDrop. Windows OLE rejects the nested loop immediately. W2 proceeds to cleanup, callingFinishTouchDrag, which uses::SendInputto simulate aLEFTUPevent atP_victim. - Malicious Drop: W2’s execution finishes. The UI thread unwinds back to W1’s active
::DoDragDroploop. W1 processes the syntheticLEFTUPevent injected by W2 and drops W1’s malicious payload exactly atP_victim.
Impact
This sequence allows an attacker to achieve the same impact as previously identified reentrancy vulnerabilities: UI spoofing, saving malicious bookmarklets (UXSS), or dropping arbitrary files onto overlapping native applications (RCE).
Suggested Fix
- Global Flag: The
drag_drop_in_progress_flag inDesktopDragDropClientWinshould be made a thread-local or static variable to ensure that no two instances can initiate a drag operation simultaneously on the same thread. - Remove IPC Processing: Investigate if the
ScopedAllowApplicationTasksInNativeNestedLoopinWebContentsViewAura::StartDraggingcan be removed. Preventing IPC processing during the drag-and-drop loop eliminates the root cause of this reentrancy class.
Evaluated with Chrome root at commit: c0eb5541aebfa4ea08806eaf6e94bcc69f87ab2f
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.