Overview

Critical
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Ozone
DescriptionUse after free in Ozone
ComponentOzone
Bug ClassUAF
Tracker513454018
Fix commit46c5d9828122 (chromium/src) +39/-0
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-02

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