Overview

High
Severity
9.6
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Ozone
DescriptionUse after free in Ozone
ComponentOzone
Sub-componentOzone/X11
Bug ClassUAF
Action tierwatch — corruption primitive but gated (uncommon) — not default-reachable
Computed by PatchGap's deterministic exploitability ruler from the fixing commit. patch-now = RCE-class & attacker-reachable in a default config, or exploited in the wild; watch = real security signal below that bar; noise = no security relevance.
Tracker513006660
Fix commitddb1091e1639 (chromium/src) +56/-1
CWECWE-416 (Use-after-free)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H
CISA KEVNot listed
CreditedGoogle
Reported2026-05-14 (per vendor advisory)
Fix landed2026-05-14 (public git)
Disclosed2026-06-02
Patch gap19 days — fix public in source before the advisory

Background

`ui::X11Window`
Ozone platform window implementation that receives raw X11 events and translates them into ui::Event objects for the browser process.
`DispatchUiEvent`
X11Window method that processes an incoming X event, calls HandleEvent, then updates cached state such as the last motion event and window-manager state.
`HandleEvent` / `OnCrossingEvent`
event-processing path that can raise activation changes, invoking the delegate’s OnActivationChanged callback.
`weak_ptr_factory_` / `WeakPtr`
base::WeakPtr mechanism used to detect whether this was destroyed across a reentrant call before dereferencing member state.

Root Cause Analysis

X11Window::DispatchUiEvent called HandleEvent(xev) and then unconditionally continued to touch instance members (this) to build last_xev/last_motion and update window-manager state. However, HandleEvent can synchronously drive OnCrossingEvent → an activation state change → the delegate’s OnActivationChanged, and that delegate is permitted to synchronously close and delete the X11Window. Because there was no reentrancy guard, control returned into DispatchUiEvent with this already freed, and the subsequent member accesses dereferenced the dangling object. This is a classic reentrancy use-after-free: a callback invoked mid-method destroys the enclosing object while the outer method still assumes it is alive. The bug lives entirely in the browser process and is triggered by ordinary window-activation events.

The fix captures auto weak_this = weak_ptr_factory_.GetWeakPtr(); before HandleEvent and returns early with if (!weak_this) return; when this was destroyed.

Key insight
A synchronous delegate callback (OnActivationChanged) reached through the event pipeline can delete the very object whose method is still on the stack. The vulnerability is not in event parsing but in the implicit lifetime assumption that this survives every reentrant call within DispatchUiEvent.

Attack Path

  1. Deliver activation event An X event reaches X11Window::DispatchUiEvent, which forwards it to HandleEvent.
  2. Reenter delegate HandleEvent triggers OnCrossingEvent, producing an activation state change that calls the delegate’s OnActivationChanged.
  3. Destroy self The delegate synchronously closes and frees the X11Window, invalidating this.
  4. Dangling access Control returns to DispatchUiEvent, which builds last_motion/window-manager state on the freed object, causing the use-after-free.

Impact Assessment

Use-after-free of a ui::X11Window in the browser process, the most privileged process in Chrome, reachable through routine window-activation events on the X11/Ozone backend. Exploitation of freed-object member writes can corrupt browser-process memory and potentially lead to arbitrary code execution outside the sandbox. Exposure is limited to Linux desktop configurations using the X11 Ozone platform.

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

Audit Directions

  • Reentrant destruction after delegate callbacks
    Audit methods that invoke delegate/observer callbacks (OnActivationChanged, OnCrossingEvent, close/focus notifications) and then continue to access this; each such path needs a WeakPtr guard or must copy needed state before the call.
  • Ozone/X11 event-dispatch lifetimes
    Review other X11Window and platform-window dispatch methods that mutate cached state (last_xev, last_motion, window-manager flags) after event handling for the same missing post-callback liveness check.
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();
 
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 [email protected]

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