Medium watch CVSS 9.6 chrome UAF 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
9.6
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Views
DescriptionUse after free in Views
ComponentViews
Sub-componentMenu
Bug ClassUAF
Action tierwatch — corruption primitive but gated (uncommon) — not default-reachable
Computed by PatchGap's deterministic exploitability ruler from the fixing commit. patch-now = RCE-class & attacker-reachable in a default config, or exploited in the wild; watch = real security signal below that bar; noise = no security relevance.
Tracker501637242
Fix commitfab84e22035b (chromium/src) +13/-3
CWECWE-416 (Use-after-free)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H
CISA KEVNot listed
CreditedGoogle
Reported2026-04-11 (per vendor advisory)
Fix landed2026-07-20 (public git)
Disclosed2026-08-25
Patch gap36 days — fix public in source before the advisory

Background

`MenuController`
The views object 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 via MenuChildrenChanged().
Re-entrancy via accessibility/activation
According to the new comments, SetSelection() and MenuChildrenChanged() 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::WeakPtr that 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.

Key insight
Two code paths kept using 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

  1. Open a menu A Views menu (e.g. a context or app menu) is shown, so a MenuController and its MenuItemView tree exist. This is browser UI, normally opened by user interaction.
  2. Trigger selection or child changes The submenu-opening path (SetSelection() with SELECTION_OPEN_SUBMENU) runs, or the menu’s children change and invoke MenuItemView::ChildrenChanged().
  3. Re-entrant destruction During SetSelection() or MenuChildrenChanged(), accessibility callbacks or an activation change (e.g. focus moving to another window) close the menu and delete the MenuController.
  4. Use after free Back in the caller, the old code called OpenMenuImpl() on a freed controller, or used submenu_ 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

The bug is a use-after-free in the browser process’s UI layer. It is reached through native menus and timing-dependent accessibility or activation events, so it needs user interaction with a menu and a specific race. The patch comment calls it rare. It is not obviously reachable from web content alone, and an assistive technology or a window-activation change may be involved (inferred). The advisory lists it as “Use after free in Views”. Any escalation to code execution in the browser process would be (inferred). The vendor advisory rates this medium severity and does not report in-the-wild exploitation; NVD scores it CVSS 9.6.

Changed Functions

FunctionChangeNotes
if
ui/views/controls/menu/menu_item_view.cc
modified

Files Changed

  • ui/views/controls/menu/menu_controller.cc
  • ui/views/controls/menu/menu_item_view.cc

Audit Directions

  • Calls followed by member access in menu and bubble code
    In MenuController, MenuItemView, SubmenuView and bubble/widget code, look for calls that can trigger accessibility notifications, focus or activation changes, or Cancel(), followed by use of this or cached pointers without a WeakPtr or destruction check.
  • Accessibility event dispatch as a re-entrancy vector
    Calls 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 controller
    The fix checks the controller’s lifetime, but the caller’s own MenuItemView (and item) may share the menu’s lifetime. Check whether locals such as item or submenu_ are still used after a guarded call on other paths.
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
Loading diff…

Original Bug Report

reported by vm...@google.com

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.cc
  • ui/views/controls/menu/menu_item_view.cc
  • ui/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:

  1. Gaining code execution in a Renderer process.
  2. Waiting for the user to open a dynamic menu (e.g., the History -> Recent Tabs submenu).
  3. Triggering a model mutation from the renderer (e.g., by executing window.close() on a popup associated with that menu).
  4. Timing heap spraying (via IPCs or Blob URLs) to reclaim the freed MenuController memory slot immediately after MenuRunnerImpl::OnMenuClosed executes, but before OpenMenuImpl accesses it.
  5. Overwriting the internal std::vector (child_menu_open_direction_) accessed by OpenMenuImpl to achieve arbitrary read/write primitives in the browser process.

Suggested Reproduction Steps

  1. Run Chrome with platform accessibility enabled (e.g., by attaching a screen reader or using --force-renderer-accessibility).
  2. Open a same-origin popup from a tab using window.open().
  3. Open the Chrome App Menu and navigate to the History -> Recent Tabs submenu.
  4. From the renderer of the popup, execute window.close().
  5. The resulting state change should trigger MenuChildrenChanged while the menu is open, potentially crashing the browser if the timing coincides with the accessibility event dispatch.

Suggested Fix

  1. In ui/views/controls/menu/menu_controller.cc, update MenuChildrenChanged to use a WeakPtr to verify this is still valid after calling SetSelection:
  auto this_ref = AsWeakPtr();
  SetSelection(item, SELECTION_OPEN_SUBMENU | SELECTION_UPDATE_IMMEDIATELY);
  if (!this_ref) return;
  if (item->HasSubmenu()) {
    OpenMenuImpl(item, false);
  }
  1. In ui/views/controls/menu/menu_item_view.cc, update MenuItemView::ChildrenChanged() to hold a WeakPtr instead 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.

View on issue tracker