Medium chrome Memory Corruption 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactMemory corruption in Views
DescriptionMemory corruption in Views
ComponentViews
Sub-componentInternals>Views
Bug ClassMemory Corruption
Tracker553163820
Fix commitefb4e25d7536 (chromium/src) +132/-8
CISA KEVNot listed
CreditedGoogle
Disclosed2026-10-20

Changed Functions

FunctionChangeNotes
if
components/remote_cocoa/app_shim/bridged_content_view.mm
modified
if
components/remote_cocoa/app_shim/native_widget_ns_window_bridge.mm
modified
if
content/app_shim_remote_cocoa/web_contents_view_cocoa.mm
modified
if
ui/views/cocoa/drag_drop_client_mac.mm
modified

Files Changed

  • ui/base/cocoa/drag_permission.mm
  • content/app_shim_remote_cocoa/web_contents_view_cocoa.mm
  • ui/base/BUILD.gn
  • ui/views/cocoa/drag_drop_client_mac_unittest.mm
  • ui/views/cocoa/drag_drop_client_mac.mm
  • ui/base/cocoa/drag_permission.h
  • content/app/mac_init.mm
  • components/remote_cocoa/app_shim/native_widget_ns_window_bridge.mm
  • components/remote_cocoa/app_shim/bridged_content_view.mm
From efb4e25d7536583cbcebc60f99f08d8ada6304ac Mon Sep 17 00:00:00 2001
From: Avi Drissman <[email protected]>
Date: Thu, 24 Sep 2026 11:47:51 -0700
Subject: [PATCH] Guard against bad drags on the Mac

There are several scenarios in which an initiated drag can go bad.
Centralize the decision-making as to whether a drag can be initiated.

Bad drag #1:

An untrustworthy renderer could disrupt a drag in progress by starting a
second one. Starting with macOS 15, AppKit checks for multiple
concurrent drags and prohibits them, but there is no such check in
earlier versions.

Also set the key in preferences to ensure that AppKit prohibits
concurrent drags.

Bad drag #2:

An untrustworthy renderer could disrupt a menu (context or popup) in
progress by starting a drag. This is not accounted for by AppKit.

Fixed: 553163770
Fixed: 553163820
Fixed: 553160293
Change-Id: I66b15a2cf220548cda92c1d42230131a6a6a6964
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8452662
Commit-Queue: Bryan Oltman <[email protected]>
Reviewed-by: Bryan Oltman <[email protected]>
Commit-Queue: Avi Drissman <[email protected]>
Auto-Submit: Avi Drissman <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1704468}
---
diff --git a/components/remote_cocoa/app_shim/bridged_content_view.mm b/components/remote_cocoa/app_shim/bridged_content_view.mm
index d8f447aa3..58c8f6d 100644
--- a/components/remote_cocoa/app_shim/bridged_content_view.mm
+++ b/components/remote_cocoa/app_shim/bridged_content_view.mm
@@ -16,6 +16,7 @@
 #include "components/remote_cocoa/app_shim/native_widget_ns_window_host_helper.h"
 #include "components/remote_cocoa/common/native_widget_ns_window_host.mojom.h"
 #import "ui/base/cocoa/appkit_utils.h"
+#include "ui/base/cocoa/drag_permission.h"
 #include "ui/base/cocoa/find_pasteboard.h"
 #include "ui/base/cocoa/tracking_area.h"
 #include "ui/base/dragdrop/drag_drop_types.h"
@@ -1704,11 +1705,18 @@
 }
 
 - (void)draggingSession:(NSDraggingSession*)session
+       willBeginAtPoint:(NSPoint)screenPoint {
+  ui::SetDragSessionInProgress(true);
+}
+
+- (void)draggingSession:(NSDraggingSession*)session
            endedAtPoint:(NSPoint)screenPoint
               operation:(NSDragOperation)operation {
+  ui::SetDragSessionInProgress(false);
   remote_cocoa::DragDropClient* client = [self dragDropClient];
-  if (client)
+  if (client) {
     client->EndDrag();
+  }
 }
 
 // NSAccessibility formal protocol implementation:
diff --git a/components/remote_cocoa/app_shim/native_widget_ns_window_bridge.mm b/components/remote_cocoa/app_shim/native_widget_ns_window_bridge.mm
index 7581294..1c4d2363 100644
--- a/components/remote_cocoa/app_shim/native_widget_ns_window_bridge.mm
+++ b/components/remote_cocoa/app_shim/native_widget_ns_window_bridge.mm
@@ -48,6 +48,7 @@
 #include "ui/base/cocoa/animation_utils.h"
 #import "ui/base/cocoa/constrained_window/constrained_window_animation.h"
 #include "ui/base/cocoa/cursor_utils.h"
