CVE-2026-13990
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifcontent/browser/web_contents/web_contents_view_aura.cc |
modified | |
TEST_Fcontent/browser/web_contents/web_contents_view_aura_unittest.cc |
modified | |
ifui/views/widget/desktop_aura/desktop_drag_drop_client_win.cc |
modified |
Files Changed
content/browser/web_contents/web_contents_view_aura.cccontent/browser/web_contents/web_contents_view_aura.hcontent/browser/web_contents/web_contents_view_aura_unittest.ccui/views/widget/desktop_aura/desktop_drag_drop_client_win.cc
Patch
From ddbfbe0032ebbf46b5f611372ee19e4b55c226bd Mon Sep 17 00:00:00 2001 From: Rohan Raja <[email protected]> Date: Wed, 27 May 2026 10:18:52 -0700 Subject: [PATCH] Clamp touch-drag screen location to browser-observed touch point Security fix for potential synthetic click on permission prompts via touch-drag on Aura/Windows. The renderer-supplied event_info.location for touch-initiated drags was passed through to platform DragDropClients; on Windows it would reach ::SendInput via DesktopWindowTreeHostWin::StartTouchDrag, where the OS routes the synthesized click to the topmost HWND at those coordinates (e.g. an overlapping permission bubble WS_POPUP). WebContentsViewAura::StartDragging now, for kTouch drags only: - Rejects the drag when no touch is in flight (aura::Env::is_touch_down() == false). - Substitutes the renderer-supplied screen location with the trusted browser-observed last touch point from aura::Env::GetLastPointerPoint(), with the renderer-supplied value as fallback. The trusted point is used for both the visibility/ bounds check and the StartDragAndDrop call. Also drops a dead-code ConvertDIPToPixels call on a default-constructed gfx::Point in DesktopDragDropClientWin::StartDragAndDrop that was overwritten before being used. Bug: 514058439 Change-Id: Ibc1372f79609f132646485e29bfd90a518b54e94 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7862906 Reviewed-by: David Bienvenu <[email protected]> Reviewed-by: Avi Drissman <[email protected]> Reviewed-by: Elly <[email protected]> Commit-Queue: Rohan Raja <[email protected]> Cr-Commit-Position: refs/heads/main@{#1637050} --- diff --git a/content/browser/web_contents/web_contents_view_aura.cc b/content/browser/web_contents/web_contents_view_aura.cc index 7f40cf79..5715f972f 100644 --- a/content/browser/web_contents/web_contents_view_aura.cc +++ b/content/browser/web_contents/web_contents_view_aura.cc @@ -1267,6 +1267,23 @@ DragOperation result_op; { gfx::NativeView content_native_view = GetContentNativeView(); + // For a touch-initiated drag the renderer-supplied `event_info.location` + // is untrusted: on Windows it would reach `::SendInput` via + // DesktopWindowTreeHostWin::StartTouchDrag and could redirect the + // synthesized click to an overlapping HWND (e.g. a permission bubble). + // Require an in-flight touch and substitute the browser-observed last + // touch point known to aura::Env. + gfx::Point trusted_location = event_info.location; + if (event_info.source == ui::mojom::DragEventSource::kTouch) { + aura::Env* env = aura::Env::GetInstance(); + if (!env->is_touch_down()) { + web_contents_->SystemDragEnded(source_rwh); + return; + } + trusted_location = + env->GetLastPointerPoint(event_info.source, content_native_view, + /*fallback=*/event_info.location); + } // Make sure event is within the web contents, and the web contents are // visible. if ( @@ -1274,8 +1291,7 @@ // TODO(https://crbug.com/454552204): Remove #if when either ChromeOS // fixes split screen mode web ui tab strip drag, or web ui tab strip is // fully deprecated. - !content_native_view->GetBoundsInScreen().Contains( - event_info.location) || + !content_native_view->GetBoundsInScreen().Contains(trusted_location) || #endif // !BUILDFLAG(IS_CHROMEOS) !content_native_view->IsVisible()) { web_contents_->SystemDragEnded(source_rwh); @@ -1285,7 +1301,7 @@ result_op = aura::client::GetDragDropClient(root_window) ->StartDragAndDrop(std::move(data), root_window, - content_native_view, event_info.location, + content_native_view, trusted_location, ConvertFromDragOperationsMask(operations), event_info.source); } diff --git a/content/browser/web_contents/web_contents_view_aura.h b/content/browser/web_contents/web_contents_view_aura.h index fe50f38..6bcab47 100644 --- a/content/browser/web_contents/web_contents_view_aura.h +++ b/content/browser/web_contents/web_contents_view_aura.h @@ -169,6 +169,8 @@ RejectDragFromHiddenWebContents); FRIEND_TEST_ALL_PREFIXES(WebContentsViewAuraTest, RejectDragFromOutsideView); FRIEND_TEST_ALL_PREFIXES(WebContentsViewAuraTest, + ClampTouchLocationToBrowserObservedPoint); + FRIEND_TEST_ALL_PREFIXES(WebContentsViewAuraTest, UrlInDropDataReturnsUrlInOSExchangeDataGetString); FRIEND_TEST_ALL_PREFIXES(WebContentsViewAuraTest, IgnoreInputs_OngoingDropGetsCleared); 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 5e1c407f1..4abd78ab 100644 --- a/content/browser/web_contents/web_contents_view_aura_unittest.cc +++ b/content/browser/web_contents/web_contents_view_aura_unittest.cc @@ -23,6 +23,7 @@ #include "testing/gtest/include/gtest/gtest.h" #include "ui/aura/client/aura_constants.h" #include "ui/aura/client/drag_drop_client.h" +#include "ui/aura/env.h" #include "ui/aura/test/test_windows.h" #include "ui/aura/test/window_test_api.h" #include "ui/aura/window.h" @@ -96,6 +97,8 @@ ui::mojom::DragEventSource source) override { drag_in_progress_ = true; drag_drop_data_ = std::move(data); + last_screen_location_ = screen_location; + last_source_ = source; return DragOperation::kCopy; } #if BUILDFLAG(IS_LINUX) @@ -109,10 +112,18 @@ } ui::OSExchangeData* GetDragDropData() { return drag_drop_data_.get(); } + const gfx::Point& last_screen_location() const { + return last_screen_location_; + } + std::optional<ui::mojom::DragEventSource> last_source() const { + return last_source_; + } private: bool drag_in_progress_ = false; std::unique_ptr<ui::OSExchangeData> drag_drop_data_; + gfx::Point last_screen_location_; + std::optional<ui::mojom::DragEventSource> last_source_; }; } // namespace @@ -922,6 +933,46 @@ #endif // BUILDFLAG(IS_CHROMEOS) } +// For a touch-initiated drag, the renderer-supplied screen location must +// not flow through to DragDropClient::StartDragAndDrop. Instead the trusted +// browser-observed last touch location (aura::Env) must be used. +TEST_F(WebContentsViewAuraTest, ClampTouchLocationToBrowserObservedPoint) { + NavigateAndCommit(GURL("https://example.com/")); + + TestDragDropClient drag_drop_client; + aura::client::SetDragDropClient(root_window(), &drag_drop_client); + + WebContentsViewAura* view = GetView(); + view->drag_in_progress_ = true; + + aura::Window* const content = view->GetContentNativeView(); + const gfx::Rect bounds = content->GetBoundsInScreen(); + const gfx::Point trusted(bounds.x() + 3, bounds.y() + 4); + const gfx::Point spoofed(bounds.right() - 2, bounds.bottom() - 2); + ASSERT_NE(trusted, spoofed); + ASSERT_TRUE(bounds.Contains(trusted)); + ASSERT_TRUE(bounds.Contains(spoofed)); + + aura::Env* const env = aura::Env::GetInstance(); + env->SetTouchDown(true); + env->SetLastTouchLocation(content, trusted); + + DropData drop_data; + view->StartDragging(*main_rfh(), drop_data, + blink::DragOperationsMask::kDragOperationNone, + gfx::ImageSkia(), gfx::Vector2d(), gfx::Rect(), + blink::mojom::DragEventSourceInfo( + spoofed, ui::mojom::DragEventSource::kTouch)); + + EXPECT_TRUE(drag_drop_client.GetDragDropData()); + EXPECT_EQ(ui::mojom::DragEventSource::kTouch, drag_drop_client.last_source()); + EXPECT_EQ(trusted, drag_drop_client.last_screen_location()) + << "Renderer-supplied screen location must be clamped to the " + "browser-observed last touch point."; + + env->SetTouchDown(false); +} + // Test that a drag from an event located outside the source view doesn't start. TEST_F(WebContentsViewAuraTest, EmptyTextInDropDataIsNonNullInOSExchangeData) { const char kGoogleUrl[] = "https://google.com/"; diff --git a/ui/views/widget/desktop_aura/desktop_drag_drop_client_win.cc b/ui/views/widget/desktop_aura/desktop_drag_drop_client_win.cc index 6d31bee..19d8cb90 100644 --- a/ui/views/widget/desktop_aura/desktop_drag_drop_client_win.cc +++ b/ui/views/widget/desktop_aura/desktop_drag_drop_client_win.cc @@ -77,7 +77,6 @@ CHECK(!g_is_dragging); gfx::Point touch_screen_point; if (source == ui::mojom::DragEventSource::kTouch) { - source_window->GetHost()->ConvertDIPToPixels(&touch_screen_point); display::Screen* screen = display::Screen::Get(); CHECK(screen); aura::Window* window =
Regression Test / PoC
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 5e1c407f1..4abd78ab 100644
--- a/content/browser/web_contents/web_contents_view_aura_unittest.cc
+++ b/content/browser/web_contents/web_contents_view_aura_unittest.cc
@@ -23,6 +23,7 @@
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/aura/client/drag_drop_client.h"
+#include "ui/aura/env.h"
#include "ui/aura/test/test_windows.h"
#include "ui/aura/test/window_test_api.h"
#include "ui/aura/window.h"
@@ -96,6 +97,8 @@
ui::mojom::DragEventSource source) override {
drag_in_progress_ = true;
drag_drop_data_ = std::move(data);
+ last_screen_location_ = screen_location;
+ last_source_ = source;
return DragOperation::kCopy;
}
#if BUILDFLAG(IS_LINUX)
@@ -109,10 +112,18 @@
}
ui::OSExchangeData* GetDragDropData() { return drag_drop_data_.get(); }
+ const gfx::Point& last_screen_location() const {
+ return last_screen_location_;
+ }
+ std::optional<ui::mojom::DragEventSource> last_source() const {
+ return last_source_;
+ }
private:
bool drag_in_progress_ = false;
std::unique_ptr<ui::OSExchangeData> drag_drop_data_;
+ gfx::Point last_screen_location_;
+ std::optional<ui::mojom::DragEventSource> last_source_;
};
} // namespace
@@ -922,6 +933,46 @@
#endif // BUILDFLAG(IS_CHROMEOS)
}
+// For a touch-initiated drag, the renderer-supplied screen location must
+// not flow through to DragDropClient::StartDragAndDrop. Instead the trusted
+// browser-observed last touch location (aura::Env) must be used.
+TEST_F(WebContentsViewAuraTest, ClampTouchLocationToBrowserObservedPoint) {
+ NavigateAndCommit(GURL("https://example.com/"));
+
+ TestDragDropClient drag_drop_client;
+ aura::client::SetDragDropClient(root_window(), &drag_drop_client);
+
+ WebContentsViewAura* view = GetView();
+ view->drag_in_progress_ = true;
+
+ aura::Window* const content = view->GetContentNativeView();
+ const gfx::Rect bounds = content->GetBoundsInScreen();
+ const gfx::Point trusted(bounds.x() + 3, bounds.y() + 4);
+ const gfx::Point spoofed(bounds.right() - 2, bounds.bottom() - 2);
+ ASSERT_NE(trusted, spoofed);
+ ASSERT_TRUE(bounds.Contains(trusted));
+ ASSERT_TRUE(bounds.Contains(spoofed));
+
+ aura::Env* const env = aura::Env::GetInstance();
+ env->SetTouchDown(true);
+ env->SetLastTouchLocation(content, trusted);
+
+ DropData drop_data;
+ view->StartDragging(*main_rfh(), drop_data,
+ blink::DragOperationsMask::kDragOperationNone,
+ gfx::ImageSkia(), gfx::Vector2d(), gfx::Rect(),
+ blink::mojom::DragEventSourceInfo(
+ spoofed, ui::mojom::DragEventSource::kTouch));
+
+ EXPECT_TRUE(drag_drop_client.GetDragDropData());
+ EXPECT_EQ(ui::mojom::DragEventSource::kTouch, drag_drop_client.last_source());
+ EXPECT_EQ(trusted, drag_drop_client.last_screen_location())
+ << "Renderer-supplied screen location must be clamped to the "
+ "browser-observed last touch point.";
+
+ env->SetTouchDown(false);
+}
+
// Test that a drag from an event located outside the source view doesn't start.
TEST_F(WebContentsViewAuraTest, EmptyTextInDropDataIsNonNullInOSExchangeData) {
const char kGoogleUrl[] = "https://google.com/";
Original Bug Report
Potential synthetic click on permission prompts via touch-drag 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: Coordinate validation for drag-and-drop on Aura fails to account for window occlusion by top-level HWNDs on Windows. A compromised renderer can potentially exploit this by spoofing touch-drag coordinates to target permission bubbles. This triggers OS-level mouse event injection that is routed to the topmost window, potentially granting sensitive permissions.
Affected files:
content/browser/web_contents/web_contents_view_aura.ccui/views/widget/desktop_aura/desktop_drag_drop_client_win.ccui/views/widget/desktop_aura/desktop_window_tree_host_win.ccui/base/win/event_creation_utils.cc
Estimated timestamp from git blame: Unknown (Google3 checkout)
Summary
A potential vulnerability exists in the coordinate validation logic of WebContentsViewAura::StartDragging that allows a compromised renderer to spoof input events on Windows. The current geometric bounds check does not account for window z-order or occlusion. On Windows, when a drag is initiated via touch, the browser injects synthetic OS-level mouse events. If the targeted coordinates are geometrically within the WebContents bounds but are occluded by a top-level permission bubble, the OS routes the injected events to the bubble, resulting in a synthetic click.
Technical Details
-
Insufficient Coordinate Validation: In
content/browser/web_contents/web_contents_view_aura.cc,StartDraggingvalidates the renderer-supplied location usingcontent_native_view->GetBoundsInScreen().Contains(event_info.location). This is a 2D geometric check that does not verify if the location is actually visible or if it is occluded by another window. Permission prompts in Chrome are separate top-level HWNDs that often overlap the content area. -
Windows Input Injection: On Windows,
DesktopDragDropClientWin::StartDragAndDrophandles touch-initiated drags by callingDesktopWindowTreeHostWin::StartTouchDrag. This function usesui::SendMouseEvent, which invokes the Windows API::SendInputto injectMOUSEEVENTF_LEFTDOWNandMOUSEEVENTF_LEFTUPevents at the specified screen coordinates. Because these are injected as system-level mouse events, the Windows OS routes them to the topmost window at that location—the permission bubble. -
Protection Bypasses: The primary defense against rapid/unintended interactions is the
InputEventActivationProtector. However, this protection is time-based (checking if interaction occurs within ~500ms of the UI showing). A compromised renderer can bypass this by simply waiting for the interval to pass before triggering the malicious IPC.
Potential Reproduction Steps
- Use a compromised renderer to request a sensitive permission (e.g., Geolocation).
- Wait for more than 500ms to bypass
InputEventActivationProtector. - Display a decoy “Tap to continue” element in the web content at a location not covered by the prompt.
- When the user touches the decoy element (satisfying the browser’s
is_touch_downrequirement), the renderer sends aLocalFrameHost.StartDraggingIPC. - The IPC contains
event_info.source = kTouchandevent_info.locationset to the screen coordinates of the permission prompt’s “Allow” button. - The browser processes the drag, injecting system-level mouse events that the OS routes to the permission bubble, granting the permission.
Suggested Fix
Coordinate validation in WebContentsViewAura::StartDragging should be made occlusion-aware. Before initiating a drag or injecting events, the browser should verify that the targeted coordinates actually correspond to the WebContents view and are not occluded by a top-level window. On Windows, this could involve using ::WindowFromPoint or similar APIs to ensure the target HWND matches the expected content_native_view.
Evaluated with Chrome root at commit: 1a8d40fc44df2088d5945c0bf53584038aa1614a
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.