CVE-2026-10972
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
DestructionWindowDelegateui/ozone/platform/x11/test/x11_window_unittest.cc |
modified | |
TEST_Fui/ozone/platform/x11/test/x11_window_unittest.cc |
modified | |
ifui/ozone/platform/x11/x11_window.cc |
modified |
Files Changed
ui/ozone/platform/x11/test/x11_window_unittest.ccui/ozone/platform/x11/x11_window.ccui/ozone/platform/x11/x11_window.h
Patch
From ddb1091e16399f030e5720a906ef5ac58c0747a8 Mon Sep 17 00:00:00 2001 From: Tom Anderson <[email protected]> Date: Thu, 14 May 2026 11:46:41 -0700 Subject: [PATCH] [Ozone/X11] Fix UAF in DispatchUiEvent during synchronous destruction A potential Use-After-Free (UAF) vulnerability was identified in X11Window::DispatchUiEvent. Synchronous destruction of the X11Window object during HandleEvent processing (e.g., due to activation changes) could leave DispatchUiEvent with a dangling 'this' pointer. This CL adds a base::WeakPtr liveness check after HandleEvent to prevent further member accesses if the window is destroyed. Fixed: 513006660 Change-Id: I4987be20f8288ff8215aebcce789529b056fe2e0 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7848608 Commit-Queue: Thomas Anderson <[email protected]> Reviewed-by: Lei Zhang <[email protected]> Cr-Commit-Position: refs/heads/main@{#1630706} --- diff --git a/ui/ozone/platform/x11/test/x11_window_unittest.cc b/ui/ozone/platform/x11/test/x11_window_unittest.cc index 7e760b8f..2b51bc07 100644 --- a/ui/ozone/platform/x11/test/x11_window_unittest.cc +++ b/ui/ozone/platform/x11/test/x11_window_unittest.cc @@ -189,6 +189,24 @@ } }; +class DestructionWindowDelegate : public TestPlatformWindowDelegate { + public: + DestructionWindowDelegate() = default; + ~DestructionWindowDelegate() override = default; + + void set_window(std::unique_ptr<X11Window> window) { + window_ = std::move(window); + } + + void OnActivationChanged(bool active) override { + // Synchronously destroy the window. + window_.reset(); + } + + private: + std::unique_ptr<X11Window> window_; +}; + // Returns the list of rectangles which describe |window|'s bounding region via // the X shape extension. std::vector<gfx::Rect> GetShapeRects(x11::Window window) { @@ -521,4 +539,35 @@ EXPECT_EQ(delegate.state(), PlatformWindowState::kNormal); } +// Tests that synchronous destruction of the window during event dispatching +// does not cause a UAF. +TEST_F(X11WindowTest, SynchronousDestructionDuringEventDispatch) { + auto delegate = std::make_unique<DestructionWindowDelegate>(); + constexpr gfx::Rect bounds(10, 10, 100, 100); + auto window = CreateX11Window(delegate.get(), bounds, nullptr); + X11Window* window_ptr = window.get(); + delegate->set_window(std::move(window)); + + // Create a CrossingEvent (EnterNotify) that will trigger OnActivationChanged. + x11::CrossingEvent enter_event; + enter_event.opcode = x11::CrossingEvent::EnterNotify; + enter_event.event = static_cast<x11::Window>(delegate->widget()); + enter_event.root = x11::Connection::Get()->default_root(); + enter_event.same_screen_focus = 1; // CROSSING_FLAG_FOCUS + enter_event.mode = x11::NotifyMode::Normal; + enter_event.detail = x11::NotifyDetail::Ancestor; + + x11::Event xev(false, std::move(enter_event)); + + MouseEvent mouse_event(ui::EventType::kMouseEntered, gfx::Point(), + gfx::Point(), base::TimeTicks(), 0, 0); + + // This should trigger HandleEvent, which triggers OnCrossingEvent, + // which triggers AfterActivationStateChanged, which triggers + // OnActivationChanged(true), which destroys the window. + // DispatchUiEvent will then continue and should return safely due to the + // liveness check. + window_ptr->DispatchUiEvent(&mouse_event, xev); +} + } // namespace ui diff --git a/ui/ozone/platform/x11/x11_window.cc b/ui/ozone/platform/x11/x11_window.cc index b4c92dd..eddc5aee 100644 --- a/ui/ozone/platform/x11/x11_window.cc +++ b/ui/ozone/platform/x11/x11_window.cc @@ -1382,7 +1382,11 @@ DCHECK(window_manager); // Process X11-specific bits + auto weak_this = weak_ptr_factory_.GetWeakPtr(); HandleEvent(xev); + if (!weak_this) { + return; + } x11::Event last_xev; std::unique_ptr<ui::Event> last_motion; diff --git a/ui/ozone/platform/x11/x11_window.h b/ui/ozone/platform/x11/x11_window.h index 6e947e3..9852bf6 100644 --- a/ui/ozone/platform/x11/x11_window.h +++ b/ui/ozone/platform/x11/x11_window.h @@ -169,9 +169,11 @@ private: FRIEND_TEST_ALL_PREFIXES(X11WindowTest, Shape); - FRIEND_TEST_ALL_PREFIXES(X11WindowTest, WindowManagerTogglesFullscreen); + FRIEND_TEST_ALL_PREFIXES(X11WindowTest, + SynchronousDestructionDuringEventDispatch); FRIEND_TEST_ALL_PREFIXES(X11WindowTest, ToggleMinimizePropogateToPlatformWindowDelegate); + FRIEND_TEST_ALL_PREFIXES(X11WindowTest, WindowManagerTogglesFullscreen); void UpdateDecorationInsets();
Regression Test / PoC
diff --git a/ui/ozone/platform/x11/test/x11_window_unittest.cc b/ui/ozone/platform/x11/test/x11_window_unittest.cc
index 7e760b8f..2b51bc07 100644
--- a/ui/ozone/platform/x11/test/x11_window_unittest.cc
+++ b/ui/ozone/platform/x11/test/x11_window_unittest.cc
@@ -189,6 +189,24 @@
}
};
+class DestructionWindowDelegate : public TestPlatformWindowDelegate {
+ public:
+ DestructionWindowDelegate() = default;
+ ~DestructionWindowDelegate() override = default;
+
+ void set_window(std::unique_ptr<X11Window> window) {
+ window_ = std::move(window);
+ }
+
+ void OnActivationChanged(bool active) override {
+ // Synchronously destroy the window.
+ window_.reset();
+ }
+
+ private:
+ std::unique_ptr<X11Window> window_;
+};
+
// Returns the list of rectangles which describe |window|'s bounding region via
// the X shape extension.
std::vector<gfx::Rect> GetShapeRects(x11::Window window) {
@@ -521,4 +539,35 @@
EXPECT_EQ(delegate.state(), PlatformWindowState::kNormal);
}
+// Tests that synchronous destruction of the window during event dispatching
+// does not cause a UAF.
+TEST_F(X11WindowTest, SynchronousDestructionDuringEventDispatch) {
+ auto delegate = std::make_unique<DestructionWindowDelegate>();
+ constexpr gfx::Rect bounds(10, 10, 100, 100);
+ auto window = CreateX11Window(delegate.get(), bounds, nullptr);
+ X11Window* window_ptr = window.get();
+ delegate->set_window(std::move(window));
+
+ // Create a CrossingEvent (EnterNotify) that will trigger OnActivationChanged.
+ x11::CrossingEvent enter_event;
+ enter_event.opcode = x11::CrossingEvent::EnterNotify;
+ enter_event.event = static_cast<x11::Window>(delegate->widget());
+ enter_event.root = x11::Connection::Get()->default_root();
+ enter_event.same_screen_focus = 1; // CROSSING_FLAG_FOCUS
+ enter_event.mode = x11::NotifyMode::Normal;
+ enter_event.detail = x11::NotifyDetail::Ancestor;
+
+ x11::Event xev(false, std::move(enter_event));
+
+ MouseEvent mouse_event(ui::EventType::kMouseEntered, gfx::Point(),
+ gfx::Point(), base::TimeTicks(), 0, 0);
+
+ // This should trigger HandleEvent, which triggers OnCrossingEvent,
+ // which triggers AfterActivationStateChanged, which triggers
+ // OnActivationChanged(true), which destroys the window.
+ // DispatchUiEvent will then continue and should return safely due to the
+ // liveness check.
+ window_ptr->DispatchUiEvent(&mouse_event, xev);
+}
+
} // namespace ui
Original Bug Report
Potential Use-After-Free in X11Window::DispatchUiEvent on Linux
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 without the Chrome Security team. 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 Linux/X11 Ozone platform implementation. Synchronous destruction of the X11Window object during event processing can leave the dispatch loop with a dangling pointer, resulting in memory corruption.
Affected files:
ui/ozone/platform/x11/x11_window.cc
Estimated timestamp from git blame: 2020-02-04
Summary
A potential Use-After-Free (UAF) vulnerability has been identified in X11Window::DispatchUiEvent within the Linux/X11 Ozone platform implementation. The issue arises when an incoming X11 event triggers a sequence of notifications that synchronously destroy the X11Window instance. The function continues to access member variables and invokes virtual methods via the freed this pointer because it lacks a liveness check after the event handling call.
Technical Details
In ui/ozone/platform/x11/x11_window.cc, the DispatchUiEvent function handles incoming X11 events. At line 1385, it calls HandleEvent(xev):
void X11Window::DispatchUiEvent(ui::Event* event, const x11::Event& xev) {
// ...
HandleEvent(xev); // [1] Possible synchronous destruction
x11::Event last_xev;
std::unique_ptr<ui::Event> last_motion;
if (CoalesceEventsIfNeeded(xev, event->type(), &last_xev)) { // [2] UAF read
// ...
}
// ...
UpdateWMUserTime(event); // [3] UAF read
DispatchEventFromNativeUiEvent(
event, base::BindOnce(&PlatformWindowDelegate::DispatchEvent,
base::Unretained(platform_window_delegate()))); // [4] UAF read + virtual call
}
Synchronous destruction can occur when processing an activation-related event (e.g., x11::CrossingEvent). Based on the code, the following sequence of events is possible:
HandleEventrecognizes aCrossingEventand callsOnCrossingEvent.OnCrossingEventupdates the window’s activation state and callsAfterActivationStateChanged().AfterActivationStateChanged()detects a change inIsActive()and notifies thePlatformWindowDelegate(typically an instance ofDesktopWindowTreeHostPlatform) viaOnXWindowIsActiveChanged().DesktopWindowTreeHostPlatform::OnActivationChanged()notifies the associatedDesktopNativeWidgetAura.- The notification propagates to the
Widget, which informs its observers. If the widget is a popup or bubble withclose_on_deactivateenabled, an observer may trigger synchronous window closure (e.g., viaWidget::CloseNow()). - The destruction of the
Widgetleads to the immediate deletion of theDesktopWindowTreeHostPlatformand its ownedX11Windowinstance.
When HandleEvent returns to DispatchUiEvent, the this pointer is dangling. The function then performs multiple UAF reads, including calls to CoalesceEventsIfNeeded and UpdateWMUserTime. Most critically, at line 1427, it reads the platform_window_delegate_ pointer from the freed memory and uses it in a base::BindOnce callback that performs a virtual call (DispatchEvent).
Potential Attack Vector
A compromised GPU process could potentially trigger this vulnerability. On Linux configurations using X11, the GPU process maintains a connection to the X server. An attacker can use this connection to send synthetic CrossingEvent (LeaveNotify) messages to the window IDs of sensitive windows in the Browser process. By targeting windows known to close synchronously upon losing focus, the attacker can trigger the UAF. This represents a potential sandbox escape from the GPU process to the Browser process.
MiraclePtr Status
This vulnerability is not protected by MiraclePtr (BackupRefPtr). The issue is a Use-After-Free of the X11Window object itself via a raw this pointer. While some member variables like platform_window_delegate_ are raw_ptr, the initial UAF occurs when reading the pointer value from the freed X11Window allocation, which MiraclePtr does not prevent.
Suggested Fix
Apply a liveness check using a base::WeakPtr after the call to HandleEvent(xev) in DispatchUiEvent. This pattern is already utilized in DesktopWindowTreeHostPlatform::OnActivationChanged to guard against similar synchronous destruction scenarios.
void X11Window::DispatchUiEvent(ui::Event* event, const x11::Event& xev) {
// ...
auto weak_this = weak_ptr_factory_.GetWeakPtr();
HandleEvent(xev);
if (!weak_this) {
return;
}
// ... proceed with subsequent member accesses
}
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.