CVE-2026-79257
Overview
Background
- `MenuController`
- the Views object that drives an open menu, routing mouse and keyboard events and managing the current selection.
- `AsWeakPtr()`
- returns a weak pointer to
thisthat becomes null once the object is destroyed, letting a method detect its own deletion after a re-entrant call. - Accessibility notification
- an event (e.g.
ax::mojom::Event::kActiveDescendantChanged) fired when the menu selection changes, which observers can handle synchronously and, as a side effect, tear down the menu and itsMenuController. - Re-entrant deletion
- the pattern where a method calls out to code that synchronously frees the very object the method is still executing on.
Root Cause Analysis
While handling mouse events, MenuController::UpdateSelectionWithEvent() and MenuController::OnMouseMoved() (named in the commit message; the enclosing function names are not in the hunk headers) changed the menu selection or showed a sibling menu, which fires accessibility notifications. An accessibility observer can respond to such a notification by synchronously destroying the MenuController, so on return the methods continued to operate on freed memory: UpdateSelectionWithEvent() went on to call UpdateActiveMouseView(source, event, mouse_menu) on the dead this, and OnMouseMoved() continued after HandleMouseLocation(source, event.location()) touching this. The violated invariant is that this remains alive for the remainder of the method after a call that can re-enter and delete it.
The fix captures auto this_ref = AsWeakPtr() before the deletion-prone call and, if !this_ref afterward, returns early (return false / return) before any further use of this. The accompanying unit test destroys the controller from an AXUpdateObserver during a mouse move to reproduce the use-after-free.
MenuController outlives its own mouse-handling methods, when a selection change can fire an accessibility notification whose observer synchronously deletes the controller; the fix detects that deletion via AsWeakPtr() and bails out before touching freed memory.Attack Path
- Open a menu
A menu with a submenu is shown, giving a live
MenuControllerhandling pointer input. - Move the pointer to change selection
A mouse-move over a different menu item reaches
MenuController::OnMouseMoved()/MenuController::UpdateSelectionWithEvent(), which updates the selection. - Fire an accessibility event
The selection change emits an accessibility notification (e.g.
kActiveDescendantChanged) to registered observers. - Delete the controller re-entrantly
An accessibility observer synchronously tears down the menu, freeing the
MenuControllerwhile its mouse-handling method is still on the stack. - Use freed memory
Control returns into the method, which (pre-fix) calls
UpdateActiveMouseView(...)or continues pastHandleMouseLocation(...)on the freedthis, a use-after-free (any further memory-corruption primitive is(inferred)).
Impact Assessment
MenuController in the browser process (Views UI). The precondition is an active menu plus an accessibility client/observer that destroys the controller in response to a selection-change notification during mouse handling, which constrains straightforward remote triggering. The advisory classifies this as a use-after-free in Views; no in-the-wild exploitation is stated, and escalation from the UAF to code execution or sandbox-relevant control is (inferred).Changed Functions
| Function | Change | Notes |
|---|---|---|
ifui/views/controls/menu/menu_controller.cc |
modified | |
CallbackOnAXEventObserverui/views/controls/menu/menu_controller_unittest.cc |
modified | |
ifui/views/controls/menu/menu_controller_unittest.cc |
modified | |
TEST_Fui/views/controls/menu/menu_controller_unittest.cc |
modified |
Files Changed
ui/views/controls/menu/menu_controller.ccui/views/controls/menu/menu_controller_unittest.cc
Audit Directions
- Re-entrancy after event dispatchSearch Views/UI methods that mutate selection or state and then keep using
this, where the mutation can fire accessibility or observer callbacks that delete the object. - Missing `AsWeakPtr()` guardsAudit long methods that call notification/observer/delegate hooks partway through and continue dereferencing members afterward without a weak-pointer liveness check.
- Accessibility-observer-driven teardownReview other controllers reachable from
AXUpdateNotifier/AXUpdateObservercallbacks for object lifetimes that assume the observed object survives the notification.
Patch
From f8a09c00af14838389f22bc5d4c7562af27e186a Mon Sep 17 00:00:00 2001 From: Keren Zhu <[email protected]> Date: Thu, 16 Jul 2026 13:23:00 -0700 Subject: [PATCH] Protect MenuController from being deleted during mouse event handling. Add checks using AsWeakPtr() in MenuController::UpdateSelectionWithEvent() and MenuController::OnMouseMoved() to ensure the MenuController instance is still valid after calls that might trigger accessibility notifications. These notifications can, as a side effect, cause the MenuController to be deleted. A new unit test is added to reproduce a crash scenario where the MenuController is destroyed by an accessibility observer during a mouse move event. Fixed: 517959443 Change-Id: I2513d4c6e8a7dfc9a7f0213b4c34e151e81c8fa0 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8104643 Reviewed-by: Allen Bauer <[email protected]> Commit-Queue: Keren Zhu <[email protected]> Cr-Commit-Position: refs/heads/main@{#1663466} --- diff --git a/ui/views/controls/menu/menu_controller.cc b/ui/views/controls/menu/menu_controller.cc index 00f21ac..51a34bfd 100644 --- a/ui/views/controls/menu/menu_controller.cc +++ b/ui/views/controls/menu/menu_controller.cc @@ -964,6 +964,9 @@ } return true; } + // Changing the selection or showing a sibling menu can cause `this` to be + // deleted as a side effect of accessibility notifications. + auto this_ref = AsWeakPtr(); MenuItemView* mouse_menu = nullptr; if (part.type == MenuPartType::kMenuItem) { // If there is no menu target, but a submenu target, then we are interacting @@ -991,6 +994,9 @@ } } } + if (!this_ref) { + return false; + } UpdateActiveMouseView(source, event, mouse_menu); return true; @@ -1121,7 +1127,13 @@ new_hot_tracked_button = Button::AsButton(view); } + // `HandleMouseLocation()` may change the selection, which can cause `this` to + // be deleted as a side effect of accessibility notifications. + auto this_ref = AsWeakPtr(); HandleMouseLocation(source, event.location()); + if (!this_ref) { + return; + } // Updating the hot tracked button should be after `HandleMouseLocation()` // which may reset the current hot tracked button. diff --git a/ui/views/controls/menu/menu_controller_unittest.cc b/ui/views/controls/menu/menu_controller_unittest.cc index a38e528..f962d71 100644 --- a/ui/views/controls/menu/menu_controller_unittest.cc +++ b/ui/views/controls/menu/menu_controller_unittest.cc @@ -16,11 +16,13 @@ #include "base/functional/callback_helpers.h" #include "base/i18n/rtl.h" #include "base/memory/raw_ptr.h" +#include "base/scoped_observation.h" #include "base/strings/stringprintf.h" #include "base/strings/to_string.h" #include "base/strings/utf_string_conversions.h" #include "base/task/current_thread.h" #include "base/task/single_thread_task_runner.h" +#include "base/test/bind.h" #include "build/build_config.h" #include "ui/accessibility/ax_action_data.h" #include "ui/accessibility/ax_mode.h" @@ -42,6 +44,8 @@ #include "ui/events/types/event_type.h" #include "ui/gfx/geometry/point.h" #include "ui/gfx/geometry/rect.h" +#include "ui/views/accessibility/ax_update_notifier.h" +#include "ui/views/accessibility/ax_update_observer.h" #include "ui/views/accessibility/view_accessibility.h" #include "ui/views/controls/button/label_button.h" #include "ui/views/controls/menu/menu_controller_delegate.h" @@ -300,6 +304,32 @@ BEGIN_METADATA(CancelMenuOnMousePressView) END_METADATA +// Runs a callback the first time an accessibility event of `event_type` is +// observed on any view. +class CallbackOnAXEventObserver : public AXUpdateObserver { + public: + CallbackOnAXEventObserver(ax::mojom::Event event_type, + base::OnceClosure callback) + : event_type_(event_type), callback_(std::move(callback)) { + observation_.Observe(AXUpdateNotifier::Get()); + } + + bool fired() const { return !callback_; } + + // AXUpdateObserver: + void OnViewEvent(View* view, ax::mojom::Event event_type) override { + if (event_type == event_type_ && callback_) { + std::move(callback_).Run(); + } + } + + private: + const ax::mojom::Event event_type_; + base::OnceClosure callback_; + base::ScopedObservation<AXUpdateNotifier, AXUpdateObserver> observation_{ + this}; +}; + } // namespace struct MenuBoundsOptions { @@ -3062,6 +3092,28 @@ submenu->Close(); } +// Tests that having the MenuController deleted from an accessibility observer +// while handling a mouse move does not cause a crash. ASAN bots should not +// detect use-after-free in MenuController. +TEST_F(MenuControllerTest, MenuControllerDestroyedDuringMouseMove) { + ShowSubmenu(); + SubmenuView* const submenu = menu_item()->GetSubmenu(); + SetPendingStateItem(submenu->GetMenuItemAt(0)); + + CallbackOnAXEventObserver observer( + ax::mojom::Event::kActiveDescendantChanged, + base::BindLambdaForTesting([this] { DestroyMenuController(); })); + + // Moving the mouse over a different item changes the selection, which fires + // accessibility events. The observer above synchronously deletes the + // controller while the move is being handled. + const gfx::Point location = submenu->GetMenuItemAt(1)->bounds().CenterPoint(); + ProcessMouseMoved( + submenu, ui::MouseEvent(ui::EventType::kMouseMoved, location, location, + ui::EventTimeForNow(), 0, 0)); + EXPECT_TRUE(observer.fired()); +} + TEST_F(MenuControllerTest, SetSelectionIndices_MenuItemsOnly) { SubmenuView* const submenu = menu_item()->GetSubmenu(); MenuItemView* const item1 = submenu->GetMenuItemAt(0);
Regression Test / PoC
diff --git a/ui/views/controls/menu/menu_controller_unittest.cc b/ui/views/controls/menu/menu_controller_unittest.cc
index a38e528..f962d71 100644
--- a/ui/views/controls/menu/menu_controller_unittest.cc
+++ b/ui/views/controls/menu/menu_controller_unittest.cc
@@ -16,11 +16,13 @@
#include "base/functional/callback_helpers.h"
#include "base/i18n/rtl.h"
#include "base/memory/raw_ptr.h"
+#include "base/scoped_observation.h"
#include "base/strings/stringprintf.h"
#include "base/strings/to_string.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/current_thread.h"
#include "base/task/single_thread_task_runner.h"
+#include "base/test/bind.h"
#include "build/build_config.h"
#include "ui/accessibility/ax_action_data.h"
#include "ui/accessibility/ax_mode.h"
@@ -42,6 +44,8 @@
#include "ui/events/types/event_type.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/geometry/rect.h"
+#include "ui/views/accessibility/ax_update_notifier.h"
+#include "ui/views/accessibility/ax_update_observer.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/controls/button/label_button.h"
#include "ui/views/controls/menu/menu_controller_delegate.h"
@@ -300,6 +304,32 @@
BEGIN_METADATA(CancelMenuOnMousePressView)
END_METADATA
+// Runs a callback the first time an accessibility event of `event_type` is
+// observed on any view.
+class CallbackOnAXEventObserver : public AXUpdateObserver {
+ public:
+ CallbackOnAXEventObserver(ax::mojom::Event event_type,
+ base::OnceClosure callback)
+ : event_type_(event_type), callback_(std::move(callback)) {
+ observation_.Observe(AXUpdateNotifier::Get());
+ }
+
+ bool fired() const { return !callback_; }
+
+ // AXUpdateObserver:
+ void OnViewEvent(View* view, ax::mojom::Event event_type) override {
+ if (event_type == event_type_ && callback_) {
+ std::move(callback_).Run();
+ }
+ }
+
+ private:
+ const ax::mojom::Event event_type_;
+ base::OnceClosure callback_;
+ base::ScopedObservation<AXUpdateNotifier, AXUpdateObserver> observation_{
+ this};
+};
+
} // namespace
struct MenuBoundsOptions {
@@ -3062,6 +3092,28 @@
submenu->Close();
}
+// Tests that having the MenuController deleted from an accessibility observer
+// while handling a mouse move does not cause a crash. ASAN bots should not
+// detect use-after-free in MenuController.
+TEST_F(MenuControllerTest, MenuControllerDestroyedDuringMouseMove) {
+ ShowSubmenu();
+ SubmenuView* const submenu = menu_item()->GetSubmenu();
+ SetPendingStateItem(submenu->GetMenuItemAt(0));
+
+ CallbackOnAXEventObserver observer(
+ ax::mojom::Event::kActiveDescendantChanged,
+ base::BindLambdaForTesting([this] { DestroyMenuController(); }));
+
+ // Moving the mouse over a different item changes the selection, which fires
+ // accessibility events. The observer above synchronously deletes the
+ // controller while the move is being handled.
+ const gfx::Point location = submenu->GetMenuItemAt(1)->bounds().CenterPoint();
+ ProcessMouseMoved(
+ submenu, ui::MouseEvent(ui::EventType::kMouseMoved, location, location,
+ ui::EventTimeForNow(), 0, 0));
+ EXPECT_TRUE(observer.fired());
+}
+
TEST_F(MenuControllerTest, SetSelectionIndices_MenuItemsOnly) {
SubmenuView* const submenu = menu_item()->GetSubmenu();
MenuItemView* const item1 = submenu->GetMenuItemAt(0);
Original Bug Report
Potential Use-After-Free in MenuController::OnMouseMoved via Accessibility Re-entrancy
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 views menu system when processing mouse movement events. Under certain conditions, synchronously dispatched platform accessibility events can cause the MenuController and its associated views to be deleted during selection updates. Because MenuController::OnMouseMoved lacks a WeakPtr safety check after updating the selection, it may continue execution using freed ’this’ and dangling view pointers.
Affected files:
ui/views/controls/menu/menu_controller.cc
Estimated timestamp from git blame: 2020-10-06
Description
A potential Use-After-Free (UAF) vulnerability has been identified in MenuController::OnMouseMoved within ui/views/controls/menu/menu_controller.cc.
When a mouse move event is processed by MenuController::OnMouseMoved, the controller retrieves a raw pointer to a child Button and calls HandleMouseLocation. Under certain conditions (such as when platform accessibility tools are active), HandleMouseLocation invokes SetSelection to update the selected menu item. This update can trigger synchronous accessibility notifications (e.g., popup focus overrides or selection changes).
If an active accessibility client or system hook responds to these events by requesting the menu to close, the menu runner synchronously destroys the MenuController and tears down the entire MenuItemView tree. Although SetSelection contains safety checks using base::WeakPtr to abort early if this is deleted, its caller OnMouseMoved does not verify if the controller is still valid upon returning from HandleMouseLocation. It subsequently attempts to use the freed this pointer and the dangling Button pointer.
Potential Attack/Trigger Steps
Note: These are suggested steps based on static code analysis; we have not executed this flow or produced a working proof of concept.
- The user (or an automated system with accessibility features enabled) opens a menu containing hot-trackable buttons (such as zoom or option controls).
- The mouse cursor is moved over a button, triggering a mouse move event that is routed to
MenuController::OnMouseMoved. - The view is resolved to a
Button* new_hot_tracked_buttonraw pointer. HandleMouseLocationis invoked, which routes toSetSelection.SetSelectionsets a popup focus override, firing synchronous platform-specific accessibility events.- A platform accessibility listener synchronously closes the menu in response to the focus event, leading to the destruction of the
MenuControllerand all associated views. - Control returns to
OnMouseMoved, which continues execution without a safety check, dereferencing the deletedthispointer and the now-danglingnew_hot_tracked_buttonpointer insideSetHotTrackedButtonandMaybeForwardToAnnotation.
Suggested Fix
Introduce a base::WeakPtr safety check in MenuController::OnMouseMoved immediately after the call to HandleMouseLocation, matching the defensive patterns used elsewhere in MenuController:
base::WeakPtr<MenuController> this_ref = AsWeakPtr();
HandleMouseLocation(source, event.location());
if (!this_ref) {
return;
}
Evaluated with Chrome root at commit: fb72408a8493c46bc75fae1c70d03daec96b3040
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.