High CVSS 7.5 chrome UAF 📄 Reporter bug report 🔧 Commit mapped

Overview

High
Severity
7.5
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Ozone
DescriptionUse after free in Ozone
ComponentOzone
Sub-componentOzone/X11
Bug ClassUAF
Tracker524395469
Fix commitfdf7bd93e6af (chromium/src) +81/-0
Related commitsfc4613ff876d previously mapped (not the fix) · chromium/src
CWECWE-416 (Use-after-free)
CVSS vectorCVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:H
CISA KEVNot listed
CreditedGoogle
Reported2026-06-16 (per vendor advisory)
Fix landed2026-06-16 (public git)
Disclosed2026-06-30
Patch gap14 days — fix public in source before the advisory
Re-mapped on 2026-10-03: our pipeline had picked the earliest commit citing this bug (fc4613ff876d), which an audit found was a preparatory/helper change, not the fix. This page now points to the commit whose diff removes the bug. No root-cause analysis is published for this CVE.

Changed Functions

FunctionChangeNotes
if
ui/ozone/platform/x11/x11_window.cc
modified
TEST_F
ui/ozone/platform/x11/x11_window_ozone_unittest.cc
modified
if
ui/ozone/platform/x11/x11_window_ozone_unittest.cc
modified

Files Changed

  • ui/ozone/platform/x11/x11_window.cc
  • ui/ozone/platform/x11/x11_window_ozone_unittest.cc
diff --git a/ui/ozone/platform/x11/x11_window.cc b/ui/ozone/platform/x11/x11_window.cc
index 1366525..27b995c5 100644
--- a/ui/ozone/platform/x11/x11_window.cc
+++ b/ui/ozone/platform/x11/x11_window.cc
@@ -1424,7 +1424,11 @@
   auto& current_xevent = *connection_->dispatching_event();
 
   if (event->IsMouseEvent()) {
+    auto weak_this = weak_ptr_factory_.GetWeakPtr();
     X11WindowManager::GetInstance()->MouseOnWindow(this);
+    if (!weak_this) {
+      return POST_DISPATCH_STOP_PROPAGATION;
+    }
   }
 #if BUILDFLAG(USE_ATK)
   if (HandleAsAtkEvent(current_xevent)) {
diff --git a/ui/ozone/platform/x11/x11_window_ozone_unittest.cc b/ui/ozone/platform/x11/x11_window_ozone_unittest.cc
index 42f63fc..c3ddf03 100644
--- a/ui/ozone/platform/x11/x11_window_ozone_unittest.cc
+++ b/ui/ozone/platform/x11/x11_window_ozone_unittest.cc
@@ -639,4 +639,81 @@
   connection->DestroyWindow({new_parent});
 }
 
+// Verifies that X11Window::DispatchEvent() does not cause a use-after-free
+// if the window is synchronously deleted during MouseOnWindow().
+TEST_F(X11WindowOzoneTest, DispatchEventSelfFreedDuringMouseOnWindow) {
+  testing::NiceMock<MockPlatformWindowDelegate> delegate;
+  gfx::AcceleratedWidget widget;
+  constexpr gfx::Rect bounds(30, 80, 800, 600);
+  std::unique_ptr<PlatformWindow> window =
+      CreatePlatformWindow(&delegate, bounds, &widget, nullptr);
+
+  auto* connection = x11::Connection::Get();
+  auto xwindow = static_cast<x11::Window>(widget);
+
+  // Step 1: Force the X11Window's GeometryCache (and its parent chain) to
+  // become Ready and record last_notified_geometry_.
+  window->GetBoundsInPixels();
+
+  // Step 2: Create a real parent window at a non-zero offset so that after
+  // reparenting, only the absolute origin changes.
+  x11::Window new_parent = connection->GenerateId<x11::Window>();
+  connection->CreateWindow({
+      .wid = new_parent,
+      .parent = connection->default_root(),
+      .x = 200,
+      .y = 200,
+      .width = 1000,
+      .height = 1000,
+      .c_class = x11::WindowClass::InputOnly,
+      .override_redirect = x11::Bool32(true),
+  });
+
+  // Step 3: Synthesize the ReparentNotify WM reparent event.
+  x11::ReparentNotifyEvent reparent{};
+  reparent.event = xwindow;
+  reparent.window = xwindow;
+  reparent.parent = new_parent;
+  reparent.x = 0;
+  reparent.y = 0;
+  x11::Event reparent_event(/*send_event=*/false, std::move(reparent));
+  connection->DispatchEvent(reparent_event);
+
+  // Step 4: Mock delegate->OnCursorUpdate() to call
+  // window->GetBoundsInPixels(), modelling production
+  // WindowTreeHostPlatform::OnCursorUpdate() -> etc.
+  ON_CALL(delegate, OnCursorUpdate()).WillByDefault([&]() {
+    if (window) {
+      window->GetBoundsInPixels();
+    }
+  });
+
+  // Step 5: Arm the delegate so that the *next* synchronous OnBoundsChanged
+  // frees the X11Window.
+  bool armed = true;
+  bool freed = false;
+  EXPECT_CALL(delegate, OnBoundsChanged(_))
+      .WillRepeatedly([&](const PlatformWindowDelegate::BoundsChange&) {
+        if (armed) {
+          armed = false;
+          freed = true;
+          window.reset();
+        }
+      });
+
+  // Step 6: Prepare a located event (e.g. mouse press) targeting widget.
+  ScopedXI2Event xi_event;
+  xi_event.InitGenericButtonEvent(kPointerDeviceId, EventType::kMousePressed,
+                                  gfx::Point(100, 100), EF_NONE);
+
+  // Step 7: Dispatch the event to widget. This will trigger DispatchEvent.
+  DispatchXEvent(xi_event, widget);
+
+  // If we got here without ASAN/crash tripping, verify the window was indeed
+  // freed.
+  EXPECT_TRUE(freed) << "delegate was never invoked synchronously";
+
+  connection->DestroyWindow({new_parent});
+}
+
 }  // namespace ui
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/ui/ozone/platform/x11/x11_window_ozone_unittest.cc b/ui/ozone/platform/x11/x11_window_ozone_unittest.cc
index 42f63fc..c3ddf03 100644
--- a/ui/ozone/platform/x11/x11_window_ozone_unittest.cc
+++ b/ui/ozone/platform/x11/x11_window_ozone_unittest.cc
@@ -639,4 +639,81 @@
   connection->DestroyWindow({new_parent});
 }
 
