Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInappropriate implementation Views
DescriptionInappropriate implementation Views
ComponentChromium
Bug ClassLogic Error
Tracker518045638
Fix commitd26312c59262 (chromium/src) +44/-0
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-11

Changed Functions

FunctionChangeNotes
if
ui/views/widget/desktop_aura/desktop_drag_drop_client_win.cc
modified
TEST_F
ui/views/widget/desktop_aura/desktop_window_tree_host_win_unittest.cc
modified
if
ui/views/win/user_resize_move_detector.cc
modified

Files Changed

  • ui/views/widget/desktop_aura/desktop_drag_drop_client_win.cc
  • ui/views/widget/desktop_aura/desktop_window_tree_host_win.cc
  • ui/views/widget/desktop_aura/desktop_window_tree_host_win.h
  • ui/views/widget/desktop_aura/desktop_window_tree_host_win_unittest.cc
  • ui/views/win/hwnd_message_handler.cc
  • ui/views/win/hwnd_message_handler.h
  • ui/views/win/user_resize_move_detector.cc
  • ui/views/win/user_resize_move_detector.h
From d26312c5926277b2086bd2db5ef5a7dcc1818b51 Mon Sep 17 00:00:00 2001
From: David Bienvenu <davidbienvenu@chromium.org>
Date: Thu, 04 Jun 2026 13:59:45 -0700
Subject: [PATCH] win: Add detection for native move loops

Prevent drag drop while Chrome windows are in a move loop.

Bug: 518045638
Change-Id: Iccac41409b278a6f7b34c77a2c3ab7cd8045bda7
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7890725
Reviewed-by: Keren Zhu <kerenzhu@chromium.org>
Commit-Queue: David Bienvenu <davidbienvenu@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1641925}
---

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 19d8cb90..c0855f9 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
@@ -75,6 +75,10 @@
     int allowed_operations,
     ui::mojom::DragEventSource source) {
   CHECK(!g_is_dragging);
+  if (desktop_host_->IsInNativeMoveResizeLoop()) {
+    return ui::PreferredDragOperation(
+        ui::DragDropTypes::DropEffectToDragOperation(DROPEFFECT_NONE));
+  }
   gfx::Point touch_screen_point;
   if (source == ui::mojom::DragEventSource::kTouch) {
     display::Screen* screen = display::Screen::Get();
diff --git a/ui/views/widget/desktop_aura/desktop_window_tree_host_win.cc b/ui/views/widget/desktop_aura/desktop_window_tree_host_win.cc
index 0547a2fc..5b1aacd9 100644
--- a/ui/views/widget/desktop_aura/desktop_window_tree_host_win.cc
+++ b/ui/views/widget/desktop_aura/desktop_window_tree_host_win.cc
@@ -176,6 +176,10 @@
   }
 }
 
+bool DesktopWindowTreeHostWin::IsInNativeMoveResizeLoop() const {
+  return message_handler_ && message_handler_->IsInNativeMoveResizeLoop();
+}
+
 // DesktopWindowTreeHostWin, DesktopWindowTreeHost implementation:
 
 void DesktopWindowTreeHostWin::Init(const Widget::InitParams& params) {
diff --git a/ui/views/widget/desktop_aura/desktop_window_tree_host_win.h b/ui/views/widget/desktop_aura/desktop_window_tree_host_win.h
index 29a4f29..829f6c9 100644
--- a/ui/views/widget/desktop_aura/desktop_window_tree_host_win.h
+++ b/ui/views/widget/desktop_aura/desktop_window_tree_host_win.h
@@ -93,6 +93,9 @@
   // false.
   void FinishTouchDrag(gfx::Point screen_point);
 
+  // Returns true if any window is in a move/resize loop.
+  bool IsInNativeMoveResizeLoop() const;
+
  protected:
   // Overridden from DesktopWindowTreeHost:
   void Init(const Widget::InitParams& params) override;
diff --git a/ui/views/widget/desktop_aura/desktop_window_tree_host_win_unittest.cc b/ui/views/widget/desktop_aura/desktop_window_tree_host_win_unittest.cc
index da9da0b..1da8815 100644
--- a/ui/views/widget/desktop_aura/desktop_window_tree_host_win_unittest.cc
+++ b/ui/views/widget/desktop_aura/desktop_window_tree_host_win_unittest.cc
@@ -267,5 +267,17 @@
   EXPECT_EQ(test_node_->ref_count_for_testing(), 1u);
 }
 
+TEST_F(DesktopWindowTreeHostWinTest, IsInNativeMoveResizeLoop) {
+  Widget widget;
+  Widget::InitParams params = CreateParams(
+      Widget::InitParams::CLIENT_OWNS_WIDGET, Widget::InitParams::TYPE_WINDOW);
+  widget.Init(std::move(params));
+  widget.Show();
+
+  DesktopWindowTreeHostWin* host = static_cast<DesktopWindowTreeHostWin*>(
+      widget.GetNativeWindow()->GetHost());
+  EXPECT_FALSE(host->IsInNativeMoveResizeLoop());
+}
+
 }  // namespace test
 }  // namespace views
