Medium chrome Logic Error 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactIncorrect authorization in Views
DescriptionIncorrect authorization in Views
ComponentViews
Bug ClassLogic Error
Tracker521616899
Fix commit7db9980b4ebf (chromium/src) +179/-37
CISA KEVNot listed
CreditedGoogle
Disclosed2026-09-08

Changed Functions

FunctionChangeNotes
if
chrome/browser/ui/views/toolbar/BUILD.gn
modified
TestDragDropClient
chrome/browser/ui/views/toolbar/webui_toolbar_interactive_uitest.cc
modified
if
chrome/browser/ui/views/toolbar/webui_toolbar_interactive_uitest.cc
modified
for
chrome/browser/ui/views/toolbar/webui_toolbar_interactive_uitest.cc
modified
BindLambdaForTesting
chrome/browser/ui/views/toolbar/webui_toolbar_interactive_uitest.cc
modified
PollUntil
chrome/browser/ui/views/toolbar/webui_toolbar_interactive_uitest.cc
modified
Do
chrome/browser/ui/views/toolbar/webui_toolbar_interactive_uitest.cc
modified

Files Changed

  • chrome/browser/glic/host/glic_drag_and_drop_browsertest.cc
  • chrome/browser/ui/views/toolbar/BUILD.gn
  • chrome/browser/ui/views/toolbar/webui_toolbar_interactive_uitest.cc
From 7db9980b4ebffff30d751384e3fb82939df3cf9c Mon Sep 17 00:00:00 2001
From: David Bienvenu <davidbienvenu@chromium.org>
Date: Thu, 30 Jul 2026 13:42:22 -0700
Subject: [PATCH] clamp mouse location for renderer initiated drag drop

touch initiated drag drops already have their location clamped. Do the
same for mouse initiated drag drops.

Change the test that makes sure a drag from a location outside of the
web contents is rejected to test instead that the drag location is
clamped to the last mouse down position.

Bug: 521616899
Change-Id: I11a85f1f94f9c94dfe71ef13646699e2f674bbf1
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7921191
Reviewed-by: Avi Drissman <avi@chromium.org>
Reviewed-by: Alex Moshchuk <alexmos@chromium.org>
Commit-Queue: David Bienvenu <davidbienvenu@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1671382}
---

diff --git a/chrome/browser/glic/host/glic_drag_and_drop_browsertest.cc b/chrome/browser/glic/host/glic_drag_and_drop_browsertest.cc
index 61b769bc..b08859b 100644
--- a/chrome/browser/glic/host/glic_drag_and_drop_browsertest.cc
+++ b/chrome/browser/glic/host/glic_drag_and_drop_browsertest.cc
@@ -264,9 +264,6 @@
     content::SimulateMouseEvent(
         source_wc, blink::WebInputEvent::Type::kMouseMove,
         blink::WebMouseEvent::Button::kLeft, drag_end_point);
-    content::SimulateMouseEvent(source_wc, blink::WebInputEvent::Type::kMouseUp,
-                                blink::WebMouseEvent::Button::kLeft,
-                                drag_end_point);
   }
 
  private:
diff --git a/chrome/browser/ui/views/toolbar/BUILD.gn b/chrome/browser/ui/views/toolbar/BUILD.gn
index 6078e828..a1ba92f 100644
--- a/chrome/browser/ui/views/toolbar/BUILD.gn
+++ b/chrome/browser/ui/views/toolbar/BUILD.gn
@@ -465,6 +465,9 @@
     if (toolkit_views) {
       deps += [ "//chrome/browser/ui:toolbar_controller_util" ]
     }
+    if (use_aura) {
+      deps += [ "//ui/aura:test_support" ]
+    }
   }
 }
 
diff --git a/chrome/browser/ui/views/toolbar/webui_toolbar_interactive_uitest.cc b/chrome/browser/ui/views/toolbar/webui_toolbar_interactive_uitest.cc
index e3fa608..6e59c8b 100644
--- a/chrome/browser/ui/views/toolbar/webui_toolbar_interactive_uitest.cc
+++ b/chrome/browser/ui/views/toolbar/webui_toolbar_interactive_uitest.cc
@@ -61,10 +61,14 @@
 #include "content/public/test/test_navigation_observer.h"
 #include "ui/aura/client/drag_drop_client.h"
 #include "ui/aura/client/drag_drop_client_observer.h"
