CVE-2026-13802
Overview
Files Changed
ui/views/controls/menu/menu_controller.cc
Patch
From b721b402bb215359398430b066bdb080c78dbf0e Mon Sep 17 00:00:00 2001
From: Allen Bauer <kylixrd@chromium.org>
Date: Fri, 15 May 2026 07:14:06 -0700
Subject: [PATCH] Check for MenuController(this) deletion after ShowAt() call.
It is possible that the ShowAt() above can synchronously re-enter and
destroy `this` and the entire MenuItemView tree. We do a CHECK() here
instead of a early return. There are still other things up the stack
that would require additional guarding. It is also unknown what state
things would be left in should it be allowed to continue.
Change-Id: Ie49260fcd60e68b828a081041611392649bf29a8
Bug: 501623322
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7849124
Reviewed-by: Thomas Lukaszewicz <tluk@chromium.org>
Commit-Queue: Allen Bauer <kylixrd@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1631254}
---
diff --git a/ui/views/controls/menu/menu_controller.cc b/ui/views/controls/menu/menu_controller.cc
index 530e10a2..10649a65 100644
--- a/ui/views/controls/menu/menu_controller.cc
+++ b/ui/views/controls/menu/menu_controller.cc
@@ -2575,7 +2575,14 @@
} else {
params.context = owner_;
}
+ auto weak_this = AsWeakPtr();
item->GetSubmenu()->ShowAt(params);
+ // It is possible that the ShowAt() above can synchronously re-enter and
+ // destroy `this` and the entire MenuItemView tree. We do a CHECK() here
+ // instead of a early return. There are still other things up the stack that
+ // would require additional guarding. It is also unknown what state things
+ // would be left in should it be allowed to continue.
+ CHECK(weak_this);
// Figure out if the mouse is under the menu; if so, remember the mouse
// location so we can ignore the first mouse move event(s) with that
Original Bug Report
Potential Use-After-Free in MenuController::OpenMenuImpl via synchronous menu destruction
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 potential Use-After-Free vulnerability exists in MenuController::OpenMenuImpl when opening a submenu. Synchronous OS events during the submenu’s display can cause the menu tree and controller to be destroyed while a raw pointer to the menu item is still on the stack. Resuming execution leads to virtual method calls on the freed memory, which may allow arbitrary code execution in the browser process.
Affected files:
ui/views/controls/menu/menu_controller.ccui/views/controls/menu/menu_runner_impl.ccui/views/controls/menu/submenu_view.ccui/views/controls/menu/menu_host.cc
Estimated timestamp from git blame: 2025-08-20
Summary
A potential Use-After-Free (UAF) vulnerability has been identified in MenuController::OpenMenuImpl (ui/views/controls/menu/menu_controller.cc). When a submenu is opened, a call to ShowAt() can synchronously pump OS events (particularly on macOS). If a window destruction or focus change event is processed during this nested loop, the entire menu tree and MenuController are deleted. Because the menu item is tracked via a raw C++ pointer on the stack, MiraclePtr protections are bypassed, and subsequent virtual method calls on the freed object can be hijacked to achieve Remote Code Execution (RCE) in the Browser Process.
Technical Details
MenuController::OpenMenuImpl(MenuItemView* item, ...)receivesitemas a standard C++ raw pointer on the stack.- The code calls
item->GetSubmenu()->ShowAt(params), which propagates down toMenuHost::ShowMenuHostand callsShowInactive(). - On macOS,
ShowInactive()interacts with native window APIs (-[NSWindow addChildWindow:ordered:]) that can synchronously dispatch OS events like window focus loss or destruction. - If an attacker triggers a window destruction or focus change at this exact moment,
MenuRunnerImpl::OnMenuClosedis invoked synchronously. MenuRunnerImplexecutesdelete controller_.get();(destroying theMenuController, which isthisinOpenMenuImpl) and thendelete this;(destroying theMenuRunnerImpland its uniquely ownedMenuItemViewtree).- During this teardown, all heap-based
raw_ptrreferences to theitemallocation are cleanly destroyed, causing the MiraclePtr (BackupRefPtr) reference count to drop to zero. The memory is immediately freed to the system allocator. - Execution unwinds and returns to
OpenMenuImplimmediately after theShowAt()call. - The code continues executing using the now-dangling
itemstack variable, evaluatingitem->GetSubmenu()->GetWidget(). - This performs a non-virtual fetch of the forged
submenu_pointer and invokes the virtual methodGetWidget()(ui/views/view.h), allowing an attacker to hijack the control flow via a forged vtable.
Potential Steps to Trigger
Note: Our tooling agent does not currently have the ability to run code, so these are suggested steps based on static analysis, lacking a working Proof-of-Concept.
- An attacker serves a malicious web page that opens a Views-based menu (e.g., via a browser extension popup or custom UI element interaction).
- The attacker programmatically triggers a submenu to open.
- Racing the submenu’s
ShowAtsequence, the attacker triggers an event that causes the parent window to close or lose focus (e.g., popping under another window or destroying the popup). - The nested event loop processes the focus loss, synchronously destroying the menu.
- The attacker sprays the browser process heap to reclaim the freed
MenuItemViewandSubmenuViewmemory with forged objects containing a malicious vtable. - When
ShowAtreturns, the browser dereferences the attacker’s vtable, achieving RCE.
Suggested Fix
To safely handle synchronous destruction, OpenMenuImpl should detect if the MenuController or the MenuItemView was destroyed during the ShowAt call.
- Obtain a weak pointer to the controller:
base::WeakPtr<MenuController> weak_this = weak_factory_.GetWeakPtr(); - Use a
views::ViewTrackeror abase::WeakPtr<MenuItemView>(sinceViewsupports weak pointers) to track theitem. - After the
item->GetSubmenu()->ShowAt(params)call, verify liveness:if (!weak_this || !weak_item) { return; }
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.