CVE-2026-19159
Overview
Background
- `MenuItemView` / `submenu_`
- A
MenuItemViewis one item in a Views menu; when it has a submenu, its children live insubmenu_, aSubmenuView. - `EmptyMenuMenuItem`
- A placeholder child (the “(empty)” item) inserted into a submenu that has no visible items, and removed again once real items appear.
- `MenuController::SetSelection`
- Moves the menu’s current selection, which can open or close submenus and run delegate and controller code synchronously, so menu objects can be destroyed before it returns.
- `ViewTracker`
- A Views helper that holds a
View*and is cleared automatically when that view is destroyed, soif (tracker)tells you whether the tracked view still exists.
Root Cause Analysis
The patched code is a MenuItemView method that walks a copy of submenu_->children() to recompute visible items and remove any EmptyMenuMenuItem placeholders. The hunk header does not show the method’s name. When the placeholder being removed was selected, the old code called GetMenuController()->SetSelection(this, SELECTION_UPDATE_IMMEDIATELY) and then kept going: it used submenu_ and continued the loop. The new in-code comment states that changing the selection “can also delete this”, so the old code could touch members of a freed MenuItemView after SetSelection returned.
The fix adds a ViewTracker tracker(this) before the loop. It reads child_menu->IsSelected() into a local selected before RemoveChildViewT() detaches the child. It only calls SetSelection when tracker is still valid, and it returns immediately if tracker has been cleared by the selection change. This removes the access to the freed object on the selected-placeholder path.
this survives MenuController::SetSelection(), but selection changes can re-enter and destroy the calling MenuItemView. The fix tracks this with a ViewTracker and bails out as soon as it disappears.Attack Path
- Open a menu with an empty submenu
The user opens a browser Views menu (e.g. a context, bookmark or app menu) that has a submenu currently showing an
EmptyMenuMenuItemplaceholder. - Select the placeholder
The user moves selection onto the “(empty)” item, so
IsSelected()is true for that child. - Menu contents change The submenu’s model gains visible items while the menu is open, so the patched method runs to remove the placeholder (the trigger for re-running it is not shown in the diff; inferred).
- Selection update frees `this`
Removing the selected placeholder calls
MenuController::SetSelection(this, ...), which can synchronously destroy thisMenuItemView(as the patch comment states). The pre-patch code then continued to usesubmenu_and the loop state on the freed object, which is the use-after-free.
Impact Assessment
EmptyMenuMenuItem, plus a contents update that removes the placeholder while selection changes tear down the item. This implies user interaction and likely a timing-dependent model change (for example, a bookmark folder or extension-driven menu entries changing while the menu is open; inferred). The diff shows no path for ordinary web content to drive this alone. The advisory gives only “Use after free in Views”, with no in-the-wild status.Changed Functions
| Function | Change | Notes |
|---|---|---|
forui/views/controls/menu/menu_item_view.cc |
modified | |
ifui/views/controls/menu/menu_item_view.cc |
modified |
Files Changed
ui/views/controls/menu/menu_item_view.cc
Audit Directions
- Post-callout use of `this` in menu codeThroughout
ui/views/controls/menu/(MenuController,SubmenuView,MenuItemView), audit calls toSetSelection,Cancel,OpenMenuand delegate callbacks that are followed by member access. Each needs either aViewTracker/WeakPtrguard or a guarantee thatthissurvives. - Residual unguarded paths in the same loopIn the patched code, the not-selected path after
RemoveChildViewT()still reachessubmenu_->InvalidateLayout()without checkingtracker. Confirm that removing a child can never destroythisthere; if it can, the same guard should cover that path. - Iterating copied child lists while mutatingLook for other loops that copy
children()and then remove or reselect views. The copy keeps iteration safe, but it does nothing to protect the owning object from re-entrant deletion.
Patch
From 355923849eca301965e0ce42a54d11308cd964a1 Mon Sep 17 00:00:00 2001
From: Dana Fried <dfried@chromium.org>
Date: Wed, 22 Jul 2026 07:53:13 -0700
Subject: [PATCH] [Menus, Views] Avoid potential UAF due to self-deletion.
Bug: 536067175
Change-Id: I107f88d2fc29a7cb5f936936acf3698b553b3af1
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8127481
Reviewed-by: Dmytro Yeroshkin <yeroshkin@chromium.org>
Commit-Queue: Dmytro Yeroshkin <yeroshkin@chromium.org>
Auto-Submit: Dana Fried <dfried@chromium.org>
Commit-Queue: Dana Fried <dfried@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1666289}
---
diff --git a/ui/views/controls/menu/menu_item_view.cc b/ui/views/controls/menu/menu_item_view.cc
index 00a60ca..ee30f96d 100644
--- a/ui/views/controls/menu/menu_item_view.cc
+++ b/ui/views/controls/menu/menu_item_view.cc
@@ -64,6 +64,7 @@
#include "ui/views/style/typography_provider.h"
#include "ui/views/vector_icons.h"
#include "ui/views/view_class_properties.h"
+#include "ui/views/view_tracker.h"
#include "ui/views/view_utils.h"
#include "ui/views/widget/widget.h"
@@ -1070,20 +1071,30 @@
// visible items. Copy the children, since we may mutate them as we go.
const Views children = submenu_->children();
bool has_visible_menu_items = false;
+
+ // Making changes to selection, etc. can cause `this` to be deleted. Track the
+ // continued existence of this object while updating.
+ ViewTracker tracker(this);
+
for (View* child : children) {
MenuItemView* const child_menu = AsViewClass<MenuItemView>(child);
if (!child_menu) {
continue;
}
if (IsViewClass<EmptyMenuMenuItem>(child)) {
+ const bool selected = child_menu->IsSelected();
// Prevent view destruction until selection is updated.
// We remove the child before updating selection in case of re-entrancy.
std::unique_ptr<View> removed_child = submenu_->RemoveChildViewT(child);
- if (child_menu->IsSelected()) {
+ if (tracker && selected) {
// Update selection to this menu before deleting the currently
// selected child.
GetMenuController()->SetSelection(
this, MenuController::SELECTION_UPDATE_IMMEDIATELY);
+ // This can also delete `this`, so check for that.
+ if (!tracker) {
+ return;
+ }
}
submenu_
->InvalidateLayout(); // Ideally the submenu would have a layout
Original Bug Report
Potential Use-After-Free in MenuItemView::UpdateEmptyMenusAndMetrics via SetSelection Reentrancy
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 browser-process Use-After-Free (UAF) memory safety vulnerability exists in MenuItemView::UpdateEmptyMenusAndMetrics. Synchronous platform accessibility notifications triggered during MenuController::SetSelection can synchronously delete the parent MenuItemView tree. When execution resumes, accessing the unique_ptr member submenu_ of the freed MenuItemView instance leads to a heap Use-After-Free sink.
Affected files:
ui/views/controls/menu/menu_item_view.cc
Estimated timestamp from git blame: 2025-01-27
Summary
A potential heap Use-After-Free (UAF) memory safety issue exists in MenuItemView::UpdateEmptyMenusAndMetrics inside the browser process. This is caused by executing synchronous layout and sizing logic on the unique_ptr member submenu_ of a freed MenuItemView immediately after invoking MenuController::SetSelection with SELECTION_UPDATE_IMMEDIATELY, which can synchronously delete this due to platform accessibility re-entrancy.
Vulnerability Details
Inside ui/views/controls/menu/menu_item_view.cc:1060, UpdateEmptyMenusAndMetrics() executes a loop that modifies the children of a submenu:
// ui/views/controls/menu/menu_item_view.cc:1060-1098
void MenuItemView::UpdateEmptyMenusAndMetrics() {
CHECK(HasSubmenu());
const Views children = submenu_->children();
bool has_visible_menu_items = false;
for (View* child : children) {
MenuItemView* const child_menu = AsViewClass<MenuItemView>(child);
...
if (IsViewClass<EmptyMenuMenuItem>(child)) {
std::unique_ptr<View> removed_child = submenu_->RemoveChildViewT(child);
if (child_menu->IsSelected()) {
GetMenuController()->SetSelection(
this, MenuController::SELECTION_UPDATE_IMMEDIATELY); // <-- Can trigger synchronous deletion of `this`
}
submenu_
->InvalidateLayout(); // <-- UAF: Access of `this->submenu_` after `this` is freed
}
...
}
...
submenu_->UpdateMenuPartSizes(); // <-- UAF: Always reached
}
If the placeholder EmptyMenuMenuItem is currently selected, calling SetSelection fires multiple synchronous platform accessibility events (via SetHotTrackedButton(nullptr) or SetPopupFocusOverride()). When assistive technologies or screen readers are active (native platform accessibility is enabled on the browser UI thread), the platform COM or AT event loops can synchronously re-enter the Chromium UI thread and request the menu tree be closed/destroyed.
This re-entrant path executes MenuRunnerImpl::Release() setting delete_after_run_ = true, which ultimately deletes the MenuController and performs delete this; on the MenuRunnerImpl itself (see ui/views/controls/menu/menu_runner_impl.cc:235-247). Deleting MenuRunnerImpl destructs its owned menu_ (which is the root MenuItemView), resulting in cascading destruction that deletes the parent MenuItemView on which UpdateEmptyMenusAndMetrics is running.
When execution control resumes and returns from SetSelection, UpdateEmptyMenusAndMetrics immediately dereferences this->submenu_ to call InvalidateLayout(). Because submenu_ is stored as std::unique_ptr<SubmenuView> (not raw_ptr<>), it is not protected by MiraclePtr and can be loaded from a freed (or potentially attacker-reclaimed) memory region, causing a browser-process control flow hijack through subsequent virtual function calls (e.g. GetLayoutManager()->InvalidateLayout()).
Potential Attack Vector / Reproduction Flow
- The user launches Chromium with native platform accessibility active (e.g., NVDA, Microsoft Narrator, or Orca screen reader running).
- The user opens an empty nested submenu (which displays the selected
(empty)placeholder item). - Web content triggers a dynamic menu model mutation (e.g., using
history.pushState()to fire aNavigationEntryCommittednavigation on a back/forward long-press model, or modifying an observed bookmark structure via extensions). - This invokes
OnMenuStructureChanged(), triggeringChildrenChanged()and subsequentlyUpdateEmptyMenusAndMetrics()on the active submenu item. - During the placeholder removal,
SetSelection(this, SELECTION_UPDATE_IMMEDIATELY)is invoked, triggering synchronous re-entrancy and closing the menu. - Both the
MenuControllerandMenuItemViewtree are deleted on the stack. - When
SetSelectionreturns, the execution flow reaches the UAF sinksubmenu_->InvalidateLayout(), causing memory corruption.
Note: These steps are based on static analysis and automated verification of the Chromium views lifecycle, as our tooling cannot run or compile production binaries dynamically.
Suggested Fix
To resolve this issue, the execution lifetime of the MenuItemView needs to be safely guarded. The safest approach is to use a views::ViewTracker to track the liveness of this across the call to SetSelection:
if (child_menu->IsSelected()) {
views::ViewTracker tracker(this);
GetMenuController()->SetSelection(
this, MenuController::SELECTION_UPDATE_IMMEDIATELY);
if (!tracker.view()) {
return;
}
}
Evaluated with Chrome root at commit: bf775e5d75cb9e1767e2cd02cc93efa0077d14a5
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.