CVE-2026-13784
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifui/views/widget/drop_helper.cc |
modified |
Files Changed
ui/views/widget/drop_helper.cc
Patch
From 64eab8c16ab13a4c6fc905154af9631f4959726e Mon Sep 17 00:00:00 2001 From: Allen Bauer <[email protected]> Date: Thu, 28 May 2026 10:25:37 -0700 Subject: [PATCH] Track view in DropHelper::OnDragOver to guard against potential UaF. Change-Id: I8b98d1477673c6aaf14d31015a12ccc42e2bfb6f Bug: 516962715 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7881707 Auto-Submit: Allen Bauer <[email protected]> Reviewed-by: David Yeung <[email protected]> Commit-Queue: Allen Bauer <[email protected]> Cr-Commit-Position: refs/heads/main@{#1637795} --- diff --git a/ui/views/widget/drop_helper.cc b/ui/views/widget/drop_helper.cc index c03265c..898dbcfe 100644 --- a/ui/views/widget/drop_helper.cc +++ b/ui/views/widget/drop_helper.cc @@ -18,6 +18,7 @@ #include "ui/base/dragdrop/os_exchange_data.h" #include "ui/compositor/layer_tree_owner.h" #include "ui/views/view.h" +#include "ui/views/view_tracker.h" #include "ui/views/widget/widget.h" namespace views { @@ -63,9 +64,15 @@ CalculateTargetViewImpl(root_view_location, data, true, &deepest_view_); if (view != target_view_) { + // Keep track of the target view to guard against potential UaF. + ViewTracker target_tracker(view); // Target changed. Notify old drag exited, then new drag entered. NotifyDragExit(); - target_view_ = view; + if (!target_tracker.view()) { + target_view_ = nullptr; + return ui::DragDropTypes::DRAG_NONE; + } + target_view_ = target_tracker.view(); NotifyDragEntered(data, root_view_location, drag_operation); }
Original Bug Report
Potential Use-After-Free in DropHelper::OnDragOver due to lack of ViewTracker protection
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 browser process within DropHelper::OnDragOver when a drag-and-drop operation transitions between target views. If the exiting view’s drag-exit handler synchronously destroys or removes the newly targeted view from the hierarchy, a dangling pointer is stored in target_view_ and subsequently dereferenced. This can lead to virtual function calls on freed memory within the unsandboxed browser process.
Affected files:
ui/views/widget/drop_helper.ccui/views/widget/drop_helper.h
Estimated timestamp from git blame: 2009-08-11
Root Cause Analysis
In DropHelper::OnDragOver (ui/views/widget/drop_helper.cc), when a drag-and-drop operation moves over a new target view, the helper calculates the new target view as a raw View* local variable, view. It then synchronously dispatches OnDragExited() to the previous target view via NotifyDragExit() before assigning view to target_view_:
// ui/views/widget/drop_helper.cc
int DropHelper::OnDragOver(const OSExchangeData& data,
const gfx::Point& root_view_location,
int drag_operation) {
const View* old_deepest_view = deepest_view_;
View* view =
CalculateTargetViewImpl(root_view_location, data, true, &deepest_view_);
if (view != target_view_) {
// Target changed. Notify old drag exited, then new drag entered.
NotifyDragExit(); // [1] Synchronously runs exiting target's OnDragExited() handler
target_view_ = view; // [2] Stores potentially-dangling pointer
NotifyDragEntered(data, root_view_location, ...); // [3] Invokes ConvertPointToTarget and virtual OnDragEntered()
}
...
return NotifyDragOver(data, root_view_location, ...);
}
If the old target’s exit handler at [1] synchronously deletes the new target view (or triggers its destruction by mutating the view hierarchy), the local pointer view becomes dangling.
MiraclePtr / BackupRefPtr (BRP) Bypass Details
While DropHelper utilizes raw_ptr for deepest_view_ and target_view_, this pattern bypasses BRP quarantine protections:
- When the old target’s exit handler removes the new target view (
view) from the hierarchy,Widget::ViewHierarchyChangedtriggers a cascading notification that eventually invokesDropHelper::ResetTargetViewIfEquals(view). - Since
target_view_has not yet been assigned the newview,ResetTargetViewIfEqualsonly finds a match fordeepest_view_and nulls it out. This drops the active BRP reference count for the new target view to0before its destructor finishes. - Because the BRP refcount is
0, PartitionAlloc bypasses quarantine and immediately returns the view’s memory slot to the freelist. - When execution returns to
OnDragOver, the assignmenttarget_view_ = viewat[2]stores the already-freed address intotarget_view_. Subsequent calls toNotifyDragEnteredandConvertPointToTargetwalk the parent chain oftarget_view_and invoke virtual methods (such astarget_view_->OnDragEntered()) on deallocated/reclaimed memory.
Potential Attack Scenario / Trigger Steps
An attacker could theoretically trigger this vulnerability by performing the following sequence:
- Construct a UI layout inside a widget with adjacent drop-target views,
ViewA(old target) andViewB(new target). - Register an override on
ViewA::OnDragExited()that synchronously deletesViewB(or its parent container) and reclaims the freed memory heap slot with controlled fake-vtable structures. - Initiate an OS drag-and-drop operation, hovering over
ViewAfirst so thatDropHelpermapstarget_view_anddeepest_view_toViewA. - Drag the cursor from
ViewAontoViewBto triggerDropHelper::OnDragOver. - During
NotifyDragExit(),ViewA::OnDragExited()runs, destroyingViewBand cleaning up its BRP references before reclaiming the memory. OnDragOvercontinues, assigning the dangling address ofViewBtotarget_view_, then dereferencing it viaNotifyDragEntered()and virtual method dispatches.
Note: These steps are based on static analysis of the control flow and code structure; our tooling does not currently have the runtime capability to execute code or build functional proof-of-concept exploits.
Suggested Fix
To prevent this, use a views::ViewTracker to track the lifetime of the new target view across the synchronous exit notification. If the new target view is destroyed during the exit event, abort the rest of the transition flow:
if (view != target_view_) {
views::ViewTracker target_tracker(view);
// Target changed. Notify old drag exited, then new drag entered.
NotifyDragExit();
if (!target_tracker.view()) {
target_view_ = nullptr;
return ui::DragDropTypes::DRAG_NONE;
}
target_view_ = target_tracker.view();
NotifyDragEntered(data, root_view_location, drag_operation);
}
Evaluated with Chrome root at commit: b1520ef4a76878853a31f0943b565e42060edec8
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.