Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInappropriate implementation in Views
DescriptionInappropriate implementation in Views
ComponentViews
Bug ClassLogic Error
Tracker518042749
Fix commit188e278c80a7 (chromium/src) +28/-0
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-16

Changed Functions

FunctionChangeNotes
ScopedSuppressForWindowMove
ui/views/widget/desktop_aura/desktop_drag_drop_client_ozone.cc
modified
TEST_F
ui/views/widget/desktop_aura/desktop_drag_drop_client_ozone_unittest.cc
modified

Files Changed

  • ui/views/widget/desktop_aura/desktop_drag_drop_client_ozone.cc
  • ui/views/widget/desktop_aura/desktop_drag_drop_client_ozone.h
  • ui/views/widget/desktop_aura/desktop_drag_drop_client_ozone_unittest.cc
  • ui/views/widget/desktop_aura/desktop_window_tree_host_platform.cc
From 188e278c80a7e2bb03c0f13068163df8de5e858f Mon Sep 17 00:00:00 2001
From: Tom Anderson <[email protected]>
Date: Tue, 09 Jun 2026 16:33:36 -0700
Subject: [PATCH] [Ozone] Suppress data drag during window move loop

DesktopWindowTreeHostPlatform::RunMoveLoop runs a nested event loop
allowing nestable tasks. While the loop is active, a compromised
renderer could start a data drag, release pointer grab/capture, take
ownership of the global XdndSelection selection atom, and substitute its
own DnD payload.

To protect against this, this CL introduces ScopedSuppressForWindowMove
under DesktopDragDropClientOzone, which sets the existing process-global
flag g_is_dragging. This flag is held for the duration of the window
move loop and used to reject re-entrant data drag starts.

Additionally, this CL adds a DCHECK(!g_is_dragging) in
ScopedSuppressForWindowMove to assert that a drag-and-drop session is
not already active when a window-move loop is entered.

Fixed: 518042749
Change-Id: I1dbe0fe181d7b65be457a32c4400688178bd5136
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7900333
Auto-Submit: Thomas Anderson <[email protected]>
Reviewed-by: Mike Wasserman <[email protected]>
Commit-Queue: Mike Wasserman <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1644315}
---

diff --git a/ui/views/widget/desktop_aura/desktop_drag_drop_client_ozone.cc b/ui/views/widget/desktop_aura/desktop_drag_drop_client_ozone.cc
index d341b848..123561d 100644
--- a/ui/views/widget/desktop_aura/desktop_drag_drop_client_ozone.cc
+++ b/ui/views/widget/desktop_aura/desktop_drag_drop_client_ozone.cc
@@ -133,6 +133,13 @@
     ui::WmDragHandler* drag_handler)
     : root_window_(root_window), drag_handler_(drag_handler) {}
 
+// static
+base::AutoReset<bool>
+DesktopDragDropClientOzone::ScopedSuppressForWindowMove() {
+  DCHECK(!g_is_dragging);
+  return base::AutoReset<bool>(&g_is_dragging, true);
+}
+
 DesktopDragDropClientOzone::~DesktopDragDropClientOzone() {
   ResetDragDropTarget();
   observers_.Notify(
diff --git a/ui/views/widget/desktop_aura/desktop_drag_drop_client_ozone.h b/ui/views/widget/desktop_aura/desktop_drag_drop_client_ozone.h
index 5a14b467..b46de97 100644
--- a/ui/views/widget/desktop_aura/desktop_drag_drop_client_ozone.h
+++ b/ui/views/widget/desktop_aura/desktop_drag_drop_client_ozone.h
@@ -7,6 +7,7 @@
 
 #include <memory>
 
+#include "base/auto_reset.h"
 #include "base/functional/callback.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
@@ -50,6 +51,11 @@
 
   ~DesktopDragDropClientOzone() override;
 
+  // Called by the window-move (tab-drag) entry point so that a
+  // renderer-initiated data drag cannot start inside the window-move's nested
+  // kNestableTasksAllowed RunLoop. See StartDragAndDrop.
+  [[nodiscard]] static base::AutoReset<bool> ScopedSuppressForWindowMove();
+
  protected:
   friend class DesktopDragDropClientOzoneTest;
 
diff --git a/ui/views/widget/desktop_aura/desktop_drag_drop_client_ozone_unittest.cc b/ui/views/widget/desktop_aura/desktop_drag_drop_client_ozone_unittest.cc
index 769cd0b..1f6b3d5f7 100644
--- a/ui/views/widget/desktop_aura/desktop_drag_drop_client_ozone_unittest.cc
+++ b/ui/views/widget/desktop_aura/desktop_drag_drop_client_ozone_unittest.cc
@@ -591,4 +591,14 @@
   EXPECT_EQ(DragOperation::kCopy, operation);
 }
 
+TEST_F(DesktopDragDropClientOzoneTest, RejectDragDuringWindowMove) {
+  // Simulate that a window-move loop is in progress.
+  auto suppress_drag =
+      DesktopDragDropClientOzone::ScopedSuppressForWindowMove();
+
+  // Attempt to start a drag operation. It should be rejected and return kNone.
+  DragOperation operation = StartDragAndDrop(ui::DragDropTypes::DRAG_COPY);
+  EXPECT_EQ(DragOperation::kNone, operation);
+}
+
 }  // namespace views
