Overview

Critical
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Ozone
DescriptionUse after free in Ozone
ComponentOzone
Bug ClassUAF
Tracker517046249
Fix commitdadcd96cf1f8 (chromium/src) +51/-0
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-02

Changed Functions

FunctionChangeNotes
if
ui/ozone/platform/wayland/host/wayland_toplevel_window.cc
modified
TEST_P
ui/ozone/platform/wayland/host/wayland_window_unittest.cc
modified

Files Changed

  • ui/ozone/platform/wayland/host/wayland_toplevel_window.cc
  • ui/ozone/platform/wayland/host/wayland_window_unittest.cc
From dadcd96cf1f88f544995631521471cf64b90a7ff Mon Sep 17 00:00:00 2001
From: Tom Anderson <thomasanderson@chromium.org>
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 <thomasanderson@chromium.org>
Commit-Queue: Thomas Lukaszewicz <tluk@chromium.org>
Auto-Submit: Thomas Anderson <thomasanderson@chromium.org>
Reviewed-by: Thomas Lukaszewicz <tluk@chromium.org>
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{}));
Loading diff…

Regression Test / PoC

shipped with the fix
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{}));
Loading diff…

Original Bug Report

reported by vm...@google.com

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
}
  1. Synchronous Deletion: Inside HandleToplevelConfigureWithOrigin [1], the method UpdateActivationState() 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 in desktop_window_tree_host_platform.cc, handling activation changes can synchronously close and destroy the widget and thereby its owned PlatformWindow (the WaylandToplevelWindow object):

    // 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() calls SetPlatformWindow(nullptr), which releases and deletes the WaylandToplevelWindow object.

  2. Use-After-Free: When execution returns to HandleToplevelConfigure [2], this has already been deleted. The method then proceeds to call UpdateSessionStateIfNeeded(), which dereferences members of the deleted object (such as xdg_toplevel_ or session_ 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:

  1. The attacker (or a compromised renderer) causes a state change that makes the Wayland compositor send an xdg_toplevel.configure event.
  2. The xdg_toplevel configure event is processed by XdgToplevel::OnToplevelConfigure, which calls HandleToplevelConfigure.
  3. The configure flow notifies the delegate of an activation state change.
  4. An observer responding to the activation change synchronously calls Widget::CloseNow(), which immediately deletes the WaylandToplevelWindow object.
  5. The stack unwinds back to HandleToplevelConfigure, which calls UpdateSessionStateIfNeeded(), dereferencing the freed this pointer.

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.

View on issue tracker