Medium chrome Logic Error 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactImproper initialization in Views
DescriptionImproper initialization in Views
ComponentViews
Bug ClassLogic Error
Tracker520469117
Fix commit47ffe7177174 (chromium/src) +36/-2
CISA KEVNot listed
CreditedGoogle
Disclosed2026-09-08

Changed Functions

FunctionChangeNotes
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_window_tree_host_win_unittest.cc
  • ui/views/win/user_resize_move_detector.cc
From 47ffe717717435d672257187a00922b391d0a27e Mon Sep 17 00:00:00 2001
From: Daniel Clark <daniec@microsoft.com>
Date: Mon, 03 Aug 2026 09:38:04 -0700
Subject: [PATCH] Set g_in_move_resize_loop as soon as we get WM_ENTERSIZEMOVE

When the user clicks on a window border to start window resizing,
Windows' native modal loop sends WM_ENTERSIZEMOVE, and
`ScopedAllowApplicationTasksInNativeNestedLoop` is set so that IPC
from renderer processes can continue to be handled.

If the user then drags the window border, WM_SIZING or WM_MOVING will
be received, at which point `UserResizeMoveDetector` will set
`g_in_move_resize_loop`. This causes
`DesktopDragDropClientWin::StartDragAndDrop` to correctly guard
against mouse actions during the window resize being treated
as a drag/drop by checking `IsInNativeMoveResizeLoop()`.

But if a compromised renderer spams `LocalFrameHost::StartDragging`
IPC messages in the gap between `WM_ENTERSIZEMOVE` and
`WM_SIZING`/`WM_MOVING`, the `g_in_move_resize_loop` guard isn't
yet set, meaning the `IsInNativeMoveResizeLoop()` check in
`DesktopDragDropClientWin::StartDragAndDrop` will return false
and a payload from the renderer could be completed as a drop despite
the user only having clicked the window border.

Fix it by setting `g_in_move_resize_loop` as soon as WM_ENTERSIZEMOVE
is received. This is consistent with the timing with which we set
`g_is_in_native_menu_loop` in `HWNDMessageHandler::OnEnterMenuLoop`.

Fixed: 520469117
Change-Id: I65722bfd78e418f9e43c2ee0eafe85382d7dabd4
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8175067
Reviewed-by: David Bienvenu <davidbienvenu@chromium.org>
Commit-Queue: Dan Clark <daniec@microsoft.com>
Reviewed-by: Allen Bauer <kylixrd@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1672718}
---

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 a4d0dd0..dccab27 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
@@ -397,5 +397,38 @@
   EXPECT_FALSE(host_b->IsInNativeMoveResizeLoop());
 }
 
+// The native size/move modal loop is already on the stack once WM_ENTERSIZEMOVE
+// has been delivered, but no WM_SIZING/WM_MOVING arrives until the user moves
+// the mouse. That loop pumps application tasks (including Mojo IPCs from
+// renderers), so IsInNativeMoveResizeLoop() must already report true at
+// WM_ENTERSIZEMOVE. Otherwise a task running in the gap sees false and can
+// start an OS-level drag while the user is holding a resize border.
+TEST_F(DesktopWindowTreeHostWinTest, IsInNativeMoveResizeLoopOnEnterSizeMove) {
+  Widget widget;
+  widget.Init(CreateParams(Widget::InitParams::CLIENT_OWNS_WIDGET,
+                           Widget::InitParams::TYPE_WINDOW));
+  widget.Show();
+
+  DesktopWindowTreeHostWin* host = static_cast<DesktopWindowTreeHostWin*>(
+      widget.GetNativeWindow()->GetHost());
+  EXPECT_FALSE(host->IsInNativeMoveResizeLoop());
+
+  HWND hwnd = widget.GetNativeWindow()->GetHost()->GetAcceleratedWidget();
+
+  // The user has pressed the mouse on a resize border. Windows has entered the
+  // modal loop, but the mouse has not moved yet.
+  ::SendMessage(hwnd, WM_ENTERSIZEMOVE, 0, 0);
+  EXPECT_TRUE(host->IsInNativeMoveResizeLoop());
+
+  // The user now drags, producing the first WM_SIZING.
+  RECT rect = {};
+  ::GetWindowRect(hwnd, &rect);
+  ::SendMessage(hwnd, WM_SIZING, WMSZ_RIGHT, reinterpret_cast<LPARAM>(&rect));
+  EXPECT_TRUE(host->IsInNativeMoveResizeLoop());
+
+  ::SendMessage(hwnd, WM_EXITSIZEMOVE, 0, 0);
+  EXPECT_FALSE(host->IsInNativeMoveResizeLoop());
+}
+
 }  // namespace test
 }  // namespace views
