CVE-2025-12726
Overview
Files Changed
ui/aura/env_input_state_controller.cc
Patch
From 083cac2c44a50242c7ac100f8fed5fc66ead9958 Mon Sep 17 00:00:00 2001 From: David Bienvenu <[email protected]> Date: Thu, 16 Oct 2025 14:29:16 -0700 Subject: [PATCH] Fix handling of touch state in EnvInputStateController Change-Id: I7357dff83de347207e6f0016429b3314daf2d5a1 Bug: 447172715 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7032454 Reviewed-by: Nico Weber <[email protected]> Commit-Queue: David Bienvenu <[email protected]> Reviewed-by: Mustaq Ahmed <[email protected]> Cr-Commit-Position: refs/heads/main@{#1531080} --- diff --git a/ui/aura/env_input_state_controller.cc b/ui/aura/env_input_state_controller.cc index 75da86584..9371488 100644 --- a/ui/aura/env_input_state_controller.cc +++ b/ui/aura/env_input_state_controller.cc @@ -4,6 +4,7 @@ #include "ui/aura/env_input_state_controller.h" +#include "build/build_config.h" #include "ui/aura/client/screen_position_client.h" #include "ui/aura/env.h" #include "ui/events/event.h" @@ -48,11 +49,17 @@ env_->SetTouchDown(touch_ids_down_ != 0); break; - // Handle EventType::kTouchCancelled only if it has a native event. case ui::EventType::kTouchCancelled: - if (!event.HasNativeEvent()) +#if BUILDFLAG(IS_CHROMEOS) + // Handle EventType::kTouchCancelled only if it has a native event. + // ChromeOS exo touch drag relies on the ability to cancel touch + // downs with synthetic events when handing off to the new consumer, + // without losing the global env touch down state. + if (!event.HasNativeEvent()) { break; + } [[fallthrough]]; +#endif // BUILDFLAG(IS_CHROMEOS) case ui::EventType::kTouchReleased: touch_ids_down_ = (touch_ids_down_ | (1 << event.pointer_details().id)) ^ (1 << event.pointer_details().id);
Original Bug Report
Security: Compromised renderer can control mouse after single tap (UXSS, sandbox escape, and more)
SUMMARY
Similar to issue 370856871, on Windows a compromised renderer can call StartDragging() to control the mouse and perform clicks. The existing mitigations can be bypassed after a single user tap until browser shutdown, allowing for many of the same impacts as the referenced issue. After the initial tap, all attacks can be performed an infinite amount of times, in any combination, at any time (such as when user is away from computer).
Standalone, we have a one-tap sandbox escape, UXSS, and other impacts.
When chained with other vulnerabilities, we have a one-tap sandbox escape with Mark of the Web (MOTW) bypass. I’ll post the chain in comments.
VULNERABILITY DETAILS
Background
A compromised renderer can call RFHI::StartDragging(), which eventually calls WebContentsViewAura::StartDragging() which ultimately calls DesktopDragDropClientWin::StartDragAndDrop().
StartDragAndDrop() performs mouse clicks through OS API calls in DesktopWindowTreeHostWin::StartTouchDrag() + ::FinishTouchDrag() and also calls the OS ::DoDragDrop() method.
Important concept: An aura::Window “represents virtual windows, including tabs, bubbles and menus” (source: crbug comment). It doesn’t always correspond to what a user typically considers a window. As a mental shortcut, in the main browser window, any UI that is typically visible immediately after navigating to a web page is likely within the same window as the web page content.
Bugs in existing mitigations
The fix for issue 370856871 added two checks within StartDragAndDrop():
touch_downmitigation: Checks if Aurais_touch_down()returns true. This will return true if on anyaura::Window, a touch was started (kTouchPressed) but has not finished (kTouchReleased) or cancelled (kTouchCancelled). Aura only tracks touches initiated by physical touch, privileged CDP client, or OS input APIs.touch_over_other_windowmitigation: Checks if cursor is over the currentaura::Window. This fails if it’s over a non-aura::Windowor a differentaura::Window.
Unfortunately, bugs prevent both of these mitigations from working as intended. There are also no restrictions on drag/clicks within the same aura::Window, which can still have serious impacts.
if (source == ui::mojom::DragEventSource::kTouch) {
// ...
aura::Window* window =
screen->GetWindowAtScreenPoint(screen->GetCursorScreenPoint()); // <-- Gets window at cursor (cursor is not necessarily at touch_screen_point)
// ...
bool touch_down = aura::Env::GetInstance()->is_touch_down();
bool touch_over_other_window =
!window || window->GetRootWindow() != root_window;
// Check that the cursor is over the window being dragged from. If not,
// don't start the drag because ::DoDragDrop will not do the drag.
if (!touch_down || touch_over_other_window) { // <-- Checks safe states before starting drag
return ui::PreferredDragOperation(
ui::DragDropTypes::DropEffectToDragOperation(DROPEFFECT_NONE));
}
desktop_host_->StartTouchDrag(touch_screen_point); // <-- Sends mouse left down and mouse move events
}
// ...
::DoDragDrop(...) // <-- Starts OS drag. Waits until drag finishes before continuing with funciton.
// ...
desktop_host_->FinishTouchDrag(touch_screen_point); // <-- Sends mouse left up event (to finish click) after drag completed
touch_down bypass
Due to an Aura bug, is_touch_down() can persistently return true and bypass the check after a single tap by user. The bypass persists until a full browser restart.
In our PoC, to reach the persistent touch down state, the user only needs to make a single tap. During the tap, the page opens a tab in the same window (and optionally closes the tab) within the touchstart event handler. After this, the touch down state is persistently true even after user finishes the tap, and attacks can be launched at any time until browser restart.
See Root Cause Analysis in comment for details, including other (potentially natural) ways to reach persistent touch down state.
touch_over_other_window bypass
This is a generally solid mitigation that mitigates most impacts against bubbles, menus, other browser windows, and non-browser windows. However, a race condition allows menus (e.g. browser menu, context menu) to bypass this check, despite being a different aura::Window.
If timed correctly, touch_over_other_window will be false when calling StartDragging() over a menu that is opening. The menu item will then be clicked as it opens. I am not sure exactly why this occurs, but it’s reliable enough for an attacker to use (usually works the first time, but in case of failure we can repeat infinitely with adjusted timing until we succeed). This means top-level menu options (i.e. not nested menu options) can be clicked on using StartDragging().
We can open and click on the browser menu (three dots on top-right of browser window) and context menu (renderer can call ShowContextMenu() to open it).
Same aura::Window drag/click allowed
As mentioned earlier, an aura::Window is conceptually different than what a user typically considers a window. If an interesting target exists within the same aura::Window, it can be clicked on because there is no mitigation against this.
We can click on any page contents, bookmarks bar, address bar, docked DevTools, and anything else within same aura::Window. We can also drag to bookmarks bar and address bar. We may also be able to drag to other UI elements.
IMPACTS
After the initial tap to trigger persistent touch down state, all attacks can be performed an infinite amount of times, in any combination, at any time (such as when user is away from computer).
Roughly divided by cause, or prerequsite of extension or chained vuln:
Impacts when chained with unfixed issue 443255991
- Sandbox escape with MOTW bypass: We can remove all user interaction requirements for issue 443255991’s PoCs. I’ll post details and chained PoCs in comments. Current PoC requires an extension.
Impacts due to same aura::Window allowed
- Create, click JS bookmarks (universal XSS): Web pages can create drags with
javascript:URLs, and the bookmarks bar will create/run bookmarks withjavascript:URLs (bookmarklets). Therefore, attacker can drag ajavascript:URL to the bookmarks bar, open a tab to the target website, and then click the attacker-created bookmark in the bookmarks bar. This gives us Universal XSS (UXSS) on anyhttp(s)://page and non-componentchrome-extension://page. WebUI pages and componentchrome-extension://pages are not allowed to run bookmarklets therefore they are not impacted. - Click on other page: We can open a tab to any web page and click anywhere on it. We can also click on any visible tab content, such as WebUI pages and docked DevTools.
- Click on side panel: We can click on any open side panel, such as Bookmarks side panel, GLIC side panel, extension side panels, etc.
Impacts due to touch_over_other_window bypass (menu race condition)
- Open Downloads page, run/open download (sandbox escape): We can click the browser menu button, then click “Downloads” to open the WebUI page. We can then click on downloaded items to run/open them and escape sandbox, bypassing user interaction requirements within browser.
- Open, click on split tab: Renderer can call
ShowContextMenu()to open context menu, click on “Open link in Split View”, and then click on the other side of the split tab. - Open Settings, change most settings: We can click the browser menu button, then click “Settings” to open the WebUI page. Most settings can be be changed, if they are buttons, toggles, dropdown menus, or otherwise clickable. This includes disabling Safe Browsing, and showing/hiding bookmarks bar (useful for UXSS impact).
- Open, click on DevTools: Renderer can call
ShowContextMenu()to open context menu, and then click on “Inspect” to open DevTools.
Impacts with malicious extension
An extension with no permissions can open any chrome:// URL that web pages cannot open. We can also enable internal debugging URLs in chrome://chrome-urls and open them.
- Open Extensions page, change extension settings (enable file access, incognito access): We can navigate to
chrome://extensions/?id={extId}, then click on the page to enable file access and incognito access. - Open site permissions, allow permissions: We can navigate to
chrome://settings/content/siteDetails?site={origin}and click to allow any listed permission, notably: camera, microphone, location, clipboard read, and local network access. - Toggle any chrome://flags: We can navigate to
chrome://flags, change any flags, and then restart the browser. This could be used to further exploit vulnerabilities in experimental Web/JS/GPU features or relax some security features. - Open DevTools for any target: We can open DevTools for any target through
chrome://inspect. We can then interact with the DevTools instance if it is docked to the same Aura window. With additional user interaction, there are significant impacts here that I’m still exploring (similar to issue 402791076: unrestricted CDP access, sandbox escape).
Prior impacts when chained with fixed issue 404000989
Prior to issue 404000989 being fixed in March 2025, a web page could drag/drop restricted URLs such as chrome:// or devtools:// URLs. The standalone vuln required drag and click user interactions to create bookmarks with restricted URLs and then navigate to them. With this drag/click vuln we can do both with one tap (or zero clicks after persistent touch down state). This would allow web pages to launch attacks that currently require extensions.
Prior impacts when chained with fixed issue 402791076
Prior to issue 402791076 being fixed in March 2025, we could gain XSS on DevTools. The standalone vuln required user clicking on a specific part of a page and then opening DevTools manually, but with this drag/click vuln we can do both with one tap (or zero clicks after persistent touch down state). This would allow further impacts, such as unrestricted CDP access, sandbox escape, and UXSS on any page (including WebUI pages).
BISECT
Can’t do proper bisect with custom build, but the initial fix was introduced in r1371525 (Oct 21, 2024). I started testing in ~r1508700 (August 29th, 2025) where it reproduced, although it likely reproduced since the fix on October 2024.
PROPOSED FIXES
One or more of these should mitigate most impacts:
-
Check position of renderer-provided drag start position in
StartDragAndDrop()to ensure it is within web page boundaries. Currently, code checks if the cursor position is within theaura::Window, which also allows parentaura::Windows such as the main browser window. The cursor check should remain for the functional reasons documented in code, but a security check should be added to validate the drag position itself. This does not mitigate all attacks (such as clicking on page context menu or on web page content), but mitigates most impacts. -
Ensure renderer that initiates drag is visible when calling
StartDragging(). This would prevent attacks on other tabs in the same window. -
Remove
event.HasNativeEvent()early break inEnvInputStateController::UpdateStateForTouchEvent. After doing some archeology on theHasNativeEvent()check, it was added in January 2013 as a fix for an X11 bug. The functionTouchEventIsGeneratedHack()is still present and used in X11 code, so it may still be needed to not regress the bug. If it’s still needed, we can add theHasNativeEvent()check only when using X11 (only Linux?) as a short-term fix. In theory the persistentis_touch_down()state may also occur in Linux, but should not have any security impacts there. -
Ensure only one drag is occuring at a time, and/or throttling drag starts. Some of the behaviors currently depend on multiple drags happening simultaneously or in close succession, so restricting active drags and throttling drags may make exploitation more difficult.
Additional mitigation:
- Prevent drag start from prerendered pages. I noticed that when we are in the persistent touch down state, we can initiate drags from a prerendered page. You can test this by triggering persistent touch down state, closing the page, and then typing/pasting the URL into address bar without navigating to the page. This may be useful in some attack scenarios.
VERSION
Chrome version: Verified with custom build based on 0ecd794f626107687c4844683a828953f49fb2b3 (Sept 11th, 2025)
Operating System: Windows 10
REPRODUCTION CASE
Patch to simulate compromised renderer
For all scenarios, apply attached patch and build Chromium.
The patch adds exploit logic to renderer:
- Adds calls to
StartDragging()andShowContextMenu()inthird_party/blink/renderer/core/dom/document.ccfor use by the exploit page. (This is loosely based on patch from issue 370856871.) - Bypasses popup blocker in
content/renderer/render_frame_impl.cc(this is same as earlier issue’s patch). - Disables multiple context menu check in
third_party/blink/renderer/core/page/context_menu_controller.ccwhich helps with context menu attacks.
The patch also adds logging and disables a DCHECK, only for development/debugging purposes:
- Disables DCHECK in
base/message_loop/message_pump_win.ccthat we hit during PoCs. You can alternatively build with DCHECKs disabled. - Adds logging in
content/browser/renderer_host/render_widget_host_view_aura.ccto show whenHide()is called (which would trigger persistent touch down state if tap is in progress). - Adds logging in
ui/aura/env.ccto showSetTouchDown(bool)calls. - Adds logging in
ui/aura/env_input_state_controller.ccto show state of touch tracking and processed touch events. - Adds logging in
ui/views/widget/desktop_aura/desktop_drag_drop_client_win.ccto show when operations occur, and iftouch_over_other_windowmitigation is hit. - Adds logging in
ui/views/widget/desktop_aura/desktop_window_tree_host_win.ccto show when mouse events are generated due to touch/drag operations.
Prerequisites
- A touch-enabled device or another way for OS/Aura to receive touch events (CDP should work).
- Compromised renderer (apply attached patch to simulate).
Web page scenario: Universal XSS (UXSS)
- Navigate to https://alesandroortiz.com/security/chromium/touch-drag-click-cr.html?mode=uxss
- Tap anywhere, or do nothing if using autorun.
Observed:
- If bookmarks bar is hidden: Renderer opens Settings page through browser menu, clicks on Appearance, and enables “Show bookmarks bar”. Then continues below:
- If bookmarks bar is shown: Renderer opens tab to target website, drags a
javascript:URL to bookmarks bar to create bookmarklet, then clicks bookmarklet to run the JS payload on target site.
Expected: Renderer cannot click on bookmarks bar. Drag cannot start from background tab. Renderer can still start a javascript: URL drag to create a bookmarklet, since this is supported functionality.
Web page scenario: Click on page
- Navigate to https://alesandroortiz.com/security/chromium/touch-drag-click-cr.html?mode=click-page
- Tap anywhere, or do nothing if using autorun.
Observed: Renderer opens tab to target website, and performs multiple clicks on target page. In this case, the clicks trigger navigations, but any action triggerable with clicks is possible.
Expected: Renderer cannot click on page on another tab. Drag cannot start from background tab.
Web page scenario: Open and click on split tab
- Navigate to https://alesandroortiz.com/security/chromium/touch-drag-click-cr.html?mode=click-split-tab
- Tap anywhere, or do nothing if using autorun.
Observed: Renderer opens split tab to target website by opening and clicking on context menu (this may take several attempts). After opening split tab, renderer performs multiple clicks on the other split tab. In this case, the clicks trigger navigations, but any action triggerable with clicks is possible.
Expected: Renderer cannot click on context menu. Renderer cannot click on the other split tab.
Web page scenario: Open Settings and disable Safe Browsing
- Navigate to https://alesandroortiz.com/security/chromium/touch-drag-click-cr.html?mode=open-settings-disable-safebrowsing
- Tap anywhere, or do nothing if using autorun.
Observed: Renderer opens Settings page by clicking on browser menu button, and then clicking “Settings” menu item. Renderer then performs multiple clicks on Settings page to disable Safe Browsing.
Expected: Renderer cannot click on browser menu button nor on browser menu items. Renderer cannot click on page on another tab. Drag cannot start from background tab.
Web page scenario: pwn all the things! 💥
Runs all web page scenarios. Use autorun for maximum pwnage.
- Navigate to https://alesandroortiz.com/security/chromium/touch-drag-click-cr.html?mode=combo&autorun=1
- Do nothing. :)
Observed: After one tap, renderer can make multiple StartDragging() calls anytime until browser restart. This means attacker can run any scenario with no user interaction.
Expected: After one tap, renderer can only make a single StartDragging() call if drag isn’t currently occurring. is_touch_down() is true only when a physical or otherwise safe touch event is in progress.
Credit Information
Reporter credit: Alesandro Ortiz https://AlesandroOrtiz.com
- https://AlesandroOrtiz.com
- https://alesandroortiz.com/security/chromium/touch-drag-click-cr.html?mode=click-page
- https://alesandroortiz.com/security/chromium/touch-drag-click-cr.html?mode=click-split-tab
- https://alesandroortiz.com/security/chromium/touch-drag-click-cr.html?mode=combo&autorun=1
- https://alesandroortiz.com/security/chromium/touch-drag-click-cr.html?mode=open-settings-disable-safebrowsing
- https://alesandroortiz.com/security/chromium/touch-drag-click-cr.html?mode=uxss
- https://chromium-review.googlesource.com/c/chromium/src/+/5943487
- https://crbug.com/169177
- https://crrev.com/ea7f83787239ae91db4ec297225905b0c8a5a595
- https://issuetracker.google.com/issues/370856871
- https://issuetracker.google.com/issues/402791076
- https://issuetracker.google.com/issues/404000989
- https://issuetracker.google.com/issues/443255991
- https://source.chromium.org/chromium/chromium/src/+/main:content/browser/web_contents/web_contents_view_aura.cc;l=1108;drc=181e7a80687c55d02e2e74d7d5db3d75ad53c0de
- https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/public/mojom/frame/frame.mojom;l=567;drc=f98c3dd3dc0193d10d5cfe549c98e2651244d567
- https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/public/mojom/frame/frame.mojom;l=797;drc=f98c3dd3dc0193d10d5cfe549c98e2651244d567
- https://source.chromium.org/chromium/chromium/src/+/main:ui/aura/env.h;l=97;drc=89f6321d4c72ccc4b16de1d3e700e66b878e624b
- https://source.chromium.org/chromium/chromium/src/+/main:ui/aura/env_input_state_controller.cc;l=46-60;drc=181e7a80687c55d02e2e74d7d5db3d75ad53c0de
- https://source.chromium.org/chromium/chromium/src/+/main:ui/aura/env_input_state_controller.cc;l=53;drc=e4f1aef5f3ec30a28950d766612cc2c04c822c71
- https://source.chromium.org/chromium/chromium/src/+/main:ui/events/x/events_x_utils.cc;l=109;drc=ffec0aed60ca494f2a228d0deba095d43ad8748b
- https://source.chromium.org/chromium/chromium/src/+/main:ui/views/widget/desktop_aura/desktop_drag_drop_client_win.cc;l=68;drc=181e7a80687c55d02e2e74d7d5db3d75ad53c0de
- https://source.chromium.org/chromium/chromium/src/+/main:ui/views/widget/desktop_aura/desktop_window_tree_host_win.cc;l=151-167;drc=0540bffebe0ced2a9ad02793b159a8af1a930b31