+// Verifies that X11Window::DispatchEvent() does not cause a use-after-free
+// if the window is synchronously deleted during MouseOnWindow().
+TEST_F(X11WindowOzoneTest, DispatchEventSelfFreedDuringMouseOnWindow) {
+  testing::NiceMock<MockPlatformWindowDelegate> delegate;
+  gfx::AcceleratedWidget widget;
+  constexpr gfx::Rect bounds(30, 80, 800, 600);
+  std::unique_ptr<PlatformWindow> window =
+      CreatePlatformWindow(&delegate, bounds, &widget, nullptr);
+
+  auto* connection = x11::Connection::Get();
+  auto xwindow = static_cast<x11::Window>(widget);
+
+  // Step 1: Force the X11Window's GeometryCache (and its parent chain) to
+  // become Ready and record last_notified_geometry_.
+  window->GetBoundsInPixels();
+
+  // Step 2: Create a real parent window at a non-zero offset so that after
+  // reparenting, only the absolute origin changes.
+  x11::Window new_parent = connection->GenerateId<x11::Window>();
+  connection->CreateWindow({
+      .wid = new_parent,
+      .parent = connection->default_root(),
+      .x = 200,
+      .y = 200,
+      .width = 1000,
+      .height = 1000,
+      .c_class = x11::WindowClass::InputOnly,
+      .override_redirect = x11::Bool32(true),
+  });
+
+  // Step 3: Synthesize the ReparentNotify WM reparent event.
+  x11::ReparentNotifyEvent reparent{};
+  reparent.event = xwindow;
+  reparent.window = xwindow;
+  reparent.parent = new_parent;
+  reparent.x = 0;
+  reparent.y = 0;
+  x11::Event reparent_event(/*send_event=*/false, std::move(reparent));
+  connection->DispatchEvent(reparent_event);
+
+  // Step 4: Mock delegate->OnCursorUpdate() to call
+  // window->GetBoundsInPixels(), modelling production
+  // WindowTreeHostPlatform::OnCursorUpdate() -> etc.
+  ON_CALL(delegate, OnCursorUpdate()).WillByDefault([&]() {
+    if (window) {
+      window->GetBoundsInPixels();
+    }
+  });
+
+  // Step 5: Arm the delegate so that the *next* synchronous OnBoundsChanged
+  // frees the X11Window.
+  bool armed = true;
+  bool freed = false;
+  EXPECT_CALL(delegate, OnBoundsChanged(_))
+      .WillRepeatedly([&](const PlatformWindowDelegate::BoundsChange&) {
+        if (armed) {
+          armed = false;
+          freed = true;
+          window.reset();
+        }
+      });
+
+  // Step 6: Prepare a located event (e.g. mouse press) targeting widget.
+  ScopedXI2Event xi_event;
+  xi_event.InitGenericButtonEvent(kPointerDeviceId, EventType::kMousePressed,
+                                  gfx::Point(100, 100), EF_NONE);
+
+  // Step 7: Dispatch the event to widget. This will trigger DispatchEvent.
+  DispatchXEvent(xi_event, widget);
+
+  // If we got here without ASAN/crash tripping, verify the window was indeed
+  // freed.
+  EXPECT_TRUE(freed) << "delegate was never invoked synchronously";
+
+  connection->DestroyWindow({new_parent});
+}
+
 }  // namespace ui
