CVE-2026-87644
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifchrome/browser/ui/views/toolbar/BUILD.gn |
modified | |
TestDragDropClientchrome/browser/ui/views/toolbar/webui_toolbar_interactive_uitest.cc |
modified | |
ifchrome/browser/ui/views/toolbar/webui_toolbar_interactive_uitest.cc |
modified | |
forchrome/browser/ui/views/toolbar/webui_toolbar_interactive_uitest.cc |
modified | |
BindLambdaForTestingchrome/browser/ui/views/toolbar/webui_toolbar_interactive_uitest.cc |
modified | |
PollUntilchrome/browser/ui/views/toolbar/webui_toolbar_interactive_uitest.cc |
modified | |
Dochrome/browser/ui/views/toolbar/webui_toolbar_interactive_uitest.cc |
modified |
Files Changed
chrome/browser/glic/host/glic_drag_and_drop_browsertest.ccchrome/browser/ui/views/toolbar/BUILD.gnchrome/browser/ui/views/toolbar/webui_toolbar_interactive_uitest.cc
Patch
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());
Regression Test / PoC
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)
Original Bug Report
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.ccui/views/widget/desktop_aura/desktop_drag_drop_client_win.ccui/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_ENTERSIZEMOVEis never dispatched.WM_MOVINGandWM_SIZINGdo not fire, leavingg_in_move_resize_loopset tofalseinUserResizeMoveDetector.menu_depth_remains0as 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.
- The user left-clicks and holds (without moving) on the empty caption/title bar of any Chrome window.
HWNDMessageHandler::HandleMouseInputForCaptionhandles the message, setsleft_button_down_on_caption_ = true, and suppresses the default window procedure. The ordinary UI message pump continues running.- While the button is held, a compromised renderer sends a Mojo message
LocalFrameHost::StartDraggingcontaining an attacker-controlled payload (e.g., ajavascript:URL bookmarklet or a virtual file) withevent_info.sourceset tokMouse. - 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. WebContentsViewAura::StartDraggingcallsDesktopDragDropClientWin::StartDragAndDrop.- The client evaluates
IsInNativeMoveResizeLoop(), which returnsfalsebecause no native move/resize or menu loops are active, bypassing the drag guards. ::DoDragDropis called with the attacker payload. The user’s hold keeps the drag active viaQueryContinueDragreturningS_OKforMK_LBUTTON.- 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.