-#include "ui/base/clipboard//clipboard_monitor.h"
-#include "ui/base/clipboard//clipboard_observer.h"
+#include "ui/aura/env.h"
+#include "ui/aura/input_state_lookup.h"
+#include "ui/aura/test/env_test_helper.h"
+#include "ui/aura/window_observer.h"
 #include "ui/base/clipboard/clipboard.h"
 #include "ui/base/clipboard/clipboard_buffer.h"
+#include "ui/base/clipboard/clipboard_monitor.h"
+#include "ui/base/clipboard/clipboard_observer.h"
 #include "ui/base/dragdrop/mojom/drag_drop_types.mojom.h"
 #include "ui/base/dragdrop/os_exchange_data.h"
 #include "ui/base/interaction/element_identifier.h"
@@ -1088,18 +1092,32 @@
 #endif  // BUILDFLAG(IS_MAC)
 
 #if defined(USE_AURA)
-class TestDragDropClient : public aura::client::DragDropClient {
+class TestDragDropClient : public aura::client::DragDropClient,
+                           public aura::WindowObserver {
  public:
   explicit TestDragDropClient(aura::Window* root_window)
       : root_window_(root_window) {
-    client_ = aura::client::GetDragDropClient(root_window_);
-    aura::client::SetDragDropClient(root_window_, this);
+    if (root_window_) {
+      root_window_->AddObserver(this);
+      client_ = aura::client::GetDragDropClient(root_window_);
+      aura::client::SetDragDropClient(root_window_, this);
+    }
   }
   ~TestDragDropClient() override {
-    for (auto& observer : observers_) {
-      observer.OnDragDropClientDestroying();
+    if (root_window_) {
+      root_window_->RemoveObserver(this);
+      for (auto& observer : observers_) {
+        observer.OnDragDropClientDestroying();
+      }
+      aura::client::SetDragDropClient(root_window_, client_);
     }
-    aura::client::SetDragDropClient(root_window_, client_);
+  }
+
+  void OnWindowDestroying(aura::Window* window) override {
+    if (window == root_window_) {
+      root_window_->RemoveObserver(this);
+      root_window_ = nullptr;
+    }
   }
 
   // aura::client::DragDropClient:
@@ -1233,12 +1251,17 @@
                               ->GetNativeWindow()
                               ->GetRootWindow();
       drag_drop_client_ = std::make_unique<TestDragDropClient>(root_window);
+      aura::test::EnvTestHelper(aura::Env::GetInstance())
+          .SetInputStateLookup(nullptr);
     })));
   }
 
   MultiStep ResetDragDropClient() {
-    return Steps(Do(
-        base::BindLambdaForTesting([this]() { drag_drop_client_.reset(); })));
+    return Steps(Do(base::BindLambdaForTesting([this]() {
+      aura::test::EnvTestHelper(aura::Env::GetInstance())
+          .SetInputStateLookup(aura::InputStateLookup::Create());
+      drag_drop_client_.reset();
+    })));
   }
 
   std::unique_ptr<TestDragDropClient> drag_drop_client_;
@@ -1275,14 +1298,17 @@
                                 ->GetNativeWindow()
                                 ->GetRootWindow();
         drag_drop_client = std::make_unique<TestDragDropClient>(root_window);
+        aura::test::EnvTestHelper(aura::Env::GetInstance())
+            .SetInputStateLookup(nullptr);
       })),
 
       // Move to icon and perform drag gesture.
       MoveMouseTo(WebUIToolbarId(), kLocationIconDeepQuery),
       DragMouseTo(base::BindOnce([]() -> gfx::Point {
-        return display::Screen::Get()->GetCursorScreenPoint() +
-               gfx::Vector2d(0, 20);
-      })),
+                    return display::Screen::Get()->GetCursorScreenPoint() +
+                           gfx::Vector2d(0, 20);
+                  }),
+                  /*release=*/false),
 
       // Verify that drag was triggered with correct data.
       PollUntil(base::BindLambdaForTesting([&]() {
@@ -1291,8 +1317,14 @@
                 }),
                 "Drag was triggered with correct URL"),
 
