CVE-2026-10895
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifui/ozone/platform/wayland/host/wayland_event_source.cc |
modified | |
TEST_Pui/ozone/platform/wayland/host/wayland_event_source_unittest.cc |
modified |
Files Changed
ui/ozone/platform/wayland/host/wayland_event_source.ccui/ozone/platform/wayland/host/wayland_event_source_unittest.cc
Patch
From 46c5d9828122ed474fc6ae51b50116edad4597db Mon Sep 17 00:00:00 2001 From: Tom Anderson <[email protected]> Date: Fri, 15 May 2026 14:37:56 -0700 Subject: [PATCH] wayland: Fix UAF in OnTabletToolProximityIn Synchronous event dispatch during OnTabletToolProximityOut can trigger a nested message loop, which may result in the destruction of the WaylandWindow. Using a raw pointer after this call leads to a Use-After-Free. This CL fixes this by using a WeakPtr to re-validate the window's existence before proceeding with the event dispatch. Fixed: 513454018 Change-Id: I4c4bdf2ff55b3d21356514a1350b85c58c4fbeed Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7851764 Commit-Queue: Thomas Anderson <[email protected]> Reviewed-by: Lei Zhang <[email protected]> Cr-Commit-Position: refs/heads/main@{#1631573} --- diff --git a/ui/ozone/platform/wayland/host/wayland_event_source.cc b/ui/ozone/platform/wayland/host/wayland_event_source.cc index 5bd7a3f..0f96ec1 100644 --- a/ui/ozone/platform/wayland/host/wayland_event_source.cc +++ b/ui/ozone/platform/wayland/host/wayland_event_source.cc @@ -584,9 +584,15 @@ const PointerDetails& details, base::TimeTicks time) { WaylandWindow* old_focus = tablet_tool_focused_window_.get(); + base::WeakPtr<WaylandWindow> window_weak = window->AsWeakPtr(); if (old_focus && old_focus != window) { OnTabletToolProximityOut(time); } + + if (!window_weak) { + return; + } + tablet_tool_focused_window_ = window->AsWeakPtr(); tablet_tool_location_ = location; diff --git a/ui/ozone/platform/wayland/host/wayland_event_source_unittest.cc b/ui/ozone/platform/wayland/host/wayland_event_source_unittest.cc index 78d3690..7082671 100644 --- a/ui/ozone/platform/wayland/host/wayland_event_source_unittest.cc +++ b/ui/ozone/platform/wayland/host/wayland_event_source_unittest.cc @@ -371,6 +371,39 @@ EXPECT_TRUE(pointer_delegate_->IsPointerButtonPressed(EF_RIGHT_MOUSE_BUTTON)); } +TEST_P(WaylandEventSourceTest, TabletToolProximityInUAF) { + auto* event_source = connection_->event_source(); + + // Create two windows. + MockWaylandPlatformWindowDelegate delegate1(connection_.get()); + auto window1 = CreateWaylandWindowWithParams(PlatformWindowType::kWindow, + kDefaultBounds, &delegate1); + + MockWaylandPlatformWindowDelegate delegate2(connection_.get()); + auto window2 = CreateWaylandWindowWithParams(PlatformWindowType::kWindow, + kDefaultBounds, &delegate2); + + // Set `window1` as focused. + event_source->OnTabletToolProximityIn(window1.get(), gfx::PointF(), {}, + base::TimeTicks::Now()); + + // Set up `delegate1` to destroy `window2` when it receives `kMouseExited`. + // When `window1` is the `tablet_tool_focused_window_`, calling + // `OnTabletToolProximityIn(window2)` will call `OnTabletToolProximityOut()`, + // which dispatches `kMouseExited` to `window1`. + + EXPECT_CALL(delegate1, DispatchEvent(::testing::_)) + .WillOnce([&](Event* event) { + if (event->type() == EventType::kMouseExited) { + window2.reset(); + } + }); + + // This should not crash. + event_source->OnTabletToolProximityIn(window2.get(), gfx::PointF(), {}, + base::TimeTicks::Now()); +} + INSTANTIATE_TEST_SUITE_P( EventsDispatchPolicyTest, WaylandEventSourceTest,
Regression Test / PoC
diff --git a/ui/ozone/platform/wayland/host/wayland_event_source_unittest.cc b/ui/ozone/platform/wayland/host/wayland_event_source_unittest.cc
index 78d3690..7082671 100644
--- a/ui/ozone/platform/wayland/host/wayland_event_source_unittest.cc
+++ b/ui/ozone/platform/wayland/host/wayland_event_source_unittest.cc
@@ -371,6 +371,39 @@
EXPECT_TRUE(pointer_delegate_->IsPointerButtonPressed(EF_RIGHT_MOUSE_BUTTON));
}
+TEST_P(WaylandEventSourceTest, TabletToolProximityInUAF) {
+ auto* event_source = connection_->event_source();
+
+ // Create two windows.
+ MockWaylandPlatformWindowDelegate delegate1(connection_.get());
+ auto window1 = CreateWaylandWindowWithParams(PlatformWindowType::kWindow,
+ kDefaultBounds, &delegate1);
+
+ MockWaylandPlatformWindowDelegate delegate2(connection_.get());
+ auto window2 = CreateWaylandWindowWithParams(PlatformWindowType::kWindow,
+ kDefaultBounds, &delegate2);
+
+ // Set `window1` as focused.
+ event_source->OnTabletToolProximityIn(window1.get(), gfx::PointF(), {},
+ base::TimeTicks::Now());
+
+ // Set up `delegate1` to destroy `window2` when it receives `kMouseExited`.
+ // When `window1` is the `tablet_tool_focused_window_`, calling
+ // `OnTabletToolProximityIn(window2)` will call `OnTabletToolProximityOut()`,
+ // which dispatches `kMouseExited` to `window1`.
+
+ EXPECT_CALL(delegate1, DispatchEvent(::testing::_))
+ .WillOnce([&](Event* event) {
+ if (event->type() == EventType::kMouseExited) {
+ window2.reset();
+ }
+ });
+
+ // This should not crash.
+ event_source->OnTabletToolProximityIn(window2.get(), gfx::PointF(), {},
+ base::TimeTicks::Now());
+}
+
INSTANTIATE_TEST_SUITE_P(
EventsDispatchPolicyTest,
WaylandEventSourceTest,
Original Bug Report
Potential Use-After-Free in WaylandEventSource::OnTabletToolProximityIn via nested loops
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 Use-After-Free (UAF) vulnerability exists in the browser process of Chromium on Linux Wayland. Re-entrant event dispatching during tablet tool proximity changes can trigger a nested message loop, during which the target window may be destroyed, leaving a dangling raw pointer. Subsequent virtual function calls on this pointer can lead to an unsandboxed browser process compromise.
Affected files:
ui/ozone/platform/wayland/host/wayland_event_source.ccui/ozone/platform/wayland/host/wayland_window.hui/ozone/platform/wayland/host/wayland_tablet_tool.cc
Estimated timestamp from git blame: 2025-06-27
Description
A potential Use-After-Free (UAF) vulnerability has been identified in WaylandEventSource::OnTabletToolProximityIn within the Ozone/Wayland implementation. The issue arises because the function uses a raw pointer to a WaylandWindow after performing a synchronous operation that can trigger a nested message loop.
Technical Analysis
The vulnerability is located in ui/ozone/platform/wayland/host/wayland_event_source.cc:
void WaylandEventSource::OnTabletToolProximityIn(WaylandWindow* window,
const gfx::PointF& location,
const PointerDetails& details,
base::TimeTicks time) {
WaylandWindow* old_focus = tablet_tool_focused_window_.get();
if (old_focus && old_focus != window) {
OnTabletToolProximityOut(time); // [1]
}
tablet_tool_focused_window_ = window->AsWeakPtr(); // [2]
...
SetTargetAndDispatchEvent(&event, window); // [3]
}
- Re-entrancy [1]: When focus changes,
OnTabletToolProximityOutdispatches akMouseExitedevent to the previous window. In Chromium, synchronous event dispatch can trigger a nested message loop (e.g., via a modal dialog or context menu). - Object Destruction: Many nested loops in the Wayland backend are started with
kNestableTasksAllowed. While the loop is running, the browser process can process a window close request (e.g., initiated by a renderer viawindow.close()). This results in the destruction of theWaylandWindowobject. - Use-After-Free [2, 3]: When the nested loop returns, execution proceeds to line 590
[2]. The code callswindow->AsWeakPtr(). SinceAsWeakPtris a pure virtual function inWaylandWindow, this is a virtual call on a potentially freed object. Further usage atSetTargetAndDispatchEvent[3]results in additional virtual calls (e.g.,target->GetParentTarget()).
Because the window pointer is a function parameter on the stack, MiraclePtr (BackupRefPtr) does not provide protection here, as it primarily targets class members of type raw_ptr<>.
Potential Attack Scenario
- The attacker convinces a user to visit a malicious site on a Linux Wayland system with a graphics tablet.
- The site opens a popup (Window B).
- The attacker ensures the main window (Window A) will trigger a nested loop (e.g., a modal dialog) on a
MouseExitedevent. - The attacker triggers a tablet proximity event for Window B while Window A has focus.
OnTabletToolProximityIncallsOnTabletToolProximityOut, triggering the nested loop in Window A.- While the loop is active, the renderer closes Window B.
- The browser returns from the loop and dereferences the dangling raw pointer to Window B.
Note: These steps are based on source code analysis; no functional proof-of-concept has been executed by our automated tools.
Suggested Fix
The function should verify the continued existence of the window after the synchronous call. This can be achieved by using a WeakPtr for the window parameter or by re-retrieving the window state after OnTabletToolProximityOut returns.
base::WeakPtr<WaylandWindow> window_weak = window->AsWeakPtr();
if (old_focus && old_focus != window) {
OnTabletToolProximityOut(time);
}
if (!window_weak) {
return;
}
Evaluated with Chrome root at commit: b3153093eb3c78c3e88ccf562bcbc20437a04b0e
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.