Overview

Medium
Severity
8.3
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactImproper initialization in Views
DescriptionImproper initialization in Views
ComponentViews
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.
Tracker520469117
Fix commit47ffe7177174 (chromium/src) +36/-2
CWECWE-665
CVSS vectorCVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:C/C:H/I:H/A:H
CISA KEVNot listed
CreditedGoogle
Reported2026-06-05 (per vendor advisory)
Fix landed2026-08-03 (public git)
Disclosed2026-09-08
Patch gap36 days — fix public in source before the advisory

Background

`WM_ENTERSIZEMOVE` / `WM_SIZING` / `WM_MOVING` / `WM_EXITSIZEMOVE`
Windows messages that bracket a native modal window move/resize loop: WM_ENTERSIZEMOVE fires when the user presses the mouse on a border, WM_SIZING/WM_MOVING fire only once the mouse actually moves, and WM_EXITSIZEMOVE ends the loop.
`g_in_move_resize_loop`
a process-global flag, read via IsInNativeMoveResizeLoop(), that records whether a native size/move modal loop is currently on the stack.
`ScopedAllowApplicationTasksInNativeNestedLoop`
a scope that lets application tasks (including Mojo IPCs from renderer processes) continue to run while the native modal move/resize loop is pumping messages.
`DesktopDragDropClientWin::StartDragAndDrop`
the entry point that turns a renderer’s drag request into an OS-level drag/drop; it consults IsInNativeMoveResizeLoop() to refuse starting a drag while the user is resizing/moving the window.

Root Cause Analysis

In UserResizeMoveDetector, the g_in_move_resize_loop flag was set only inside OnSizing() and OnMoving(), i.e. when the first WM_SIZING/WM_MOVING arrived, not when the modal loop actually began. Because Windows delivers WM_ENTERSIZEMOVE (and enters its message-pumping modal loop) as soon as the user presses the border, but does not deliver WM_SIZING/WM_MOVING until the mouse moves, there is a window in which the modal loop is live and pumping renderer IPC while g_in_move_resize_loop is still false. During that gap IsInNativeMoveResizeLoop() returns false, so the guard in DesktopDragDropClientWin::StartDragAndDrop is not triggered and a renderer-requested drag can be started even though the user is holding a resize border. The invariant violated is that IsInNativeMoveResizeLoop() must report true for the entire duration the native modal loop is on the stack.

The fix sets g_in_move_resize_loop = true in OnEnterSizeMove() (the kNotResizing → kInSizeMove transition) and removes the now-redundant assignments in OnSizing()/OnMoving(), closing the gap so the guard is armed the instant the loop starts.

Key insight
The single mistake is initializing the “in native move/resize loop” flag too late — at the first drag movement rather than at loop entry — leaving a message-pumping window unguarded; the fix hoists the flag set to WM_ENTERSIZEMOVE, matching the timing already used for the menu loop flag g_is_in_native_menu_loop.

Attack Path

  1. User grabs a window border The user presses the mouse on a resize/move border, and Windows delivers WM_ENTERSIZEMOVE and enters its native modal loop, which keeps pumping application tasks including renderer Mojo IPC.
  2. Flag is not yet set Before the patch, g_in_move_resize_loop remains false until the first WM_SIZING/WM_MOVING, which only arrives after the mouse moves, so IsInNativeMoveResizeLoop() reports false during the gap.
  3. Compromised renderer spams the IPC A compromised renderer floods LocalFrameHost::StartDragging IPC messages, one of which is dispatched by the modal loop during the unguarded gap.
  4. Guard is bypassed DesktopDragDropClientWin::StartDragAndDrop checks IsInNativeMoveResizeLoop(), sees false, and proceeds to start an OS-level drag with the renderer-supplied payload instead of refusing it.
  5. Unintended drop completes The drag/drop can be completed with an attacker-chosen payload even though the user only clicked the window border, without the deliberate drag gesture the guard was meant to require (inferred as to what the dropped payload achieves).

Impact Assessment

An attacker controlling a compromised renderer can initiate an OS-level drag/drop from a renderer-supplied payload while the user has merely pressed a window border, defeating the check that is supposed to require a genuine user drag gesture; the drag/drop runs in the browser (UI) process on Windows. Preconditions are a compromised/malicious renderer, the Windows platform, and the user pressing a window resize/move border to open the timing window. The advisory classifies this as improper initialization (LogicError) in Views; any further consequence of the completed drop, such as local file access or content spoofing, is (inferred) and not demonstrated by the patch.

Changed Functions

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

Files Changed

  • ui/views/win/user_resize_move_detector.cc
  • ui/views/widget/desktop_aura/desktop_window_tree_host_win_unittest.cc

Audit Directions

  • Guard flags set on a later event than the condition they protect
    Look for state that gates a security check but is assigned in a handler that fires after the guarded window opens (e.g. on first movement rather than loop entry, or on a data event rather than connection start), leaving a gap where a pumped/queued task sees the stale value.
  • Message-pumping nested loops
    Audit every native modal loop that runs under ScopedAllowApplicationTasksInNativeNestedLoop (menu loops, drag loops, resize loops, print/dialog loops) to confirm its “in loop” indicator is set before any application task or IPC can be dispatched and cleared only after the loop unwinds.
  • Renderer-triggered UI-process actions gated by a single boolean
    Review sinks like DesktopDragDropClientWin::StartDragAndDrop that let a renderer request a privileged UI action, verifying the gating predicate is true for the entire vulnerable interval and cannot be raced by IPC spam.
From 47ffe717717435d672257187a00922b391d0a27e Mon Sep 17 00:00:00 2001
From: Daniel Clark <[email protected]>
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 <[email protected]>
Commit-Queue: Dan Clark <[email protected]>
Reviewed-by: Allen Bauer <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1672718}
---
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();
   }
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…

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 [email protected]

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