diff --git a/ui/views/win/hwnd_message_handler.cc b/ui/views/win/hwnd_message_handler.cc
index 16751cc7..24901df 100644
--- a/ui/views/win/hwnd_message_handler.cc
+++ b/ui/views/win/hwnd_message_handler.cc
@@ -899,6 +899,11 @@
   ::SendMessage(hwnd(), WM_CANCELMODE, 0, 0);
 }
 
+// static
+bool HWNDMessageHandler::IsInNativeMoveResizeLoop() {
+  return UserResizeMoveDetector::InMoveResizeLoop();
+}
+
 void HWNDMessageHandler::SendFrameChanged() {
   ::SetWindowPos(hwnd(), nullptr, 0, 0, 0, 0,
                  SWP_FRAMECHANGED | SWP_NOACTIVATE | SWP_NOCOPYBITS |
diff --git a/ui/views/win/hwnd_message_handler.h b/ui/views/win/hwnd_message_handler.h
index 6a683fedc..4f3095c5 100644
--- a/ui/views/win/hwnd_message_handler.h
+++ b/ui/views/win/hwnd_message_handler.h
@@ -167,6 +167,9 @@
                            bool hide_on_escape);
   virtual void EndMoveLoop();
 
+  // Returns true if any HWndMessageHandler is in a native move/resize loop.
+  static bool IsInNativeMoveResizeLoop();
+
   // Tells the HWND its client area has changed.
   virtual void SendFrameChanged();
 
diff --git a/ui/views/win/user_resize_move_detector.cc b/ui/views/win/user_resize_move_detector.cc
index 936cc49..7f76fe48 100644
--- a/ui/views/win/user_resize_move_detector.cc
+++ b/ui/views/win/user_resize_move_detector.cc
@@ -10,6 +10,8 @@
 
 namespace views {
 
+static bool g_in_move_resize_loop = false;
+
 UserResizeMoveDetector::UserResizeMoveDetector(
     HWNDMessageHandlerDelegate* hwnd_delegate)
     : hwnd_delegate_(hwnd_delegate) {}
@@ -22,6 +24,7 @@
 
 void UserResizeMoveDetector::OnSizing() {
   if (state_ == State::kInSizeMove) {
+    g_in_move_resize_loop = true;
     state_ = State::kInSizing;
     hwnd_delegate_->HandleBeginUserResize();
   }
@@ -29,6 +32,7 @@
 
 void UserResizeMoveDetector::OnMoving() {
   if (state_ == State::kInSizeMove) {
+    g_in_move_resize_loop = true;
     state_ = State::kInMoving;
     hwnd_delegate_->HandleBeginUserDrag();
   }
@@ -40,7 +44,13 @@
   } else if (state_ == State::kInMoving) {
     hwnd_delegate_->HandleEndUserDrag();
   }
+  g_in_move_resize_loop = false;
   state_ = State::kNotResizing;
 }
 
+// static
+bool UserResizeMoveDetector::InMoveResizeLoop() {
+  return g_in_move_resize_loop;
+}
+
 }  // namespace views
diff --git a/ui/views/win/user_resize_move_detector.h b/ui/views/win/user_resize_move_detector.h
index 049e5470..450a0b3d 100644
--- a/ui/views/win/user_resize_move_detector.h
+++ b/ui/views/win/user_resize_move_detector.h
@@ -29,6 +29,9 @@
   // Called on WM_EXITSIZEMOVE.
   void OnExitSizeMove();
 
+  // Returns true if any window is being resized or moved..
+  static bool InMoveResizeLoop();
+
  private:
   enum class State {
     // Start with not resizing.
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/ui/views/widget/desktop_aura/desktop_window_tree_host_win_unittest.cc b/ui/views/widget/desktop_aura/desktop_window_tree_host_win_unittest.cc
index da9da0b..1da8815 100644
--- a/ui/views/widget/desktop_aura/desktop_window_tree_host_win_unittest.cc
+++ b/ui/views/widget/desktop_aura/desktop_window_tree_host_win_unittest.cc
@@ -267,5 +267,17 @@
   EXPECT_EQ(test_node_->ref_count_for_testing(), 1u);
 }
 
+TEST_F(DesktopWindowTreeHostWinTest, IsInNativeMoveResizeLoop) {
+  Widget widget;
+  Widget::InitParams params = CreateParams(
+      Widget::InitParams::CLIENT_OWNS_WIDGET, Widget::InitParams::TYPE_WINDOW);
+  widget.Init(std::move(params));
+  widget.Show();
+
+  DesktopWindowTreeHostWin* host = static_cast<DesktopWindowTreeHostWin*>(
+      widget.GetNativeWindow()->GetHost());
+  EXPECT_FALSE(host->IsInNativeMoveResizeLoop());
+}
+
 }  // namespace test
 }  // namespace views
Loading diff…

Original Bug Report

reported by vm...@google.com