+      ReleaseMouse(),
+
       // Cleanup.
-      Do(base::BindLambdaForTesting([&]() { drag_drop_client.reset(); })));
+      Do(base::BindLambdaForTesting([&]() {
+        aura::test::EnvTestHelper(aura::Env::GetInstance())
+            .SetInputStateLookup(aura::InputStateLookup::Create());
+        drag_drop_client.reset();
+      })));
 #endif  // defined(USE_AURA) && !BUILDFLAG(IS_CHROMEOS)
 }
 
@@ -1349,7 +1381,7 @@
       MoveMouseTo(WebUIToolbarId(), kTextSpanDeepQuery),
 
       // Drag to a sibling Views-level element to trigger the move.
-      DragMouseTo(kReloadButtonElementId),
+      DragMouseTo(kReloadButtonElementId, CenterPoint(), /*release=*/false),
 
       PollUntil(base::BindLambdaForTesting([&]() {
                   return drag_drop_client_->drag_triggered() &&
@@ -1358,6 +1390,8 @@
                 }),
                 "Drag was triggered with correct plain text"),
 
+      ReleaseMouse(),
+
       ResetDragDropClient());
 #endif
 }
@@ -1402,7 +1436,7 @@
       MoveMouseTo(WebUIToolbarId(), kTextSpanDeepQuery),
 
       // Drag to a sibling Views-level element to trigger the move.
-      DragMouseTo(kReloadButtonElementId),
+      DragMouseTo(kReloadButtonElementId, CenterPoint(), /*release=*/false),
 
       PollUntil(base::BindLambdaForTesting([&]() {
                   return drag_drop_client_->drag_triggered() &&
@@ -1412,6 +1446,8 @@
                 }),
                 "Drag was triggered with correct URL and plain text"),
 
+      ReleaseMouse(),
+
       ResetDragDropClient());
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/chrome/browser/glic/host/glic_drag_and_drop_browsertest.cc b/chrome/browser/glic/host/glic_drag_and_drop_browsertest.cc
index 61b769bc..b08859b 100644
--- a/chrome/browser/glic/host/glic_drag_and_drop_browsertest.cc
+++ b/chrome/browser/glic/host/glic_drag_and_drop_browsertest.cc
@@ -264,9 +264,6 @@
     content::SimulateMouseEvent(
         source_wc, blink::WebInputEvent::Type::kMouseMove,
         blink::WebMouseEvent::Button::kLeft, drag_end_point);
-    content::SimulateMouseEvent(source_wc, blink::WebInputEvent::Type::kMouseUp,
-                                blink::WebMouseEvent::Button::kLeft,
-                                drag_end_point);
   }
 
  private:
diff --git a/chrome/browser/ui/views/toolbar/webui_toolbar_interactive_uitest.cc b/chrome/browser/ui/views/toolbar/webui_toolbar_interactive_uitest.cc
index e3fa608..6e59c8b 100644
--- a/chrome/browser/ui/views/toolbar/webui_toolbar_interactive_uitest.cc
+++ b/chrome/browser/ui/views/toolbar/webui_toolbar_interactive_uitest.cc
@@ -61,10 +61,14 @@
 #include "content/public/test/test_navigation_observer.h"
 #include "ui/aura/client/drag_drop_client.h"
 #include "ui/aura/client/drag_drop_client_observer.h"
-#include "ui/base/clipboard//clipboard_monitor.h"
-#include "ui/base/clipboard//clipboard_observer.h"
+#include "ui/aura/env.h"
+#include "ui/aura/input_state_lookup.h"
+#include "ui/aura/test/env_test_helper.h"
+#include "ui/aura/window_observer.h"
 #include "ui/base/clipboard/clipboard.h"
 #include "ui/base/clipboard/clipboard_buffer.h"