+#include "ui/base/cocoa/drag_permission.h"
 #include "ui/base/cocoa/remote_accessibility_api.h"
 #import "ui/base/cocoa/window_size_constants.h"
 #include "ui/base/emoji/emoji_panel_helper.h"
@@ -1406,6 +1407,10 @@
     DLOG(WARNING) << "BeginFileDrag failed: window or bridged_view is null";
     return;
   }
+  if (!ui::IsDragSessionInitiationAllowed()) {
+    DLOG(WARNING) << "BeginFileDrag failed: drag session not allowed";
+    return;
+  }
 
   NSURL* file_url = base::apple::FilePathToNSURL(file_drag_data->file_path);
   if (!file_url) {
diff --git a/content/app/mac_init.mm b/content/app/mac_init.mm
index dc4067c6..25452ebd 100644
--- a/content/app/mac_init.mm
+++ b/content/app/mac_init.mm
@@ -28,6 +28,19 @@
     // Chrome is unusable for a long period after returning from sleep.
     // https://crbug.com/41406192.
     @"NSAppSleepDisabled" : @YES,
+
+    // Prior to macOS 15, AppKit assumed that apps would not be so silly as to
+    // attempt to start a concurrent drag session while one was already in
+    // progress. Starting in macOS 15, AppKit guards against it, but it has a
+    // BoolAppConfig to allow it, presumably to allow for a case where this
+    // would be needed for compatibility. Given that starting a second drag
+    // while one is in progress could be used to mount an attack on the user,
+    // explicitly set the config. See -[NSCoreDragManager
+    // beginDraggingSessionWithItems:fromWindow:withClipRect:event:source:] for
+    // where the config is accessed, and see https://crbug.com/553160293,
+    // https://crbug.com/553163770, and https://crbug.com/553163820 for the
+    // rationale.
+    @"NSAllowMultipleDrags" : @NO,
   }];
 
   // Disable NSAutoFillHeuristicController on macOS 26.0 and 26.1. On those OS
diff --git a/content/app_shim_remote_cocoa/web_contents_view_cocoa.mm b/content/app_shim_remote_cocoa/web_contents_view_cocoa.mm
index 94da754..60bb2a2 100644
--- a/content/app_shim_remote_cocoa/web_contents_view_cocoa.mm
+++ b/content/app_shim_remote_cocoa/web_contents_view_cocoa.mm
@@ -20,6 +20,7 @@
 #include "ui/base/clipboard/clipboard_constants.h"
 #include "ui/base/clipboard/clipboard_util_mac.h"
 #include "ui/base/clipboard/custom_data_helper.h"
+#include "ui/base/cocoa/drag_permission.h"
 #include "ui/base/dragdrop/drag_drop_types.h"
 #include "ui/events/event_utils.h"
 #include "ui/events/platform_event.h"
