CVE-2026-11636
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifchrome/browser/ui/views/autofill/popup/popup_base_view.cc |
modified | |
ifchrome/browser/ui/views/autofill/popup/popup_row_view.cc |
modified |
Files Changed
chrome/browser/ui/views/autofill/popup/popup_base_view.ccchrome/browser/ui/views/autofill/popup/popup_bnpl_footnote_view.ccchrome/browser/ui/views/autofill/popup/popup_row_view.cc
Patch
From c2df61a54db1d495380fbd7dca9062e292b7d223 Mon Sep 17 00:00:00 2001
From: Bruno Braga <brunobraga@google.com>
Date: Fri, 29 May 2026 06:37:52 -0700
Subject: [PATCH] [Autofill] Fix UAF in popup views due to COM reentrancy on Windows
Fix potential Use-After-Free (UAF) vulnerabilities in Autofill popup
views on Windows.
On Windows, firing accessibility events (such as focus or selection
changes) can synchronously spin a COM message pump if assistive
technologies are active. This reentrancy window allows queued tasks
on the browser UI thread to run, which can destroy the executing
view hierarchy (e.g., by rebuilding the popup). Subsequent member
accesses on the deleted views result in UAF.
This CL introduces `views::ViewTracker` to track the lifetime of the
executing views across calls that trigger accessibility events and
aborts execution early if the view is synchronously destroyed.
Touched files:
- popup_row_with_button_view.cc: Guard key press handlers.
- popup_row_view.cc: Guard selected cell updates.
- popup_bnpl_footnote_view.cc: Guard settings link focus.
- popup_view_views.cc: Guard SetSelectedCell path.
TAG=agy
CONV=d11a46e3-2dd7-4aea-9644-0f62932c301c
Bug: 517014512, 516938039, 517023053
Change-Id: I93ce0650d32f477d5bd9a0b0d23ef20018a3e523
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7878305
Reviewed-by: Christoph Schwering <schwering@google.com>
Commit-Queue: Bruno Braga <brunobraga@google.com>
Cr-Commit-Position: refs/heads/main@{#1638448}
---
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 5c9c09d1..77555e50 100644
--- a/chrome/browser/ui/views/autofill/popup/popup_base_view.cc
+++ b/chrome/browser/ui/views/autofill/popup/popup_base_view.cc
@@ -329,19 +329,29 @@
// navigates into the menu, otherwise some screen readers will ignore
// any focus events outside of the menu, including a focus event on
// the form control itself.
- NotifyAccessibilityEventDeprecated(ax::mojom::Event::kMenuPopupEnd, true);
- NotifyAccessibilityEventDeprecated(ax::mojom::Event::kMenuEnd, true);
- GetViewAccessibility().EndPopupFocusOverride();
-
- // Also fire an accessible focus event on what currently has focus,
- // typically the widget associated with this popup.
- if (parent_widget_) {
- if (views::FocusManager* focus_manager =
- parent_widget_->GetFocusManager()) {
- if (View* focused_view = focus_manager->GetFocusedView()) {
- focused_view->GetViewAccessibility().FireFocusAfterMenuClose();
- }
- }
+ if (!TrackAndRun(
+ this,
+ [this]() {
+ NotifyAccessibilityEventDeprecated(
+ ax::mojom::Event::kMenuPopupEnd, true);
+ },
+ [this]() {
+ NotifyAccessibilityEventDeprecated(ax::mojom::Event::kMenuEnd,
+ true);
+ },
+ [this]() { GetViewAccessibility().EndPopupFocusOverride(); },
+ [this]() {
+ if (parent_widget_) {
+ if (views::FocusManager* focus_manager =
+ parent_widget_->GetFocusManager()) {
+ if (View* focused_view = focus_manager->GetFocusedView()) {
+ focused_view->GetViewAccessibility()
+ .FireFocusAfterMenuClose();
+ }
+ }
+ }
+ })) {
+ return;
}
}
@@ -369,8 +379,18 @@
// readers that the focus is only changing temporarily, and the screen
// reader will restore the focus back to the appropriate textfield when the
// menu closes.
- NotifyAccessibilityEventDeprecated(ax::mojom::Event::kMenuStart, true);
- NotifyAccessibilityEventDeprecated(ax::mojom::Event::kMenuPopupStart, true);
+ if (!TrackAndRun(
+ this,
+ [this]() {
+ NotifyAccessibilityEventDeprecated(ax::mojom::Event::kMenuStart,
+ true);
+ },
+ [this]() {
+ NotifyAccessibilityEventDeprecated(
+ ax::mojom::Event::kMenuPopupStart, true);
+ })) {
+ return;
+ }
is_ax_menu_start_event_fired_ = true;
}
diff --git a/chrome/browser/ui/views/autofill/popup/popup_bnpl_footnote_view.cc b/chrome/browser/ui/views/autofill/popup/popup_bnpl_footnote_view.cc
index 4f29d09..0fc1b86 100644
--- a/chrome/browser/ui/views/autofill/popup/popup_bnpl_footnote_view.cc
+++ b/chrome/browser/ui/views/autofill/popup/popup_bnpl_footnote_view.cc
@@ -120,9 +120,16 @@
is_settings_link_selected_ = true;
link->SetBorder(views::CreateSolidBorder(kSelectionBorderThickness,
ui::kColorFocusableBorderFocused));
- a11y_selection_delegate_->NotifyAXSelection(*this);
- this->NotifyAccessibilityEventDeprecated(ax::mojom::Event::kFocus, true);
- this->GetViewAccessibility().SetIsSelected(true);
+ if (!TrackAndRun(
+ this,
+ [this]() { a11y_selection_delegate_->NotifyAXSelection(*this); },
+ [this]() {
+ NotifyAccessibilityEventDeprecated(ax::mojom::Event::kFocus,
+ true);
+ },
+ [this]() { GetViewAccessibility().SetIsSelected(true); })) {
+ return;
+ }
announce_callback_.Run(std::u16string(link->GetText()), /*polite=*/false);
}
}
diff --git a/chrome/browser/ui/views/autofill/popup/popup_row_view.cc b/chrome/browser/ui/views/autofill/popup/popup_row_view.cc
index 333fb65..0634e7b 100644
--- a/chrome/browser/ui/views/autofill/popup/popup_row_view.cc
+++ b/chrome/browser/ui/views/autofill/popup/popup_row_view.cc
@@ -366,7 +366,11 @@
// If the previous cell was content, set it as unselected.
if (selected_cell_ == CellType::kContent) {
content_view_->UpdateStyle(/*selected=*/false);
- content_view_->GetViewAccessibility().SetIsSelected(false);
+ if (!TrackAndRun(this, [this]() {
+ content_view_->GetViewAccessibility().SetIsSelected(false);
+ })) {
+ return;
+ }
controller_->UnselectSuggestion();
}
@@ -377,18 +381,34 @@
// is required for a11y focus working on a non-activatable popup. Consider
// moving `SetIsSelected()` into `NotifyAXSelection()` (and rename it) to
// hide this API complexity from clients.
- GetA11ySelectionDelegate().NotifyAXSelection(*this);
- GetViewAccessibility().SetIsSelected(true);
- NotifyAccessibilityEventDeprecated(
- ax::mojom::Event::kSelectedChildrenChanged, true);
+ if (!TrackAndRun(
+ this,
+ [this]() { GetA11ySelectionDelegate().NotifyAXSelection(*this); },
+ [this]() { GetViewAccessibility().SetIsSelected(true); },
+ [this]() {
+ NotifyAccessibilityEventDeprecated(
+ ax::mojom::Event::kSelectedChildrenChanged, true);
+ })) {
+ return;
+ }
selected_cell_ = new_cell;
} else if (new_cell == CellType::kContent) {
controller_->SelectSuggestion(line_number_);
content_view_->UpdateStyle(/*selected=*/true);
- GetA11ySelectionDelegate().NotifyAXSelection(*content_view_);
- content_view_->GetViewAccessibility().SetIsSelected(true);
- NotifyAccessibilityEventDeprecated(
- ax::mojom::Event::kSelectedChildrenChanged, true);
+ if (!TrackAndRun(
+ this,
+ [this]() {
+ GetA11ySelectionDelegate().NotifyAXSelection(*content_view_);
+ },
+ [this]() {
+ content_view_->GetViewAccessibility().SetIsSelected(true);
+ },
+ [this]() {
+ NotifyAccessibilityEventDeprecated(
+ ax::mojom::Event::kSelectedChildrenChanged, true);
+ })) {
+ return;
+ }
selected_cell_ = new_cell;
} else {
// Set the selected cell to none in case an invalid choice was made (e.g.
@@ -396,8 +416,13 @@
// explicitly with `std::nullopt`.
selected_cell_ = std::nullopt;
- GetViewAccessibility().SetIsSelected(false);
- content_view_->GetViewAccessibility().SetIsSelected(false);
+ if (!TrackAndRun(
+ this, [this]() { GetViewAccessibility().SetIsSelected(false); },
+ [this]() {
+ content_view_->GetViewAccessibility().SetIsSelected(false);
+ })) {
+ return;
Regression Test / PoC
diff --git a/chrome/browser/ui/views/autofill/popup/popup_view_utils_unittest.cc b/chrome/browser/ui/views/autofill/popup/popup_view_utils_unittest.cc
index 1e92c102..c6ae677 100644
--- a/chrome/browser/ui/views/autofill/popup/popup_view_utils_unittest.cc
+++ b/chrome/browser/ui/views/autofill/popup/popup_view_utils_unittest.cc
@@ -4,11 +4,13 @@
#include "chrome/browser/ui/views/autofill/popup/popup_view_utils.h"
+#include <memory>
#include <vector>
#include "chrome/browser/ui/views/autofill/popup/popup_base_view.h"
#include "components/autofill/core/browser/ui/popup_open_enums.h"
#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/views/view.h"
namespace autofill {
@@ -544,4 +546,16 @@
gfx::Rect(50, 50, 100, 100), {non_overlapping_popup, kBasePopup}));
}
+TEST(PopupViewsUtilsTest, TrackAndRun_Basic) {
+ auto view = std::make_unique<views::View>();
+ bool cb2_called = false;
+
+ bool survived = TrackAndRun(
+ view.get(), [&view]() { view.reset(); },
+ [&cb2_called]() { cb2_called = true; });
+
+ EXPECT_FALSE(survived);
+ EXPECT_FALSE(cb2_called);
+}
+
} // namespace autofill
Original Bug Report
Potential UAF in PopupViewViews::SetSelectedCell via synchronous Windows accessibility event pumping
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 desktop Autofill popup UI on Windows. When updating the selected cell, firing native accessibility events synchronously pumps the STA COM message loop. If a queued UI thread task executes during this pump and updates the suggestions, the underlying row view is deleted, leading to a use-after-free when the function resumes execution.
Affected files:
chrome/browser/ui/views/autofill/popup/popup_view_views.ccchrome/browser/ui/views/autofill/popup/popup_row_view.cc
Estimated timestamp from git blame: 2023-02-16
Description
A potential Use-After-Free (UAF) vulnerability has been identified in PopupViewViews::SetSelectedCell (located in chrome/browser/ui/views/autofill/popup/popup_view_views.cc) on Windows.
When updating the selected cell of the Autofill popup, the code obtains a bare C++ reference to the target PopupRowView on the stack. It then invokes SetSelectedCell on this row, which eventually calls NotifyAXSelection to fire native Windows accessibility (MSAA/UIA) events. Firing these native events on Windows synchronously pumps the Single-Threaded Apartment (STA) COM message loop.
If a queued UI thread task (such as an IPC task from the renderer containing updated suggestions) is processed during this synchronous pump, it can invoke OnSuggestionsChanged(). This synchronously invokes CreateSuggestionViews(), which clears and deletes all existing child row views via RemoveAllChildViews(). When the nested event pump returns, the stack reference to the deleted PopupRowView is dereferenced to call ScrollViewToVisible(), resulting in a Use-After-Free on a bare reference that is not protected by MiraclePtr/BackupRefPtr.
Potential Step-by-Step Trigger Sequence
- User Interaction: A Windows user with an active accessibility utility (such as Narrator or NVDA) triggers an Autofill popup and navigates it (e.g., using keyboard arrow keys).
- Selection Initiation:
PopupViewViews::SetSelectedCellis invoked, and retrieves a bare reference to the row:PopupRowView& new_selected_row = GetPopupRowViewAt(...). - Event Dispatching:
new_selected_row.SetSelectedCell(...)is called, which callsNotifyAXSelectionto fire native accessibility events. - STA Message Pump: The Windows accessibility subsystem synchronously pumps the message loop on the UI thread to dispatch the COM messages.
- Re-entrant Deletion: A queued UI task runs re-entrantly during the pump, executing
OnSuggestionsChanged()and deleting all existingPopupRowViewelements. - Use-After-Free: After the pump completes,
new_selected_row.ScrollViewToVisible()is executed on the deleted reference, initiating a virtual method dispatch via the deleted object’s vtable.
Note: These steps represent a potential execution flow based on static analysis; our tooling does not currently have the capability to run code or execute a live proof of concept.
Suggested Fix
To prevent this re-entrant Use-After-Free, track the lifetime of the new_selected_row view across synchronous accessibility calls using a views::ViewTracker. If the view is destroyed during the call, execution should return early:
// chrome/browser/ui/views/autofill/popup/popup_view_views.cc
// Wrap the view in a tracker to detect re-entrant destruction
views::ViewTracker row_tracker(&new_selected_row);
new_selected_row.SetSelectedCell(cell_index->second);
// Check if the row view was destroyed during the synchronous call
if (!row_tracker.view()) {
return;
}
new_selected_row.ScrollViewToVisible();
Evaluated with Chrome root at commit: b1520ef4a76878853a31f0943b565e42060edec8
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.