+#include "ui/base/clipboard/clipboard_monitor.h"
+#include "ui/base/clipboard/clipboard_observer.h"
 #include "ui/base/dragdrop/mojom/drag_drop_types.mojom.h"
 #include "ui/base/dragdrop/os_exchange_data.h"
 #include "ui/base/interaction/element_identifier.h"
@@ -1088,18 +1092,32 @@
 #endif  // BUILDFLAG(IS_MAC)
 
 #if defined(USE_AURA)
-class TestDragDropClient : public aura::client::DragDropClient {
+class TestDragDropClient : public aura::client::DragDropClient,
+                           public aura::WindowObserver {
  public:
   explicit TestDragDropClient(aura::Window* root_window)
       : root_window_(root_window) {
-    client_ = aura::client::GetDragDropClient(root_window_);
-    aura::client::SetDragDropClient(root_window_, this);
+    if (root_window_) {
+      root_window_->AddObserver(this);
+      client_ = aura::client::GetDragDropClient(root_window_);
+      aura::client::SetDragDropClient(root_window_, this);
+    }
   }
   ~TestDragDropClient() override {
-    for (auto& observer : observers_) {
-      observer.OnDragDropClientDestroying();
+    if (root_window_) {
+      root_window_->RemoveObserver(this);
+      for (auto& observer : observers_) {
+        observer.OnDragDropClientDestroying();
+      }
+      aura::client::SetDragDropClient(root_window_, client_);
     }
-    aura::client::SetDragDropClient(root_window_, client_);
+  }
+
+  void OnWindowDestroying(aura::Window* window) override {
+    if (window == root_window_) {
+      root_window_->RemoveObserver(this);
+      root_window_ = nullptr;
+    }
   }
 
   // aura::client::DragDropClient:
@@ -1233,12 +1251,17 @@
                               ->GetNativeWindow()
                               ->GetRootWindow();
       drag_drop_client_ = std::make_unique<TestDragDropClient>(root_window);
+      aura::test::EnvTestHelper(aura::Env::GetInstance())
+          .SetInputStateLookup(nullptr);
     })));
   }
 
   MultiStep ResetDragDropClient() {
-    return Steps(Do(
-        base::BindLambdaForTesting([this]() { drag_drop_client_.reset(); })));
+    return Steps(Do(base::BindLambdaForTesting([this]() {
+      aura::test::EnvTestHelper(aura::Env::GetInstance())
+          .SetInputStateLookup(aura::InputStateLookup::Create());
+      drag_drop_client_.reset();
+    })));
   }
 
   std::unique_ptr<TestDragDropClient> drag_drop_client_;
@@ -1275,14 +1298,17 @@
                                 ->GetNativeWindow()
                                 ->GetRootWindow();
         drag_drop_client = std::make_unique<TestDragDropClient>(root_window);
+        aura::test::EnvTestHelper(aura::Env::GetInstance())
+            .SetInputStateLookup(nullptr);
       })),
 
       // Move to icon and perform drag gesture.
       MoveMouseTo(WebUIToolbarId(), kLocationIconDeepQuery),
       DragMouseTo(base::BindOnce([]() -> gfx::Point {
-        return display::Screen::Get()->GetCursorScreenPoint() +
-               gfx::Vector2d(0, 20);
-      })),
+                    return display::Screen::Get()->GetCursorScreenPoint() +
+                           gfx::Vector2d(0, 20);
+                  }),
+                  /*release=*/false),
 
       // Verify that drag was triggered with correct data.
       PollUntil(base::BindLambdaForTesting([&]() {
@@ -1291,8 +1317,14 @@
                 }),
                 "Drag was triggered with correct URL"),
 
+      ReleaseMouse(),
+
       // Cleanup.
-      Do(base::BindLambdaForTesting([&]() { drag_drop_client.reset(); })));
+      Do(base::BindLambdaForTesting([&]() {
+        aura::test::EnvTestHelper(aura::Env::GetInstance())
+            .SetInputStateLookup(aura::InputStateLookup::Create());
+        drag_drop_client.reset();
+      })));
 #endif  // defined(USE_AURA) && !BUILDFLAG(IS_CHROMEOS)
 }
 