@@ -241,8 +242,9 @@
                         image:(NSImage*)image
                        offset:(NSPoint)offset
                  isPrivileged:(BOOL)isPrivileged {
-  if (!_host)
+  if (!_host || !ui::IsDragSessionInitiationAllowed()) {
     return;
+  }
 
   NSPoint mouseLocation = [self.window mouseLocationOutsideOfEventStream];
   NSEvent* dragEvent = [NSEvent mouseEventWithType:NSEventTypeLeftMouseDragged
@@ -325,10 +327,16 @@
   return _dragOperation;
 }
 
+- (void)draggingSession:(NSDraggingSession*)session
+       willBeginAtPoint:(NSPoint)screenPoint {
+  ui::SetDragSessionInProgress(true);
+}
+
 // Called when a drag initiated in our view ends.
 - (void)draggingSession:(NSDraggingSession*)session
            endedAtPoint:(NSPoint)screenPoint
               operation:(NSDragOperation)operation {
+  ui::SetDragSessionInProgress(false);
   if (!_host) {
     return;
   }
diff --git a/ui/base/cocoa/drag_permission.h b/ui/base/cocoa/drag_permission.h
new file mode 100644
index 0000000..841e1d21
--- /dev/null
+++ b/ui/base/cocoa/drag_permission.h
@@ -0,0 +1,37 @@
+// Copyright 2026 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_BASE_COCOA_DRAG_PERMISSION_H_
+#define UI_BASE_COCOA_DRAG_PERMISSION_H_
+
+#include "base/component_export.h"
+
+// There are several times when it would be bad to allow a drag to be initiated.
+// These are often not accounted for by AppKit, which assumes a reasonable and
+// non-adversarial usage of its APIs. This file handles coordination of defense
+// against those attacks.
+//
+// This class is meant to prevent the initiation of a drag session while:
+//   - a drag session is already in progress
+//   - a menu tracking session is already in progress
+//
+// macOS 15+ does prevent the first, but it does not prevent the second. Given
+// that these are security concerns, take this into our own hands.
+//
+// See https://crbug.com/553160293, https://crbug.com/553163770, and
+// https://crbug.com/553163820 for the concerns with concurrent drag sessions.
+
+namespace ui {
+
+// Returns whether a drag session may be initiated.
+COMPONENT_EXPORT(UI_BASE)
+bool IsDragSessionInitiationAllowed();
+
+// Sets the state of whether a drag session is in progress.
+COMPONENT_EXPORT(UI_BASE)
+void SetDragSessionInProgress(bool in_progress);
+
+}  // namespace ui
+
+#endif  // UI_BASE_COCOA_DRAG_PERMISSION_H_
diff --git a/ui/base/cocoa/drag_permission.mm b/ui/base/cocoa/drag_permission.mm
new file mode 100644
index 0000000..7008f90
--- /dev/null
+++ b/ui/base/cocoa/drag_permission.mm
@@ -0,0 +1,33 @@
+// Copyright 2026 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/base/cocoa/drag_permission.h"
+
+#import <AppKit/AppKit.h>
+
+#include "base/check_op.h"
+
+namespace ui {
+
+namespace {
+
+// There is SPI to inquire as to the existence of a current drag session
+// ([_NSDragManager isDragging]). However, usage of SPI is avoided if it is easy
+// to do so, and it is easy in this case.
+bool gDragSessionIsInProgress;
+
+}  // namespace
+
+bool IsDragSessionInitiationAllowed() {
+  CHECK_EQ(NSRunLoop.currentRunLoop, NSRunLoop.mainRunLoop);
+  return !gDragSessionIsInProgress &&
+         NSRunLoop.currentRunLoop.currentMode != NSEventTrackingRunLoopMode;
+}
+
+void SetDragSessionInProgress(bool in_progress) {
+  CHECK_NE(gDragSessionIsInProgress, in_progress);
+  gDragSessionIsInProgress = in_progress;
+}
+
+}  // namespace ui
diff --git a/ui/views/cocoa/drag_drop_client_mac.mm b/ui/views/cocoa/drag_drop_client_mac.mm
index 2a583d1..22934a2 100644
--- a/ui/views/cocoa/drag_drop_client_mac.mm
+++ b/ui/views/cocoa/drag_drop_client_mac.mm
@@ -10,6 +10,7 @@
 #import "components/remote_cocoa/app_shim/bridged_content_view.h"
 #import "components/remote_cocoa/app_shim/native_widget_ns_window_bridge.h"
 #include "components/remote_cocoa/app_shim/window_move_loop.h"
+#include "ui/base/cocoa/drag_permission.h"
 #include "ui/base/dragdrop/drag_drop_types.h"
 #include "ui/base/dragdrop/mojom/drag_drop_types.mojom.h"
 #import "ui/base/dragdrop/os_exchange_data_provider_mac.h"
@@ -38,6 +39,10 @@
     return;
   }
 
+  if (!ui::IsDragSessionInitiationAllowed()) {
+    return;
+  }
+
   exchange_data_ = std::move(data);
   source_operation_ = operation;
   is_drag_source_ = true;
@@ -97,9 +102,17 @@
     }
   }
 
-  [bridge_->ns_view() beginDraggingSessionWithItems:drag_items
-                                              event:event
-                                             source:bridge_->ns_view()];
+  NSDraggingSession* session =
+      [bridge_->ns_view() beginDraggingSessionWithItems:drag_items
+                                                  event:event
+                                                 source:bridge_->ns_view()];
+  if (!session) {
+    // The return value from -beginDraggingSessionWithItems:event:source: is
+    // marked as nonnull, but in the edge case where there already is an ongoing
+    // dragging session, nil is returned. (The issue with the SDK is filed as
+    // FB24907244.) In that case, bail.
+    return;
+  }
 
   // Since Drag and drop is asynchronous on the Mac, spin a nested run loop for
   // consistency with other platforms.
