Overview

Critical
Severity
8.8
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Views
DescriptionUse after free in Views
ComponentViews
Sub-componentViews
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.
Tracker524045160
Fix commitb17402fd2c84 (chromium/src) +9/-8
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-06-15 (per vendor advisory)
Fix landed2026-06-29 (public git)
Disclosed2026-07-08
Patch gap9 days — fix public in source before the advisory

Background

`FocusManager`
the Views class that tracks which View currently holds keyboard focus and which View should be restored (the “stored” focus view) after focus is temporarily cleared.
`ViewTracker`
a helper that holds a weak, self-nulling reference to a View, returning nullptr from view() once the tracked View is destroyed, so it survives dangling where a raw View* 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 that View.
Focus event reentrancy
calling SetFocusedView(nullptr) / ClearNativeFocus() fires focus-change notifications that can run arbitrary observer code, which may destroy the very View whose 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.

Key insight
The single mistake is holding a raw 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

  1. Reach a focus-clearing path Trigger a UI action that invokes FocusManager::ClearFocus() or FocusManager::StoreFocusedView() while a View holds stored/current focus, so the code captures that View (GetStoredFocusView() or focused_view_) as a raw pointer. (inferred: the specific web/UI interaction that drives these entry points is not shown in the diff.)
  2. Free the View during the interim events During the subsequent SetFocusedView(nullptr) / ClearNativeFocus() calls, arrange for the fired focus-change notifications to destroy the captured View (e.g. by tearing down the widget/subtree from an observer). (inferred: the concrete observer that destroys the View is not in the diff.)
  3. Trigger the stale-pointer use Control returns and the pre-patch code reuses the now-freed pointer: SetStoredFocusView(focused_view) re-stores a dangling View*, and in StoreFocusedView() v->SchedulePaint() dereferences the freed object, producing the use-after-free.
  4. Escalate from the UAF A freed-and-reallocated View object could yield a controlled read/write or vtable dereference leading to further memory corruption. (inferred — the diff only demonstrates the dangling re-store and SchedulePaint() dereference, not any exploitation primitive.)

Impact Assessment

An attacker who can drive focus changes in the Views UI gains a use-after-free of a 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

FunctionChangeNotes
if
ui/views/focus/focus_manager.cc
modified

Files Changed

  • ui/views/focus/focus_manager.cc

Audit Directions

  • Reentrant destruction from focus/observer callbacks
    Review other FocusManager methods (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 pointers
    Audit 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.
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.
   }
 }
 
Loading diff…

Original Bug Report

reported by [email protected]

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():

  1. The view is removed from the hierarchy, and all raw_ptr<View> references to it (including FocusManager::focused_view_) are cleared.
  2. Because all raw_ptr references are removed, the MiraclePtr (BackupRefPtr) reference count drops to exactly zero.
  3. PartitionAlloc genuinely frees the memory, returning it to the thread cache.
  4. Execution returns to StoreFocusedView, where the raw stack pointers focused_view and v remain 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.)

  1. 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).
  2. Trigger Focus Change: The attacker uses Javascript (e.g., window.blur()) or user interaction to forcefully deactivate the browser window. This triggers DesktopNativeWidgetAura::HandleActivationChanged(), which calls GetWidget()->GetFocusManager()->StoreFocusedView(false);.
  3. Synchronous Destruction: As focus is cleared, the transient UI element receives the Blur() signal and synchronously destroys itself and its widget.
  4. Heap Spray: The attacker utilizes web-accessible heap spraying techniques to reclaim the freed View memory chunk before the vulnerable functions resume.
  5. Execution: When StoreFocusedView resumes, it executes v->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.

View on issue tracker