diff --git a/ui/views/win/user_resize_move_detector.cc b/ui/views/win/user_resize_move_detector.cc
index 7f76fe48..af3580d 100644
--- a/ui/views/win/user_resize_move_detector.cc
+++ b/ui/views/win/user_resize_move_detector.cc
@@ -19,12 +19,14 @@
 void UserResizeMoveDetector::OnEnterSizeMove() {
   if (state_ == State::kNotResizing) {
     state_ = State::kInSizeMove;
+    // The native modal size/move loop is on the stack from here until
+    // WM_EXITSIZEMOVE.
+    g_in_move_resize_loop = true;
   }
 }
 
 void UserResizeMoveDetector::OnSizing() {
   if (state_ == State::kInSizeMove) {
-    g_in_move_resize_loop = true;
     state_ = State::kInSizing;
     hwnd_delegate_->HandleBeginUserResize();
   }
@@ -32,7 +34,6 @@
 
 void UserResizeMoveDetector::OnMoving() {
   if (state_ == State::kInSizeMove) {
-    g_in_move_resize_loop = true;
     state_ = State::kInMoving;
     hwnd_delegate_->HandleBeginUserDrag();
   }
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 a4d0dd0..dccab27 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
@@ -397,5 +397,38 @@
   EXPECT_FALSE(host_b->IsInNativeMoveResizeLoop());
 }
 
+// The native size/move modal loop is already on the stack once WM_ENTERSIZEMOVE
+// has been delivered, but no WM_SIZING/WM_MOVING arrives until the user moves
+// the mouse. That loop pumps application tasks (including Mojo IPCs from
+// renderers), so IsInNativeMoveResizeLoop() must already report true at
+// WM_ENTERSIZEMOVE. Otherwise a task running in the gap sees false and can
+// start an OS-level drag while the user is holding a resize border.
+TEST_F(DesktopWindowTreeHostWinTest, IsInNativeMoveResizeLoopOnEnterSizeMove) {
+  Widget widget;
+  widget.Init(CreateParams(Widget::InitParams::CLIENT_OWNS_WIDGET,
+                           Widget::InitParams::TYPE_WINDOW));
+  widget.Show();
+
+  DesktopWindowTreeHostWin* host = static_cast<DesktopWindowTreeHostWin*>(
+      widget.GetNativeWindow()->GetHost());
+  EXPECT_FALSE(host->IsInNativeMoveResizeLoop());
+
+  HWND hwnd = widget.GetNativeWindow()->GetHost()->GetAcceleratedWidget();
+
+  // The user has pressed the mouse on a resize border. Windows has entered the
+  // modal loop, but the mouse has not moved yet.
+  ::SendMessage(hwnd, WM_ENTERSIZEMOVE, 0, 0);
+  EXPECT_TRUE(host->IsInNativeMoveResizeLoop());
+
+  // The user now drags, producing the first WM_SIZING.
+  RECT rect = {};
+  ::GetWindowRect(hwnd, &rect);
+  ::SendMessage(hwnd, WM_SIZING, WMSZ_RIGHT, reinterpret_cast<LPARAM>(&rect));
+  EXPECT_TRUE(host->IsInNativeMoveResizeLoop());
+
+  ::SendMessage(hwnd, WM_EXITSIZEMOVE, 0, 0);
+  EXPECT_FALSE(host->IsInNativeMoveResizeLoop());
+}
+
 }  // namespace test
 }  // namespace views
Loading diff…

Original Bug Report

reported by vm...@google.com

Potential bypass of drag-drop block during window resize initialization 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: A potential logic flaw exists in the Windows-specific window resize/move tracking where the global state tracking flag g_in_move_resize_loop is set on the first WM_SIZING/WM_MOVING message rather than on WM_ENTERSIZEMOVE. A compromised renderer can potentially exploit this timing window to bypass the drag-and-drop prevention check (IsInNativeMoveResizeLoop()) when a user clicks a resize border but has not yet moved the mouse. This could allow the renderer to initiate an unauthorized OS-level drag-and-drop operation with attacker-controlled payloads.

