CVE-2026-7989
Overview
Background
- `StartDragging`
- the browser-process entry point (per the commit subject, in
WebContentsViewAuraandWebContentsViewMac) that begins a drag-and-drop operation requested by the renderer; the enclosing function name is not shown in the@@hunk headers and is taken from the commit subject. - Reentrancy via nested run loops
- on both platforms
StartDraggingspins a nested message loop (the mac comment notes “allowing nested tasks … also allowsClose()”), so renderer-driven events can re-enter the same code before the outer call finishes. - `drag_security_info_` / `drag_source_start_rwh_`
- per-view state tracking an in-flight drag:
drag_security_info_.did_initiate()records that this view started a drag, anddrag_source_start_rwh_holds the sourceRenderWidgetHostImplfor the active mac drag.
Root Cause Analysis
On both aura and mac, StartDragging could be entered again while a drag it had already begun was still running inside its nested loop, because neither path checked whether a drag was already in progress before mutating drag state. The aura path proceeds to set up drag state and (if there is no DragDropClient) call web_contents_->SystemDragEnded(source_rwh), while the mac path reads source_rfh.GetLastCommittedOrigin() and re-enters logic that can Close()/deallocate this; a reentrant call therefore manipulates or tears down drag state that an outer frame is still relying on.
The fix adds an early return at the top of each function: aura bails when drag_security_info_.did_initiate() is already set, and mac bails when drag_source_start_rwh_ is already populated. The mac path additionally calls drag_source_start_rwh_.reset() after the drag completes so the guard is cleared for legitimate subsequent drags. This tightens the entry condition, enforcing the invariant that only one drag may be initiated per view at a time.
StartDragging to be re-entered while a drag it started was still active, letting a renderer manipulate drag state mid-flight; the fix rejects reentrant invocations with an early return keyed on existing in-flight drag state.Attack Path
- Begin a legitimate drag
A malicious renderer requests a drag, entering browser-side
StartDragging, which records in-flight state and spins a nested run loop. - Re-enter during the nested loop
While that loop is pumping, the renderer drives another drag-start (or related event) so
StartDraggingis invoked a second time before the first returns. - Corrupt or exploit drag state
The reentrant call manipulates the same drag state (per the added comment, “an attempt to exploit drag state”); on mac this path can even reach
Close()and deallocatethiswhile the outer call still references it. - Memory-safety / state-confusion consequence (inferred)
The patch does not demonstrate a concrete use-after-free or read/write primitive, so any such outcome beyond corrupted drag state is
(inferred).
Impact Assessment
(inferred) rather than shown by the diff.Changed Functions
| Function | Change | Notes |
|---|---|---|
ifcontent/browser/web_contents/web_contents_view_mac.mm |
modified |
Files Changed
content/browser/web_contents/web_contents_view_aura.cccontent/browser/web_contents/web_contents_view_mac.mm
Audit Directions
- Nested-loop reentrancyreview browser-process handlers that spin nested run loops (drag/drop, modal dialogs, context menus) for missing in-progress guards that let a renderer re-enter and mutate state.
- Self-deallocation during nested tasksflag paths whose comments warn that nested tasks may
Close()/deletethis, and verify every post-nested-loop access re-checks liveness. - Platform-parallel view codewhen one
WebContentsView*platform backend gains a guard, audit the other backends (aura, mac, and others) for the same unguarded entry point.
Patch
From d96c0d8181b6829ef9d9a258080ac87d9b72a72b Mon Sep 17 00:00:00 2001 From: Joel Hockey <[email protected]> Date: Fri, 24 Apr 2026 13:27:16 -0700 Subject: [PATCH] Disallow reentrant StartDragging() for aura and mac Bug: 498765082 Change-Id: I6c3cfd64925deb4c8e40f22574226fcc340e9738 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7792498 Auto-Submit: Joel Hockey <[email protected]> Reviewed-by: Daniel Cheng <[email protected]> Commit-Queue: Daniel Cheng <[email protected]> Reviewed-by: Rakina Zata Amni <[email protected]> Cr-Commit-Position: refs/heads/main@{#1620425} --- diff --git a/content/browser/web_contents/web_contents_view_aura.cc b/content/browser/web_contents/web_contents_view_aura.cc index aca68d7..a71c6b9b 100644 --- a/content/browser/web_contents/web_contents_view_aura.cc +++ b/content/browser/web_contents/web_contents_view_aura.cc @@ -1205,6 +1205,10 @@ aura::Window* root_window = GetNativeView()->GetRootWindow(); RenderWidgetHostImpl* const source_rwh = static_cast<RenderWidgetHostImpl*>(source_rfh.GetRenderWidgetHost()); + // Disallow reentrant drag which could be an attempt to exploit drag state. + if (drag_security_info_.did_initiate()) { + return; + } if (!aura::client::GetDragDropClient(root_window)) { web_contents_->SystemDragEnded(source_rwh); return; diff --git a/content/browser/web_contents/web_contents_view_mac.mm b/content/browser/web_contents/web_contents_view_mac.mm index ff26bbb..2f4f6d6 100644 --- a/content/browser/web_contents/web_contents_view_mac.mm +++ b/content/browser/web_contents/web_contents_view_mac.mm @@ -210,6 +210,10 @@ const blink::mojom::DragEventSourceInfo& event_info) { RenderWidgetHostImpl* source_rwh = static_cast<RenderWidgetHostImpl*>(source_rfh.GetRenderWidgetHost()); + // Disallow reentrant drag which could be an attempt to exploit drag state. + if (drag_source_start_rwh_) { + return; + } url::Origin source_origin = source_rfh.GetLastCommittedOrigin(); // By allowing nested tasks, the code below also allows Close(), // which would deallocate |this|. The same problem can occur while @@ -692,6 +696,8 @@ transformed_screen_point.x(), transformed_screen_point.y(), static_cast<ui::mojom::DragOperation>(drag_operation), drag_source_start_rwh_.get()); + + drag_source_start_rwh_.reset(); } void WebContentsViewMac::DraggingEntered(DraggingInfoPtr dragging_info,
Original Bug Report
Potential Site Isolation bypass via StartDragging reentrancy in WebContentsViewAndroid
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 without the security team.
Overview: A lack of active-drag checks in WebContentsViewAndroid::StartDragging allows a compromised renderer to overwrite the browser’s drag security state. By sending a malicious StartDragging IPC during an active legitimate drag, an attacker can bypass Site Isolation to intercept cross-site drag data and gain unauthorized read access to dropped local files.
Affected files:
content/browser/web_contents/web_contents_view_android.cccontent/browser/renderer_host/render_frame_host_impl.cccontent/browser/renderer_host/render_widget_host_impl.cccontent/browser/web_contents/web_contents_view_drag_security_info.cc
Estimated timestamp from git blame: 2026-01-23
Summary
There is a potential vulnerability in how Android Chrome handles concurrent drag-and-drop operations. Specifically, WebContentsViewAndroid::StartDragging lacks reentrancy guards or checks for an already-active drag. This allows a compromised renderer (e.g., a cross-site iframe) to spam the StartDragging IPC while a legitimate drag from the main frame is ongoing. Doing so overwrites the browser’s internal security state, bypassing the IsValidDragTarget protection (introduced in crbug.com/59081) and allowing the attacker to intercept sensitive cross-site drag data and gain unauthorized file access.
Vulnerability Details
When a drag starts, WebContentsViewAndroid::StartDragging caches the initiating frame’s SiteInstanceGroup ID into drag_security_info_ and calls the Android OS via native_view->StartDragAndDrop(). This cached ID is later used by IsValidDragTarget to ensure that data dragged from one origin cannot be dropped into a cross-origin frame on the same page.
However, because there are no browser-side state checks preventing concurrent StartDragging IPCs, a compromised renderer can send its own StartDragging IPC immediately after a legitimate drag begins. When the browser processes this malicious IPC:
- It unconditionally overwrites
current_source_rwh_for_drag_with the attacker’sRenderWidgetHostImpl. - It calls
drag_security_info_.OnDragInitiated, which unconditionally overwrites the cachedsite_instance_group_id_with the attacker’s ID, completely poisoning the security state. - It attempts to call
native_view->StartDragAndDrop()a second time. Because the OS already has an active drag, this returnsfalse.
Crucially, the failure path for StartDragAndDrop calls OnSystemDragEnded, which notifies the renderer but does not reset drag_security_info_. The secure state reset only happens in OnDragEnded, which only fires when the OS successfully finishes a drag.
Consequently, the browser is left with an active legitimate drag, but its security tracker is locked to the attacker’s ID. When the user drops the item onto the attacker’s iframe, IsValidDragTarget compares the target’s ID against the poisoned cached ID. The check passes, and the browser dispatches the drop to the attacker.
Impact
- Site Isolation Bypass: A compromised renderer can exfiltrate sensitive data (text, URLs, images) dragged from other origins within the same WebContents.
- Unauthorized File Access / Partial Sandbox Escape: If the user drags a local file into the browser,
RenderWidgetHostImpl::DragTargetDropcallsGrantFileAccessFromDropData. Because the drop was erroneously validated, the browser explicitly grants the attacker’s renderer process read access to the local file viaChildProcessSecurityPolicyImpl::GrantReadFile. - Denial of Service: Overwriting
current_source_rwh_for_drag_prevents the victim frame from receiving theDragSourceEndedAtevent, leaving the frame permanently suppressing input.
Suggested Exploitation Steps
(Note: These are potential steps based on code analysis; our tooling agent cannot execute working exploits.)
- Navigate to
https://victim.com, which embeds a cross-site iframe fromhttps://attacker.com. - Assume the
attacker.comrenderer process is compromised and can send arbitrary Mojo IPCs. - The user starts a physical drag operation on a sensitive element in the
victim.commain frame. - The browser process begins the drag and caches the SiteInstanceGroup ID for
victim.com. - The compromised
attacker.comrenderer immediately sends ablink::mojom::LocalFrameHost::StartDraggingIPC to the browser. - The browser unconditionally processes the IPC, overwriting the cached SiteInstanceGroup ID with
attacker.com’s ID. The subsequent OS drag call fails, but the poisoned state persists. - The user drops the item onto the
attacker.comiframe. - The browser’s
IsValidDragTargetcheck passes because the state now matches the attacker’s ID. - The
attacker.comrenderer receives the victim’s drag data and any associated file access grants.
Suggested Fix
- Add Active Drag Checks: In
WebContentsViewAndroid::StartDragging(and potentially other platform implementations), check if a drag is already active before processing a newStartDraggingrequest. If an OS drag is in progress, reject the incoming IPC or ignore it. - Robust Cleanup: Ensure that if
native_view->StartDragAndDrop()fails, the resulting cleanup (e.g.,OnSystemDragEnded) properly resetsdrag_security_info_by callingdrag_security_info_.OnDragEnded()to clear the poisoned state.
Evaluated with Chrome root at commit: ff3d2b74fa39431785bd60e51463b08fcc71ee33
Results 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.