CVE-2026-10891
Overview
Background
- `GeometryCache`
- an X11 helper that caches a window’s parent and geometry and resolves absolute bounds in
GetBoundsPx(). - `XFuture::DispatchNow`
- a synchronous X11 round-trip that flushes and processes the reply inline, which can run arbitrary queued callbacks before returning.
- `base::WeakPtr`
- a non-owning pointer that becomes null when its referent is destroyed, used here to detect self-destruction across a synchronous call.
Root Cause Analysis
GeometryCache::GetBoundsPx() performs up to three synchronous operations — parent_future_.DispatchNow(), geometry_future_.DispatchNow(), and parent_->GetBoundsPx() — each of which can run an X11 callback chain that destroys the GeometryCache instance (the shipped GeometryCacheTest.DestroyInCallback resets the owning unique_ptr from within a bounds-changed callback). After each such call the pre-patch code continued to touch this (the CHECK(have_parent_)/CHECK(have_geometry_) assertions on members, and the final geometry_ + offset using the geometry_ member), dereferencing freed memory. The invariant violated is that GetBoundsPx assumed this survives each synchronous dispatch.
The fix captures weak_this = weak_ptr_factory_.GetWeakPtr() at entry and, after each DispatchNow() and after parent_->GetBoundsPx(), returns an empty gfx::Rect{} if weak_this is null, so no member is read once the object is gone.
have_parent_, have_geometry_, geometry_) after synchronous X11 dispatches that can delete the object; the fix inserts WeakPtr liveness checks immediately after each synchronous call and bails out with an empty rect.Attack Path
- Register a destroying callback
A
GeometryCacheis created with a bounds-changed callback that, when fired, destroys theGeometryCache(as in the regression test). - Call `GetBoundsPx()`
The method issues
parent_future_.DispatchNow()(orgeometry_future_.DispatchNow()), which synchronously processes an X11 reply and runs the callback chain. - Object freed mid-call
The callback destroys the
GeometryCache, freeingthis; control returns intoGetBoundsPx(). - Use-after-free
The pre-patch code executes
CHECK(have_parent_)/ later readsgeometry_on the freed instance, a use-after-free read of member state.
Impact Assessment
GeometryCache-owning object to be destroyed from within an X11 bounds callback triggers a use-after-free in the browser/GPU UI code on X11 (ui/gfx/x). The vendor advisory rates this critical and classifies it as a use-after-free in GFX; the diff demonstrates the dangling member access but any escalation to a read/write primitive or code execution is (inferred). The bug is X11-platform-specific and requires the synchronous-dispatch-destroys-self timing shown by the shipped test.Changed Functions
| Function | Change | Notes |
|---|---|---|
ifui/gfx/x/geometry_cache.cc |
modified | |
TESTui/gfx/x/geometry_cache_unittest.cc |
modified |
Files Changed
ui/gfx/x/geometry_cache.ccui/gfx/x/geometry_cache_unittest.cc
Audit Directions
- Synchronous X11 round-trips in methodsFlag any method that calls
DispatchNow()(or other synchronousXFutureflushes) and then readsthis/members; wrap with aWeakPtrcheck. - Recursive bounds/geometry resolutionReview code that recurses into parent objects (
parent_->GetBoundsPx()) where the recursion may process events that destroy the caller. - `weak_ptr_factory_` presence vs. useAudit classes that own a
weak_ptr_factory_but invoke potentially-reentrant platform calls without consulting it afterward.
Patch
From e18e2f6c1117ff73c7b23ec1ac6c961ef13bf004 Mon Sep 17 00:00:00 2001 From: Tom Anderson <[email protected]> Date: Thu, 14 May 2026 10:33:44 -0700 Subject: [PATCH] [X11] Fix Use-After-Free in GeometryCache::GetBoundsPx() Synchronous X11 dispatching in GetBoundsPx() can trigger a callback chain that destroys the GeometryCache instance. This CL adds WeakPtr checks after each synchronous call to protect against this. Fixed: 513160681 Test: GeometryCacheTest.DestroyInCallback Change-Id: I6fbce5def4a521eee2f4e04a8a28311c15471b44 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7849103 Auto-Submit: Thomas Anderson <[email protected]> Commit-Queue: Lei Zhang <[email protected]> Commit-Queue: Thomas Anderson <[email protected]> Reviewed-by: Lei Zhang <[email protected]> Cr-Commit-Position: refs/heads/main@{#1630691} --- diff --git a/ui/gfx/x/geometry_cache.cc b/ui/gfx/x/geometry_cache.cc index 2549838..60c40b3d 100644 --- a/ui/gfx/x/geometry_cache.cc +++ b/ui/gfx/x/geometry_cache.cc @@ -34,12 +34,19 @@ GeometryCache::~GeometryCache() = default; gfx::Rect GeometryCache::GetBoundsPx() { + auto weak_this = weak_ptr_factory_.GetWeakPtr(); if (!have_parent_) { parent_future_.DispatchNow(); + if (!weak_this) { + return {}; + } } CHECK(have_parent_); if (!have_geometry_) { geometry_future_.DispatchNow(); + if (!weak_this) { + return {}; + } } CHECK(have_geometry_); @@ -47,6 +54,9 @@ return geometry_; } auto parent_bounds = parent_->GetBoundsPx(); + if (!weak_this) { + return {}; + } gfx::Vector2d offset(parent_bounds.x(), parent_bounds.y()); return geometry_ + offset; } diff --git a/ui/gfx/x/geometry_cache_unittest.cc b/ui/gfx/x/geometry_cache_unittest.cc index b37a9fe..01e55747 100644 --- a/ui/gfx/x/geometry_cache_unittest.cc +++ b/ui/gfx/x/geometry_cache_unittest.cc @@ -161,4 +161,24 @@ new_child_bounds + gfx::Vector2d(parent_bounds.x(), parent_bounds.y())); } +TEST(GeometryCacheTest, DestroyInCallback) { + Connection* connection = Connection::Get(); + ScopedWindow window(connection, connection->default_root(), + gfx::Rect(12, 34, 56, 78)); + + std::unique_ptr<GeometryCache> geometry_cache; + auto bounds_changed_callback = + [](std::unique_ptr<GeometryCache>* geometry_cache_ptr, + const std::optional<gfx::Rect>& old_bounds, + const gfx::Rect& new_bounds) { geometry_cache_ptr->reset(); }; + + geometry_cache = std::make_unique<GeometryCache>( + connection, window.id(), + base::BindRepeating(bounds_changed_callback, &geometry_cache)); + + // This will trigger the callback via DispatchNow() and then return. + // If there's a UAF, this should crash or trigger ASAN. + geometry_cache->GetBoundsPx(); +} + } // namespace x11
Regression Test / PoC
diff --git a/ui/gfx/x/geometry_cache_unittest.cc b/ui/gfx/x/geometry_cache_unittest.cc
index b37a9fe..01e55747 100644
--- a/ui/gfx/x/geometry_cache_unittest.cc
+++ b/ui/gfx/x/geometry_cache_unittest.cc
@@ -161,4 +161,24 @@
new_child_bounds + gfx::Vector2d(parent_bounds.x(), parent_bounds.y()));
}
+TEST(GeometryCacheTest, DestroyInCallback) {
+ Connection* connection = Connection::Get();
+ ScopedWindow window(connection, connection->default_root(),
+ gfx::Rect(12, 34, 56, 78));
+
+ std::unique_ptr<GeometryCache> geometry_cache;
+ auto bounds_changed_callback =
+ [](std::unique_ptr<GeometryCache>* geometry_cache_ptr,
+ const std::optional<gfx::Rect>& old_bounds,
+ const gfx::Rect& new_bounds) { geometry_cache_ptr->reset(); };
+
+ geometry_cache = std::make_unique<GeometryCache>(
+ connection, window.id(),
+ base::BindRepeating(bounds_changed_callback, &geometry_cache));
+
+ // This will trigger the callback via DispatchNow() and then return.
+ // If there's a UAF, this should crash or trigger ASAN.
+ geometry_cache->GetBoundsPx();
+}
+
} // namespace x11
Original Bug Report
Potential Heap Use-After-Free in x11::GeometryCache::GetBoundsPx
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 heap Use-After-Free (UAF) vulnerability exists in the Linux/X11 browser process within x11::GeometryCache::GetBoundsPx(). The issue is caused by synchronous dispatching of X11 replies which can trigger a callback chain that destroys the GeometryCache object while its methods are still executing on the stack.
Affected files:
ui/gfx/x/geometry_cache.ccui/ozone/platform/x11/x11_window.ccui/aura/window_tree_host_platform.cc
Estimated timestamp from git blame: 2023-12-01
Summary
A potential heap Use-After-Free vulnerability has been identified in the browser process on Linux/X11. The vulnerability occurs in x11::GeometryCache::GetBoundsPx() because it performs synchronous X11 reply dispatching that can trigger a recursive callback chain resulting in the destruction of the GeometryCache instance while its code is still executing.
Root Cause Analysis
In ui/gfx/x/geometry_cache.cc, the GetBoundsPx() function ensures that parent and geometry information is retrieved by calling DispatchNow() on pending X11 futures:
gfx::Rect GeometryCache::GetBoundsPx() {
if (!have_parent_) {
parent_future_.DispatchNow(); // [1] Synchronous dispatch
}
CHECK(have_parent_);
if (!have_geometry_) {
geometry_future_.DispatchNow(); // [2] Synchronous dispatch
}
CHECK(have_geometry_); // [3] Potential UAF read
if (!parent_) {
return geometry_; // [4] Potential UAF read
}
// ...
}
DispatchNow() (in ui/gfx/x/future.cc) is synchronous; it waits for the X11 response and immediately executes the associated callback. For the GeometryCache instance owned by an X11Window, the response callback eventually triggers X11Window::OnBoundsChanged via NotifyGeometryChanged().
X11Window::OnBoundsChanged propagates the bounds update to its delegate, typically WindowTreeHostPlatform. In ui/aura/window_tree_host_platform.cc, the OnBoundsChanged implementation explicitly warns that propagating these changes can synchronously destroy the host:
void WindowTreeHostPlatform::OnBoundsChanged(const BoundsChange& change) {
// ...
auto weak_ref = GetWeakPtr();
OnHostMovedInPixels();
// Changing the bounds may destroy this.
if (!weak_ref) return;
// ...
}
If an observer of these bounds changes (e.g., during window snapping or closure) causes the Widget to close, the X11Window is destroyed. The X11Window destructor resets its geometry_cache_ (a std::unique_ptr), which deletes the GeometryCache object. When the stack unwinds back to GetBoundsPx() from the synchronous DispatchNow() call, the function continues to access members of the now-freed this object at locations [3], [4], and during subsequent logic.
Potential Attack Vector
An attacker could potentially trigger this by influencing window destruction timing during bounds updates. For example, by carefully timed window manipulation (like snapping a window into a tab strip or closing a popup window) that triggers a synchronous bounds check while X11 replies are pending. Since this occurs in the browser process, successful exploitation could lead to arbitrary code execution with the user’s privileges.
Impact
This is a browser-process heap UAF. The affected members in GeometryCache (have_geometry_, parent_, and geometry_) are not protected by MiraclePtr/BackupRefPtr. The browser process is unsandboxed, making this a critical security concern.
Suggested Fix
The GetBoundsPx() method should protect itself against object destruction during DispatchNow() calls. This can be achieved by using a base::WeakPtr to check for object validity after each synchronous dispatch:
gfx::Rect GeometryCache::GetBoundsPx() {
auto weak_this = weak_ptr_factory_.GetWeakPtr();
if (!have_parent_) {
parent_future_.DispatchNow();
if (!weak_this) return {};
}
CHECK(have_parent_);
if (!have_geometry_) {
geometry_future_.DispatchNow();
if (!weak_this) return {};
}
CHECK(have_geometry_);
// ...
}
Alternatively, avoiding synchronous dispatch in this context or ensuring that bounds change notifications are always debounced/asynchronous would mitigate the risk.
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.