Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Ozone
DescriptionUse after free in Ozone
ComponentOzone
Bug ClassUAF
Tracker513006660
Fix commitddb1091e1639 (chromium/src) +56/-1
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-02

Changed Functions

FunctionChangeNotes
DestructionWindowDelegate
ui/ozone/platform/x11/test/x11_window_unittest.cc
modified
TEST_F
ui/ozone/platform/x11/test/x11_window_unittest.cc
modified
if
ui/ozone/platform/x11/x11_window.cc
modified

Files Changed

  • ui/ozone/platform/x11/test/x11_window_unittest.cc
  • ui/ozone/platform/x11/x11_window.cc
  • ui/ozone/platform/x11/x11_window.h
From ddb1091e16399f030e5720a906ef5ac58c0747a8 Mon Sep 17 00:00:00 2001
From: Tom Anderson <thomasanderson@chromium.org>
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 <thomasanderson@chromium.org>
Reviewed-by: Lei Zhang <thestig@chromium.org>
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();
 
Loading diff…

Regression Test / PoC

shipped with the fix
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
Loading diff…

Original Bug Report

reported by vm...@google.com

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:

  1. HandleEvent recognizes a CrossingEvent and calls OnCrossingEvent.
  2. OnCrossingEvent updates the window’s activation state and calls AfterActivationStateChanged().
  3. AfterActivationStateChanged() detects a change in IsActive() and notifies the PlatformWindowDelegate (typically an instance of DesktopWindowTreeHostPlatform) via OnXWindowIsActiveChanged().
  4. DesktopWindowTreeHostPlatform::OnActivationChanged() notifies the associated DesktopNativeWidgetAura.
  5. The notification propagates to the Widget, which informs its observers. If the widget is a popup or bubble with close_on_deactivate enabled, an observer may trigger synchronous window closure (e.g., via Widget::CloseNow()).
  6. The destruction of the Widget leads to the immediate deletion of the DesktopWindowTreeHostPlatform and its owned X11Window instance.

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.

View on issue tracker