CVE-2026-78909
Overview
Background
- `MenuController`
- The
viewsobject that drives a running menu: it tracks selection, opens and closes submenus, and dispatches events. It can be destroyed when the menu is dismissed, e.g. on activation changes. - `MenuItemView`
- A single menu item view.
ChildrenChanged()is called when its submenu contents change and forwards to the controller viaMenuChildrenChanged(). - Re-entrancy via accessibility/activation
- According to the new comments,
SetSelection()andMenuChildrenChanged()can trigger accessibility callbacks and window activation changes that indirectly tear down the menu and delete the controller while the calling frame is still running. - `AsWeakPtr()`
- Returns a
base::WeakPtrthat becomes null when the object is destroyed. The patch uses it to detect self-deletion after a call that can re-enter.
Root Cause Analysis
In menu_controller.cc, a MenuController method (its name is not shown in the hunk) calls SetSelection(item, SELECTION_OPEN_SUBMENU | SELECTION_UPDATE_IMMEDIATELY) and then unconditionally evaluates item->HasSubmenu() and calls OpenMenuImpl(item, false). If SetSelection() caused the controller to be destroyed through accessibility or activation callbacks, OpenMenuImpl() ran on a freed this, and item might also have been torn down along with the menu. Similarly, MenuItemView::ChildrenChanged() held a raw MenuController*. After controller->MenuChildrenChanged(this) it went on to use submenu_ and do layout and paint work, even though the menu, and possibly this MenuItemView, could have been destroyed during that call. The missing invariant was “the controller is still alive after a re-entrant call”.
The fix takes a WeakPtr before each re-entrant call and checks it afterwards. In the controller, weak_this && short-circuits before item->HasSubmenu() and OpenMenuImpl(). In ChildrenChanged(), the function returns early if controller was invalidated.
this/controller state after calling into code that can re-enter and destroy the menu. Capturing a WeakPtr before the call and bailing out when it becomes null removes the use-after-free.Attack Path
- Open a menu
A Views menu (e.g. a context or app menu) is shown, so a
MenuControllerand itsMenuItemViewtree exist. This is browser UI, normally opened by user interaction. - Trigger selection or child changes
The submenu-opening path (
SetSelection()withSELECTION_OPEN_SUBMENU) runs, or the menu’s children change and invokeMenuItemView::ChildrenChanged(). - Re-entrant destruction
During
SetSelection()orMenuChildrenChanged(), accessibility callbacks or an activation change (e.g. focus moving to another window) close the menu and delete theMenuController. - Use after free
Back in the caller, the old code called
OpenMenuImpl()on a freed controller, or usedsubmenu_after the menu was destroyed. Turning this into memory corruption beyond a crash would depend on heap control in the browser process (inferred).
Impact Assessment
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifui/views/controls/menu/menu_item_view.cc |
modified |
Files Changed
ui/views/controls/menu/menu_controller.ccui/views/controls/menu/menu_item_view.cc
Audit Directions
- Calls followed by member access in menu and bubble codeIn
MenuController,MenuItemView,SubmenuViewand bubble/widget code, look for calls that can trigger accessibility notifications, focus or activation changes, orCancel(), followed by use ofthisor cached pointers without aWeakPtror destruction check. - Accessibility event dispatch as a re-entrancy vectorCalls such as
NotifyAccessibilityEvent()and selection or focus updates can synchronously reach platform AT bridges that change activation. Treat them as potentially destructive calls. - Guards that only protect the controllerThe fix checks the controller’s lifetime, but the caller’s own
MenuItemView(anditem) may share the menu’s lifetime. Check whether locals such asitemorsubmenu_are still used after a guarded call on other paths.
Patch
From fab84e22035be6fcae0aa519816d2caf6cc254f2 Mon Sep 17 00:00:00 2001
From: Dana Fried <dfried@chromium.org>
Date: Mon, 20 Jul 2026 13:15:23 -0700
Subject: [PATCH] [Menu] Avoid potential access after self-deletion
Fixed: 501637242
Change-Id: Iccb7c5dba0519e938934dc9da1bf260ad75184d4
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8124974
Auto-Submit: Dana Fried <dfried@chromium.org>
Reviewed-by: Steven Luong <stluong@chromium.org>
Commit-Queue: Dana Fried <dfried@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1664888}
---
diff --git a/ui/views/controls/menu/menu_controller.cc b/ui/views/controls/menu/menu_controller.cc
index 51a34bfd..6eb15680 100644
--- a/ui/views/controls/menu/menu_controller.cc
+++ b/ui/views/controls/menu/menu_controller.cc
@@ -2651,8 +2651,12 @@
return;
}
}
+ // Setting the selection can indirectly destroy this object via accessibility
+ // system callbacks and activation changes. This should be rare bug must be
+ // protected against.
+ const auto weak_this = AsWeakPtr();
SetSelection(item, SELECTION_OPEN_SUBMENU | SELECTION_UPDATE_IMMEDIATELY);
- if (item->HasSubmenu()) {
+ if (weak_this && item->HasSubmenu()) {
OpenMenuImpl(item, false);
}
}
diff --git a/ui/views/controls/menu/menu_item_view.cc b/ui/views/controls/menu/menu_item_view.cc
index 0bca671..00a60ca 100644
--- a/ui/views/controls/menu/menu_item_view.cc
+++ b/ui/views/controls/menu/menu_item_view.cc
@@ -791,11 +791,17 @@
}
void MenuItemView::ChildrenChanged() {
- MenuController* controller = GetMenuController();
- if (controller) {
+ auto* const controller_ptr = GetMenuController();
+ if (controller_ptr) {
UpdateEmptyMenusAndMetrics();
+ // Certain accessibility callbacks could destroy the menu indirectly through
+ // activation changes.
+ const auto controller = controller_ptr->AsWeakPtr();
controller->MenuChildrenChanged(this);
+ if (!controller) {
+ return;
+ }
if (submenu_) {
// Force a paint and a synchronous layout. This needs a synchronous layout
Original Bug Report
Potential UAF in MenuController via accessibility re-entrancy during MenuChildrenChanged
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 without the Chrome Security team.
Overview: A Use-After-Free (UAF) vulnerability exists in the browser process due to synchronous object deletion during MenuController::MenuChildrenChanged. Accessibility events triggered during a SetSelection call can synchronously destroy the MenuController, but the caller unconditionally accesses the freed this pointer afterward. MiraclePtr does not protect against this because the dangling references are an implicit this pointer and a local raw pointer.
Affected files:
ui/views/controls/menu/menu_controller.ccui/views/controls/menu/menu_item_view.ccui/views/controls/menu/menu_runner_impl.cc
Estimated timestamp from git blame: 2024-01-29
Description
A potential Use-After-Free (UAF) vulnerability in the browser process has been identified in MenuController::MenuChildrenChanged. This function updates the menu state when child items are added or removed (e.g., due to a renderer-timed model mutation).
The vulnerability is triggered because MenuController::MenuChildrenChanged calls SetSelection(item, SELECTION_OPEN_SUBMENU | SELECTION_UPDATE_IMMEDIATELY). Within SetSelection, interactions with the accessibility system (specifically menu_item->GetViewAccessibility().SetPopupFocusOverride()) can trigger synchronous accessibility events if platform accessibility is enabled. On platforms like Linux/Aura, these events can cause the active window to lose activation, leading MenuPreTargetHandlerAura to call MenuController::Cancel(ExitType::kAll). This cancellation synchronously deletes the MenuController via MenuRunnerImpl::OnMenuClosed.
While SetSelection anticipates this risk and guards against its own deletion using a base::WeakPtr (if (!this_ref) return;), the caller MenuChildrenChanged lacks any such guard. Once SetSelection safely returns, MenuChildrenChanged unconditionally dereferences the freed this pointer by calling OpenMenuImpl(item, false).
Furthermore, the function that called MenuChildrenChanged, which is MenuItemView::ChildrenChanged(), also saves the controller as a raw local pointer (MenuController* controller = GetMenuController();) and subsequently calls controller->UpdateSubmenuSelection(submenu_.get());, creating a second UAF site on the same freed object.
MiraclePtr (BackupRefPtr) does not protect against either UAF because the dangling references are not raw_ptr<T> members; they are an implicit this pointer held in a register and a local raw pointer. Consequently, the memory is immediately returned to PartitionAlloc.
Potential Exploitation Path
While we do not have a working Proof of Concept, an attacker could potentially exploit this by:
- Gaining code execution in a Renderer process.
- Waiting for the user to open a dynamic menu (e.g., the History -> Recent Tabs submenu).
- Triggering a model mutation from the renderer (e.g., by executing
window.close()on a popup associated with that menu). - Timing heap spraying (via IPCs or Blob URLs) to reclaim the freed
MenuControllermemory slot immediately afterMenuRunnerImpl::OnMenuClosedexecutes, but beforeOpenMenuImplaccesses it. - Overwriting the internal
std::vector(child_menu_open_direction_) accessed byOpenMenuImplto achieve arbitrary read/write primitives in the browser process.
Suggested Reproduction Steps
- Run Chrome with platform accessibility enabled (e.g., by attaching a screen reader or using
--force-renderer-accessibility). - Open a same-origin popup from a tab using
window.open(). - Open the Chrome App Menu and navigate to the History -> Recent Tabs submenu.
- From the renderer of the popup, execute
window.close(). - The resulting state change should trigger
MenuChildrenChangedwhile the menu is open, potentially crashing the browser if the timing coincides with the accessibility event dispatch.
Suggested Fix
- In
ui/views/controls/menu/menu_controller.cc, updateMenuChildrenChangedto use aWeakPtrto verifythisis still valid after callingSetSelection:
auto this_ref = AsWeakPtr();
SetSelection(item, SELECTION_OPEN_SUBMENU | SELECTION_UPDATE_IMMEDIATELY);
if (!this_ref) return;
if (item->HasSubmenu()) {
OpenMenuImpl(item, false);
}
- In
ui/views/controls/menu/menu_item_view.cc, updateMenuItemView::ChildrenChanged()to hold aWeakPtrinstead of a raw pointer:
base::WeakPtr<MenuController> controller = GetMenuController()->AsWeakPtr();
if (controller) {
UpdateEmptyMenusAndMetrics();
controller->MenuChildrenChanged(this);
if (!controller) return;
if (submenu_) {
submenu_->DeprecatedLayoutImmediately();
submenu_->SchedulePaint();
controller->UpdateSubmenuSelection(submenu_.get());
}
}
Evaluated with Chrome root at commit: 096fc8fdbfacf2546485756d03f160a3d04fcc9b
Results so far have been promising, but there can be wrong deductions. If this proves to be a false positive, please close as WAI; 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.