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

Overview

Medium
Severity
7.5
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Views
DescriptionUse after free in Views
ComponentViews
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.
Tracker497736679
Fix commite2b78d75a6d1 (chromium/src) +6/-1
CWECWE-416 (Use-after-free)
CVSS vectorCVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:H
CISA KEVNot listed
CreditedGoogle
Reported2026-03-30 (per vendor advisory)
Fix landed2026-04-03 (public git)
Disclosed2026-05-05
Patch gap32 days — fix public in source before the advisory

Background

`MenuController`
the Views component that drives menu interaction, including initiating drag-and-drop from a menu via StartDrag.
`SubmenuView`
the view passed to StartDrag as source; 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_ptr still references it, with DisableDanglingPtrDetection suppressing 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.

Key insight
The core mistake was keeping a plain raw 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

  1. Open a menu and begin a drag A user/attacker-driven interaction starts a drag from a menu, invoking MenuController::StartDrag with a SubmenuView* source.
  2. Enter the nested drag loop StartDrag calls RunDragDropLoop, spinning a re-entrant message loop that processes events while the drag is in progress.
  3. Destroy the source submenu During the loop, event processing tears down the SubmenuView, leaving the plain source pointer dangling.
  4. Use the dangling pointer After the loop returns, StartDrag dereferences source, hitting the use-after-free (inferred: no concrete exploitation primitive is shown by the patch).

Impact Assessment

An attacker able to orchestrate a menu drag and a concurrent teardown of the source submenu can reach a use-after-free in the browser/UI process via 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 loops
    Audit 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/modal
    Review Views drag-and-drop and modal paths for view pointers used after a blocking loop returns, preferring raw_ptr or weak references.
  • DisableDanglingPtrDetection usages
    Treat each DisableDanglingPtrDetection as a flagged “expected dangle”; verify such pointers are only ever dereferenced while BackupRefPtr quarantine actually protects them.
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