Medium CVSS 8.8 chrome UAF 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
8.8
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Ozone
DescriptionUse after free in Ozone
ComponentOzone
Sub-componentOzone/X11
Bug ClassUAF
Tracker518245882
Fix commit910e29f8f9f5 (chromium/src) +84/-0
Related commitsfc4613ff876d previously mapped (not the fix) · chromium/src
CWECWE-416 (Use-after-free)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
CISA KEVNot listed
CreditedGoogle
Reported2026-05-30 (per vendor advisory)
Fix landed2026-06-01 (public git)
Disclosed2026-06-30
Patch gap29 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
chrome/browser/ui/views/frame/browser_desktop_window_tree_host_linux.cc
modified
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

  • chrome/browser/ui/views/frame/browser_desktop_window_tree_host_linux.cc
  • chrome/browser/ui/views/frame/browser_desktop_window_tree_host_linux.h
  • ui/ozone/platform/x11/x11_window.cc
  • ui/ozone/platform/x11/x11_window_ozone_unittest.cc
diff --git a/chrome/browser/ui/views/frame/browser_desktop_window_tree_host_linux.cc b/chrome/browser/ui/views/frame/browser_desktop_window_tree_host_linux.cc
index 53816cc..81a993c 100644
--- a/chrome/browser/ui/views/frame/browser_desktop_window_tree_host_linux.cc
+++ b/chrome/browser/ui/views/frame/browser_desktop_window_tree_host_linux.cc
@@ -141,7 +141,11 @@
         browser_widget_->tab_drag_kind() == TabDragKind::kAllTabs;
     bool is_dragging_window = tab_drag_kind == TabDragKind::kAllTabs;
     if (is_dragging_window != was_dragging_window) {
+      auto weak_this = weak_factory_.GetWeakPtr();
       x11_extension->SetOverrideRedirect(is_dragging_window);
+      if (!weak_this) {
+        return;
+      }
     }
   }
 
diff --git a/chrome/browser/ui/views/frame/browser_desktop_window_tree_host_linux.h b/chrome/browser/ui/views/frame/browser_desktop_window_tree_host_linux.h
index b4a7275b..97b931d 100644
--- a/chrome/browser/ui/views/frame/browser_desktop_window_tree_host_linux.h
+++ b/chrome/browser/ui/views/frame/browser_desktop_window_tree_host_linux.h
@@ -6,6 +6,7 @@
 #define CHROME_BROWSER_UI_VIEWS_FRAME_BROWSER_DESKTOP_WINDOW_TREE_HOST_LINUX_H_
 
 #include "base/memory/raw_ptr.h"
+#include "base/memory/weak_ptr.h"
 #include "build/build_config.h"
 #include "build/config/linux/dbus/buildflags.h"
 #include "chrome/browser/ui/views/frame/browser_desktop_window_tree_host.h"
@@ -109,6 +110,8 @@
 
   base::ScopedObservation<ui::LinuxUi, ui::DeviceScaleFactorObserver>
       scale_observation_{this};
+
+  base::WeakPtrFactory<BrowserDesktopWindowTreeHostLinux> weak_factory_{this};
 };
 
 #endif  // CHROME_BROWSER_UI_VIEWS_FRAME_BROWSER_DESKTOP_WINDOW_TREE_HOST_LINUX_H_
diff --git a/ui/ozone/platform/x11/x11_window.cc b/ui/ozone/platform/x11/x11_window.cc
index 57ffaee..fd71f18 100644
--- a/ui/ozone/platform/x11/x11_window.cc
+++ b/ui/ozone/platform/x11/x11_window.cc
@@ -1333,7 +1333,11 @@
       .override_redirect = x11::Bool32(override_redirect),
   });
   if (remap) {
+    auto weak_this = weak_ptr_factory_.GetWeakPtr();
     Map();
+    if (!weak_this) {
+      return;
+    }
     // We cannot regrab the pointer now since unmapping/mapping
     // happens asynchronously.  We must wait until the window is
     // mapped to issue a grab request.
diff --git a/ui/ozone/platform/x11/x11_window_ozone_unittest.cc b/ui/ozone/platform/x11/x11_window_ozone_unittest.cc
index e56645c..9289d8c 100644
--- a/ui/ozone/platform/x11/x11_window_ozone_unittest.cc
+++ b/ui/ozone/platform/x11/x11_window_ozone_unittest.cc
@@ -486,4 +486,77 @@
   connection->DestroyWindow({new_parent});
 }
 