Affected files:

  • ui/views/win/user_resize_move_detector.cc
  • ui/views/widget/desktop_aura/desktop_drag_drop_client_win.cc
  • ui/views/win/hwnd_message_handler.cc

Estimated timestamp from git blame: 2026-06-04

Description

In Chromium on Windows, a protection is implemented in DesktopDragDropClientWin::StartDragAndDrop to block renderer-initiated drag-and-drop operations while the desktop window is in a native move/resize modal loop:

// ui/views/widget/desktop_aura/desktop_drag_drop_client_win.cc
if (desktop_host_->IsInNativeMoveResizeLoop()) {
  return ui::PreferredDragOperation(
      ui::DragDropTypes::DropEffectToDragOperation(DROPEFFECT_NONE));
}

This check relies on UserResizeMoveDetector::InMoveResizeLoop(), which returns a file-static boolean g_in_move_resize_loop defined in ui/views/win/user_resize_move_detector.cc. However, review of the codebase indicates a logic gap in how this state is initialized and updated:

// ui/views/win/user_resize_move_detector.cc
static bool g_in_move_resize_loop = false;

void UserResizeMoveDetector::OnEnterSizeMove() {
  if (state_ == State::kNotResizing) {
    state_ = State::kInSizeMove;          // ← Does not set g_in_move_resize_loop
  }
}

void UserResizeMoveDetector::OnSizing() {
  if (state_ == State::kInSizeMove) {
    g_in_move_resize_loop = true;         // ← Set here on first resize/move message
    state_ = State::kInSizing;
    hwnd_delegate_->HandleBeginUserResize();
  }
}

When a user clicks a window resize border, Windows enters the native modal loop, dispatching a WM_ENTERSIZEMOVE message which triggers OnEnterSizeMove(). However, the g_in_move_resize_loop flag remains false until the first WM_SIZING or WM_MOVING message is dispatched, which requires the user to actually move the mouse cursor after clicking. This creates a potential window of opportunity where the native move/resize loop is active, but the protection predicate is still false.

Potential Attack Scenario

Although our automated tooling has not yet executed a dynamic proof-of-concept, the static code path suggests the following potential exploit flow:

  1. Renderer Preparation: A compromised renderer continuously posts LocalFrameHost::StartDragging IPC messages with a malicious DragData payload and setting the source to kMouse.
  2. User Interaction: The user clicks and holds the left mouse button down on a window resize border (triggering hit-tests such as HTLEFT or HTRIGHT).
  3. Modal Loop Entry: The click is processed via DefWindowProc(WM_NCLBUTTONDOWN), synchronously entering the native modal loop. The loop dispatches WM_ENTERSIZEMOVE which transitions UserResizeMoveDetector to State::kInSizeMove, but g_in_move_resize_loop remains false.
  4. Task Dispatching: The native modal loop uses ScopedAllowApplicationTasksInNativeNestedLoop to allow application tasks to run. One of the queued StartDragging Mojo IPC tasks from the compromised renderer is processed on the UI thread.
  5. Check Bypass: DesktopDragDropClientWin::StartDragAndDrop is reached. The call to desktop_host_->IsInNativeMoveResizeLoop() evaluates g_in_move_resize_loop (which is still false), bypassing the guard.
  6. OS-Level Drag: The browser calls the blocking Win32 API ::DoDragDrop(). Since the user is holding down the left mouse button, the drag loop persists. When the user moves and releases the mouse button, the drop action is completed with the attacker’s payload.

Suggested Fix

To close this logic gap and prevent any potential bypass, g_in_move_resize_loop should be set to true immediately when the modal loop begins, within UserResizeMoveDetector::OnEnterSizeMove():

void UserResizeMoveDetector::OnEnterSizeMove() {
  if (state_ == State::kNotResizing) {
    state_ = State::kInSizeMove;
    g_in_move_resize_loop = true; // Set to true immediately upon entering size/move loop
  }
}

Evaluated with Chrome root at commit: e9507a33bb4148ee071aaaf8a7e9ad68770359bf


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