Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Views
DescriptionUse after free in Views
ComponentViews
Bug ClassUAF
Tracker497948894
Fix commite3463127992e (chromium/src) +7/-8
CISA KEVNot listed
CreditedGoogle
Disclosed2026-08-25

Changed Functions

FunctionChangeNotes
if
ui/views/widget/root_view.cc
modified

Files Changed

  • ui/views/widget/root_view.cc
From e3463127992e74aadb45161c851d9642f6964336 Mon Sep 17 00:00:00 2001
From: David Yeung <dayeung@chromium.org>
Date: Mon, 27 Jul 2026 12:23:52 -0700
Subject: [PATCH] Use ViewTracker for mouse_pressed_handler in OnMouseReleased

Replace raw pointer usage with a ViewTracker for `mouse_pressed_handler`
during the `OnMouseReleased` event handling in RootView. This ensures
that if the view is deleted or modified during event dispatch, safe
access patterns are maintained, preventing potential dangling pointer
issues and crashes.

Bug: 497948894
Change-Id: I03969feffe5d6cdb2da30b8ec512d738ecfe82c1
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8143940
Reviewed-by: Allen Bauer <kylixrd@chromium.org>
Commit-Queue: David Yeung <dayeung@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1668880}
---

diff --git a/ui/views/widget/root_view.cc b/ui/views/widget/root_view.cc
index ab508f4..cc90464 100644
--- a/ui/views/widget/root_view.cc
+++ b/ui/views/widget/root_view.cc
@@ -616,19 +616,18 @@
                                   mouse_pressed_handler_.get());
     // We allow the view to delete us from the event dispatch callback. As such,
     // configure state such that we're done first, then call View.
-    // TODO(crbug.com/497948894): according to the suggestion, use `ViewTracker`
-    // instead.
-    raw_ptr<View, DisableDanglingPtrDetection> mouse_pressed_handler =
-        mouse_pressed_handler_.get();
+    ViewTracker mouse_pressed_handler_tracker(mouse_pressed_handler_.get());
 
     // During mouse event handling, `SetMouseAndGestureHandler()` may be called
     // to set the gesture handler. Therefore we should reset the gesture handler
     // when mouse is released.
     SetMouseAndGestureHandler(nullptr);
-    ui::EventDispatchDetails dispatch_details =
-        DispatchEvent(mouse_pressed_handler, &mouse_released);
-    if (dispatch_details.dispatcher_destroyed) {
-      return;
+    if (mouse_pressed_handler_tracker.view()) {
+      ui::EventDispatchDetails dispatch_details =
+          DispatchEvent(mouse_pressed_handler_tracker.view(), &mouse_released);
+      if (dispatch_details.dispatcher_destroyed) {
+        return;
+      }
     }
   }
 }
Loading diff…

Original Bug Report

reported by vm...@google.com

Use-After-Free in RootView::OnMouseReleased via synchronous gesture dispatch

Project Fortify, an experimental security project, has identified the following potential security issue.

Overview: A potential Use-After-Free vulnerability exists in RootView::OnMouseReleased during simultaneous mouse and touch interactions. The method caches a raw pointer to a View, prematurely drops its raw_ptr protection, and triggers synchronous gesture events that can destroy the View before the cached pointer is used.

Affected files:

  • ui/views/widget/root_view.cc
  • ui/views/widget/root_view.h

Estimated timestamp from git blame: 2024-12-22

Vulnerability Details

A potential Use-After-Free (UAF) vulnerability exists in ui/views/widget/root_view.cc within the RootView::OnMouseReleased method. The vulnerability allows an attacker to bypass MiraclePtr (BackupRefPtr) protections because a raw_ptr reference is cleared right before a synchronous event dispatch that can trigger object destruction.

In RootView::OnMouseReleased, the following sequence occurs:

void RootView::OnMouseReleased(const ui::MouseEvent& event) {
  // ...
  if (mouse_pressed_handler_) {
    ui::MouseEvent mouse_released(...);

    // 1. A local raw C++ pointer is created. This does NOT increment the MiraclePtr refcount.
    View* mouse_pressed_handler = mouse_pressed_handler_;

    // 2. This call nullifies `mouse_pressed_handler_` (the raw_ptr), dropping the MiraclePtr refcount.
    // It then synchronously dispatches gesture events if a touch is active.
    SetMouseAndGestureHandler(nullptr);
    
    // 3. The raw pointer is used. If the view was destroyed in step 2, this is a UAF.
    ui::EventDispatchDetails dispatch_details = 
        DispatchEvent(mouse_pressed_handler, &mouse_released);
    // ...
  }
}

Inside SetMouseAndGestureHandler(nullptr):

  1. SetMouseHandler(nullptr) is called, which clears the mouse_pressed_handler_ raw_ptr. If this was the only raw_ptr holding the View, its BackupRefPtr count drops to 0.
  2. MaybeNotifyGestureHandlerBeforeReplacement() is called. If there is an active touch and a valid gesture_handler_, it calls gesture_recognizer->SendSynthesizedEndEvents().
  3. This synchronous event dispatch can invoke arbitrary UI logic. If the gesture handler deletes the mouse_pressed_handler View, the underlying memory is immediately freed and returned to the allocator, as MiraclePtr no longer observes any active raw_ptr references to it.
  4. When SetMouseAndGestureHandler returns, DispatchEvent is called with the dangling mouse_pressed_handler raw pointer, leading to a UAF.

Potential Steps to Reproduce

Note: These are suggested theoretical steps based on static analysis. Our tooling agent does not currently have the capability to execute code to produce a live Proof-of-Concept.

  1. The user interacts with a browser UI Widget (e.g., Omnibox or a WebUI surface) using both a mouse and a touchscreen simultaneously.
  2. A mouse press sets mouse_pressed_handler_ to View A.
  3. A simultaneous touch interaction sets gesture_handler_ to View B (which may be the same or a different View).
  4. The user releases the mouse while the touch is still active, triggering RootView::OnMouseReleased.
  5. RootView caches View A in a local raw pointer and clears its mouse_pressed_handler_ raw_ptr, stripping MiraclePtr protection.
  6. RootView synchronously dispatches a synthesized gesture end event to View B.
  7. View B’s event handler executes logic (e.g., closing a dialog or swapping a UI panel) that destroys View A. View A’s memory is freed.
  8. RootView::OnMouseReleased calls DispatchEvent on the dangling View A pointer, triggering the Use-After-Free.

Suggested Fix

To fix this, the code should safely track the lifetime of the mouse_pressed_handler across the synchronous call to SetMouseAndGestureHandler(). In the Views framework, this is idiomatically done using views::ViewTracker.

    // Use a ViewTracker to safely observe the view's lifetime.
    views::ViewTracker tracker(mouse_pressed_handler_.get());

    // During mouse event handling, `SetMouseAndGestureHandler()` may be called
    // to set the gesture handler. Therefore we should reset the gesture handler
    // when mouse is released.
    SetMouseAndGestureHandler(nullptr);
    
    if (tracker.view()) {
      ui::EventDispatchDetails dispatch_details =
          DispatchEvent(tracker.view(), &mouse_released);
      if (dispatch_details.dispatcher_destroyed) {
        return;
      }
    }

Alternatively, declaring the local variable as raw_ptr<View> mouse_pressed_handler = mouse_pressed_handler_; would maintain the MiraclePtr refcount across the dispatch, turning an exploitable UAF into a safe crash, but ViewTracker is the preferred pattern here to gracefully handle View destruction.

Evaluated with Chrome root at commit: a9cbf6e8b275fe4147435aa905f3b7f5a656f5f0


Results from so far have been promising, but there can be wrong deductions. If this proves to be a false positive, please close as WAI; 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