+// Verifies that X11Window::SetOverrideRedirect() does not cause a
+// use-after-free if the window is synchronously deleted during Map().
+TEST_F(X11WindowOzoneTest, SetOverrideRedirectSelfDeleteUaf) {
+  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);
+
+  // Prime the X11Window's `GeometryCache` so the first `Map()` /
+  // `GetBoundsInPixels()` does not itself fire a synchronous
+  // `OnBoundsChanged()`.
+  window->GetBoundsInPixels();
+
+  // Show the window so window_mapped_in_client_ becomes true (allowing remap).
+  window->Show(false);
+
+  // Create a real parent window at a non-zero offset so that after
+  // a synthetic reparent, only the absolute origin of `xwindow` changes.
+  // `override_redirect` avoids any WM interference.
+  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),
+  });
+
+  // Synthesize a `ReparentNotify` so the leaf `GeometryCache` becomes
+  // un-ready. The next `GetBoundsInPixels()` (inside the `Map()` called
+  // from `SetOverrideRedirect()`) will block on the new parent's geometry,
+  // observe an origin change, and fire `OnBoundsChanged()` synchronously.
+  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);
+
+  // Arm the delegate so 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();
+        }
+      });
+
+  // Call `SetOverrideRedirect` via the `X11Extension` interface on X11Window.
+  auto* x11_window_raw = static_cast<X11Window*>(window.get());
+  x11_window_raw->SetOverrideRedirect(true);
+
+  // The synchronous-free path must have been exercised; otherwise the test
+  // didn't reach the vulnerable frame.
+  EXPECT_TRUE(freed) << "delegate->OnBoundsChanged() was not invoked "
+                        "synchronously from SetOverrideRedirect";
+
+  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 e56645c..9289d8c 100644
--- a/ui/ozone/platform/x11/x11_window_ozone_unittest.cc
+++ b/ui/ozone/platform/x11/x11_window_ozone_unittest.cc
@@ -486,4 +486,77 @@
   connection->DestroyWindow({new_parent});
 }
 
+// Verifies that X11Window::SetOverrideRedirect() does not cause a
+// use-after-free if the window is synchronously deleted during Map().
+TEST_F(X11WindowOzoneTest, SetOverrideRedirectSelfDeleteUaf) {
+  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);
+
+  // Prime the X11Window's `GeometryCache` so the first `Map()` /
+  // `GetBoundsInPixels()` does not itself fire a synchronous
+  // `OnBoundsChanged()`.
+  window->GetBoundsInPixels();
+
+  // Show the window so window_mapped_in_client_ becomes true (allowing remap).
+  window->Show(false);
+
+  // Create a real parent window at a non-zero offset so that after
+  // a synthetic reparent, only the absolute origin of `xwindow` changes.
+  // `override_redirect` avoids any WM interference.
+  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),
+  });
+
+  // Synthesize a `ReparentNotify` so the leaf `GeometryCache` becomes
+  // un-ready. The next `GetBoundsInPixels()` (inside the `Map()` called
+  // from `SetOverrideRedirect()`) will block on the new parent's geometry,
+  // observe an origin change, and fire `OnBoundsChanged()` synchronously.
+  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);
+
+  // Arm the delegate so 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();
+        }
+      });
+
+  // Call `SetOverrideRedirect` via the `X11Extension` interface on X11Window.
+  auto* x11_window_raw = static_cast<X11Window*>(window.get());
+  x11_window_raw->SetOverrideRedirect(true);
+
+  // The synchronous-free path must have been exercised; otherwise the test
+  // didn't reach the vulnerable frame.
+  EXPECT_TRUE(freed) << "delegate->OnBoundsChanged() was not invoked "
+                        "synchronously from SetOverrideRedirect";
+
+  connection->DestroyWindow({new_parent});
+}
+
 }  // namespace ui
Loading diff…

