Overview

Critical
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Ozone
DescriptionUse after free in Ozone
ComponentOzone
Bug ClassUAF
Tracker518006275
Fix commit06d1bbdddd1f (chromium/src) +22/-0
CISA KEVNot listed
CreditedGoogle
Disclosed2026-07-08

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
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