Potential Mojo task pumping during SC_MOVE/SC_SIZE loop allows drag-and-drop hijack on Windows

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. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.

Overview: Entering a native window move/resize modal loop on Windows allows nested Mojo task execution via ScopedAllowApplicationTasksInNativeNestedLoop without setting the global drag-and-drop reentrancy guard. A compromised renderer can potentially exploit this window to programmatically initiate a drag-and-drop operation, hijacking the user’s ongoing physical click gesture. This can allow an attacker to bypass standard drag-and-drop restrictions and drop payloads onto privileged UI surfaces.

Affected files:

  • ui/views/win/hwnd_message_handler.cc
  • ui/views/widget/desktop_aura/desktop_drag_drop_client_win.cc
  • content/browser/web_contents/web_contents_view_aura.cc
  • ui/base/dragdrop/drag_source_win.cc

Estimated timestamp from git blame: 2024-05-03

Potential Root Cause

On Windows, the process-global drag-reentrancy guard g_is_dragging is utilized inside DesktopDragDropClientWin::StartDragAndDrop (ui/views/widget/desktop_aura/desktop_drag_drop_client_win.cc) to prevent unexpected or re-entrant drag-and-drop sessions.

However, during ordinary title bar drags (SC_MOVE) or window resizing (SC_SIZE), Windows enters a native nested modal loop via HWNDMessageHandler::OnSysCommand (ui/views/win/hwnd_message_handler.cc). This loop explicitly enables UI-thread application tasks (such as processing incoming Mojo IPCs) via base::CurrentThread::ScopedAllowApplicationTasksInNativeNestedLoop without setting the g_is_dragging guard:

// ui/views/win/hwnd_message_handler.cc
base::WeakPtr<HWNDMessageHandler> ref(msg_handler_weak_factory_.GetWeakPtr());
base::CurrentThread::ScopedAllowApplicationTasksInNativeNestedLoop allow;
::DefWindowProc(hwnd(), WM_SYSCOMMAND, notification_code,
                MAKELPARAM(point.x(), point.y()));

Because g_is_dragging remains false while the modal loop is running and processing incoming tasks, a compromised renderer can programmatically dispatch a LocalFrameHost::StartDragging Mojo IPC. This IPC is successfully dispatched on the UI thread inside the nested DefWindowProc loop.

Potential Trigger Path

Note: Our analysis is static-based, as our tooling currently does not have the ability to run code or dynamically verify the exploit chain.

  1. User Gesture: The user clicks and holds the title bar (HTCAPTION) or a resize border of a browser window to move or resize it. Windows sends a synchronous WM_SYSCOMMAND message.
  2. Nested Loop Entry: HWNDMessageHandler::OnSysCommand receives this, instantiates ScopedAllowApplicationTasksInNativeNestedLoop, and forwards it to ::DefWindowProc(hwnd(), WM_SYSCOMMAND, SC_MOVE/SC_SIZE). This enters the OS-level move/size loop with the mouse button held and g_is_dragging set to false.
  3. Mojo IPC Injection: A compromised renderer sends a LocalFrameHost::StartDragging Mojo IPC. Since the nested loop allows processing application tasks, the IPC is processed on the UI thread under the active DefWindowProc stack.
  4. WebContents Verification Bypass: Inside WebContentsViewAura::StartDragging (content/browser/web_contents/web_contents_view_aura.cc), the per-WebContents drag_security_info_.did_initiate() flag is false (passing the reentrancy check). The trusted location check only requires the renderer-chosen screen coordinates to fall inside the content view’s bounds and for the content view to be visible—both of which are satisfied.
  5. OLE Hijack: The drag client calls DesktopDragDropClientWin::StartDragAndDrop. The global g_is_dragging is false, so the CHECK(!g_is_dragging) safety check passes. ::DoDragDrop is then called with the attacker-controlled data object.
  6. Capture & Drop: Because the user is holding down the left mouse button, Windows COM sees MK_LBUTTON as active inside DragSourceWin::QueryContinueDrag, allowing the drag loop to run successfully and steal cursor capture. When the user releases the mouse button, the payload (which can contain text, HTML, or javascript: URLs) is dropped on whatever target is under the cursor (such as the bookmarks bar, other tabs, or external OS applications).

Impact

This is a potential browser-process logic flaw allowing a compromised renderer to hijack an ordinary, benign user gesture (window moving/resizing) and transform it into an arbitrary drag-and-drop operation. This can allow cross-origin data injection, bookmarklet execution via javascript: URLs, and file/download injection without triggering standard user-gesture defenses.

Suggested Fix

To remediate this issue, DesktopDragDropClientWin::StartDragAndDrop should verify whether the host window is currently in a native modal move/resize loop and reject programmatic drag initiation under those conditions. Alternatively, HWNDMessageHandler could expose a state indicating a native move/resize loop is active, which the drag client can query to reject the operation.

Evaluated with Chrome root at commit: fb72408a8493c46bc75fae1c70d03daec96b3040


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