Original Bug Report

reported by [email protected]

Potential UAF in X11Window::SetOverrideRedirect and BrowserDesktopWindowTreeHostLinux

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 the Linux/X11 tab dragging path. Synchronous event dispatch during window remapping can trigger the destruction of both the X11Window and BrowserDesktopWindowTreeHostLinux objects. Subsequent execution on the unwinding call stack results in memory access on these freed objects.

Affected files:

  • ui/ozone/platform/x11/x11_window.cc
  • chrome/browser/ui/views/frame/browser_desktop_window_tree_host_linux.cc

Estimated timestamp from git blame: 2025-03-06

Detailed Description

A potential Use-After-Free (UAF) vulnerability has been identified in the Linux/X11 window management code path, specifically during tab dragging under tiling window managers. The vulnerability stems from synchronous re-entrancy during geometry queries, which can destroy the window and host objects while their methods are still active on the call stack.

Note: These findings and reproduction steps are based on source-code analysis; our tooling does not currently have the capability to run code or verify a live exploit.

Vulnerability Mechanism

  1. Call Stack Entry: When a window’s tab dragging state changes, BrowserDesktopWindowTreeHostLinux::TabDraggingKindChanged is invoked, which calls x11_extension->SetOverrideRedirect(is_dragging_window) (see chrome/browser/ui/views/frame/browser_desktop_window_tree_host_linux.cc).

  2. Synchronous Mapping and Re-entrancy: X11Window::SetOverrideRedirect hides and then maps the window via Map() (see ui/ozone/platform/x11/x11_window.cc). Inside Map(), GetBoundsInPixels() is called to query size hints. This delegates to geometry_cache_->GetBoundsPx().

  3. Synchronous Event Pump: If the geometry cache is currently un-Ready (for example, following a recent window reparenting event), GeometryCache::GetBoundsPx() forces a synchronous dispatch via parent_future_.DispatchNow() (see ui/gfx/x/geometry_cache.cc). This synchronously pumps pending X server responses.

  4. Re-entrant Object Destruction: The synchronous dispatch runs the bounds change callback, notifying WindowTreeHostPlatform::OnBoundsChanged. If an observer reacts to the bounds/position change by closing the widget (e.g., detaching the last tab from the window during a drag), a sequence of synchronous destructions is triggered:

    • X11Window is destroyed via SetPlatformWindow(nullptr).
    • BrowserDesktopWindowTreeHostLinux is destroyed via host_.reset().
  5. Unwinding Use-After-Free: When the nested synchronous dispatch completes, the execution stack unwinds back to the caller frames:

    • In X11Window::SetOverrideRedirect, the code attempts to read has_pointer_grab_ and write to should_grab_pointer_after_map_ on a freed X11Window instance.
    • In BrowserDesktopWindowTreeHostLinux::TabDraggingKindChanged, the code attempts to dereference platform_window() on the freed host instance to perform a Wayland extension check, leading to a potential wild pointer dereference.

Potential Trigger Steps

To trigger this path, a tester or user would potentially perform the following actions:

  1. Run Chromium on Linux/X11 under a tiling window manager (e.g., i3 or awesome).
  2. Open a browser window containing tabs.
  3. Initiate a tab drag operation that causes the dragged tab to be detached or snapped, synchronously closing the source window while a geometry sync is pending.

Suggested Fix

To mitigate this issue, implement WeakPtr guards across the synchronous boundaries to detect object destruction and abort further execution:

  1. In X11Window::SetOverrideRedirect, check a weak reference after calling Map():
auto weak_this = weak_ptr_factory_.GetWeakPtr();
Map();
if (!weak_this) {
  return;
}
if (has_pointer_grab_) {
  should_grab_pointer_after_map_ = true;
}
  1. In BrowserDesktopWindowTreeHostLinux::TabDraggingKindChanged, verify the host’s liveness using a weak pointer check after calling SetOverrideRedirect:
auto weak_this = GetWeakPtr();
x11_extension->SetOverrideRedirect(is_dragging_window);
if (!weak_this) {
  return;
}

Evaluated with Chrome root at commit: 208ca3371d87589335b108c431b95a36d768dc47


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