CVE-2026-19158
Overview
Background
- `AppMenu` / `ZoomView`
AppMenuis the browser’s three-dot menu, andZoomViewis the inline row inside it that holds the zoom-/+buttons (decrement_button_), the percentage labelzoom_label_and the full-screen button.- `zoom_label_max_width_`
- A cached width for the zoom label, held as a
ZoomViewmember;.reset()clears it so that it is recalculated after the label text changes. - `NotifyAccessibilityEventDeprecated`
- A
views::Viewcall that fires an accessibility event (hereax::mojom::Event::kAlert) synchronously into the accessibility system, so arbitrary observer code can run before it returns. - Re-entrancy UAF
- A use-after-free in which a call made by an object runs code that destroys that same object, and the caller then touches its own members after the call returns.
Root Cause Analysis
The patched block lives in a ZoomView method that updates the zoom controls. The hunk does not show the method’s name, only that it takes an on_construction flag. The method sets zoom_label_ to the new percentage and, when not called during construction, fires a kAlert accessibility event on the label so screen readers announce the change. Before the patch, zoom_label_max_width_.reset() ran after NotifyAccessibilityEventDeprecated(). The commit identifies this as a UAF on zoom_label_max_width_, which means the ZoomView (or its owning menu) could be destroyed during the accessibility notification, and the later reset() would then write into freed memory. The commit does not say how the notification leads to destruction; synchronous accessibility dispatch causing the menu to close and tear down its views is the likely mechanism (inferred).
The fix moves zoom_label_max_width_.reset() before the notification, so the potentially re-entrant call is the last statement in the block and no member of this is accessed after it returns.
this (zoom_label_max_width_) was modified after a synchronous, potentially re-entrant accessibility notification that can free this. Reordering so all state updates happen before the notification removes the post-callback access.Attack Path
- Open the app menu
The user opens the browser’s
AppMenu, which constructs aZoomView; the initial update runs withon_constructiontrue and skips the alert. - Change zoom while the menu is open
A later zoom change (e.g. pressing the menu’s
+/-buttons) re-runs the update withon_constructionfalse, settingzoom_label_text and firing thekAlertaccessibility event. - Teardown during notification
While the event is dispatched, other code destroys the
ZoomViewor its menu (inferred; the patch does not show what performs the deletion). - Write to freed member
Back in the pre-patch code,
zoom_label_max_width_.reset()writes into the freedZoomView, which is the use-after-free the advisory names.
Impact Assessment
std::optional-style reset of a freed object into code execution has not been demonstrated (inferred). Reaching the bug requires user interaction with the browser’s own app menu, specifically a zoom change while the menu is open. It also requires a condition that tears down the menu during the accessibility alert, which likely depends on accessibility clients being active or on a menu-closing race (inferred). Nothing in the diff shows ordinary web content triggering it on its own. The vendor advisory rates this high severity and does not report in-the-wild exploitation; NVD scores it CVSS 7.5.Changed Functions
| Function | Change | Notes |
|---|---|---|
ifchrome/browser/ui/views/toolbar/app_menu.cc |
modified |
Files Changed
chrome/browser/ui/views/toolbar/app_menu.cc
Audit Directions
- Member access after synchronous notificationsIn
chrome/browser/ui/viewsandui/views, look for code that firesNotifyAccessibilityEvent*, observer callbacks orSetText/SetEnabledside effects and then keeps usingthis. Either move state updates before the callout or guard the access with abase::WeakPtr/ViewTracker. - Menu-hosted custom viewsOther custom rows in
AppMenuand similar menus (e.g. the edit/cut-copy-paste row and extension menu rows) run while the menu can be closed asynchronously. Check their update paths for the same “notify, then touch members” ordering. - Cached layout state reset after eventsAudit patterns where cached sizes (
*_max_width_, preferred-size caches) are invalidated after a notification rather than before it. These are easy to reorder without changing behaviour, as this fix did.
Patch
From bab2ac4268d7dfd0efc20fc6ae3a941b417c1a70 Mon Sep 17 00:00:00 2001 From: Emily Shack <[email protected]> Date: Fri, 24 Jul 2026 10:29:00 -0700 Subject: [PATCH] Prevent a UAF in AppMenu::ZoomView Reorders two calls to prevent a UAF on zoom_label_max_width_. ZoomView logic should not be impacted by this reordering. Bug: 535749174 Change-Id: Iaafdc0238690f8361156bcc3cf0136700481a7bd Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8137962 Commit-Queue: Emily Shack <[email protected]> Reviewed-by: Allen Bauer <[email protected]> Cr-Commit-Position: refs/heads/main@{#1667971} --- diff --git a/chrome/browser/ui/views/toolbar/app_menu.cc b/chrome/browser/ui/views/toolbar/app_menu.cc index 58fd795..82231536 100644 --- a/chrome/browser/ui/views/toolbar/app_menu.cc +++ b/chrome/browser/ui/views/toolbar/app_menu.cc @@ -916,13 +916,13 @@ decrement_button_->SetEnabled(zoom > contents->GetMinimumZoomPercent()); } zoom_label_->SetText(base::FormatPercent(zoom)); + zoom_label_max_width_.reset(); if (!on_construction) { // An alert notification will ensure that the zoom label is always // announced even if is not focusable. zoom_label_->NotifyAccessibilityEventDeprecated(ax::mojom::Event::kAlert, true); } - zoom_label_max_width_.reset(); } void UpdateFullScreenButton() {
Original Bug Report
Potential UAF in AppMenu::ZoomView::UpdateZoomControls due to reentrant accessibility event
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 AppMenu::ZoomView::UpdateZoomControls because a synchronous platform accessibility alert is notified without a liveness guard. Reentrant UI events during synchronous event dispatch on Windows can trigger a menu teardown, causing subsequent heap operations on freed ZoomView and Label instances. This can lead to memory corruption in the unsandboxed browser process.
Affected files:
chrome/browser/ui/views/toolbar/app_menu.cc
Estimated timestamp from git blame: 2018-03-12
Root Cause Analysis
In AppMenu::ZoomView::UpdateZoomControls inside chrome/browser/ui/views/toolbar/app_menu.cc, the code fires a synchronous platform kAlert accessibility event. Immediately after this call, with no liveness or validity check on the view, it performs a write to an inline member variable zoom_label_max_width_:
// chrome/browser/ui/views/toolbar/app_menu.cc:906-926
void UpdateZoomControls(bool on_construction = false) {
WebContents* contents = GetActiveWebContents();
int zoom = 100;
if (contents) {
...
increment_button_->SetEnabled(zoom < contents->GetMaximumZoomPercent());
decrement_button_->SetEnabled(zoom > contents->GetMinimumZoomPercent());
}
zoom_label_->SetText(base::FormatPercent(zoom));
if (!on_construction) {
// An alert notification will ensure that the zoom label is always
// announced even if is not focusable.
zoom_label_->NotifyAccessibilityEventDeprecated(ax::mojom::Event::kAlert,
true); // Synchronous Event Call
}
zoom_label_max_width_.reset(); // Write to member of |this|
}
On Windows, zoom_label_->NotifyAccessibilityEventDeprecated(ax::mojom::Event::kAlert, true) triggers synchronous native UI Automation (UIA) and MSAA event firing (::NotifyWinEvent and ::UiaRaiseAutomationEvent in ui/accessibility/platform/ax_platform_node_win.cc).
Per ui/views/SECURITY.md, executing these synchronous COM/UIA calls invokes a nested Single-Threaded Apartment (STA) message pump. An active UIA client (such as a screen reader or assistive technology) can process this alert and reenter Chromium synchronously by invoking accessible actions.
Specifically, if the client queries the collapse pattern (IExpandCollapseProvider::Collapse) on the app menu button:
- The call enters
BrowserAppMenuButton::HandleAccessibleActionwithax::mojom::Action::kCollapse. - This invokes
CloseMenu()and synchronously resets the unique pointer toAppMenuviamenu_.reset(). - Synchronous menu runner cleanup (
~MenuRunnerImpl) destroys the parentMenuItemViewstructure, which immediately deletes theZoomViewand its childzoom_label_from the PartitionAlloc heap.
When control returns:
- Inside
ViewAccessibility::NotifyEvent,view_->OnAccessibilityEvent(event_type)is called. Sinceview_(zoom_label_) has been freed, this results in a virtual call Use-After-Free (UAF). - Inside
AppMenu::ZoomView::UpdateZoomControls,zoom_label_max_width_.reset()is called. Sincethis(ZoomView) has been freed, this writes a boolean flag to a fixed offset inside the freed PartitionAlloc slot.
Because these operations are performed via raw implicit this pointers and raw C++ object references, MiraclePtr (BackupRefPtr) does not offer protection.
Suggested/Potential Trigger Steps
Note: These steps are based on static code path analysis and have not been validated with a live proof-of-concept exploit.
- Run Chrome on Windows with an active UI Automation client or screen reader.
- Open the 3-dot App Menu.
- Click the ‘+’ or ‘−’ button in the Zoom row.
- During the synchronous
UiaRaiseAutomationEventinsideNotifyAccessibilityEventDeprecated, the UIA client issues anIExpandCollapseProvider::Collapsecall on the app menu button. BrowserAppMenuButton::HandleAccessibleAction(kCollapse)executes, callingCloseMenu(), which synchronously deletes theAppMenu,ZoomView, andzoom_label_.- Upon return, the browser process accesses the freed
zoom_label_duringOnAccessibilityEventand writes to the freedZoomViewmemory slot duringzoom_label_max_width_.reset().
Recommended Fix
To prevent UAF from reentrant menu teardowns during accessibility events, wrap the synchronous event notification in a views::ViewTracker to verify if the view is still alive before executing any further operations.
void UpdateZoomControls(bool on_construction = false) {
WebContents* contents = GetActiveWebContents();
int zoom = 100;
if (contents) {
...
increment_button_->SetEnabled(zoom < contents->GetMaximumZoomPercent());
decrement_button_->SetEnabled(zoom > contents->GetMinimumZoomPercent());
}
zoom_label_->SetText(base::FormatPercent(zoom));
if (!on_construction) {
views::ViewTracker tracker(this);
zoom_label_->NotifyAccessibilityEventDeprecated(ax::mojom::Event::kAlert,
true);
if (!tracker.view()) {
return;
}
}
zoom_label_max_width_.reset();
}
Evaluated with Chrome root at commit: b5b015ea5f690560237d1f0cff1405844cd12b8d
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.