Loading diff…

Original Bug Report

reported by [email protected]

Potential browser-process UAF in X11Window::DispatchEvent due to re-entrant window destruction

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 browser-process Use-After-Free (UAF) vulnerability exists in X11Window::DispatchEvent on Linux/X11. Synchronous dispatch of pending X11 events during mouse dispatch can lead to re-entrant window destruction. Since there is no WeakPtr guard on ’this’ inside DispatchEvent, execution proceeds with a freed window pointer, resulting in a UAF.

Affected files:

  • ui/ozone/platform/x11/x11_window.cc
  • ui/ozone/platform/x11/x11_window_manager.cc
  • ui/ozone/platform/x11/x11_screen_ozone.cc

Estimated timestamp from git blame: 2025-03-06

Root Cause

In ui/ozone/platform/x11/x11_window.cc, X11Window::DispatchEvent invokes X11WindowManager::GetInstance()->MouseOnWindow(this) for incoming mouse events, but does not guard itself against potential re-entrant destruction of this:

uint32_t X11Window::DispatchEvent(const PlatformEvent& event) {
  ...
  if (event->IsMouseEvent()) {
    X11WindowManager::GetInstance()->MouseOnWindow(this);   // Synchronously dispatches events/replies
  }
#if BUILDFLAG(USE_ATK)
  if (HandleAsAtkEvent(current_xevent)) {                  // Dereferences freed 'this'
    return POST_DISPATCH_STOP_PROPAGATION;
  }
#endif

  DispatchUiEvent(event, current_xevent);                   // Dereferences freed 'this'
  return POST_DISPATCH_STOP_PROPAGATION;
}

MouseOnWindow can synchronously reach window->GetBoundsInPixels(), which in turn queries geometry_cache_->GetBoundsPx(). If the geometry cache is not yet ready, it can synchronously run pending X server events via parent_future_.DispatchNow() and geometry_future_.DispatchNow(). Synchronous event processing can trigger re-entrant window destruction via observers (e.g. Widget::CloseNow()), rendering the current this pointer dangling prior to the remaining member accesses in DispatchEvent.

Potential Trigger Path

  1. A mouse event is delivered to a top-level X11Window W (where the mouse has newly entered). This triggers X11Window::DispatchEvent -> MouseOnWindow(W) -> W->OnCursorUpdate().
  2. OnCursorUpdate triggers screen display lookups via ScreenOzone::GetDisplayNearestWindow and X11ScreenOzone::GetDisplayForAcceleratedWidget.
  3. GetDisplayForAcceleratedWidget calls window->GetBoundsInPixels() to determine screen coordinates, which delegates to geometry_cache_->GetBoundsPx().
  4. GetBoundsPx processes pending responses synchronously via DispatchNow(). Re-entrant callbacks trigger window observers on bounds change, which may synchronously invoke Widget::CloseNow() and destroy W.
  5. Upon unwinding to X11Window::DispatchEvent, the this pointer is dangling and subsequent member function calls (HandleAsAtkEvent or DispatchUiEvent) trigger a browser-process Use-After-Free (UAF).

Note: These are potential steps and our tooling agent does not currently have the capability to run code to confirm this with a fully working PoC.

Suggested Fix

Add a WeakPtr<X11Window> guard in X11Window::DispatchEvent right after MouseOnWindow(this) is called:

uint32_t X11Window::DispatchEvent(const PlatformEvent& event) {
  TRACE_EVENT1("views", "X11PlatformWindow::Dispatch", "event->type()",
               event->type());

  DCHECK_NE(window(), x11::Window::None);
  DCHECK(event);

  auto& current_xevent = *connection_->dispatching_event();

  auto weak_this = weak_ptr_factory_.GetWeakPtr();

  if (event->IsMouseEvent()) {
    X11WindowManager::GetInstance()->MouseOnWindow(this);
    if (!weak_this) {
      return POST_DISPATCH_STOP_PROPAGATION;
    }
  }
#if BUILDFLAG(USE_ATK)
  if (HandleAsAtkEvent(current_xevent)) {
    return POST_DISPATCH_STOP_PROPAGATION;
  }
#endif

  DispatchUiEvent(event, current_xevent);
  return POST_DISPATCH_STOP_PROPAGATION;
}

Evaluated with Chrome root at commit: 70c6813870b6701fa16670076bf633ee6c3a439f


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