CVE-2026-113165
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifcomponents/remote_cocoa/app_shim/bridged_content_view.mm |
modified | |
ifcomponents/remote_cocoa/app_shim/native_widget_ns_window_bridge.mm |
modified | |
ifcontent/app_shim_remote_cocoa/web_contents_view_cocoa.mm |
modified | |
ifui/views/cocoa/drag_drop_client_mac.mm |
modified |
Files Changed
ui/base/cocoa/drag_permission.mmcontent/app_shim_remote_cocoa/web_contents_view_cocoa.mmui/base/BUILD.gnui/views/cocoa/drag_drop_client_mac_unittest.mmui/base/cocoa/drag_permission.hui/views/cocoa/drag_drop_client_mac.mmcomponents/remote_cocoa/app_shim/native_widget_ns_window_bridge.mmcontent/app/mac_init.mmcomponents/remote_cocoa/app_shim/bridged_content_view.mm
Patch
From efb4e25d7536583cbcebc60f99f08d8ada6304ac Mon Sep 17 00:00:00 2001
From: Avi Drissman <avi@chromium.org>
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 <bryanoltman@google.com>
Reviewed-by: Bryan Oltman <bryanoltman@google.com>
Commit-Queue: Avi Drissman <avi@chromium.org>
Auto-Submit: Avi Drissman <avi@chromium.org>
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",
Regression Test / PoC
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
Original Bug Report
Mac: Potential drag hijacking via nested context menu loop and ungated StartDragging
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, the native web context menu tracking loop processes nested tasks, allowing renderer Mojo IPC messages to be dispatched while the user holds down a mouse button. Because the macOS StartDragging implementation lacks input-state validation and native menu loop gating, a compromised renderer can initiate an OS drag session with attacker-controlled data during an ordinary context menu interaction. This could potentially allow a compromised renderer to hijack the user’s held-button gesture to drop arbitrary payloads into external applications.
Affected files:
content/browser/web_contents/web_contents_view_mac.mmui/base/cocoa/menu_utils.mmcontent/app_shim_remote_cocoa/web_contents_view_cocoa.mmcontent/app_shim_remote_cocoa/web_contents_ns_view_bridge.mmchrome/browser/ui/cocoa/renderer_context_menu/render_view_context_menu_mac_cocoa.mmcontent/browser/renderer_host/render_frame_host_impl.cc
Estimated timestamp from git blame: 2024-02-08
Potential Vulnerability: macOS Drag Session Hijacking via Nested Context Menu Run Loop
(Note: The reproduction steps and behavior described below are theoretical and based on static code analysis, as our tooling cannot currently execute live code.)
Description
On macOS, Chromium displays web context menus on mouse-down (third_party/blink/public/common/web_preferences/web_preferences.h:64 sets context_menu_on_mouse_up = BUILDFLAG(IS_WIN) which evaluates to false). The native context menu is displayed using AppKit’s [NSMenu popUpContextMenu:withEvent:forView:] inside ui::ShowContextMenu (ui/base/cocoa/menu_utils.mm:60-93).
To allow certain events to be processed while a menu is open, RenderViewContextMenuMacCocoa::Show (chrome/browser/ui/cocoa/renderer_context_menu/render_view_context_menu_mac_cocoa.mm:98-100) passes allow_nested_tasks = true, instantiating a base::CurrentThread::ScopedAllowApplicationTasksInNativeNestedLoop. Consequently, incoming Mojo IPC messages from the renderer process (such as LocalFrameHost::StartDragging) are dispatched on the browser UI thread while the user’s physical mouse button remains held down inside the modal menu loop.
Unlike other platforms:
- Aura (
content/browser/web_contents/web_contents_view_aura.cc:1288-1310) validates that a matching mouse button or touch point is actively pressed (env->IsMouseButtonDown()), uses trusted browser coordinates, and ensures the location is within visible contents. - Windows (
ui/views/widget/desktop_aura/desktop_drag_drop_client_win.cc:78) checksdesktop_host_->IsInNativeMoveResizeLoop()(which incorporatesg_is_in_native_menu_loopfromui/views/win/hwnd_message_handler.cc:307, 970) and rejects drag initiation while a menu loop is active.
On macOS, WebContentsViewMac::StartDragging (content/browser/web_contents/web_contents_view_mac.mm:204-269) only checks drag_source_start_rwh_, remote_cocoa::CocoaWindowMoveLoop::IsActive(), and enterprise DLP policy. It completely ignores event_info, performs no mouse button state validation, and does not check whether a context menu loop is active (even though web_contents_->IsShowingContextMenu() and ui::GetActiveCocoaMenuAnchorLocation() exist).
Subsequently, WebContentsViewCocoa startDragWithDropData: (content/app_shim_remote_cocoa/web_contents_view_cocoa.mm:236-319) creates a synthetic NSEventTypeLeftMouseDragged event with coordinates from [self.window mouseLocationOutsideOfEventStream] and initiates an OS drag session via [self beginDraggingSessionWithItems:draggingItems event:dragEvent source:self]. This converts the user’s ongoing held-button gesture into an OS-level drag session carrying attacker-controlled payload data.
Suggested Potential Trigger Steps
- The attacker operates with a compromised renderer process (standard sandbox boundary precondition).
- The user presses and holds down the right mouse button over the attacker page. Blink triggers context menu handling on mouse-down, and the browser displays the native context menu via
ui::ShowContextMenu, entering the modal AppKit menu tracking loop whileScopedAllowApplicationTasksInNativeNestedLoopis active. - The compromised renderer immediately sends
LocalFrameHost::StartDraggingwith attacker-controlledDragData(e.g. command text or promised downloads). - The nested message pump on the UI thread dispatches
LocalFrameHost::StartDragginginside the menu loop. WebContentsViewMac::StartDraggingpasses execution without verifying mouse button state or checking for an active menu loop, and calls intoWebContentsViewCocoato start anNSDraggingSessionwith a synthesized mouse-drag event.- If the user moves and releases the mouse button over an external window (such as a Terminal or desktop folder), the drag session completes the drop of the attacker’s payload outside the sandbox boundary.
Suggested Fix
- In
content/browser/web_contents/web_contents_view_mac.mm, updateWebContentsViewMac::StartDraggingto refuse drag operations when a context menu is active (e.g., checkweb_contents_->IsShowingContextMenu()orui::GetActiveCocoaMenuAnchorLocation().has_value()), similar to the check againstCocoaWindowMoveLoop::IsActive(). - Introduce input state validation on macOS to ensure that a corresponding physical mouse button or touch is actually held down before initiating an OS drag session, aligning behavior with
WebContentsViewAura::StartDragging.
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.