Remote drag file-promise principal confusion lets compromised content hand a parent/chrome-read file to a native GTK drop target
Categories
(Core :: DOM: Copy & Paste and Drag & Drop, defect)
Tracking
()
People
(Reporter: iifizzyx, Unassigned)
Details
(Keywords: ai-involved, reporter-external, sec-moderate, Whiteboard: [client-bounty-form])
Attachments
(4 files)
Overview
Under the compromised-content-process threat model, an ordinary web content process can send PBrowser::InvokeDragSession with internal drag-and-drop file-promise flavors that normal web JavaScript is not allowed to set.
The parent accepts those remote drag transferables and installs the normal file-promise provider. When the parent later creates the real drag session, nsBaseDragSession::InvokeDragSession overwrites each transferable's data principal with the parent chrome <browser> source node principal. The file-promise provider and GTK native drag sink then resolve the child-supplied source URL with parent/chrome authority.
I validated this on Linux/GTK Nightly default configuration. The full native-drop PoC starts from a normal web content process, stages a remote drag whose promised source is chrome://browser/content/browser.xhtml, drives a real native GTK drag, and uses a separate GTK receiver process to request text/uri-list. The receiver gets a Firefox-created /tmp/dnd_file-* URI and reads the materialized chrome URL bytes outside the content sandbox.
This is a native-drop-mediated sandbox escape / local privileged read primitive. It is not code execution, not arbitrary destination write, and not silent web-readable exfiltration.
Build / Environment
Validated build:
Firefox Nightly 153.0a1
Build ID: 20260526085427
Source stamp: 4addf98c4ca88d363eed3b9bf63cc7f467c9aa61
Platform: Linux x86_64 / Ubuntu 24.04
Toolkit: gtk
e10s: true
Fission: true
has_sandbox: true
Build type: opt
Nightly build: true
No content sandbox prefs were relaxed. The native receiver run uses xvfb-run and GDK_BACKEND=x11 only to automate a GTK/XDND drag in CI-like conditions.
Steps To Reproduce
-
Add the test-only
nsIDOMWindowUtilshelper described in the attachedpoc_helper_requirements.md, or port the same helper logic into an equivalent local harness.The helper does not create the vulnerability. It only simulates compromised-content raw IPC so the parent-side drag handling and production GTK native drag sink can be tested deterministically.
-
Copy the direct PoC attachments into
dom/base/test/:
poc_native_gtk_receiver.js -> dom/base/test/browser_raw_drag_filepromise_native_gtk_receiver_poc.js
poc_gtk_uri_list_receiver.py -> dom/base/test/poc_gtk_uri_list_receiver.py
- Register the browser test in
dom/base/test/browser.toml:
["browser_raw_drag_filepromise_native_gtk_receiver_poc.js"]
support-files = ["poc_gtk_uri_list_receiver.py"]
skip-if = [
"true", # Local PoC harness drives a real native GTK drag and must be run in isolation with --force.
]
- Build normally:
./mach build
- Run the isolated native GTK receiver validation:
timeout 120s xvfb-run -a env GDK_BACKEND=x11 \
./mach test --force dom/base/test/browser_raw_drag_filepromise_native_gtk_receiver_poc.js
The native receiver test is intentionally skipped by default in browser.toml because it drives the real GTK drag service and should be run in isolation with --force.
Actual Results
The native receiver validation passes:
PASS external GTK receiver window is mapped
PASS external choreographer pressed the source
PASS content sent raw chrome-url PBrowser::InvokeDragSession
PASS external choreographer moved the pointer across the drag threshold
native GTK receiver result: {
"files": [
{
"contains": true,
"exists": true,
"path": "/tmp/dnd_file-6/native-gtk-chrome-url-leak.xhtml",
"read": 326817,
"size": 326817,
"uri": "file:///tmp/dnd_file-6/native-gtk-chrome-url-leak.xhtml"
}
],
"status": "drop",
"target": "text/uri-list",
"uris": [
"file:///tmp/dnd_file-6/native-gtk-chrome-url-leak.xhtml"
]
}
PASS native GTK receiver observed a drop
PASS native GTK receiver received Firefox-created dnd_file URI
PASS native GTK receiver could read materialized chrome URL bytes
TEST_END: Test PASS. Subtests passed 7/7. Unexpected 0
Overall Summary: OK
This proves that a separate native process receives and reads a file whose bytes came from chrome://browser/content/browser.xhtml, fetched by Firefox with parent/chrome authority after a content-process remote drag staged the source URL.
The attached validation log also includes additional local sanity checks for the parent file-promise provider and targeted GTK text/uri-list path. Those checks are supporting evidence, but the direct attached PoC above is the smallest initial triage reproducer.
Expected Results
A content process must not be able to use remote drag IPC to make the parent or native drag service resolve internal file-promise flavors under the chrome <browser> principal.
Expected behavior would be one of:
- reject internal file-promise flavors (
application/x-moz-file-promise,application/x-moz-file-promise-url,application/x-moz-file-promise-dest-filename,application/x-moz-file-promise-dir) when they originate from remote content drag IPC; - preserve and enforce the original content principal on remote drag transferables all the way through file-promise resolution;
- reject privileged or non-web source schemes such as
file:,chrome:,resource:, andmoz-extension:for remote-content file-promise data; - avoid overwriting remote-content transferable principals with the chrome browser-element source node principal.
| Reporter | ||
Comment 1•5 months ago
|
||
| Reporter | ||
Comment 2•5 months ago
|
||
| Reporter | ||
Comment 3•5 months ago
|
||
Updated•5 months ago
|
Updated•5 months ago
|
Updated•5 months ago
|
Comment 4•4 months ago
|
||
The rating assigned by our bug rating tool (currently under development) is:
Rating: sec-moderate
Explanation: The vulnerability is plausible: code analysis confirms that in nsBaseDragSession::InvokeDragSession (lines 367-376 of nsBaseDragService.cpp), each transferable's data principal is overwritten with mSourceNode->NodePrincipal() — which for a remote drag is the parent chrome <browser> element's principal. When a compromised content process sends PBrowser::InvokeDragSession with application/x-moz-file-promise-url set to a chrome:// (or potentially file://) URI, the file-promise provider (nsContentAreaDragDropDataProvider::GetFlavorData) later reads the overwritten chrome principal from the transferable and uses it to call SaveURIToFile, causing the parent process to fetch the privileged URL and materialize it to /tmp/dnd_file-*/. A native GTK drop target can then read the file. This would normally qualify as a sandbox escape (sec-high), but requires: (1) a prior, separate content-process compromise, (2) unusual user interaction — the user must physically drag from Firefox to an external application, which is not a normal browsing action, and (3) is limited to Linux/GTK only. The combination of unusual required user interaction and limited platform scope reduces this from sec-high to sec-moderate per the triage guidelines.
Updated•4 months ago
|
Updated•4 months ago
|
Updated•9 days ago
|
Description
•