@@ -1349,7 +1381,7 @@
       MoveMouseTo(WebUIToolbarId(), kTextSpanDeepQuery),
 
       // Drag to a sibling Views-level element to trigger the move.
-      DragMouseTo(kReloadButtonElementId),
+      DragMouseTo(kReloadButtonElementId, CenterPoint(), /*release=*/false),
 
       PollUntil(base::BindLambdaForTesting([&]() {
                   return drag_drop_client_->drag_triggered() &&
@@ -1358,6 +1390,8 @@
                 }),
                 "Drag was triggered with correct plain text"),
 
+      ReleaseMouse(),
+
       ResetDragDropClient());
 #endif
 }
@@ -1402,7 +1436,7 @@
       MoveMouseTo(WebUIToolbarId(), kTextSpanDeepQuery),
 
       // Drag to a sibling Views-level element to trigger the move.
-      DragMouseTo(kReloadButtonElementId),
+      DragMouseTo(kReloadButtonElementId, CenterPoint(), /*release=*/false),
 
       PollUntil(base::BindLambdaForTesting([&]() {
                   return drag_drop_client_->drag_triggered() &&
@@ -1412,6 +1446,8 @@
                 }),
                 "Drag was triggered with correct URL and plain text"),
 
+      ReleaseMouse(),
+
       ResetDragDropClient());
 #endif
 }
@@ -1450,7 +1486,7 @@
       MoveMouseTo(WebUIToolbarId(), kTextSpanDeepQuery),
 
       // Drag to a sibling Views-level element to trigger the move.
-      DragMouseTo(kReloadButtonElementId),
+      DragMouseTo(kReloadButtonElementId, CenterPoint(), /*release=*/false),
 
       PollUntil(base::BindLambdaForTesting([&]() {
                   return drag_drop_client_->drag_triggered() &&
@@ -1460,6 +1496,8 @@
                 }),
                 "Drag was triggered with javascript as plain text only"),
 
+      ReleaseMouse(),
+
       ResetDragDropClient());
 #endif
 }
@@ -1498,7 +1536,7 @@
       MoveMouseTo(WebUIToolbarId(), kTextSpanDeepQuery),
 
       // Drag to a sibling Views-level element to trigger the move.
-      DragMouseTo(kReloadButtonElementId),
+      DragMouseTo(kReloadButtonElementId, CenterPoint(), /*release=*/false),
 
       // Verify that chrome:// URL is dragged as a URL.
       PollUntil(base::BindLambdaForTesting([&]() {
@@ -1510,6 +1548,8 @@
                 }),
                 "Drag was triggered with chrome URL"),
 
+      ReleaseMouse(),
+
       ResetDragDropClient());
 #endif
 }
@@ -1548,7 +1588,7 @@
       MoveMouseTo(WebUIToolbarId(), kTextSpanDeepQuery),
 
       // Drag to a sibling Views-level element to trigger the move.
-      DragMouseTo(kReloadButtonElementId),
+      DragMouseTo(kReloadButtonElementId, CenterPoint(), /*release=*/false),
 
       // Verify that the adjusted URL is dragged.
       PollUntil(
@@ -1567,6 +1607,8 @@
           "Drag was triggered with partial selection adjusted to full GURL and "
           "plain text"),
 
+      ReleaseMouse(),
+
       ResetDragDropClient());
 #endif
 }
diff --git a/content/browser/web_contents/web_contents_view_aura_unittest.cc b/content/browser/web_contents/web_contents_view_aura_unittest.cc
index bd41996..20e6442 100644
--- a/content/browser/web_contents/web_contents_view_aura_unittest.cc
+++ b/content/browser/web_contents/web_contents_view_aura_unittest.cc
@@ -28,6 +28,7 @@
 #include "ui/aura/client/aura_constants.h"
 #include "ui/aura/client/drag_drop_client.h"
 #include "ui/aura/env.h"
