CVE-2026-12031
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifui/views/widget/desktop_aura/desktop_drag_drop_client_win.cc |
modified | |
TEST_Fui/views/widget/desktop_aura/desktop_window_tree_host_win_unittest.cc |
modified | |
ifui/views/win/user_resize_move_detector.cc |
modified |
Files Changed
ui/views/widget/desktop_aura/desktop_drag_drop_client_win.ccui/views/widget/desktop_aura/desktop_window_tree_host_win.ccui/views/widget/desktop_aura/desktop_window_tree_host_win.hui/views/widget/desktop_aura/desktop_window_tree_host_win_unittest.ccui/views/win/hwnd_message_handler.ccui/views/win/hwnd_message_handler.hui/views/win/user_resize_move_detector.ccui/views/win/user_resize_move_detector.h
Patch
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.
Regression Test / PoC
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
Original Bug Report
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.ccui/views/widget/desktop_aura/desktop_drag_drop_client_win.cccontent/browser/web_contents/web_contents_view_aura.ccui/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.
- 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 synchronousWM_SYSCOMMANDmessage. - Nested Loop Entry:
HWNDMessageHandler::OnSysCommandreceives this, instantiatesScopedAllowApplicationTasksInNativeNestedLoop, 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 andg_is_draggingset tofalse. - Mojo IPC Injection: A compromised renderer sends a
LocalFrameHost::StartDraggingMojo IPC. Since the nested loop allows processing application tasks, the IPC is processed on the UI thread under the activeDefWindowProcstack. - WebContents Verification Bypass: Inside
WebContentsViewAura::StartDragging(content/browser/web_contents/web_contents_view_aura.cc), the per-WebContentsdrag_security_info_.did_initiate()flag isfalse(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. - OLE Hijack: The drag client calls
DesktopDragDropClientWin::StartDragAndDrop. The globalg_is_draggingisfalse, so theCHECK(!g_is_dragging)safety check passes.::DoDragDropis then called with the attacker-controlled data object. - Capture & Drop: Because the user is holding down the left mouse button, Windows COM sees
MK_LBUTTONas active insideDragSourceWin::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, orjavascript: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.