CVE-2026-7976
Overview
Background
- `MenuController`
- the Views component that drives menu interaction, including initiating drag-and-drop from a menu via
StartDrag. - `SubmenuView`
- the view passed to
StartDragassource; it can be torn down while the drag loop runs. - `RunDragDropLoop` (nested run loop)
- a blocking, re-entrant message loop that processes events during drag-and-drop, during which arbitrary view teardown can occur.
- `raw_ptr` / MiraclePtr (BackupRefPtr)
- Chromium’s smart raw pointer that quarantines the underlying allocation while any
raw_ptrstill references it, withDisableDanglingPtrDetectionsuppressing the dangling-pointer detector for pointers expected to legitimately dangle.
Root Cause Analysis
MenuController::StartDrag held its source SubmenuView* as a plain raw pointer across a call to the drag loop (RunDragDropLoop), which spins a nested, re-entrant message loop. During that loop the SubmenuView that source points at can be destroyed, so any subsequent use of source after the loop returns is a use-after-free of a dangling raw pointer.
The patch renames the incoming parameter to source_raw and wraps it in raw_ptr<SubmenuView, DisableDanglingPtrDetection> source(source_raw); the raw_ptr/BackupRefPtr quarantine keeps the freed allocation from being reused while the raw_ptr is alive, neutralizing the exploitable UAF, and the TODO comment explicitly documents that source may legitimately dangle, which is why dangling-pointer detection is disabled rather than treated as a crash. This does not add or tighten a logical validation check; it changes the pointer type to a memory-safety-hardened one. The explicit “detecting source is dangling is expected” note confirms the intent is to tolerate the dangle safely, not to prove the pointer stays valid.
SubmenuView* live across the re-entrant RunDragDropLoop, where the pointee can be freed; wrapping it in a raw_ptr brings BackupRefPtr quarantine protection to that window so a dangling source cannot be exploited as a UAF.Attack Path
- Open a menu and begin a drag
A user/attacker-driven interaction starts a drag from a menu, invoking
MenuController::StartDragwith aSubmenuView* source. - Enter the nested drag loop
StartDragcallsRunDragDropLoop, spinning a re-entrant message loop that processes events while the drag is in progress. - Destroy the source submenu
During the loop, event processing tears down the
SubmenuView, leaving the plainsourcepointer dangling. - Use the dangling pointer
After the loop returns,
StartDragdereferencessource, hitting the use-after-free (inferred: no concrete exploitation primitive is shown by the patch).
Impact Assessment
MenuController::StartDrag. The advisory rates this medium severity (use after free in Views) and does not report in-the-wild exploitation; the fix relies on raw_ptr/BackupRefPtr quarantine as mitigation, and escalation beyond the UAF is (inferred). The precondition is the ability to trigger a menu drag whose source view is destroyed while the drag loop runs.Files Changed
ui/views/controls/menu/menu_controller.cc
Audit Directions
- Raw pointers across nested run loopsAudit code holding plain
T*locals/params across re-entrant loops (RunDragDropLoop, nested message loops, modal dialogs) where the pointee can be destroyed. - Views lifetime during drag/modalReview Views drag-and-drop and modal paths for view pointers used after a blocking loop returns, preferring
raw_ptror weak references. - DisableDanglingPtrDetection usagesTreat each
DisableDanglingPtrDetectionas a flagged “expected dangle”; verify such pointers are only ever dereferenced while BackupRefPtr quarantine actually protects them.
Patch
From e2b78d75a6d18e68ce5c541d439e33962e493390 Mon Sep 17 00:00:00 2001
From: Takashi Sakamoto <tasak@google.com>
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 <keishi@chromium.org>
Commit-Queue: Takashi Sakamoto <tasak@google.com>
Owners-Override: Keishi Hattori <keishi@chromium.org>
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
Original Bug Report
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.ccchrome/browser/ui/views/bookmarks/bookmark_menu_delegate.ccui/views/controls/menu/menu_item_view.ccui/views/controls/menu/submenu_view.ccui/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.
- Setup: An attacker installs a malicious extension with
bookmarkspermissions. - 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.
- Nested Loop: The browser UI thread enters
MenuController::StartDrag. It recordshad_capture = source->host()->HasCapture()and callsRunShellDrag, entering a nested message loop. - Trigger Destruction: The malicious extension sends an asynchronous IPC (e.g.,
chrome.bookmarks.move()) to move the parent folder of the dragged item. - Teardown: The browser processes the IPC, triggering
BookmarkMenuDelegate::BookmarkNodeMoved. This deletes the parentMenuItemViewand its associatedSubmenuView(thesourcepointer). Allraw_ptrreferences are cleared, and the memory is freed. - Heap Spray: The attacker uses the extension to heap spray the browser process, reclaiming the freed
SubmenuViewmemory block and planting a fakehost_pointer. - Exploitation: The drag operation aborts/completes, and control returns to
StartDrag. The code checksshowing_ && had_capture(which remain true because the drag was active) and executessource->host()->SetCapture(nullptr). - Code Execution: The dereference uses the attacker’s fake
host_pointer (a spoofedWidget). The call toSetCapturereads a fakenative_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.