+#include "ui/aura/test/env_test_helper.h"
 #include "ui/aura/test/test_windows.h"
 #include "ui/aura/test/window_test_api.h"
 #include "ui/aura/window.h"
@@ -198,10 +199,20 @@
          .window_type = aura::client::WINDOW_TYPE_NORMAL,
          .window_id = 0,
          .show = false});
+    // Force Env's IsMouseButtonDown to rely on mouse_button_flags_ instead of
+    // querying the native OS system (which would return false in headless/unit
+    // tests).
+    aura::test::EnvTestHelper(aura::Env::GetInstance())
+        .SetInputStateLookup(nullptr);
+    aura::Env::GetInstance()->set_mouse_button_flags(ui::EF_LEFT_MOUSE_BUTTON);
   }
 
   void TearDown() override {
     occluding_window_.reset();
+    aura::Env::GetInstance()->SetLastMouseLocation(gfx::Point());
+    aura::test::EnvTestHelper(aura::Env::GetInstance())
+        .SetInputStateLookup(aura::InputStateLookup::Create());
+    aura::Env::GetInstance()->set_mouse_button_flags(0);
     RenderViewHostTestHarness::TearDown();
   }
 
@@ -220,7 +231,8 @@
               drop_complete_data_->target_rwh.get());
     EXPECT_EQ(kClientPt, drop_complete_data_->client_pt);
     // Screen point of event is ignored, instead cursor position used.
-    EXPECT_EQ(gfx::PointF(), drop_complete_data_->screen_pt);
+    EXPECT_EQ(gfx::PointF(aura::Env::GetInstance()->last_mouse_location()),
+              drop_complete_data_->screen_pt);
     EXPECT_EQ(0, drop_complete_data_->key_modifiers);
   }
 
@@ -1075,6 +1087,8 @@
   view->drag_in_progress_ = true;
 
   DropData drop_data;
+  aura::Env::GetInstance()->SetLastMouseLocation(
+      view->GetContentNativeView()->GetBoundsInScreen().CenterPoint());
   view->StartDragging(*main_rfh(), drop_data,
                       blink::DragOperationsMask::kDragOperationNone,
                       gfx::ImageSkia(), gfx::Vector2d(), gfx::Rect(),
@@ -1111,9 +1125,10 @@
   EXPECT_FALSE(exchange_data);
 }
 
