Overview

Critical
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Views
DescriptionUse after free in Views
ComponentViews
Bug ClassUAF
Tracker516962715
Fix commit64eab8c16ab1 (chromium/src) +8/-1
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-30

Changed Functions

FunctionChangeNotes
if
ui/views/widget/drop_helper.cc
modified

Files Changed

  • ui/views/widget/drop_helper.cc
From 64eab8c16ab13a4c6fc905154af9631f4959726e Mon Sep 17 00:00:00 2001
From: Allen Bauer <kylixrd@chromium.org>
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 <kylixrd@chromium.org>
Reviewed-by: David Yeung <dayeung@chromium.org>
Commit-Queue: Allen Bauer <kylixrd@chromium.org>
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);
   }
 
Loading diff…

Original Bug Report

reported by vm...@google.com

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.cc
  • ui/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:

  1. When the old target’s exit handler removes the new target view (view) from the hierarchy, Widget::ViewHierarchyChanged triggers a cascading notification that eventually invokes DropHelper::ResetTargetViewIfEquals(view).
  2. Since target_view_ has not yet been assigned the new view, ResetTargetViewIfEquals only finds a match for deepest_view_ and nulls it out. This drops the active BRP reference count for the new target view to 0 before its destructor finishes.
  3. Because the BRP refcount is 0, PartitionAlloc bypasses quarantine and immediately returns the view’s memory slot to the freelist.
  4. When execution returns to OnDragOver, the assignment target_view_ = view at [2] stores the already-freed address into target_view_. Subsequent calls to NotifyDragEntered and ConvertPointToTarget walk the parent chain of target_view_ and invoke virtual methods (such as target_view_->OnDragEntered()) on deallocated/reclaimed memory.

Potential Attack Scenario / Trigger Steps

An attacker could theoretically trigger this vulnerability by performing the following sequence:

  1. Construct a UI layout inside a widget with adjacent drop-target views, ViewA (old target) and ViewB (new target).
  2. Register an override on ViewA::OnDragExited() that synchronously deletes ViewB (or its parent container) and reclaims the freed memory heap slot with controlled fake-vtable structures.
  3. Initiate an OS drag-and-drop operation, hovering over ViewA first so that DropHelper maps target_view_ and deepest_view_ to ViewA.
  4. Drag the cursor from ViewA onto ViewB to trigger DropHelper::OnDragOver.
  5. During NotifyDragExit(), ViewA::OnDragExited() runs, destroying ViewB and cleaning up its BRP references before reclaiming the memory.
  6. OnDragOver continues, assigning the dangling address of ViewB to target_view_, then dereferencing it via NotifyDragEntered() 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.

View on issue tracker