Critical watch CVSS 4.2 chrome Logic Error 📄 Reporter bug report 🔧 Commit mapped

Overview

Critical
Severity
4.2
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInappropriate implementation in UI
DescriptionInappropriate implementation in UI
ComponentUI
Sub-componentWindows
Bug ClassLogic Error
Action tierwatch — logic/policy bug — security-relevant, no memory-unsafety
Computed by PatchGap's deterministic exploitability ruler from the fixing commit. patch-now = RCE-class & attacker-reachable in a default config, or exploited in the wild; watch = real security signal below that bar; noise = no security relevance.
Tracker503551154
Fix commit16dcbc3d1476 (chromium/src) +11/-12
CWECWE-451
CVSS vectorCVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:L/I:N/A:L
CISA KEVNot listed
CreditedGoogle
Reported2026-04-20 (per vendor advisory)
Fix landed2026-05-13 (public git)
Disclosed2026-05-19
Patch gap6 days — fix public in source before the advisory

Background

`DesktopDragDropClientWin`
the Windows Aura drag-and-drop client; one instance exists per desktop window tree host, and StartDragAndDrop runs 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 any DesktopDragDropClientWin is 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_dragging around 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_.

Key insight
The single mistake was scoping the “drag in progress” guard per-instance, so a second window could start a drag while another was already active; the fix makes the guard a process-wide static so at most one drag runs at a time.

Attack Path

  1. Start a first drag Initiate a drag-and-drop from one browser/desktop window, entering DesktopDragDropClientWin::StartDragAndDrop and its nested OLE drag loop.
  2. 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 passes CHECK(!drag_drop_in_progress_), beginning a concurrent drag.
  3. 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).
  4. Fix enforcement With g_is_dragging global and set via AutoReset, the second window’s CHECK(!g_is_dragging) now fails fast and IsDragDropInProgress() reports the true process-wide state.

Impact Assessment

The flaw is an inappropriate-implementation logic error on Windows allowing drag-and-drop operations from multiple windows to overlap, which could be abused to confuse the drag/drop UI (e.g., spoofing or mis-targeting a drop); the advisory classifies it as critical inappropriate implementation in UI. Reaching it requires user-driven drag gestures across windows in a stock Windows build; the diff shows only the concurrency/guard change and the 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

FunctionChangeNotes
if
ui/views/widget/desktop_aura/desktop_drag_drop_client_win.cc
modified

Files Changed

  • ui/views/widget/desktop_aura/desktop_drag_drop_client_win.cc
  • ui/views/widget/desktop_aura/desktop_drag_drop_client_win.h

Audit Directions

  • Per-instance flags that should be global
    Audit 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 cleanup
    Review flags cleared by hand-written conditional code (if (alive) flag = false;) on functions with multiple exit paths; prefer base::AutoReset/scope guards that cannot be skipped.
  • Unchecked COM/handle dereference on teardown
    Flag cancel/cleanup methods (like DragCancel) that dereference members which may be unset when invoked from destructors or early-exit paths.
From 16dcbc3d1476a35a652d71c034c851fa6da109d0 Mon Sep 17 00:00:00 2001
From: David Bienvenu <[email protected]>
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 <[email protected]>
Commit-Queue: David Bienvenu <[email protected]>
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_;
Loading diff…

Original Bug Report

reported by [email protected]

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.cc
  • ui/views/widget/desktop_aura/desktop_drag_drop_client_win.h
  • ui/views/widget/desktop_aura/desktop_window_tree_host_win.cc
  • content/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:

  1. 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).
  2. Initial Drag: The user is tricked into initiating a touch-based drag on W1. aura::Env::GetInstance()->SetTouchDown(true) is called.
  3. W1 Loop: W1’s renderer sends a StartDragging IPC. The browser calls DesktopDragDropClientWin::StartDragAndDrop. W1 passes the CHECK(!drag_drop_in_progress_), sets its flag, and enters the blocking ::DoDragDrop loop.
  4. 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.
  5. Malicious Nested Drag: W2’s renderer sends a second StartDragging IPC, specifying kTouch as the source and P_victim as the location.
  6. Reentrancy: Because of the ScopedAllowApplicationTasksInNativeNestedLoop applied during W1’s drag, the UI thread pulls W2’s IPC and executes it.
  7. Bypass: W2 calls DesktopDragDropClientWin::StartDragAndDrop. Because W2 is a different instance than W1, its drag_drop_in_progress_ flag is false, bypassing the CHECK.
  8. 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 invokes ui::SendMouseEvent, which uses the Windows ::SendInput API to forcibly teleport the OS cursor to P_victim and simulate a LEFTDOWN event.
  9. Artificial Release: W2 sets its flag and calls ::DoDragDrop. Windows OLE rejects the nested loop immediately. W2 proceeds to cleanup, calling FinishTouchDrag, which uses ::SendInput to simulate a LEFTUP event at P_victim.
  10. Malicious Drop: W2’s execution finishes. The UI thread unwinds back to W1’s active ::DoDragDrop loop. W1 processes the synthetic LEFTUP event injected by W2 and drops W1’s malicious payload exactly at P_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

  1. Global Flag: The drag_drop_in_progress_ flag in DesktopDragDropClientWin should be made a thread-local or static variable to ensure that no two instances can initiate a drag operation simultaneously on the same thread.
  2. Remove IPC Processing: Investigate if the ScopedAllowApplicationTasksInNativeNestedLoop in WebContentsViewAura::StartDragging can 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.

View on issue tracker