CVE-2026-78945
Overview
Background
- `RootView`
- the top-level
views::Viewof aWidget. It receives native mouse and gesture events and routes them to child views, remembering the view that took a mouse press inmouse_pressed_handler_. - `mouse_pressed_handler_`
- the view that handled the last mouse press. The matching mouse release is delivered to it, even if the cursor has moved.
- `ViewTracker`
- a Views helper that observes one
Viewand resets its stored pointer to null when that view is destroyed.view()therefore returns either a live view ornullptr. - `raw_ptr<..., DisableDanglingPtrDetection>`
- a
raw_ptrwith dangling-pointer detection deliberately switched off. It gives no protection if the pointee is freed.
Root Cause Analysis
According to the commit message, the vulnerable code is the mouse-release branch of RootView::OnMouseReleased in ui/views/widget/root_view.cc. The hunk itself does not show the function header. Before the patch, the code copied mouse_pressed_handler_.get() into a local raw_ptr<View, DisableDanglingPtrDetection>, then called SetMouseAndGestureHandler(nullptr), then called DispatchEvent(mouse_pressed_handler, &mouse_released) with no check that the view still existed. The removed TODO(crbug.com/497948894) comment, which suggested ViewTracker, shows the dangling risk was already known. If the pressed-handler view was destroyed between taking the pointer and dispatching, DispatchEvent received a freed View*.
The patch’s stated trigger is “if the view is deleted or modified during event dispatch”.
The patch replaces the raw local with a ViewTracker and dispatches only if (mouse_pressed_handler_tracker.view()). A view deleted before dispatch is skipped instead of used. The existing dispatcher_destroyed early return is kept, inside the new guard.
ViewTracker and does not dispatch if the view has been destroyed.Attack Path
- Press on a browser-UI view
The user presses the mouse on a Views control in a browser window, and
RootViewrecords it asmouse_pressed_handler_. - Release begins the vulnerable sequence
On release,
RootViewcaches the handler pointer and callsSetMouseAndGestureHandler(nullptr)before dispatching the release event. - Handler view destroyed in the window The cached view is deleted or reparented before the release is dispatched. The commit says this can happen during event dispatch. Which concrete UI flow does this, and whether web content can drive it, is not shown in the patch (inferred).
- Release dispatched to freed view
Before the fix,
DispatchEventwas called on the stale pointer, a use-after-free in the browser process. Any controlled reuse of the freed object is (inferred).
Impact Assessment
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifui/views/widget/root_view.cc |
modified |
Files Changed
ui/views/widget/root_view.cc
Audit Directions
- Raw locals cached across re-entrant callsIn
root_view.ccand other event routers (View::OnMouseEventforwarding, gesture, drag-and-drop and capture code), look for localView*/raw_ptrcopies held across calls likeSetMouseAndGestureHandler,DispatchEvent, focus changes or callbacks. Check whether those copies are re-validated. - `DisableDanglingPtrDetection` and dangling TODOsSearch for
DisableDanglingPtrDetectionand TODOs that mentionViewTrackeror dangling pointers inui/views. Each one marks a known spot where a pointee may be freed while still referenced. - Post-dispatch use of handlersAfter
DispatchEvent, onlydispatcher_destroyedis checked. Audit code that keeps using handler views (mouse_move_handler_,gesture_handler_,drag_info) after dispatch without a tracker or a re-lookup.
Patch
From e3463127992e74aadb45161c851d9642f6964336 Mon Sep 17 00:00:00 2001 From: David Yeung <[email protected]> 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 <[email protected]> Commit-Queue: David Yeung <[email protected]> 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; + } } } }
Original Bug Report
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.ccui/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):
SetMouseHandler(nullptr)is called, which clears themouse_pressed_handler_raw_ptr. If this was the onlyraw_ptrholding the View, its BackupRefPtr count drops to 0.MaybeNotifyGestureHandlerBeforeReplacement()is called. If there is an active touch and a validgesture_handler_, it callsgesture_recognizer->SendSynthesizedEndEvents().- This synchronous event dispatch can invoke arbitrary UI logic. If the gesture handler deletes the
mouse_pressed_handlerView, the underlying memory is immediately freed and returned to the allocator, as MiraclePtr no longer observes any activeraw_ptrreferences to it. - When
SetMouseAndGestureHandlerreturns,DispatchEventis called with the danglingmouse_pressed_handlerraw 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.
- The user interacts with a browser UI Widget (e.g., Omnibox or a WebUI surface) using both a mouse and a touchscreen simultaneously.
- A mouse press sets
mouse_pressed_handler_toView A. - A simultaneous touch interaction sets
gesture_handler_toView B(which may be the same or a different View). - The user releases the mouse while the touch is still active, triggering
RootView::OnMouseReleased. RootViewcachesView Ain a local raw pointer and clears itsmouse_pressed_handler_raw_ptr, stripping MiraclePtr protection.RootViewsynchronously dispatches a synthesized gesture end event toView B.View B’s event handler executes logic (e.g., closing a dialog or swapping a UI panel) that destroysView A.View A’s memory is freed.RootView::OnMouseReleasedcallsDispatchEventon the danglingView Apointer, 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.