Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Views
DescriptionUse after free in Views
ComponentViews
Bug ClassUAF
Tracker517959443
Fix commitf8a09c00af14 (chromium/src) +64/-0
CISA KEVNot listed
CreditedGoogle
Disclosed2026-08-25

Changed Functions

FunctionChangeNotes
if
ui/views/controls/menu/menu_controller.cc
modified
CallbackOnAXEventObserver
ui/views/controls/menu/menu_controller_unittest.cc
modified
if
ui/views/controls/menu/menu_controller_unittest.cc
modified
TEST_F
ui/views/controls/menu/menu_controller_unittest.cc
modified

Files Changed

  • ui/views/controls/menu/menu_controller.cc
  • ui/views/controls/menu/menu_controller_unittest.cc
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);
Loading diff…

Regression Test / PoC

shipped with the fix
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);
Loading diff…

Original Bug Report

reported by [email protected]

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.

  1. The user (or an automated system with accessibility features enabled) opens a menu containing hot-trackable buttons (such as zoom or option controls).
  2. The mouse cursor is moved over a button, triggering a mouse move event that is routed to MenuController::OnMouseMoved.
  3. The view is resolved to a Button* new_hot_tracked_button raw pointer.
  4. HandleMouseLocation is invoked, which routes to SetSelection.
  5. SetSelection sets a popup focus override, firing synchronous platform-specific accessibility events.
  6. A platform accessibility listener synchronously closes the menu in response to the focus event, leading to the destruction of the MenuController and all associated views.
  7. Control returns to OnMouseMoved, which continues execution without a safety check, dereferencing the deleted this pointer and the now-dangling new_hot_tracked_button pointer inside SetHotTrackedButton and MaybeForwardToAnnotation.

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.

View on issue tracker