Critical watch CVSS 8.8 chrome UAF 📄 Reporter bug report 🔧 Commit mapped

Overview

Critical
Severity
8.8
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Ozone
DescriptionUse after free in Ozone
ComponentOzone
Sub-componentWayland
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.
Tracker513454018
Fix commit46c5d9828122 (chromium/src) +39/-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-15 (per vendor advisory)
Fix landed2026-05-15 (public git)
Disclosed2026-06-02
Patch gap18 days — fix public in source before the advisory

Background

`WaylandEventSource`
the Ozone/Wayland host component that translates Wayland input events (including tablet-tool proximity) into Chromium ui::Events dispatched to WaylandWindows.
`OnTabletToolProximityIn` / `OnTabletToolProximityOut`
handlers for a tablet stylus entering/leaving a window; proximity-in into a new window first drives proximity-out of the previously focused one.
Synchronous event dispatch / nested message loop
dispatching a ui::Event (e.g. kMouseExited) to a window can re-enter a nested loop and run delegate code that destroys a WaylandWindow.
`base::WeakPtr` / `AsWeakPtr`
a non-owning handle that nulls out when the WaylandWindow is destroyed, used to detect destruction across the synchronous call.

Root Cause Analysis

WaylandEventSource::OnTabletToolProximityIn(window, ...) first checks the previously focused window and, if different, calls OnTabletToolProximityOut(time), which synchronously dispatches a kMouseExited event. That dispatch can enter a nested message loop and run delegate code that destroys the incoming window (the shipped TabletToolProximityInUAF test wires delegate1 to window2.reset() on kMouseExited). The pre-patch code then used the raw window pointer — tablet_tool_focused_window_ = window->AsWeakPtr() — dereferencing a freed object, a use-after-free. The invariant violated is that window outlives the OnTabletToolProximityOut call.

The fix captures window_weak = window->AsWeakPtr() before the proximity-out call and, immediately after it, returns early if !window_weak, so the freed window is never dereferenced.

Key insight
The handler kept using the raw window pointer after a synchronous OnTabletToolProximityOut dispatch that can destroy that very window; the fix takes a WeakPtr to window beforehand and returns early when it is invalidated, before any further use.

Attack Path

  1. Focus a first window The tablet tool enters window1, so OnTabletToolProximityIn(window1, ...) sets it as tablet_tool_focused_window_.
  2. Proximity-in to a second window OnTabletToolProximityIn(window2, ...) runs; since old_focus (window1) differs from window2, it calls OnTabletToolProximityOut(time).
  3. Destroy during dispatch OnTabletToolProximityOut synchronously dispatches kMouseExited, and the window delegate destroys window2 from within that callback, freeing it.
  4. Use-after-free Control returns to OnTabletToolProximityIn, which (pre-patch) calls window->AsWeakPtr() on the freed window2, dereferencing freed memory.
  5. Downstream corruption Any escalation from the dangling dereference to a controllable primitive or code execution is not shown by the diff (inferred).

Impact Assessment

An attacker able to arrange that a WaylandWindow is destroyed from a delegate during tablet-tool proximity transitions triggers a use-after-free in the browser process on Linux/Wayland with a tablet input device. The vendor advisory rates this critical and labels it a use-after-free in Ozone; the diff demonstrates the dangling window dereference but any memory-corruption escalation beyond it is (inferred). Preconditions are the Wayland platform, tablet-tool input, and a window-destroying reaction to the synchronous kMouseExited dispatch; the advisory does not state in-the-wild exploitation.

Changed Functions

FunctionChangeNotes
if
ui/ozone/platform/wayland/host/wayland_event_source.cc
modified
TEST_P
ui/ozone/platform/wayland/host/wayland_event_source_unittest.cc
modified

Files Changed

  • ui/ozone/platform/wayland/host/wayland_event_source.cc
  • ui/ozone/platform/wayland/host/wayland_event_source_unittest.cc

Audit Directions

  • Raw pointer use after synchronous event dispatch
    Flag input/event handlers that dispatch a ui::Event (which may re-enter) and then reuse a window/delegate pointer; guard with a WeakPtr.
  • Focus-transition handlers
    Review enter/leave, focus-in/out, and proximity-in/out pairs where handling one transition can destroy the object involved in the other.
  • Nested message loops in Ozone backends
    Audit Wayland/X11 host code paths where DispatchEvent can spin a nested loop, invalidating locals captured before the call.
From 46c5d9828122ed474fc6ae51b50116edad4597db Mon Sep 17 00:00:00 2001
From: Tom Anderson <thomasanderson@chromium.org>
Date: Fri, 15 May 2026 14:37:56 -0700
Subject: [PATCH] wayland: Fix UAF in OnTabletToolProximityIn