-// If the event location is not in the WebContentsViewAura, the drag will not be
-// started.
-TEST_F(WebContentsViewAuraTest, RejectDragFromOutsideView) {
+// For a mouse-initiated drag, the renderer-supplied screen location must
+// not flow through to DragDropClient::StartDragAndDrop. Instead the trusted
+// browser-observed last mouse location (aura::Env) must be used.
+TEST_F(WebContentsViewAuraTest, ClampMouseLocationToBrowserObservedPoint) {
   const char kGoogleUrl[] = "https://google.com/";
 
   std::u16string url_string = u"https://google.com/";
@@ -1132,6 +1147,15 @@
   DropData drop_data;
   drop_data.url_infos = {ui::ClipboardUrlInfo{GURL(kGoogleUrl), u""}};
 
+  // This condition is needed to avoid calling WebContentsViewAura::EndDrag
+  // which will result NOTREACHED being called in
+  // `RenderWidgetHostViewBase::TransformPointToCoordSpaceForView`.
+  view->drag_in_progress_ = true;
+
+  const gfx::Point trusted_location(view_bounds_on_screen.x() + 3,
+                                    view_bounds_on_screen.y() + 4);
+  aura::Env::GetInstance()->SetLastMouseLocation(trusted_location);
+
... (truncated)
Loading diff…

Original Bug Report

reported by vm...@google.com

Potential bypass of drag-and-drop guards during title-bar click-hold 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: Chrome’s Windows non-client mouse handler defers processing WM_NCLBUTTONDOWN for HTCAPTION until a mouse move occurs to keep the UI responsive during a click-hold. During this deferral, neither the native move-resize loop nor menu loop flags are set, which allows a compromised renderer to bypass drag guards. An attacker can initiate an arbitrary drag-and-drop session with controlled payload while the user is click-holding the title bar.

Affected files:

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

Estimated timestamp from git blame: 2015-12-11

Root Cause Analysis

When a user left-clicks and holds on the title bar (caption area) of a top-level Chrome window on Windows, the message WM_NCLBUTTONDOWN with wParam == HTCAPTION is generated. To keep page and Mojo dispatch responsive during a click-hold, HWNDMessageHandler::HandleMouseInputForCaption deliberately intercepts and handles this message:

// ui/views/win/hwnd_message_handler.cc
case WM_NCLBUTTONDOWN: {
  if (w_param == HTCAPTION) {
    left_button_down_on_caption_ = true;
    caption_left_button_click_pos_.set_x(CR_GET_X_LPARAM(l_param));
    caption_left_button_click_pos_.set_y(CR_GET_Y_LPARAM(l_param));
    handled = true; // DefWindowProc is NOT called here
  }
  break;
}

Because handled is set to true, the default window procedure (::DefWindowProc) is suppressed. As a result, the OS does not enter the native move/resize loop, meaning:

  • WM_ENTERSIZEMOVE is never dispatched.
  • WM_MOVING and WM_SIZING do not fire, leaving g_in_move_resize_loop set to false in UserResizeMoveDetector.
  • menu_depth_ remains 0 as no menu loop is entered.

The ordinary UI message pump continues to run normally and processes Mojo IPCs from the renderer.

Normally, DesktopDragDropClientWin::StartDragAndDrop relies on desktop_host_->IsInNativeMoveResizeLoop() to reject dragging requests if the user is in the middle of a window drag or resize operation. However, during the HTCAPTION hold deferral, IsInNativeMoveResizeLoop() returns false since none of the native loops have technically started yet, and the private flag left_button_down_on_caption_ is not checked by the drag client. This allows the compromised renderer to successfully start an OLE drag-and-drop loop via ::DoDragDrop.

Since the physical mouse button is still held down by the user, DragSourceWin::QueryContinueDrag returns S_OK to continue dragging. When the user moves the mouse (intending to move the window), they instead drag the attacker-supplied payload and drop it wherever they release the button.

Potential Attack Path

Note: These are potential steps as our tooling agent does not currently have the ability to execute code and we do not have a working proof of concept.

  1. The user left-clicks and holds (without moving) on the empty caption/title bar of any Chrome window.
  2. HWNDMessageHandler::HandleMouseInputForCaption handles the message, sets left_button_down_on_caption_ = true, and suppresses the default window procedure. The ordinary UI message pump continues running.
  3. While the button is held, a compromised renderer sends a Mojo message LocalFrameHost::StartDragging containing an attacker-controlled payload (e.g., a javascript: URL bookmarklet or a virtual file) with event_info.source set to kMouse.
  4. The browser process receives this and calls WebContentsViewAura::StartDragging. The renderer satisfies the bounds check by supplying a coordinate location inside its own visible WebContents.
  5. WebContentsViewAura::StartDragging calls DesktopDragDropClientWin::StartDragAndDrop.
  6. The client evaluates IsInNativeMoveResizeLoop(), which returns false because no native move/resize or menu loops are active, bypassing the drag guards.
  7. ::DoDragDrop is called with the attacker payload. The user’s hold keeps the drag active via QueryContinueDrag returning S_OK for MK_LBUTTON.
  8. The user moves and releases the mouse, dropping the attacker-controlled payload onto an arbitrary drop target (such as Windows Explorer, another Chrome window, or the bookmarks bar).

Suggested Remediation

To prevent this bypass, DesktopDragDropClientWin::StartDragAndDrop should reject mouse-initiated drag requests unless the browser has itself observed a left-button press captured within the initiating WebContents (similar to how touch drags are verified via is_touch_down()). Alternatively, HWNDMessageHandler should expose the state of left_button_down_on_caption_ so it can be OR’ed into the IsInNativeMoveResizeLoop() guard predicate in DesktopWindowTreeHostWin::IsInNativeMoveResizeLoop().

Evaluated with Chrome root at commit: 3947e01999a53d4e2382e39736cb79d79c7dffcf


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