Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Views
DescriptionUse after free in Views
ComponentViews
Bug ClassUAF
Tracker497736679
Fix commite2b78d75a6d1 (chromium/src) +6/-1
CISA KEVNot listed
CreditedGoogle
Disclosed2026-05-05

Files Changed

  • ui/views/controls/menu/menu_controller.cc
From e2b78d75a6d18e68ce5c541d439e33962e493390 Mon Sep 17 00:00:00 2001
From: Takashi Sakamoto <[email protected]>
Date: Fri, 03 Apr 2026 01:07:02 -0700
Subject: [PATCH] Use `raw_ptr<SubmenuView>` to avoid destroying `source` while RunDragDropLoop().

Bug: 497736679
Change-Id: I40febebddc57373306e24cd93a46d4ed4bd06e53
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7719121
Reviewed-by: Keishi Hattori <[email protected]>
Commit-Queue: Takashi Sakamoto <[email protected]>
Owners-Override: Keishi Hattori <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1609709}
---

diff --git a/ui/views/controls/menu/menu_controller.cc b/ui/views/controls/menu/menu_controller.cc
index 1b779f7..63f140c6 100644
--- a/ui/views/controls/menu/menu_controller.cc
+++ b/ui/views/controls/menu/menu_controller.cc
@@ -1826,8 +1826,13 @@
   SetSelection(part.menu, selection_types);
 }
 
-void MenuController::StartDrag(SubmenuView* source,
+void MenuController::StartDrag(SubmenuView* source_raw,
                                const gfx::Point& location) {
+  // TODO(crbug.com/497736679): Intended to keep `source_raw` quarantined inside
+  // StartDrag(). Since `source_raw` might be destroyed while RunDrawDropLoop(),
+  // `source` will be sometimes dangling pointer. So detecting
+  // `source` is dangling is expected.
+  raw_ptr<SubmenuView, DisableDanglingPtrDetection> source(source_raw);
   MenuItemView* item = state_.item;
   DCHECK(item);
   // Points are in the coordinates of the submenu, need to map to that of
Loading diff…

Original Bug Report

reported by [email protected]

Use-After-Free in MenuController::StartDrag via nested message loop

Project Fortify, an experimental security project, has identified the following potential security issue.

Overview: A potential Use-After-Free vulnerability exists in MenuController::StartDrag due to a raw pointer being held across a nested message loop. An attacker can trigger the destruction of the SubmenuView while the loop is running, bypassing MiraclePtr protections. When the loop returns, the freed pointer is dereferenced, potentially leading to a browser process sandbox escape.

Affected files:

  • ui/views/controls/menu/menu_controller.cc
  • chrome/browser/ui/views/bookmarks/bookmark_menu_delegate.cc
  • ui/views/controls/menu/menu_item_view.cc
  • ui/views/controls/menu/submenu_view.cc
  • ui/views/widget/widget.cc

Estimated timestamp from git blame: 2024-11-04

Vulnerability Description

A potential Use-After-Free (UAF) vulnerability exists in the browser process within MenuController::StartDrag (ui/views/controls/menu/menu_controller.cc).

The StartDrag function takes a raw SubmenuView* source pointer. To initiate the drag-and-drop operation, it calls item->GetWidget()->RunShellDrag(...). This function enters a nested message loop (ScopedAllowApplicationTasksInNativeNestedLoop), which allows the browser to process other tasks and IPC messages while the drag is active.

If the underlying data model is modified during this nested loop (for example, a malicious extension moving or deleting the dragged bookmark’s parent folder), the UI elements including the SubmenuView are destroyed. When RunShellDrag returns, StartDrag attempts to access source->host(), dereferencing the freed SubmenuView memory.

Because source is a raw stack pointer, and all legitimate raw_ptr references are cleanly nulled out during teardown (e.g., MenuHost::submenu_ = nullptr), MiraclePtr (BackupRefPtr) reference counts drop to zero and the memory is freed to PartitionAlloc, completely bypassing BRP protections.

Potential Attack Steps

Note: These are suggested steps based on static analysis. Our tooling agent does not currently have the ability to run code to verify a working proof of concept.

  1. Setup: An attacker installs a malicious extension with bookmarks permissions.
  2. User Interaction: The user opens a bookmark folder from the bookmarks bar and clicks and drags a bookmark to initiate a drag-and-drop operation.
  3. Nested Loop: The browser UI thread enters MenuController::StartDrag. It records had_capture = source->host()->HasCapture() and calls RunShellDrag, entering a nested message loop.
  4. Trigger Destruction: The malicious extension sends an asynchronous IPC (e.g., chrome.bookmarks.move()) to move the parent folder of the dragged item.
  5. Teardown: The browser processes the IPC, triggering BookmarkMenuDelegate::BookmarkNodeMoved. This deletes the parent MenuItemView and its associated SubmenuView (the source pointer). All raw_ptr references are cleared, and the memory is freed.
  6. Heap Spray: The attacker uses the extension to heap spray the browser process, reclaiming the freed SubmenuView memory block and planting a fake host_ pointer.
  7. Exploitation: The drag operation aborts/completes, and control returns to StartDrag. The code checks showing_ && had_capture (which remain true because the drag was active) and executes source->host()->SetCapture(nullptr).
  8. Code Execution: The dereference uses the attacker’s fake host_ pointer (a spoofed Widget). The call to SetCapture reads a fake native_widget_ and performs a virtual method call (HasCapture()), granting the attacker a reliable primitive for a vtable hijack and Remote Code Execution (RCE) in the browser process.

Suggested Fix

Update MenuController::StartDrag to use a base::WeakPtr<SubmenuView> (or base::SafeRef if appropriate) to track the liveness of the source view across the RunShellDrag call.

Since SubmenuView already inherits a base::WeakPtrFactory, the fix can be implemented by capturing a weak pointer before the nested loop:

base::WeakPtr<SubmenuView> safe_source = source->AsWeakPtr();
item->GetWidget()->RunShellDrag(...);
// ... 
if (showing_ && had_capture && safe_source) {
  safe_source->host()->SetCapture(nullptr);
}

Alternatively, converting the raw pointer argument SubmenuView* source to a base::raw_ptr<SubmenuView> across the stack frame would trigger MiraclePtr protections and turn the vulnerability into a safe crash.

Evaluated with Chrome root at commit: a9cbf6e8b275fe4147435aa905f3b7f5a656f5f0


Results from 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