Overview

Critical
Severity
8.8
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Ozone
DescriptionUse after free in Ozone
ComponentOzone
Sub-componentOzone/wayland
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.
Tracker518006275
Fix commit06d1bbdddd1f (chromium/src) +22/-0
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-29 (per vendor advisory)
Fix landed2026-06-24 (public git)
Disclosed2026-07-08
Patch gap14 days — fix public in source before the advisory

Background

Ozone/Wayland
Chromium’s platform abstraction where ui::WaylandWindow bridges Wayland surface state and the browser’s views layer.
Surface configure
a Wayland compositor event carrying window state (size, activation) that the client must acknowledge via RequestStateFromServer().
`OnStateUpdate()`
the delegate callout reached transitively from RequestStateFromServer() that lets the views layer react to state, including synchronously closing the widget.
UAF
use-after-free; here the browser-process WaylandWindow object is freed mid-call yet the caller keeps writing its members.

Root Cause Analysis

ui::WaylandWindow::ProcessPendingConfigureState applies a pending configure by calling RequestStateFromServer(), which transitively invokes delegate()->OnStateUpdate(). That delegate callout is a reentrancy hazard: the views layer may synchronously close the widget and destroy the WaylandWindow, so control returns to a method whose this no longer exists. An inner guard in MaybeApplyLatestStateRequest() already detected destruction and returned early, but that early return only unwound the inner frame; the outer ProcessPendingConfigureState() resumed and reset pending_configure_state_ on the freed object. Writing to a member of a deleted WaylandWindow is a use-after-free in the browser process.

The fix captures auto weak_this = AsWeakPtr() before the call and returns immediately when !weak_this, before touching any members.

Key insight
An inner weak-ptr guard protects only its own frame; every outer frame that continues to touch instance state after a reentrant, delegate-triggering call needs its own post-call liveness check.

Attack Path

  1. Trigger configure a page or compositor interaction drives a Wayland configure that queues pending_configure_state_.
  2. Reentrant callout ProcessPendingConfigureState() calls RequestStateFromServer(), reaching delegate()->OnStateUpdate().
  3. Synchronous destruction the views layer closes the widget inside the callout, freeing the WaylandWindow.
  4. Stale write the unwound outer frame resets pending_configure_state_ on the freed object, corrupting reclaimed browser-process heap.

Impact Assessment

Use-after-free in the browser process, the highest-privilege Chrome process, giving an attacker a write to freed heap memory that can lead to corruption and potential code execution. Exploitation requires driving a specific window-close-during-configure race under Wayland, so it is timing-dependent and Linux-desktop-scoped. Severity is high given the browser-process context despite the narrow trigger surface.

Changed Functions

FunctionChangeNotes
if
ui/ozone/platform/wayland/host/wayland_window.cc
modified
TEST_P
ui/ozone/platform/wayland/host/wayland_window_unittest.cc
modified
PostToServerAndWait
ui/ozone/platform/wayland/host/wayland_window_unittest.cc
modified

Files Changed

  • ui/ozone/platform/wayland/host/wayland_window.cc
  • ui/ozone/platform/wayland/host/wayland_window_unittest.cc

Audit Directions

  • Post-callout member access
    audit every method that touches instance members after invoking a delegate/observer/client callback; an inner guard does not protect outer frames on the stack.
  • Reentrant destruction paths
    trace all RequestStateFromServer() / OnStateUpdate() style callouts in Ozone for widget-close reentrancy and confirm each caller re-checks WeakPtr liveness before continuing.
From 06d1bbdddd1f176c3443ca2ddb1f1d21dfe9a094 Mon Sep 17 00:00:00 2001
From: Kramer Ge <[email protected]>
Date: Wed, 24 Jun 2026 08:10:04 -0700
Subject: [PATCH] [ozone/wayland]Handle window destruction during surface configure

ProcessPendingConfigureState() calls RequestStateFromServer(), which
reaches the delegate's OnStateUpdate(). The views layer may
synchronously close the widget and destroy the platform window inside
that callout. The existing weak-ptr guard in
MaybeApplyLatestStateRequest() returns early in that case, but
ProcessPendingConfigureState() then continued to access members after
the call returned.

Add a weak-ptr liveness check around RequestStateFromServer(), mirroring
the existing pattern used elsewhere in this file and in
WaylandToplevelWindow::HandleToplevelConfigure().

Fixed: 518006275
Change-Id: I8812d780c4e46b42bc1a8302c69e4f2e8256b660
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7981871
Commit-Queue: Kramer Ge <[email protected]>
Reviewed-by: Thomas Anderson <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1651724}
---

diff --git a/ui/ozone/platform/wayland/host/wayland_window.cc b/ui/ozone/platform/wayland/host/wayland_window.cc
index 81603cc..dcd3ff3 100644
--- a/ui/ozone/platform/wayland/host/wayland_window.cc
+++ b/ui/ozone/platform/wayland/host/wayland_window.cc
@@ -1349,7 +1349,13 @@
     }
   }
 
+  // RequestStateFromServer transitively calls delegate()->OnStateUpdate(),
+  // which may synchronously delete |this|.
+  auto weak_this = AsWeakPtr();
   RequestStateFromServer(state, serial);
+  if (!weak_this) {
+    return;
+  }
 
   // Reset values.
   pending_configure_state_ = PendingConfigureState();