diff --git a/ui/views/widget/desktop_aura/desktop_window_tree_host_platform.cc b/ui/views/widget/desktop_aura/desktop_window_tree_host_platform.cc
index b953949d0..d538135 100644
--- a/ui/views/widget/desktop_aura/desktop_window_tree_host_platform.cc
+++ b/ui/views/widget/desktop_aura/desktop_window_tree_host_platform.cc
@@ -770,6 +770,11 @@
     const gfx::Vector2d& drag_offset,
     Widget::MoveLoopSource source,
     Widget::MoveLoopEscapeBehavior escape_behavior) {
+  // The window-move loop runs a kNestableTasksAllowed RunLoop that pumps Mojo
+  // IPC; suppress renderer-initiated data drags for its duration so a
+  // compromised renderer in another window cannot hijack the user's gesture.
+  auto suppress_data_drag =
+      DesktopDragDropClientOzone::ScopedSuppressForWindowMove();
   auto* move_loop_handler = ui::GetWmMoveLoopHandler(*platform_window());
   if (move_loop_handler && move_loop_handler->RunMoveLoop(drag_offset)) {
     return Widget::MoveLoopResult::kSuccessful;
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/ui/views/widget/desktop_aura/desktop_drag_drop_client_ozone_unittest.cc b/ui/views/widget/desktop_aura/desktop_drag_drop_client_ozone_unittest.cc
index 769cd0b..1f6b3d5f7 100644
--- a/ui/views/widget/desktop_aura/desktop_drag_drop_client_ozone_unittest.cc
+++ b/ui/views/widget/desktop_aura/desktop_drag_drop_client_ozone_unittest.cc
@@ -591,4 +591,14 @@
   EXPECT_EQ(DragOperation::kCopy, operation);
 }
 
+TEST_F(DesktopDragDropClientOzoneTest, RejectDragDuringWindowMove) {
+  // Simulate that a window-move loop is in progress.
+  auto suppress_drag =
+      DesktopDragDropClientOzone::ScopedSuppressForWindowMove();
+
+  // Attempt to start a drag operation. It should be rejected and return kNone.
+  DragOperation operation = StartDragAndDrop(ui::DragDropTypes::DRAG_COPY);
+  EXPECT_EQ(DragOperation::kNone, operation);
+}
+
 }  // namespace views
Loading diff…

Original Bug Report

reported by [email protected]

Tab-drag nested loop bypass of g_is_dragging guard on Linux/X11

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 in Chromium’s Linux/X11 window-move loop allows a compromised renderer to bypass the process-global drag-and-drop re-entrancy guard. When a user performs a tab-drag, a nested event loop is entered without setting the g_is_dragging guard, allowing a compromised renderer to initiate a re-entrant drag-and-drop operation. This can let the attacker hijack the active pointer grab and drop a custom payload, potentially leading to Universal Cross-Site Scripting (UXSS).

