CVE-2026-15129
Overview
Background
- `FocusManager`
- the Views class that tracks which
Viewcurrently holds keyboard focus and whichViewshould be restored (the “stored” focus view) after focus is temporarily cleared. - `ViewTracker`
- a helper that holds a weak, self-nulling reference to a
View, returningnullptrfromview()once the trackedViewis destroyed, so it survives dangling where a rawView*would not. - Use-after-free (UAF)
- a bug where a pointer is dereferenced after the object it points to has been freed; here it arises because a raw
View*is captured, then reused after intervening code may have destroyed thatView. - Focus event reentrancy
- calling
SetFocusedView(nullptr)/ClearNativeFocus()fires focus-change notifications that can run arbitrary observer code, which may destroy the veryViewwhose pointer was captured just before.
Root Cause Analysis
Both FocusManager::ClearFocus() and FocusManager::StoreFocusedView() captured a bare views::View* (via GetStoredFocusView() and focused_view_ respectively) and then continued to call SetFocusedView(nullptr) and ClearNativeFocus(), which — as the new comments state — send out focus-change events “in the interim.” Those events can synchronously run code that destroys the captured View, after which the stale raw pointer was reused: ClearFocus() passed it back into SetStoredFocusView(focused_view), and StoreFocusedView() both re-stored it and dereferenced it with v->SchedulePaint(). The invariant violated is that a raw View* remains valid across a call that can reenter and free the object.
The fix replaces the raw pointers with ViewTracker instances (view_tracker_for_stored_view_ in ClearFocus(), the local old_view in StoreFocusedView()), so that after the events fire the code reads old_view.view(), which safely returns nullptr if the View was destroyed rather than a dangling pointer. Because every subsequent use goes through ViewTracker::view() and the if (auto* const view = old_view.view()) guard, the freed View is never re-stored or dereferenced.
View* across SetFocusedView(nullptr) / ClearNativeFocus() — calls that can reenter and destroy the View — and then reusing it; the fix wraps the pointer in a ViewTracker so the post-event access self-nulls instead of dangling.Attack Path
- Reach a focus-clearing path
Trigger a UI action that invokes
FocusManager::ClearFocus()orFocusManager::StoreFocusedView()while aViewholds stored/current focus, so the code captures thatView(GetStoredFocusView()orfocused_view_) as a raw pointer. (inferred: the specific web/UI interaction that drives these entry points is not shown in the diff.) - Free the View during the interim events
During the subsequent
SetFocusedView(nullptr)/ClearNativeFocus()calls, arrange for the fired focus-change notifications to destroy the capturedView(e.g. by tearing down the widget/subtree from an observer). (inferred: the concrete observer that destroys theViewis not in the diff.) - Trigger the stale-pointer use
Control returns and the pre-patch code reuses the now-freed pointer:
SetStoredFocusView(focused_view)re-stores a danglingView*, and inStoreFocusedView()v->SchedulePaint()dereferences the freed object, producing the use-after-free. - Escalate from the UAF
A freed-and-reallocated
Viewobject could yield a controlled read/write or vtable dereference leading to further memory corruption. (inferred — the diff only demonstrates the dangling re-store andSchedulePaint()dereference, not any exploitation primitive.)
Impact Assessment
View object in the browser process, where FocusManager runs. Exploitation requires steering the intervening focus-change events to destroy the focused/stored View before the stale pointer is reused, which is described in the patch as only “(theoretically) possible.The vendor advisory rates this critical severity and does not report in-the-wild exploitation; NVD scores it CVSS 8.8. The advisory metadata classifies this as a UAF in Views and does not state exploited-in-the-wild status or a severity rating.Changed Functions
| Function | Change | Notes |
|---|---|---|
ifui/views/focus/focus_manager.cc |
modified |
Files Changed
ui/views/focus/focus_manager.cc
Audit Directions
- Reentrant destruction from focus/observer callbacksReview other
FocusManagermethods (e.g. focus advancement and restore paths) and observer notification sites where synchronous callbacks may destroy the object whose pointer the caller still holds. - Store/restore state pointersAudit places that cache a “stored” or “previous”
View/widget across UI teardown and later re-store or dereference it, confirming the cached reference is a self-nulling tracker rather than a raw pointer.
Patch
From b17402fd2c84ab1a58ff094c122a3c4f55c79b38 Mon Sep 17 00:00:00 2001 From: Dana Fried <[email protected]> Date: Mon, 29 Jun 2026 07:19:16 -0700 Subject: [PATCH] [Views] avoid possible UAF in FocusManager This replaces raw View* with ViewTracker in a couple of instances where it is (theoretically) possible for a View to disappear in the middle of a function, because events are potentially generated between capture and use. Fixed: 524045160 Change-Id: I4f9c42ca0e706c5042e08aa451d3944367b98a83 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8015894 Commit-Queue: Kaan Alsan <[email protected]> Commit-Queue: Dana Fried <[email protected]> Auto-Submit: Dana Fried <[email protected]> Reviewed-by: Kaan Alsan <[email protected]> Cr-Commit-Position: refs/heads/main@{#1654051} --- diff --git a/ui/views/focus/focus_manager.cc b/ui/views/focus/focus_manager.cc index 8c154ec..7bb61c0 100644 --- a/ui/views/focus/focus_manager.cc +++ b/ui/views/focus/focus_manager.cc @@ -389,11 +389,12 @@ void FocusManager::ClearFocus() { // SetFocusedView(nullptr) is going to clear out the stored view to. We need - // to persist it in this case. - views::View* focused_view = GetStoredFocusView(); + // to persist it in this case (assuming it survives the events sent out in the + // interim). + auto focused_view = std::make_unique<ViewTracker>(GetStoredFocusView()); SetFocusedView(nullptr); ClearNativeFocus(); - SetStoredFocusView(focused_view); + view_tracker_for_stored_view_ = std::move(focused_view); } void FocusManager::AdvanceFocusIfNecessary() { @@ -414,14 +415,14 @@ } void FocusManager::StoreFocusedView(bool clear_native_focus) { - View* focused_view = focused_view_; // Don't do anything if no focused view. Storing the view (which is nullptr), // in this case, would clobber the view that was previously saved. if (!focused_view_) { return; } - View* v = focused_view_; + // This could go away when sending events below, so guard access. + ViewTracker old_view(focused_view_); if (clear_native_focus) { // Temporarily disable notification. ClearFocus() will set the focus to the @@ -433,11 +434,11 @@ ClearFocus(); } else { SetFocusedView(nullptr); - SetStoredFocusView(focused_view); + SetStoredFocusView(old_view.view()); } - if (v) { - v->SchedulePaint(); // Remove focus border. + if (auto* const view = old_view.view()) { + view->SchedulePaint(); // Remove focus border. } }
Original Bug Report
Potential Heap-use-after-free in FocusManager::StoreFocusedView
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 heap-use-after-free vulnerability exists in FocusManager::StoreFocusedView and ClearFocus because raw View* pointers are cached on the stack. Operations like SetFocusedView(nullptr) can synchronously destroy the view via focus change callbacks, leaving the stack pointers dangling and bypassing MiraclePtr protections.
Affected files:
ui/views/focus/focus_manager.cc
Estimated timestamp from git blame: Unknown (Google3 checkout)
Summary
A potential heap-use-after-free (UAF) vulnerability has been identified in FocusManager::StoreFocusedView and FocusManager::ClearFocus within ui/views/focus/focus_manager.cc. These functions cache raw pointers to View objects on the stack and subsequently perform operations that can trigger the synchronous destruction of those views via focus change callbacks. Because the pointers are raw stack variables, they bypass MiraclePtr (BackupRefPtr) protections, leading to a UAF that could be leveraged for arbitrary code execution in the browser process.
Vulnerability Details
In FocusManager::StoreFocusedView, raw pointers to the currently focused view are cached on the stack (focused_view and v):
void FocusManager::StoreFocusedView(bool clear_native_focus) {
View* focused_view = focused_view_;
// ...
View* v = focused_view_;
if (clear_native_focus) {
// ...
ClearFocus();
} else {
SetFocusedView(nullptr);
SetStoredFocusView(focused_view); // <--- Potential Use-After-Free
}
if (v) {
v->SchedulePaint(); // <--- Potential Use-After-Free
}
}
A similar pattern exists in FocusManager::ClearFocus.
The vulnerability occurs because the call to SetFocusedView(nullptr) (or ClearFocus, which also sets focus to null) triggers SetFocusedViewWithReason. This method explicitly calls Blur() on the previously focused view.
In the Chromium UI framework, it is a supported and documented behavior for transient UI components (e.g., text selection handles, dropdown menus, or elements utilizing ash::SystemTextfield) to synchronously destroy themselves when losing focus. The framework anticipates this; SetFocusedViewWithReason itself uses a ViewTracker specifically to handle the view being destroyed during the Blur() call (crbug.com/1221133).
However, StoreFocusedView does not use ViewTracker for its local variables. If the view destroys itself during Blur():
- The view is removed from the hierarchy, and all
raw_ptr<View>references to it (includingFocusManager::focused_view_) are cleared. - Because all
raw_ptrreferences are removed, the MiraclePtr (BackupRefPtr) reference count drops to exactly zero. - PartitionAlloc genuinely frees the memory, returning it to the thread cache.
- Execution returns to
StoreFocusedView, where the raw stack pointersfocused_viewandvremain dangling.
Subsequently, SetStoredFocusView(focused_view) passes the dangling pointer to ViewTracker::SetView, which attempts to register an observer on the freed object. Additionally, v->SchedulePaint() calls a virtual method (GetWidget()) on the freed object.
Suggested Attacker Steps
(Note: These are potential steps based on code analysis; a working proof of concept has not been developed.)
- Setup: An attacker controls web content that triggers the display of a transient, focusable browser UI element (e.g., a specific prompt, bubble, or textfield).
- Trigger Focus Change: The attacker uses Javascript (e.g.,
window.blur()) or user interaction to forcefully deactivate the browser window. This triggersDesktopNativeWidgetAura::HandleActivationChanged(), which callsGetWidget()->GetFocusManager()->StoreFocusedView(false);. - Synchronous Destruction: As focus is cleared, the transient UI element receives the
Blur()signal and synchronously destroys itself and its widget. - Heap Spray: The attacker utilizes web-accessible heap spraying techniques to reclaim the freed
Viewmemory chunk before the vulnerable functions resume. - Execution: When
StoreFocusedViewresumes, it executesv->SchedulePaint(), invoking a virtual method on the reclaimed memory. By placing a fake vtable in the sprayed memory, the attacker can redirect execution to an arbitrary address, achieving an S0 sandbox escape.
Proposed Fix
Update StoreFocusedView and ClearFocus to use ViewTracker to track views instead of using raw stack pointers. This ensures that the local variables safely become null if the view is destroyed during synchronous callbacks, preventing the UAF.
Evaluated with Chrome root at commit: 65b3256311f3ab6fb9870eaa522de7e6dd2663bb
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.