Synchronous event dispatch during OnTabletToolProximityOut can trigger
a nested message loop, which may result in the destruction of the
WaylandWindow. Using a raw pointer after this call leads to a
Use-After-Free.

This CL fixes this by using a WeakPtr to re-validate the window's
existence before proceeding with the event dispatch.

Fixed: 513454018
Change-Id: I4c4bdf2ff55b3d21356514a1350b85c58c4fbeed
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7851764
Commit-Queue: Thomas Anderson <thomasanderson@chromium.org>
Reviewed-by: Lei Zhang <thestig@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1631573}
---

diff --git a/ui/ozone/platform/wayland/host/wayland_event_source.cc b/ui/ozone/platform/wayland/host/wayland_event_source.cc
index 5bd7a3f..0f96ec1 100644
--- a/ui/ozone/platform/wayland/host/wayland_event_source.cc
+++ b/ui/ozone/platform/wayland/host/wayland_event_source.cc
@@ -584,9 +584,15 @@
                                                  const PointerDetails& details,
                                                  base::TimeTicks time) {
   WaylandWindow* old_focus = tablet_tool_focused_window_.get();
+  base::WeakPtr<WaylandWindow> window_weak = window->AsWeakPtr();
   if (old_focus && old_focus != window) {
     OnTabletToolProximityOut(time);
   }
+
+  if (!window_weak) {
+    return;
+  }
+
   tablet_tool_focused_window_ = window->AsWeakPtr();
   tablet_tool_location_ = location;
 
diff --git a/ui/ozone/platform/wayland/host/wayland_event_source_unittest.cc b/ui/ozone/platform/wayland/host/wayland_event_source_unittest.cc
index 78d3690..7082671 100644
--- a/ui/ozone/platform/wayland/host/wayland_event_source_unittest.cc
+++ b/ui/ozone/platform/wayland/host/wayland_event_source_unittest.cc
@@ -371,6 +371,39 @@
   EXPECT_TRUE(pointer_delegate_->IsPointerButtonPressed(EF_RIGHT_MOUSE_BUTTON));
 }
 
+TEST_P(WaylandEventSourceTest, TabletToolProximityInUAF) {
+  auto* event_source = connection_->event_source();
+
+  // Create two windows.
+  MockWaylandPlatformWindowDelegate delegate1(connection_.get());
+  auto window1 = CreateWaylandWindowWithParams(PlatformWindowType::kWindow,
+                                               kDefaultBounds, &delegate1);
+
+  MockWaylandPlatformWindowDelegate delegate2(connection_.get());
+  auto window2 = CreateWaylandWindowWithParams(PlatformWindowType::kWindow,
+                                               kDefaultBounds, &delegate2);
+
+  // Set `window1` as focused.
+  event_source->OnTabletToolProximityIn(window1.get(), gfx::PointF(), {},
+                                        base::TimeTicks::Now());
+
+  // Set up `delegate1` to destroy `window2` when it receives `kMouseExited`.
+  // When `window1` is the `tablet_tool_focused_window_`, calling
+  // `OnTabletToolProximityIn(window2)` will call `OnTabletToolProximityOut()`,
+  // which dispatches `kMouseExited` to `window1`.
+
+  EXPECT_CALL(delegate1, DispatchEvent(::testing::_))
+      .WillOnce([&](Event* event) {
+        if (event->type() == EventType::kMouseExited) {
+          window2.reset();
+        }
+      });
+
+  // This should not crash.
+  event_source->OnTabletToolProximityIn(window2.get(), gfx::PointF(), {},
+                                        base::TimeTicks::Now());
+}
+
 INSTANTIATE_TEST_SUITE_P(
     EventsDispatchPolicyTest,
     WaylandEventSourceTest,
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/ui/ozone/platform/wayland/host/wayland_event_source_unittest.cc b/ui/ozone/platform/wayland/host/wayland_event_source_unittest.cc
index 78d3690..7082671 100644
--- a/ui/ozone/platform/wayland/host/wayland_event_source_unittest.cc
+++ b/ui/ozone/platform/wayland/host/wayland_event_source_unittest.cc
@@ -371,6 +371,39 @@
   EXPECT_TRUE(pointer_delegate_->IsPointerButtonPressed(EF_RIGHT_MOUSE_BUTTON));
 }
 