Affected files:

  • ui/views/widget/desktop_aura/desktop_drag_drop_client_ozone.cc
  • chrome/browser/ui/views/tabs/dragging/tab_drag_controller.cc
  • ui/ozone/platform/x11/x11_window.cc
  • ui/base/x/x11_desktop_window_move_client.cc
  • ui/base/x/x11_whole_screen_move_loop.cc

Estimated timestamp from git blame: 2026-05-28

Root Cause Analysis

During a tab-detach or window-move operation on Linux/Ozone (X11), the browser process enters a nested event loop via X11WholeScreenMoveLoop::RunMoveLoop (defined in ui/base/x/x11_whole_screen_move_loop.cc line 140) which is run with kNestableTasksAllowed (line 184).

Crucially, the process-global re-entrancy guard g_is_dragging (defined in ui/views/widget/desktop_aura/desktop_drag_drop_client_ozone.cc line 39) is only set and checked during system drag-and-drop operations initiated via DesktopDragDropClientOzone::StartDragAndDrop(). Because window-move and tab-drag operations are triggered directly via TabDragController and X11DesktopWindowMoveClient without involving the drag-and-drop client, the g_is_dragging variable remains false during the active window move.

While W1’s nested event loop is pumping tasks, a compromised renderer in a second visible window (W2) can invoke a re-entrant drag operation by sending the LocalFrameHost::StartDragging Mojo IPC. Since the nested loop allows nestable tasks, the IPC is processed re-entrantly. When DesktopDragDropClientOzone::StartDragAndDrop is called on W2’s root window, the guard check:

if (g_is_dragging) {
  return DragOperation::kNone;
}

passes because g_is_dragging is still false. W2’s drag client then releases the active mouse capture on W1, grabs the pointer for itself, and hijacks the global XdndSelection selection atom. Subsequent user drag actions are then routed to the attacker’s drag-and-drop handler, substituting the user’s tab drag with the attacker’s payload.

Note that on Windows, this scenario triggers a CHECK(!g_is_dragging) in DesktopDragDropClientWin and terminates the browser process, limiting the impact to Denial of Service. On Linux/Ozone (Wayland), system drag-and-drop is utilized for window moves, which correctly sets g_is_dragging. Consequently, the hijack scenario is specific to the Ozone/X11 platform.

Potential Attack Path

An attacker controlling a compromised renderer process could potentially execute the following steps to trigger this vulnerability (please note that these are suggested/potential steps as our tooling agent does not have the capability to execute code or run a live proof of concept):

  1. User Action: The user begins dragging a tab in a browser window (W1). This causes TabDragController to initiate a native window move, spawning X11WholeScreenMoveLoop::RunMoveLoop with a nested event loop allowing nestable tasks. The global g_is_dragging flag remains false.
  2. IPC Dispatch: The compromised renderer in a second visible window (W2) sends the LocalFrameHost::StartDragging Mojo IPC with the drag source set to mouse and coordinates targeting W2.
  3. Re-entrant Capture Release: The IPC is processed re-entrantly inside W1’s nested event loop. The guard check in DesktopDragDropClientOzone::StartDragAndDrop passes. W2’s drag client executes ReleaseCapture() to strip capture from W1.
  4. Pointer Grab and Selection Hijack: W2’s drag client starts its drag operation, grabs the pointer (which overrides W1’s grab on X11), and takes ownership of the global XdndSelection with the attacker’s payload (e.g., a javascript: URL).
  5. Payload Drop: When the user releases the mouse button over a drop target (such as the bookmarks bar or the page content), the attacker’s payload is dropped instead of the tab. If a javascript: URL is dropped onto a sensitive surface, it can execute arbitrary script, leading to Universal Cross-Site Scripting (UXSS).

Suggested Remediation

To resolve this issue, the process-global re-entrancy guard should be extended to cover both system drag-and-drop operations and platform-level window-move/tab-drag operations.

This can be accomplished by setting a process-global flag or checking if any native move loop is currently active on the platform inside DesktopDragDropClientOzone::StartDragAndDrop(). If a move loop is active, the re-entrant StartDragAndDrop call should be rejected immediately, returning DragOperation::kNone.

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