CVE-2026-15112
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifui/ozone/platform/wayland/host/wayland_window.cc |
modified | |
TEST_Pui/ozone/platform/wayland/host/wayland_window_unittest.cc |
modified | |
PostToServerAndWaitui/ozone/platform/wayland/host/wayland_window_unittest.cc |
modified |
Files Changed
ui/ozone/platform/wayland/host/wayland_window.ccui/ozone/platform/wayland/host/wayland_window_unittest.cc
Patch
From 06d1bbdddd1f176c3443ca2ddb1f1d21dfe9a094 Mon Sep 17 00:00:00 2001
From: Kramer Ge <fangzhoug@chromium.org>
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 <fangzhoug@chromium.org>
Reviewed-by: Thomas Anderson <thomasanderson@chromium.org>
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) {
Regression Test / PoC
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) {
Original Bug Report
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.ccui/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:
RequestStateFromServercallsRequestState(Line 1343).RequestStatecallsMaybeApplyLatestStateRequest(Line 1427).MaybeApplyLatestStateRequesttriggersdelegate()->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:
- An attacker-controlled web page in a sandboxed renderer process opens a popup window (
window.open). - The page forces frequent configure and window state updates (e.g. through Fullscreen API toggles or
resizeTo). - The attacker engineers or triggers a synchronous widget closure (e.g.
Widget::CloseNow()) within the synchronous observer callback path duringOnStateUpdate. - When the
WaylandWindowis deleted synchronously, execution returns back toProcessPendingConfigureState, 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.