CVE-2026-10902
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifui/ozone/platform/wayland/host/wayland_toplevel_window.cc |
modified | |
TEST_Pui/ozone/platform/wayland/host/wayland_window_unittest.cc |
modified |
Files Changed
ui/ozone/platform/wayland/host/wayland_toplevel_window.ccui/ozone/platform/wayland/host/wayland_window_unittest.cc
Patch
From dadcd96cf1f88f544995631521471cf64b90a7ff Mon Sep 17 00:00:00 2001 From: Tom Anderson <[email protected]> Date: Wed, 27 May 2026 17:37:03 -0700 Subject: [PATCH] [Ozone/Wayland] Fix UAF in HandleToplevelConfigure via OnActivationChanged During HandleToplevelConfigure, calling HandleToplevelConfigureWithOrigin can trigger OnActivationChanged on the delegate. Since views/widget layer handling can synchronously destroy the widget and thereby the platform window itself, calling subsequent functions on `this` results in a Use-After-Free (UAF). This CL adds WeakPtr guards to both HandleToplevelConfigure and HandleToplevelConfigureWithOrigin to prevent referencing the object after it has been synchronously closed. Fixed: 517046249 Change-Id: Ic7d3ac624033af9fc07d7f73a98e4307e803492d Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7880866 Commit-Queue: Thomas Anderson <[email protected]> Commit-Queue: Thomas Lukaszewicz <[email protected]> Auto-Submit: Thomas Anderson <[email protected]> Reviewed-by: Thomas Lukaszewicz <[email protected]> Cr-Commit-Position: refs/heads/main@{#1637352} --- diff --git a/ui/ozone/platform/wayland/host/wayland_toplevel_window.cc b/ui/ozone/platform/wayland/host/wayland_toplevel_window.cc index 6c3e6a341..fe83d31 100644 --- a/ui/ozone/platform/wayland/host/wayland_toplevel_window.cc +++ b/ui/ozone/platform/wayland/host/wayland_toplevel_window.cc @@ -465,7 +465,15 @@ int32_t width_dip, int32_t height_dip, const WindowStates& window_states) { + // HandleToplevelConfigureWithOrigin() calls into the delegate + // (OnActivationChanged et al.), which the views layer documents may + // synchronously close the widget and destroy this platform window. See + // DesktopWindowTreeHostPlatform::OnActivationChanged(). + auto alive = weak_ptr_factory_.GetWeakPtr(); HandleToplevelConfigureWithOrigin(0, 0, width_dip, height_dip, window_states); + if (!alive) { + return; + } UpdateSessionStateIfNeeded(); } @@ -566,7 +574,11 @@ SetRestoredBoundsInDIP(GetBoundsInDIP()); } + auto alive = weak_ptr_factory_.GetWeakPtr(); UpdateActivationState(); + if (!alive) { + return; + } if (prev_suspended != is_suspended_) { frame_manager()->OnWindowSuspensionChanged(); } diff --git a/ui/ozone/platform/wayland/host/wayland_window_unittest.cc b/ui/ozone/platform/wayland/host/wayland_window_unittest.cc index d0afdc8..7aea341 100644 --- a/ui/ozone/platform/wayland/host/wayland_window_unittest.cc +++ b/ui/ozone/platform/wayland/host/wayland_window_unittest.cc @@ -5428,6 +5428,45 @@ EXPECT_EQ(window_->applied_state(), previous_state); } +// Regression POC: WaylandToplevelWindow::HandleToplevelConfigure() continues to +// use `this` after delegate()->OnActivationChanged() synchronously destroys the +// platform window. This mirrors the production path documented at +// DesktopWindowTreeHostPlatform::OnActivationChanged where +// HandleActivationChanged() can synchronously close the widget, which in turn +// calls SetPlatformWindow(nullptr) and frees the WaylandToplevelWindow while +// the xdg_toplevel.configure handler is still on the stack. +TEST_P(WaylandWindowTest, HandleToplevelConfigureSyncCloseOnDeactivate) { + // After SetUp(), |window_| has already received an activated configure, so + // is_xdg_active_ == is_active_ == true. + ASSERT_TRUE(window_); + WaylandWindow* raw_window = window_.get(); + + // Simulate a delegate that destroys the platform window inside + // OnActivationChanged(false) — exactly what happens in production when a + // WidgetObserver calls Widget::CloseNow() on deactivation, leading to + // DesktopWindowTreeHostPlatform::OnClosed -> SetPlatformWindow(nullptr). + EXPECT_CALL(delegate_, OnActivationChanged(Eq(false))) + .WillOnce(InvokeWithoutArgs([this]() { window_.reset(); })); + + // Don't try to talk to the server after the window has been torn down + // mid-dispatch. + DisableSyncOnTearDown(); + + // Drive the standard xdg_toplevel.configure entry point with the activated + // bit cleared. This calls HandleToplevelConfigureWithOrigin() -> + // UpdateActivationState() -> delegate()->OnActivationChanged(false), which + // (via the mock above) frees `this`. Control then returns to + // HandleToplevelConfigure:469 which calls UpdateSessionStateIfNeeded() on + // the freed object. + WaylandWindow::WindowStates deactivated_states; + deactivated_states.is_activated = false; + raw_window->HandleToplevelConfigure(800, 600, deactivated_states); + + // If we got here without ASAN reporting a heap-use-after-free, the bug is + // fixed. + EXPECT_FALSE(window_); +} + INSTANTIATE_TEST_SUITE_P(XdgVersionStableTest, WaylandWindowTest, Values(wl::ServerConfig{}));
Regression Test / PoC
diff --git a/ui/ozone/platform/wayland/host/wayland_window_unittest.cc b/ui/ozone/platform/wayland/host/wayland_window_unittest.cc
index d0afdc8..7aea341 100644
--- a/ui/ozone/platform/wayland/host/wayland_window_unittest.cc
+++ b/ui/ozone/platform/wayland/host/wayland_window_unittest.cc
@@ -5428,6 +5428,45 @@
EXPECT_EQ(window_->applied_state(), previous_state);
}
+// Regression POC: WaylandToplevelWindow::HandleToplevelConfigure() continues to
+// use `this` after delegate()->OnActivationChanged() synchronously destroys the
+// platform window. This mirrors the production path documented at
+// DesktopWindowTreeHostPlatform::OnActivationChanged where
+// HandleActivationChanged() can synchronously close the widget, which in turn
+// calls SetPlatformWindow(nullptr) and frees the WaylandToplevelWindow while
+// the xdg_toplevel.configure handler is still on the stack.
+TEST_P(WaylandWindowTest, HandleToplevelConfigureSyncCloseOnDeactivate) {
+ // After SetUp(), |window_| has already received an activated configure, so
+ // is_xdg_active_ == is_active_ == true.
+ ASSERT_TRUE(window_);
+ WaylandWindow* raw_window = window_.get();
+
+ // Simulate a delegate that destroys the platform window inside
+ // OnActivationChanged(false) — exactly what happens in production when a
+ // WidgetObserver calls Widget::CloseNow() on deactivation, leading to
+ // DesktopWindowTreeHostPlatform::OnClosed -> SetPlatformWindow(nullptr).
+ EXPECT_CALL(delegate_, OnActivationChanged(Eq(false)))
+ .WillOnce(InvokeWithoutArgs([this]() { window_.reset(); }));
+
+ // Don't try to talk to the server after the window has been torn down
+ // mid-dispatch.
+ DisableSyncOnTearDown();
+
+ // Drive the standard xdg_toplevel.configure entry point with the activated
+ // bit cleared. This calls HandleToplevelConfigureWithOrigin() ->
+ // UpdateActivationState() -> delegate()->OnActivationChanged(false), which
+ // (via the mock above) frees `this`. Control then returns to
+ // HandleToplevelConfigure:469 which calls UpdateSessionStateIfNeeded() on
+ // the freed object.
+ WaylandWindow::WindowStates deactivated_states;
+ deactivated_states.is_activated = false;
+ raw_window->HandleToplevelConfigure(800, 600, deactivated_states);
+
+ // If we got here without ASAN reporting a heap-use-after-free, the bug is
+ // fixed.
+ EXPECT_FALSE(window_);
+}
+
INSTANTIATE_TEST_SUITE_P(XdgVersionStableTest,
WaylandWindowTest,
Values(wl::ServerConfig{}));
Original Bug Report
Potential UAF in WaylandToplevelWindow::HandleToplevelConfigure via OnActivationChanged
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 WaylandToplevelWindow::HandleToplevelConfigure due to synchronous destruction of the window object during activation state callbacks. When the delegate callback synchronously closes the widget, this is deleted, resulting in a UAF when the function subsequently calls UpdateSessionStateIfNeeded(). This potential issue affects the browser process on Linux/Wayland and is not protected by MiraclePtr.
Affected files:
ui/ozone/platform/wayland/host/wayland_toplevel_window.cc
Estimated timestamp from git blame: 2025-03-27
Summary
There is a potential Use-After-Free (UAF) vulnerability in WaylandToplevelWindow::HandleToplevelConfigure inside the desktop Linux/Wayland browser process. When handling window configuration events, the platform window delegate can synchronously close and destroy the widget and its associated platform window. This deletes the WaylandToplevelWindow object (this) while the execution is still inside one of its member functions, leading to subsequent member variable accesses and method calls on the freed memory.
Root Cause Analysis
In ui/ozone/platform/wayland/host/wayland_toplevel_window.cc, the method WaylandToplevelWindow::HandleToplevelConfigure is implemented as follows:
void WaylandToplevelWindow::HandleToplevelConfigure(
int32_t width_dip, int32_t height_dip,
const WindowStates& window_states) {
HandleToplevelConfigureWithOrigin(0, 0, width_dip, height_dip, window_states); // [1] Can synchronously delete `this`
UpdateSessionStateIfNeeded(); // [2] Use-After-Free
}
-
Synchronous Deletion: Inside
HandleToplevelConfigureWithOrigin[1], the methodUpdateActivationState()is called (line 569). If the activation state changes, it notifies the delegate (line 459):delegate()->OnActivationChanged(is_active_);On Wayland, the platform window delegate is
DesktopWindowTreeHostPlatform. As documented indesktop_window_tree_host_platform.cc, handling activation changes can synchronously close and destroy the widget and thereby its ownedPlatformWindow(theWaylandToplevelWindowobject):// HandleActivationChanged() notifications can cause the widget to be // synchronously closed. auto weak_this = weak_factory_.GetWeakPtr(); desktop_native_widget_aura_->HandleActivationChanged(active);When the widget is synchronously closed,
DesktopWindowTreeHostPlatform::OnClosed()callsSetPlatformWindow(nullptr), which releases and deletes theWaylandToplevelWindowobject. -
Use-After-Free: When execution returns to
HandleToplevelConfigure[2],thishas already been deleted. The method then proceeds to callUpdateSessionStateIfNeeded(), which dereferences members of the deleted object (such asxdg_toplevel_orsession_at line 978), leading to a potential Use-After-Free.
Potential Trigger Steps
Because our tooling lacks the capability to execute code or build functional exploits, these are theoretical steps representing how an attacker might attempt to trigger this potential vulnerability:
- The attacker (or a compromised renderer) causes a state change that makes the Wayland compositor send an
xdg_toplevel.configureevent. - The
xdg_toplevelconfigure event is processed byXdgToplevel::OnToplevelConfigure, which callsHandleToplevelConfigure. - The configure flow notifies the delegate of an activation state change.
- An observer responding to the activation change synchronously calls
Widget::CloseNow(), which immediately deletes theWaylandToplevelWindowobject. - The stack unwinds back to
HandleToplevelConfigure, which callsUpdateSessionStateIfNeeded(), dereferencing the freedthispointer.
Suggested Remediation
Use a WeakPtr guard in HandleToplevelConfigure to verify if the window is still alive before calling UpdateSessionStateIfNeeded(). A similar safe pattern is used elsewhere in the Ozone platform (e.g., in X11):
void WaylandToplevelWindow::HandleToplevelConfigure(
int32_t width_dip,
int32_t height_dip,
const WindowStates& window_states) {
auto weak_this = weak_ptr_factory_.GetWeakPtr();
HandleToplevelConfigureWithOrigin(0, 0, width_dip, height_dip, window_states);
if (!weak_this) {
return;
}
UpdateSessionStateIfNeeded();
}
Evaluated with Chrome root at commit: b1520ef4a76878853a31f0943b565e42060edec8
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.