@@ -107,12 +120,12 @@
   quit_closure_ = run_loop.QuitClosure();
   run_loop.Run();
 
-  // As of MacOS 26, dragging with a trackpad results in a leftover mouse-up
+  // As of macOS 26, dragging with a trackpad results in a leftover mouse-up
   // event remaining in the queue when the drag session is terminated. This
   // ensures any leftover message is pumped to avoid handling the "release"
   // action as a click.
   [NSApp nextEventMatchingMask:NSEventMaskLeftMouseUp
-                     untilDate:[NSDate distantPast]
+                     untilDate:NSDate.distantPast
                         inMode:NSEventTrackingRunLoopMode
                        dequeue:YES];
 }
diff --git a/ui/views/cocoa/drag_drop_client_mac_unittest.mm b/ui/views/cocoa/drag_drop_client_mac_unittest.mm
index 15de877..5f4f25d 100644
--- a/ui/views/cocoa/drag_drop_client_mac_unittest.mm
+++ b/ui/views/cocoa/drag_drop_client_mac_unittest.mm
@@ -40,7 +40,12 @@
                                                 source:(id<NSDraggingSource>)
                                                            source {
   ++g_begin_dragging_session_call_count;
-  return nil;
+
+  // Return a fake dragging session to pretend that the dragging initiation was
+  // successful. The only thing done to the return object is a nil check, so a
+  // raw NSObject will do, and if that ever changes, a blank object will explode
+  // immediately, alerting to the issue.
+  return (NSDraggingSession*)[[NSObject alloc] init];
 }
 @end
 
diff --git a/ui/base/BUILD.gn b/ui/base/BUILD.gn
index 39c46572..78a13bb 100644
--- a/ui/base/BUILD.gn
+++ b/ui/base/BUILD.gn
@@ -319,6 +319,8 @@
       "cocoa/default_command_dispatcher_delegate.mm",
       "cocoa/defaults_utils.h",
       "cocoa/defaults_utils.mm",
+      "cocoa/drag_permission.h",
+      "cocoa/drag_permission.mm",
       "cocoa/find_pasteboard.h",
       "cocoa/find_pasteboard.mm",
       "cocoa/menu_utils.h",
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/ui/views/cocoa/drag_drop_client_mac_unittest.mm b/ui/views/cocoa/drag_drop_client_mac_unittest.mm
index 15de877..5f4f25d 100644
--- a/ui/views/cocoa/drag_drop_client_mac_unittest.mm
+++ b/ui/views/cocoa/drag_drop_client_mac_unittest.mm
@@ -40,7 +40,12 @@
                                                 source:(id<NSDraggingSource>)
                                                            source {
   ++g_begin_dragging_session_call_count;
-  return nil;
+
+  // Return a fake dragging session to pretend that the dragging initiation was
+  // successful. The only thing done to the return object is a nil check, so a
+  // raw NSObject will do, and if that ever changes, a blank object will explode
+  // immediately, alerting to the issue.
+  return (NSDraggingSession*)[[NSObject alloc] init];
 }
 @end
Loading diff…

Original Bug Report

reported by [email protected]

Lack of in-flight drag state in BeginFileDrag allows potential drag hijacking on macOS

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: On macOS, initiating a file drag for a downloaded item via BeginFileDrag starts an asynchronous dragging session without recording global in-flight drag state. Because no nested loop is run, the main thread continues processing IPCs, allowing a compromised renderer to issue a StartDragging IPC and begin a second dragging session. This can potentially hijack the user’s active drag gesture and deliver attacker-controlled content or URLs to the drop target.

Affected files:

  • components/remote_cocoa/app_shim/native_widget_ns_window_bridge.mm
  • content/browser/web_contents/web_contents_view_mac.mm
  • chrome/browser/download/drag_download_item_mac.mm
  • content/app_shim_remote_cocoa/web_contents_view_cocoa.mm

Estimated timestamp from git blame: 2026-02-04

Summary

On macOS, NativeWidgetNSWindowBridge::BeginFileDrag starts an asynchronous NSDraggingSession without setting any process-global in-flight drag state and without running a nested run loop. Consequently, reentrancy guards in WebContentsViewMac::StartDragging fail to detect that a download drag session is currently active. A compromised renderer can issue a LocalFrameHost::StartDragging IPC during the user’s drag gesture to initiate a secondary drag session, potentially hijacking the active drag stream and replacing the intended drop item with attacker-controlled content, files, or javascript: bookmarklet URLs.