diff --git a/ui/ozone/platform/wayland/host/wayland_window_unittest.cc b/ui/ozone/platform/wayland/host/wayland_window_unittest.cc
index 410f828d..dd00658 100644
--- a/ui/ozone/platform/wayland/host/wayland_window_unittest.cc
+++ b/ui/ozone/platform/wayland/host/wayland_window_unittest.cc
@@ -483,6 +483,22 @@
   window_->SetBoundsInDIP(gfx::Rect(1024, 768));
 }
 
+// Regression test for https://crbug.com/495948109.
+TEST_P(WaylandWindowTest, DeleteWindowFromOnStateUpdateDuringSurfaceConfigure) {
+  delegate_.set_on_state_update_callback(base::BindLambdaForTesting([&]() {
+    window_.reset();
+    return false;
+  }));
+
+  WaylandWindow* window = window_.get();
+  WaylandWindow::WindowStates window_states;
+  window_states.is_activated = true;
+  window->HandleToplevelConfigure(1024, 768, window_states);
+  window->HandleSurfaceConfigure(2);
+
+  EXPECT_FALSE(window_);
+}
+
 TEST_P(WaylandWindowTest, SetTitle) {
   window_->SetTitle(u"hello");
   PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/ui/ozone/platform/wayland/host/wayland_window_unittest.cc b/ui/ozone/platform/wayland/host/wayland_window_unittest.cc
index 410f828d..dd00658 100644
--- a/ui/ozone/platform/wayland/host/wayland_window_unittest.cc
+++ b/ui/ozone/platform/wayland/host/wayland_window_unittest.cc
@@ -483,6 +483,22 @@
   window_->SetBoundsInDIP(gfx::Rect(1024, 768));
 }
 
+// Regression test for https://crbug.com/495948109.
+TEST_P(WaylandWindowTest, DeleteWindowFromOnStateUpdateDuringSurfaceConfigure) {
+  delegate_.set_on_state_update_callback(base::BindLambdaForTesting([&]() {
+    window_.reset();
+    return false;
+  }));
+
+  WaylandWindow* window = window_.get();
+  WaylandWindow::WindowStates window_states;
+  window_states.is_activated = true;
+  window->HandleToplevelConfigure(1024, 768, window_states);
+  window->HandleSurfaceConfigure(2);
+
+  EXPECT_FALSE(window_);
+}
+
 TEST_P(WaylandWindowTest, SetTitle) {
   window_->SetTitle(u"hello");
   PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {
Loading diff…

Original Bug Report

reported by [email protected]

Potential UAF write and virtual call in WaylandWindow::ProcessPendingConfigureState

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 WaylandWindow::ProcessPendingConfigureState due to the absence of a liveness check after a synchronous, deletion-capable callout. The state application chain can synchronously delete the WaylandWindow instance, leading to subsequent writes to and reads from the freed ’this’ object when execution returns to the caller function.

Affected files:

  • ui/ozone/platform/wayland/host/wayland_window.cc
  • ui/ozone/platform/wayland/host/wayland_window.h

Estimated timestamp from git blame: Unknown (Google3 checkout)

Root Cause Analysis

In ui/ozone/platform/wayland/host/wayland_window.cc, WaylandWindow::ProcessPendingConfigureState invokes RequestStateFromServer() which subsequently delegates state updates to the window delegate:

void WaylandWindow::ProcessPendingConfigureState(uint32_t serial) {
  ...
  RequestStateFromServer(state, serial);

  // Reset values.
  pending_configure_state_ = PendingConfigureState(); // Potential UAF WRITE

  if (state == applied_state_ && state == latched_state_ &&
      in_flight_requests_.empty() && root_surface()->has_buffer()) {
    root_surface_->Commit(/*flush=*/true);             // Potential wild pointer dereference and virtual call
  }
}

When RequestStateFromServer runs, the call path is as follows:

  1. RequestStateFromServer calls RequestState (Line 1343).
  2. RequestState calls MaybeApplyLatestStateRequest (Line 1427).
  3. MaybeApplyLatestStateRequest triggers delegate()->OnStateUpdate(old, latest.state) (Line 1599).

The delegate callback triggers synchronous bounds and visibility observer notifications. In certain scenarios, these observers can synchronously call Widget::CloseNow(), leading to the synchronous deletion of the WaylandWindow instance.

While MaybeApplyLatestStateRequest contains a weak pointer guard (weak_this) to detect self-deletion and abort cleanly, the outer callers (RequestState and RequestStateFromServer) do not check liveness before returning. Consequently, execution returns back to ProcessPendingConfigureState where the freed this pointer is unconditionally accessed, causing a use-after-free write on pending_configure_state_ and a potential use-after-free read and virtual call on root_surface_.

Potential Attack Path / Trigger Scenario

Because our automated analysis tools do not have the capability to execute code dynamically, these steps represent a potential exploitation sequence:

  1. An attacker-controlled web page in a sandboxed renderer process opens a popup window (window.open).
  2. The page forces frequent configure and window state updates (e.g. through Fullscreen API toggles or resizeTo).
  3. The attacker engineers or triggers a synchronous widget closure (e.g. Widget::CloseNow()) within the synchronous observer callback path during OnStateUpdate.
  4. When the WaylandWindow is deleted synchronously, execution returns back to ProcessPendingConfigureState, causing memory corruption in the browser process.

Suggested Fix

To prevent this issue, introduce a weak-pointer liveness check immediately after calling RequestStateFromServer inside WaylandWindow::ProcessPendingConfigureState:

  auto weak_this = AsWeakPtr();
  RequestStateFromServer(state, serial);
  if (!weak_this) {
    return;
  }

Evaluated with Chrome root at commit: fb72408a8493c46bc75fae1c70d03daec96b3040


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