+TEST_P(WaylandEventSourceTest, TabletToolProximityInUAF) {
+  auto* event_source = connection_->event_source();
+
+  // Create two windows.
+  MockWaylandPlatformWindowDelegate delegate1(connection_.get());
+  auto window1 = CreateWaylandWindowWithParams(PlatformWindowType::kWindow,
+                                               kDefaultBounds, &delegate1);
+
+  MockWaylandPlatformWindowDelegate delegate2(connection_.get());
+  auto window2 = CreateWaylandWindowWithParams(PlatformWindowType::kWindow,
+                                               kDefaultBounds, &delegate2);
+
+  // Set `window1` as focused.
+  event_source->OnTabletToolProximityIn(window1.get(), gfx::PointF(), {},
+                                        base::TimeTicks::Now());
+
+  // Set up `delegate1` to destroy `window2` when it receives `kMouseExited`.
+  // When `window1` is the `tablet_tool_focused_window_`, calling
+  // `OnTabletToolProximityIn(window2)` will call `OnTabletToolProximityOut()`,
+  // which dispatches `kMouseExited` to `window1`.
+
+  EXPECT_CALL(delegate1, DispatchEvent(::testing::_))
+      .WillOnce([&](Event* event) {
+        if (event->type() == EventType::kMouseExited) {
+          window2.reset();
+        }
+      });
+
+  // This should not crash.
+  event_source->OnTabletToolProximityIn(window2.get(), gfx::PointF(), {},
+                                        base::TimeTicks::Now());
+}
+
 INSTANTIATE_TEST_SUITE_P(
     EventsDispatchPolicyTest,
     WaylandEventSourceTest,
Loading diff…

Original Bug Report

reported by vm...@google.com

Potential Use-After-Free in WaylandEventSource::OnTabletToolProximityIn via nested loops

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 browser process of Chromium on Linux Wayland. Re-entrant event dispatching during tablet tool proximity changes can trigger a nested message loop, during which the target window may be destroyed, leaving a dangling raw pointer. Subsequent virtual function calls on this pointer can lead to an unsandboxed browser process compromise.

Affected files:

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

Estimated timestamp from git blame: 2025-06-27

Description

A potential Use-After-Free (UAF) vulnerability has been identified in WaylandEventSource::OnTabletToolProximityIn within the Ozone/Wayland implementation. The issue arises because the function uses a raw pointer to a WaylandWindow after performing a synchronous operation that can trigger a nested message loop.

Technical Analysis

The vulnerability is located in ui/ozone/platform/wayland/host/wayland_event_source.cc:

void WaylandEventSource::OnTabletToolProximityIn(WaylandWindow* window,
                                                 const gfx::PointF& location,
                                                 const PointerDetails& details,
                                                 base::TimeTicks time) {
  WaylandWindow* old_focus = tablet_tool_focused_window_.get();
  if (old_focus && old_focus != window) {
    OnTabletToolProximityOut(time);          // [1]
  }
  tablet_tool_focused_window_ = window->AsWeakPtr();   // [2]
  ...
  SetTargetAndDispatchEvent(&event, window);           // [3]
}
  1. Re-entrancy [1]: When focus changes, OnTabletToolProximityOut dispatches a kMouseExited event to the previous window. In Chromium, synchronous event dispatch can trigger a nested message loop (e.g., via a modal dialog or context menu).
  2. Object Destruction: Many nested loops in the Wayland backend are started with kNestableTasksAllowed. While the loop is running, the browser process can process a window close request (e.g., initiated by a renderer via window.close()). This results in the destruction of the WaylandWindow object.
  3. Use-After-Free [2, 3]: When the nested loop returns, execution proceeds to line 590 [2]. The code calls window->AsWeakPtr(). Since AsWeakPtr is a pure virtual function in WaylandWindow, this is a virtual call on a potentially freed object. Further usage at SetTargetAndDispatchEvent [3] results in additional virtual calls (e.g., target->GetParentTarget()).

Because the window pointer is a function parameter on the stack, MiraclePtr (BackupRefPtr) does not provide protection here, as it primarily targets class members of type raw_ptr<>.

Potential Attack Scenario

  1. The attacker convinces a user to visit a malicious site on a Linux Wayland system with a graphics tablet.
  2. The site opens a popup (Window B).
  3. The attacker ensures the main window (Window A) will trigger a nested loop (e.g., a modal dialog) on a MouseExited event.
  4. The attacker triggers a tablet proximity event for Window B while Window A has focus.
  5. OnTabletToolProximityIn calls OnTabletToolProximityOut, triggering the nested loop in Window A.
  6. While the loop is active, the renderer closes Window B.
  7. The browser returns from the loop and dereferences the dangling raw pointer to Window B.

Note: These steps are based on source code analysis; no functional proof-of-concept has been executed by our automated tools.

Suggested Fix

The function should verify the continued existence of the window after the synchronous call. This can be achieved by using a WeakPtr for the window parameter or by re-retrieving the window state after OnTabletToolProximityOut returns.

  base::WeakPtr<WaylandWindow> window_weak = window->AsWeakPtr();
  if (old_focus && old_focus != window) {
    OnTabletToolProximityOut(time);
  }
  if (!window_weak) {
    return;
  }

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.

View on issue tracker