Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInsufficient validation of untrusted input in File Input
DescriptionInsufficient validation of untrusted input in File Input
ComponentFile Input
Bug ClassLogic Error
Tracker511735715
Fix commita65204340bb9 (chromium/src) +25/-3
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-30

Files Changed

  • ui/android/java/src/org/chromium/ui/base/SelectFileDialog.java
  • ui/android/junit/src/org/chromium/ui/base/SelectFileDialogTest.java
From a65204340bb91618c2ccae256dfe2fae945271f3 Mon Sep 17 00:00:00 2001
From: Min Qin <[email protected]>
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 <[email protected]>
Commit-Queue: Min Qin <[email protected]>
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);
Loading diff…

Original Bug Report

reported by [email protected]

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.java
  • base/android/java/src/org/chromium/base/ContentUriUtils.java
  • ui/shell_dialogs/select_file_dialog_android.cc
  • chrome/browser/file_select_helper.cc
  • content/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

  1. Install a malicious Android app that registers an intent-filter for android.intent.action.GET_CONTENT with category OPENABLE.
  2. Navigate to an attacker-controlled web page containing an <input type="file"> element and click it.
  3. When the Android app chooser appears, select the malicious app.
  4. The malicious app constructs an Intent where intent.setData(null) and the ClipData contains Uri.parse("/data/data/com.android.chrome/app_chrome/Default/Cookies").
  5. The malicious app calls setResult(RESULT_OK, intent) and finishes.
  6. The attacker’s web page receives the change event on the file input, yielding a File object.
  7. The attacker’s JavaScript uses FileReader to 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.

View on issue tracker