Root Cause Analysis

  1. Stateless and Asynchronous Drag Initiation: In components/remote_cocoa/app_shim/native_widget_ns_window_bridge.mm:1345-1398, NativeWidgetNSWindowBridge::BeginFileDrag starts a drag session:

    NSEvent* dragEvent = [NSEvent mouseEventWithType:NSEventTypeLeftMouseDragged ...];
    [bridged_view_ beginDraggingSessionWithItems:@[ file_item ]
                                           event:dragEvent
                                          source:bridged_view_];
    

    This call returns immediately without recording any global or class-level drag-in-progress state, and without spinning a nested event loop. Its production caller DragDownloadItem (chrome/browser/download/drag_download_item_mac.mm:18-65), triggered via DownloadBubbleRowView::OnMouseDragged (chrome/browser/ui/views/download/bubble/download_bubble_row_view.cc:562) or DownloadsDOMHandler (chrome/browser/ui/webui/downloads/downloads_dom_handler.cc:308), is fire-and-forget.

  2. Bypassed Reentrancy Guards: Because BeginFileDrag returns immediately, the browser main thread continues processing queued Mojo messages while the user physically holds down the left mouse button. When a compromised renderer sends LocalFrameHost::StartDragging, RenderWidgetHostImpl::StartDragging (content/browser/renderer_host/render_widget_host_impl.cc:3020-3153) verifies no gesture requirements and dispatches to WebContentsViewMac::StartDragging (content/browser/web_contents/web_contents_view_mac.mm:204-269). WebContentsViewMac::StartDragging checks two reentrancy conditions (:214-223):

    if (drag_source_start_rwh_) {
      return;
    }
    if (remote_cocoa::CocoaWindowMoveLoop::IsActive()) {
      web_contents_->SystemDragEnded(source_rwh);
      return;
    }
    
    • drag_source_start_rwh_ is only set for web-content drags initiated on that specific WebContents (so it is null here).
    • CocoaWindowMoveLoop::IsActive() is only true during window move operations.
  3. Drag Hijacking Mechanism: Passing these checks, WebContentsViewMac::StartDragging calls WebContentsNSViewBridge::StartDrag -> -[WebContentsViewCocoa startDragWithDropData:...] (content/app_shim_remote_cocoa/web_contents_view_cocoa.mm:236-319), which invokes [self beginDraggingSessionWithItems:draggingItems event:dragEvent source:self]. In AppKit, starting a new drag session while the mouse button is actively held replaces the active dragging items and pasteboard data for the in-progress gesture.

Potential Attack Scenario

(Note: These are potential steps based on static code analysis; automated execution tooling has not run dynamic validation.)

  1. An attacker gains code execution in a sandboxed renderer process (e.g., via a renderer vulnerability).
  2. The user downloads a file and initiates a drag gesture from the downloads bubble (DownloadBubbleRowView) toward a local folder (e.g., Desktop or Finder).
  3. DragDownloadItem calls BeginFileDrag, starting an asynchronous NSDraggingSession while the user continues holding the mouse button.
  4. During the gesture, the compromised renderer issues LocalFrameHost::StartDragging containing attacker-controlled DropData (such as file_contents, download_metadata, or a javascript: URL).
  5. WebContentsViewMac::StartDragging’s guards evaluate to false/null, allowing WebContentsViewCocoa to start a secondary NSDraggingSession.
  6. The secondary drag session attaches to the held-mouse gesture stream, substituting the original download item with the attacker’s payload.
  7. Upon releasing the mouse over the destination folder, WebDragSource::DragPromisedFileTo -> CreateFileForDrop writes the attacker’s promised file at the user’s chosen location, or drops a bookmarklet onto the bookmarks bar.

Suggested Fix

  1. Introduce a process-global or bridge-level drag-in-flight tracker (e.g., in NativeWidgetNSWindowBridge / remote_cocoa) whenever BeginFileDrag or any Cocoa dragging session is started.
  2. Ensure WebContentsViewMac::StartDragging and DragDropClientMac::StartDragAndDrop check this global in-flight drag tracker and reject secondary drag initiation while another drag session is active.

Evaluated with Chrome root at commit: c6718ebbf2c677c6f63691e28c7801c563f6b19c


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