High watch ✔︎ verified CVSS 7.5 chrome UAF 📄 Reporter bug report 🔧 Commit mapped

Overview

High
Severity
7.5
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 not attacker-reachable (local) — factors set by independent review by AI
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.
Independent review by AIConfirmed
A second, independent AI reviewer (not a human) re-derived this analysis from the fixing commit and the vendor advisory, looking for errors.
Tracker513609324
Fix commit5c153abc2d85 (chromium/src) +10/-4
CWECWE-416 (Use-after-free)
CVSS vectorCVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:H
CISA KEVNot listed
CreditedGoogle
Reported2026-05-15 (per vendor advisory)
Fix landed2026-05-20 (public git)
Disclosed2026-05-27
Patch gap7 days — fix public in source before the advisory

Background

`WidgetDelegate`
the Views object that supplies a Widget’s configuration, including which view should receive focus first (widget_delegate.h:262).
`initially_focused_view`
a Params field holding the widget’s initially focused view, settable only before the widget is initialized (widget_delegate.h:323-325).
`views::ViewTracker`
a utility that holds a non-owning reference to a View and reports null once that view is destroyed.

Root Cause Analysis

Pre-fix, WidgetDelegate::Params stored the initially focused view as a bare std::optional<View*> initially_focused_view (widget_delegate.h:325). WidgetDelegate::SetInitiallyFocusedView assigns the raw pointer directly with params_.initially_focused_view = initially_focused_view (widget_delegate.cc:469), and WidgetDelegate::GetInitiallyFocusedView hands it back via params_.initially_focused_view.value_or(nullptr) (widget_delegate.cc:81). The invariant these accessors assume is that the referenced View outlives the delegate’s use of the pointer, but nothing clears the pointer when the view is destroyed; because the pointer lives inside a std::optional, Chromium’s automated rewriter does not convert it to raw_ptr<View>, so it also lacks BackupRefPtr protection (per the commit message and reporter). If the view is destroyed while the delegate still holds it, GetInitiallyFocusedView returns a dangling pointer, and a later focus operation dereferences freed memory.

The fix changes the field to std::unique_ptr<ViewTracker> (widget_delegate.h:325 post-fix): SetInitiallyFocusedView now lazily allocates a ViewTracker and calls SetView(initially_focused_view) (widget_delegate.cc:468-471 post-fix), and GetInitiallyFocusedView returns params_.initially_focused_view->view() or null (widget_delegate.cc:80-81 post-fix). Because ViewTracker observes the view and nulls itself on destruction, the accessor now returns null for a freed view instead of a stale pointer, restoring the lifetime safety invariant.

Key insight
The initially focused view was stored as an untracked raw pointer that was never invalidated when the view died; replacing it with a ViewTracker makes the handle self-null on view destruction so no dangling pointer can be returned.

Attack Path

  1. Configure an initial focus view A widget (e.g. a bubble) is shown with SetInitiallyFocusedView pointing at one of its child views (widget_delegate.cc:467-469).
  2. Destroy the referenced view The child view is removed while the widget remains alive, leaving initially_focused_view dangling since nothing clears it (widget_delegate.h:325). (inferred that a specific download-row removal does this)
  3. Query initial focus A focus operation calls GetInitiallyFocusedView, which returns the stale pointer (widget_delegate.cc:80-81).
  4. Use freed memory The caller performs a virtual focus call on the freed view. (inferred; the sink is outside the changed files)

Impact Assessment

An attacker who can arrange for a widget’s initially focused view to be freed while the widget persists can cause a use-after-free (and a virtual call on freed memory) in the unsandboxed browser process. The advisory rates this high severity with no in-the-wild exploitation. Escalation to code execution is (inferred); the shown code demonstrates only the dangling-pointer return.

Changed Functions

