CVE-2026-13816
Overview
Files Changed
ui/android/java/src/org/chromium/ui/base/SelectFileDialog.javaui/android/junit/src/org/chromium/ui/base/SelectFileDialogTest.java
Patch
From a65204340bb91618c2ccae256dfe2fae945271f3 Mon Sep 17 00:00:00 2001
From: Min Qin <qinmin@chromium.org>
Date: Sun, 17 May 2026 13:42:26 -0700
Subject: [PATCH] Reject scheme-less and invalid scheme URIs in SelectFileDialog
Bug: 511735715
Change-Id: I718ac01a0c92c057d6ce22457953db530b100e0e
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7853378
Reviewed-by: Wenyu Fu <wenyufu@chromium.org>
Commit-Queue: Min Qin <qinmin@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1631916}
---
diff --git a/ui/android/java/src/org/chromium/ui/base/SelectFileDialog.java b/ui/android/java/src/org/chromium/ui/base/SelectFileDialog.java
index a1eae375..46713bd3 100644
--- a/ui/android/java/src/org/chromium/ui/base/SelectFileDialog.java
+++ b/ui/android/java/src/org/chromium/ui/base/SelectFileDialog.java
@@ -1303,12 +1303,13 @@
return null;
}
mFilePaths[i] = mUris[i].getSchemeSpecificPart();
- } else {
- if (ContentResolver.SCHEME_CONTENT.equals(mUris[i].getScheme())
- && isContentUriUnderAppDir(mUris[i], mContext)) {
+ } else if (ContentResolver.SCHEME_CONTENT.equals(mUris[i].getScheme())) {
+ if (isContentUriUnderAppDir(mUris[i], mContext)) {
return null;
}
mFilePaths[i] = mUris[i].toString();
+ } else {
+ return null;
}
displayNames[i] =
diff --git a/ui/android/junit/src/org/chromium/ui/base/SelectFileDialogTest.java b/ui/android/junit/src/org/chromium/ui/base/SelectFileDialogTest.java
index 5673bae..3861d0a 100644
--- a/ui/android/junit/src/org/chromium/ui/base/SelectFileDialogTest.java
+++ b/ui/android/junit/src/org/chromium/ui/base/SelectFileDialogTest.java
@@ -960,6 +960,27 @@
assertEquals("///storage/emulated/0/DCIM/Camera/IMG_1.jpg", task.mFilePaths[1].toString());
}
+ @Test
+ public void testMultipleFileSelectorWithSchemelessUris() {
+ SelectFileDialog selectFileDialog = new SelectFileDialog(0);
+ Uri[] filePathArray =
+ new Uri[] {Uri.parse("/data/data/com.android.chrome/app_chrome/Default/Cookies")};
+ SelectFileDialog.GetDisplayNameTask task =
+ selectFileDialog
+ .new GetDisplayNameTask(ContextUtils.getApplicationContext(), true, filePathArray);
+ assertEquals(null, task.doInBackground());
+ }
+
+ @Test
+ public void testMultipleFileSelectorWithInvalidSchemeUris() {
+ SelectFileDialog selectFileDialog = new SelectFileDialog(0);
+ Uri[] filePathArray = new Uri[] {Uri.parse("http://example.com/test.jpg")};
+ SelectFileDialog.GetDisplayNameTask task =
+ selectFileDialog
+ .new GetDisplayNameTask(ContextUtils.getApplicationContext(), true, filePathArray);
+ assertEquals(null, task.doInBackground());
+ }
+
private void testFilePath(
String path, SelectFileDialog selectFileDialog, boolean expectedPass) {
testFilePath(path, selectFileDialog, expectedPass, expectedPass);
Original Bug Report
Potential local file read via scheme-less URI bypass in SelectFileDialog.java
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 Chrome Security team. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.
Overview: A malicious Android app acting as a file picker can return a scheme-less URI in ClipData to bypass Chrome’s path validation checks. This results in the browser process granting the renderer read access to arbitrary files, including Chrome’s private data directory. A malicious website can then read these sensitive files using the FileReader API.
Affected files:
ui/android/java/src/org/chromium/ui/base/SelectFileDialog.javabase/android/java/src/org/chromium/base/ContentUriUtils.javaui/shell_dialogs/select_file_dialog_android.ccchrome/browser/file_select_helper.cccontent/browser/web_contents/file_chooser_impl.cc
Estimated timestamp from git blame: 2024-09-26
Description
A vulnerability exists in Chrome for Android’s SelectFileDialog.java where path validation for multi-URI results (via ClipData) can be bypassed by omitting the scheme from the URI. This allows a malicious file picker application to grant a renderer process read access to Chrome’s private data directory, potentially exfiltrating highly sensitive information like cookies and saved passwords.
Technical Details
When a user interacts with an <input type="file"> element, Chrome launches an Intent (ACTION_GET_CONTENT) to allow the user to select a file. If the user selects a malicious file-picker app, that app can return a crafted Intent back to Chrome where intent.getData() == null and the ClipData contains one or more URIs without a scheme (e.g., Uri.parse("/data/data/com.android.chrome/app_chrome/Default/Cookies")).
In SelectFileDialog.java’s onIntentCompleted, the multiple-URI path is triggered because getData() is null. The code extracts the ClipData and instantiates a GetDisplayNameTask, passing the scheme-less URIs.
Inside GetDisplayNameTask.doInBackground(), the logic evaluates the URI’s scheme:
if (ContentResolver.SCHEME_FILE.equals(mUris[i].getScheme())) {
if (isPathUnderAppDir(mUris[i].getSchemeSpecificPart(), mContext)) {
return null;
}
mFilePaths[i] = mUris[i].getSchemeSpecificPart();
} else {
if (ContentResolver.SCHEME_CONTENT.equals(mUris[i].getScheme())
&& isContentUriUnderAppDir(mUris[i], mContext)) {
return null;
}
mFilePaths[i] = mUris[i].toString();
}
Because the scheme is null, both ContentResolver.SCHEME_FILE.equals(...) and ContentResolver.SCHEME_CONTENT.equals(...) evaluate to false. This completely bypasses the isPathUnderAppDir and isContentUriUnderAppDir security checks designed to protect Chrome’s internal directories.
The logic falls through and executes mFilePaths[i] = mUris[i].toString(), assigning the raw absolute path to mFilePaths. While the subsequent call to ContentUriUtils.getDisplayName() throws an exception when querying a raw path, the exception is caught, and an empty string is returned, allowing the task to complete successfully.
The raw path is passed to the native layer (ui/shell_dialogs/select_file_dialog_android.cc), wrapped in a base::FilePath, and treated as a native file. Ultimately, FileChooserImpl::FileSelected in content/browser/web_contents/file_chooser_impl.cc blindly calls ChildProcessSecurityPolicy::GrantReadFile for the renderer process. The attacker’s webpage can then use FileReader.readAsText() to read the contents of the file.
(Note: These are potential steps based on static analysis, as our tooling agent cannot execute code to verify the exploit.)
Potential Reproduction Steps
- Install a malicious Android app that registers an intent-filter for
android.intent.action.GET_CONTENTwith categoryOPENABLE. - Navigate to an attacker-controlled web page containing an
<input type="file">element and click it. - When the Android app chooser appears, select the malicious app.
- The malicious app constructs an Intent where
intent.setData(null)and theClipDatacontainsUri.parse("/data/data/com.android.chrome/app_chrome/Default/Cookies"). - The malicious app calls
setResult(RESULT_OK, intent)and finishes. - The attacker’s web page receives the
changeevent on the file input, yielding aFileobject. - The attacker’s JavaScript uses
FileReaderto read the file, exfiltrating Chrome’s cookie database.
Suggested Fix
In SelectFileDialog.java (GetDisplayNameTask.doInBackground()), implement a fail-safe default. If the URI scheme is neither SCHEME_FILE nor SCHEME_CONTENT, the URI should be strictly validated or rejected outright to prevent raw paths from falling through to mUris[i].toString().
Evaluated with Chrome root at commit: eca8648a4e1cdfdda68c495a6003059fed641955
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.