CVE-2026-10000
Overview
Background
- `PasswordGenerationPopupViewViews`
- the Views implementation of Chrome’s password-generation popup, which owns child views such as the nudge buttons (
password_generation_popup_view_views.cc:306). - `NudgePasswordButtons`
- a child
views::Viewholding the accept/cancel buttons of the generation nudge (password_generation_popup_view_views.cc:97). - `NotifyAXSelection`
- a
PopupBaseViewmethod that fires native accessibility events for the currently selected view (popup_base_view.cc:363). - `views::ViewTracker`
- a utility that holds a non-owning handle to a
views::Viewand reports null once that view is destroyed.
Root Cause Analysis
PasswordGenerationPopupViewViews::ButtonSelectionUpdated caches a raw pointer to the nudge buttons by static_casting the nudge_password_buttons_view_ member at password_generation_popup_view_views.cc:366-367, then calls NotifyAXSelection(*nudge_password_buttons->GetAcceptButton()) or the cancel-button variant at :368-372, and finally dereferences the cached pointer with nudge_password_buttons->UpdateFocus(...) at :373. NotifyAXSelection fires native accessibility events (NotifyAccessibilityEventDeprecated(ax::mojom::Event::kMenuStart, ...) and kMenuPopupStart) at popup_base_view.cc:370-371 and then dereferences selected_view at :375. The invariant assumed by both functions is that the selected view and the nudge-buttons view remain alive across the accessibility-event call, but PasswordGenerationPopupViewViews::UpdateState (:343-348) sets nudge_password_buttons_view_ = nullptr and calls RemoveAllChildViews(), synchronously destroying those child views. If the accessibility-event firing re-enters and runs UpdateState, the cached nudge_password_buttons pointer and the selected_view reference become dangling, and the later UpdateFocus/SetPopupFocusOverride dereferences are use-after-free.
The fix wraps both sites in a views::ViewTracker (nudge_buttons_tracker, selected_view_tracker) and adds an early if (!tracker.view()) return; guard after the event call, so the stale pointers are never dereferenced once the view has been freed.
NotifyAXSelection) that can synchronously re-enter and destroy that view; the fix detects the destruction with a views::ViewTracker and bails out instead of touching freed memory.Attack Path
- Show the generation popup
Bring up the password-generation nudge so
nudge_password_buttons_view_is populated (password_generation_popup_view_views.cc:398-399). - Select a button
Cause
ButtonSelectionUpdatedto run, cachingnudge_password_buttonsand callingNotifyAXSelection(:366-372). - Re-enter during AX firing
With a native accessibility client active, the event fired at
popup_base_view.cc:370-371synchronously re-enters the browser thread. (inferred) - Destroy the views
The re-entrant work drives
UpdateState, which nullsnudge_password_buttons_view_and runsRemoveAllChildViews(), freeing the cached buttons view (:343-346). - Use freed memory
On return,
nudge_password_buttons->UpdateFocus(...)(:373) /selected_view...SetPopupFocusOverride()(popup_base_view.cc:375) dereference the freed view.
Impact Assessment
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifchrome/browser/ui/views/autofill/popup/popup_base_view.cc |
modified |
Files Changed
chrome/browser/ui/views/autofill/popup/popup_base_view.ccchrome/browser/ui/views/passwords/password_generation_popup_view_views.cc
Audit Directions
- Re-entrancy across accessibility eventsTreat any native accessibility event call (e.g.
NotifyAccessibilityEventDeprecated,NotifyAXSelection) as a potential synchronous re-entry point and re-validate view lifetimes afterward. - Raw pointers cached across callsAudit UI code that caches a
views::View*or reference and dereferences it after a callback; guard withviews::ViewTracker/base::WeakPtr. - Synchronous state-reset pathsFlag methods like
UpdateStatethat callRemoveAllChildViews()and null member view pointers, and verify no in-flight caller holds those views.
Patch
From 8112a56553fa6c8213c80e3e18816dd11f5530cd Mon Sep 17 00:00:00 2001 From: Rafał Godlewski <[email protected]> Date: Mon, 18 May 2026 04:04:35 -0700 Subject: [PATCH] [Passwords] Use view tracker for generation popup buttons Ensures that the view, for which `NotifyAXSelection` was invoked, is still alive after the call ends in both PasswordGenerationPopupViewViews and PopupBaseView. Fixed: 513505608 Change-Id: I466e4e748f1ba686d75f2eccf9a20c95a1472b82 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7852262 Reviewed-by: Vasilii Sukhanov <[email protected]> Commit-Queue: Rafał Godlewski <[email protected]> Cr-Commit-Position: refs/heads/main@{#1632069} --- diff --git a/chrome/browser/ui/views/autofill/popup/popup_base_view.cc b/chrome/browser/ui/views/autofill/popup/popup_base_view.cc index 269b903..5efd1fde 100644 --- a/chrome/browser/ui/views/autofill/popup/popup_base_view.cc +++ b/chrome/browser/ui/views/autofill/popup/popup_base_view.cc @@ -48,6 +48,7 @@ #include "ui/views/focus/focus_manager.h" #include "ui/views/layout/fill_layout.h" #include "ui/views/layout/layout_provider.h" +#include "ui/views/view_tracker.h" #include "ui/views/widget/widget.h" #if DCHECK_IS_ON() @@ -361,6 +362,7 @@ } void PopupBaseView::NotifyAXSelection(views::View& selected_view) { + views::ViewTracker selected_view_tracker(&selected_view); if (!is_ax_menu_start_event_fired_) { // Fire the menu start event once, right before the first item is selected. // By firing these and the matching kMenuEnd events, we are telling screen @@ -372,6 +374,15 @@ is_ax_menu_start_event_fired_ = true; } + + // Ensure the selected view was not destroyed, as e.g. firing native + // accessibility events on Windows can synchronously re-enter the browser + // thread and destroy the view. + // TODO(crbug.com/514228954): Consider removing accessibility event calls. + if (!selected_view_tracker.view()) { + return; + } + selected_view.GetViewAccessibility().SetPopupFocusOverride(); #if DCHECK_IS_ON() constexpr auto kDerivedClasses = base::MakeFixedFlatSet<std::string_view>( diff --git a/chrome/browser/ui/views/passwords/password_generation_popup_view_views.cc b/chrome/browser/ui/views/passwords/password_generation_popup_view_views.cc index d01d6ea0..279974d 100644 --- a/chrome/browser/ui/views/passwords/password_generation_popup_view_views.cc +++ b/chrome/browser/ui/views/passwords/password_generation_popup_view_views.cc @@ -41,6 +41,7 @@ #include "ui/views/layout/box_layout.h" #include "ui/views/metadata/view_factory.h" #include "ui/views/vector_icons.h" +#include "ui/views/view_tracker.h" #include "ui/views/widget/widget.h" namespace { @@ -365,11 +366,19 @@ auto* nudge_password_buttons = static_cast<NudgePasswordButtons*>(nudge_password_buttons_view_); + views::ViewTracker nudge_buttons_tracker(nudge_password_buttons); + if (controller_->accept_button_selected()) { NotifyAXSelection(*nudge_password_buttons->GetAcceptButton()); } else if (controller_->cancel_button_selected()) { NotifyAXSelection(*nudge_password_buttons->GetCancelButton()); } + + // Ensure the buttons were not destroyed during the NotifyAXSelection call. + if (!nudge_buttons_tracker.view()) { + return; + } + nudge_password_buttons->UpdateFocus(controller_->accept_button_selected()); }
Original Bug Report
Potential Browser-process UAF in PasswordGenerationPopupViewViews on Windows via UIA Re-entrancy
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 (UAF) vulnerability exists in the password generation popup on Windows. Synchronous re-entrancy during accessibility event firing allows a compromised renderer to trigger the destruction of the popup’s views while they are still being accessed by the browser process. This can lead to memory corruption or arbitrary code execution in the unsandboxed browser process.
Affected files:
chrome/browser/ui/views/passwords/password_generation_popup_view_views.ccchrome/browser/ui/views/autofill/popup/popup_base_view.cc
Estimated timestamp from git blame: 2024-01-19
Root Cause Analysis
A potential Use-After-Free (UAF) vulnerability has been identified in PasswordGenerationPopupViewViews::ButtonSelectionUpdated on Windows. The issue stems from caching a raw pointer to a child view across a synchronous re-entrancy point triggered by Windows UI Automation (UIA).
In chrome/browser/ui/views/passwords/password_generation_popup_view_views.cc, the function ButtonSelectionUpdated caches a local pointer to nudge_password_buttons_view_ and calls NotifyAXSelection:
void PasswordGenerationPopupViewViews::ButtonSelectionUpdated() {
// ...
auto* nudge_password_buttons =
static_cast<NudgePasswordButtons*>(nudge_password_buttons_view_);
if (controller_->accept_button_selected()) {
NotifyAXSelection(*nudge_password_buttons->GetAcceptButton()); // Re-entrancy point
} else if (controller_->cancel_button_selected()) {
NotifyAXSelection(*nudge_password_buttons->GetCancelButton());
}
nudge_password_buttons->UpdateFocus(controller_->accept_button_selected()); // Potential UAF
}
The call to NotifyAXSelection reaches PopupBaseView::NotifyAXSelection, which fires accessibility events (such as kMenuStart or kMenuPopupStart). On Windows, these events reach ::UiaRaiseAutomationEvent synchronously. When a UIA client (such as a screen reader) is active, this Windows API call can synchronously pump the message loop (STA pump).
If a compromised renderer has queued a Mojo message that triggers a state update in the popup (e.g., PasswordGenerationDriver::ShowPasswordEditingPopup), that message can be processed during this nested message loop. This results in a call to PasswordGenerationPopupViewViews::UpdateState(), which synchronously destroys the popup’s child views:
void PasswordGenerationPopupViewViews::UpdateState() {
password_view_ = nullptr;
nudge_password_buttons_view_ = nullptr; // Nulling member raw_ptr
RemoveAllChildViews(); // Synchronously deletes child views
CreateLayoutAndChildren();
}
When the UIA pump returns, the original ButtonSelectionUpdated execution flow continues. The local pointer nudge_password_buttons now points to freed memory, and the subsequent call to UpdateFocus results in a Use-After-Free. Additionally, PopupBaseView::NotifyAXSelection may dereference a dangling reference to the selected_view after the re-entrant call returns.
MiraclePtr / BRP Protection
MiraclePtr (BRP) does not mitigate this issue because:
- The vulnerability involves a function-local raw pointer (
nudge_password_buttons) and a reference (selected_view). - The
raw_ptrmembernudge_password_buttons_view_is explicitly set tonullptrinUpdateState()immediately before destruction, ensuring the object is not held in the quarantine when it is deleted.
Suggested Potential Reproduction Steps
- Ensure Windows is running with an active UIA client (e.g., Narrator).
- From a compromised renderer, trigger the password generation popup.
- Send a Mojo message to the browser process (e.g.,
ShowPasswordEditingPopup) that would triggerUpdateState(). - Trigger navigation within the popup (e.g., via a synthetic arrow key event) to cause
ButtonSelectionUpdatedto be called. - Observe the browser process dereferencing the dangling pointer in
ButtonSelectionUpdatedafter the synchronous accessibility event firing returns.
Fix Suggestion
Avoid using raw pointers to views across calls that can trigger synchronous re-entrancy. The ButtonSelectionUpdated function should either use base::WeakPtr to track the lifetime of the NudgePasswordButtons view or re-verify the validity of nudge_password_buttons_view_ after the call to NotifyAXSelection. More broadly, firing accessibility events on Windows should be evaluated for re-entrancy safety.
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.