FunctionChangeNotes
if
ui/views/widget/widget_delegate.cc
modified
TabModalConfirmDialogViews
ui/views/widget/widget_delegate.h
modified
TestBaseWidgetDelegate
ui/views/widget/widget_delegate.h
modified
UpdateRecommendedMessageBox
ui/views/widget/widget_delegate.h
modified
ViewTracker
ui/views/widget/widget_delegate.h
modified
WebDialogBrowserTest
ui/views/widget/widget_delegate.h
modified

Files Changed

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

Audit Directions

  • Raw pointers inside `std::optional`/containers
    Flag View*/object pointers wrapped so the raw_ptr rewriter skips them; they silently lose BackupRefPtr coverage.
  • Stored view pointers without invalidation
    Any long-lived View* field must be a ViewTracker/base::WeakPtr or be cleared on the view’s destruction.
  • Set-once-use-later configuration fields
    Audit delegate/params fields set at construction and dereferenced much later for lifetime gaps between set and use.
From 5c153abc2d8518e22f3c9de4f149dcfb7308bb6b Mon Sep 17 00:00:00 2001
From: Dana Fried <[email protected]>
Date: Wed, 20 May 2026 12:26:25 -0700
Subject: [PATCH] [Views] Prevent dangling focus preference

Widget keeps an optional pointer to its initially-focused view. This
should be robust to the view going away at some later point, not just
during teardown (where it's not safe either, but less likely to get
dereferenced).

This changes the optional<View*> to unique_ptr<ViewTracker>.

Unique pointer is used to avoid importing ViewTracker, ViewObserver,
etc. into the #includes of the delegate class, which is included in
many places (and because many widgets don't bother setting this
parameter, so there's no point in allocating additional memory unless
it's needed).

Fixed: 513609324
Change-Id: Id7c393894a1b1a21584c6e43720a5f94154dab04
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7864987
Reviewed-by: Keren Zhu <[email protected]>
Commit-Queue: Keren Zhu <[email protected]>
Commit-Queue: Dana Fried <[email protected]>
Auto-Submit: Dana Fried <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1633754}
---

diff --git a/ui/views/widget/widget_delegate.cc b/ui/views/widget/widget_delegate.cc
index 897ec458..f80ad158 100644
--- a/ui/views/widget/widget_delegate.cc
+++ b/ui/views/widget/widget_delegate.cc
@@ -18,6 +18,7 @@
 #include "ui/display/screen.h"
 #include "ui/gfx/image/image_skia.h"
 #include "ui/views/view.h"
+#include "ui/views/view_tracker.h"
 #include "ui/views/views_delegate.h"
 #include "ui/views/widget/widget.h"
 #include "ui/views/window/client_view.h"
@@ -78,11 +79,12 @@
 }
 
 View* WidgetDelegate::GetInitiallyFocusedView() {
-  return params_.initially_focused_view.value_or(nullptr);
+  return params_.initially_focused_view ? params_.initially_focused_view->view()
+                                        : nullptr;
 }
 
 bool WidgetDelegate::HasConfiguredInitiallyFocusedView() const {
-  return params_.initially_focused_view.has_value();
+  return params_.initially_focused_view != nullptr;
 }
 
 BubbleDialogDelegate* WidgetDelegate::AsBubbleDialogDelegate() {
@@ -466,7 +468,10 @@
 
 void WidgetDelegate::SetInitiallyFocusedView(View* initially_focused_view) {
   DCHECK(!GetWidget());
-  params_.initially_focused_view = initially_focused_view;
+  if (!params_.initially_focused_view) {
+    params_.initially_focused_view = std::make_unique<ViewTracker>();
+  }
+  params_.initially_focused_view->SetView(initially_focused_view);
 }
 
 void WidgetDelegate::SetModalType(ui::mojom::ModalType modal_type) {
diff --git a/ui/views/widget/widget_delegate.h b/ui/views/widget/widget_delegate.h
index 07ba390..cb2d7b2f 100644
--- a/ui/views/widget/widget_delegate.h
+++ b/ui/views/widget/widget_delegate.h
@@ -53,6 +53,7 @@
 class TabModalConfirmDialogViews;
 class TestBaseWidgetDelegate;
 class UpdateRecommendedMessageBox;
+class ViewTracker;
 class WebDialogBrowserTest;
 FORWARD_DECLARE_TEST(AcceleratorCommandsFullscreenBrowserTest,
                      ToggleFullscreen);
@@ -322,7 +323,7 @@
 
     // The widget's initially focused view, if any. This can only be set before
     // this WidgetDelegate is used to initialize a Widget.
-    std::optional<View*> initially_focused_view;
+    std::unique_ptr<ViewTracker> initially_focused_view;
 
     // This is used by modal dialogs to override and constrain desired bounds
     // calculations.
Loading diff…

Original Bug Report

reported by [email protected]

Browser-process Use-After-Free in BrowserFocusControllerDelegateViews

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 vulnerability exists in the browser process due to a dangling pointer in WidgetDelegate’s initially_focused_view. This occurs because the pointer is stored in an unprotected std::optional and is not cleared when the referenced View is destroyed. Triggering accessibility shortcuts like F6 or Alt+Shift+A can result in a virtual function call on freed memory.

Affected files:

  • chrome/browser/ui/focus/browser_focus_controller_delegate_views.cc
  • ui/views/widget/widget_delegate.h
  • chrome/browser/ui/views/download/bubble/download_toolbar_ui_controller.cc
  • ui/views/widget/widget_delegate.cc
  • ui/views/window/dialog_delegate.cc

Estimated timestamp from git blame: Unknown (Google3 checkout)

Description

A potential Use-After-Free (UAF) vulnerability has been identified in the browser process, specifically within BrowserFocusControllerDelegateViews::ActivateFirstInactiveBubbleForAccessibility(). The issue stems from how WidgetDelegate manages its initially_focused_view pointer, which is used to determine focus when a bubble or dialog is activated for accessibility.

Root Cause Analysis

In ui/views/widget/widget_delegate.h, the initially_focused_view field is defined as:

std::optional<View*> initially_focused_view;

Because this pointer is wrapped in std::optional, it is not converted to raw_ptr<View> by Chromium’s automated rewriter and thus lacks MiraclePtr/BackupRefPtr protection. Furthermore, there is no mechanism (such as a ViewTracker or ViewObserver) to clear this pointer if the referenced View is destroyed during the lifetime of the delegate.

In the context of the Download Bubble, this field can be set to a child view (e.g., a button within a download row). If that download row is removed while the bubble remains open, the pointer becomes dangling.

Vulnerable Sink

The vulnerability is potentially triggered when a user presses the F6 (Rotate Pane Focus) or Alt+Shift+A (Focus Inactive Popup) keyboard shortcuts. The following code path in chrome/browser/ui/focus/browser_focus_controller_delegate_views.cc retrieves the dangling pointer and performs a virtual call:

// line 112
views::View* focusable = bubble->GetInitiallyFocusedView();
// ...
if (focusable) {
  focusable->RequestFocus(); // Virtual call on potentially freed memory
}

Potential Attack Scenario

While these steps are theoretical and have not been validated by a running proof-of-concept, an attacker could potentially trigger the vulnerability as follows:

  1. Induce the browser to show the Download Bubble in an inactive state (e.g., by initiating multiple downloads from a site).
  2. Cause a specific download to be removed (e.g., via interaction in another tab or automated cleanup), which destroys the associated View that was captured as the initially_focused_view.
  3. The user, perhaps guided by a screen reader or keyboard navigation, presses F6 or Alt+Shift+A to focus the bubble.
  4. The system attempts to call RequestFocus() on the dangling pointer, potentially allowing an attacker who has groomed the heap to gain arbitrary code execution in the unsandboxed browser process.

Suggested Fix

The initially_focused_view should be managed using a views::ViewTracker or a base::WeakPtr<views::View> to ensure the pointer is invalidated when the View is destroyed. Additionally, the kAnchoredDialogKey property should be audited to ensure it does not bypass modern memory safety protections.

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