Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactIncorrect reference resolution in Core
DescriptionIncorrect reference resolution in Core
ComponentCore
Bug ClassLogic Error
Tracker525167753
Fix commitca63b3b97211 (chromium/src) +6/-2
CISA KEVNot listed
CreditedGoogle
Disclosed2026-08-18

Files Changed

  • base/android/java/src/org/chromium/base/ContentUriUtils.java
  • ui/android/junit/src/org/chromium/ui/base/ClipboardTest.java
From ca63b3b97211104dfcb1835e62e9a939d47a2b69 Mon Sep 17 00:00:00 2001
From: Charles Cai <charlesyc@google.com>
Date: Thu, 18 Jun 2026 11:01:21 -0700
Subject: [PATCH] Fix confused deputy bypass via userId prefix in ContentUriUtils

Malicious apps could bypass the Clipboard confused deputy defense
by prepending a `userId@` (e.g., `0@`) prefix to the authority of
Chrome's FileProvider. This prefix is stripped by Android's internal
ContentResolver logic when resolving providers, but ContentUriUtils
failed to strip it before evaluating package ownership.

This CL updates `ContentUriUtils.isUriFromThisApp` to strip the
userId prefix before performing package resolution, closing the bypass.

Bug: 525167753
Change-Id: Ie140616c19081d1157e36165a3c7ee7b7771e2cb
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7963797
Commit-Queue: Charles Cai <charlesyc@google.com>
Reviewed-by: Calder Kitagawa <ckitagawa@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1649180}
---

diff --git a/base/android/java/src/org/chromium/base/ContentUriUtils.java b/base/android/java/src/org/chromium/base/ContentUriUtils.java
index 7bb1bb2f..0e9a0cc 100644
--- a/base/android/java/src/org/chromium/base/ContentUriUtils.java
+++ b/base/android/java/src/org/chromium/base/ContentUriUtils.java
@@ -729,6 +729,10 @@
         if (uri == null || !ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) return false;
         String authority = uri.getAuthority();
         if (TextUtils.isEmpty(authority)) return false;
+
+        // Remove userId prefix in the authority.
+        authority = authority.substring(authority.lastIndexOf('@') + 1);
+
         PackageManager pm = context.getPackageManager();
         ProviderInfo info = pm.resolveContentProvider(authority, 0);
         return info != null && TextUtils.equals(info.packageName, context.getPackageName());
diff --git a/ui/android/junit/src/org/chromium/ui/base/ClipboardTest.java b/ui/android/junit/src/org/chromium/ui/base/ClipboardTest.java
index d94f7cb..ec4037e 100644
--- a/ui/android/junit/src/org/chromium/ui/base/ClipboardTest.java
+++ b/ui/android/junit/src/org/chromium/ui/base/ClipboardTest.java
@@ -180,7 +180,7 @@
     }
 
     private ClipData createTwoFilesClipData() {
-        String file1 = "content://tmp/test/file1.jpg";
+        String file1 = "content://0@tmp/test/file1.jpg";
         String file2 = "content://tmp/test/file2.txt";
         registerMockFileUri(file1);
         registerMockFileUri(file2);
@@ -193,7 +193,7 @@
     private void assertTwoFilesReturned(String[][] filenames) {
         assertEquals(2, filenames.length);
         assertEquals(2, filenames[0].length);
-        assertEquals("content://tmp/test/file1.jpg", filenames[0][0]);
+        assertEquals("content://0@tmp/test/file1.jpg", filenames[0][0]);
         assertEquals("", filenames[0][1]);
         assertEquals(2, filenames[1].length);
         assertEquals("content://tmp/test/file2.txt", filenames[1][0]);
Loading diff…

Original Bug Report

reported by vm...@google.com

Potential Bypass of Clipboard Confused Deputy Defenses via '0@' Userinfo Prefix

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: A potential vulnerability in ContentUriUtils.isUriFromThisApp() allows bypassing Chrome’s Clipboard Confused Deputy Defenses on Android. Using content URIs with a ‘userId@’ userinfo prefix (e.g., ‘0@’) causes the package validation check to incorrectly identify the URI as external. When ContentResolver subsequently accesses the URI, it normalizes the authority, allowing a malicious app to read Chrome’s private, non-exported FileProvider.

Affected files:

  • base/android/java/src/org/chromium/base/ContentUriUtils.java
  • ui/android/java/src/org/chromium/ui/base/ClipboardImpl.java
  • chrome/browser/file_system_access/chrome_file_system_access_permission_context.cc

Estimated timestamp from git blame: 2026-06-16

Potential Security Vulnerability in Chrome for Android Clipboard and FileProvider Access

Summary

A potential logic vulnerability has been identified in ContentUriUtils.isUriFromThisApp() where the use of a userId@ prefix (such as 0@) in the content URI authority bypasses validation checks. This can potentially allow a co-installed malicious app to trick Chrome into acting as a confused deputy and reading its own private files (e.g., exported passwords, cache files, and downloads) on behalf of untrusted web content.

Note: The following findings and steps are potential/suggested scenarios based on code review; our automated tooling does not currently have the capability to run or execute proof-of-concept exploit code on live environments.


1. Root Cause Analysis

In base/android/java/src/org/chromium/base/ContentUriUtils.java:

public static boolean isUriFromThisApp(@Nullable Uri uri, Context context) {
    if (uri == null || !ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) return false;
    String authority = uri.getAuthority();
    if (TextUtils.isEmpty(authority)) return false;
    PackageManager pm = context.getPackageManager();
    ProviderInfo info = pm.resolveContentProvider(authority, 0);
    return info != null && TextUtils.equals(info.packageName, context.getPackageName());
}
  • When given a URI like content://0@com.android.chrome.FileProvider/passwords/export.csv, uri.getAuthority() returns "0@com.android.chrome.FileProvider" (as the authority includes the userinfo prefix).
  • PackageManager.resolveContentProvider() expects the raw authority name declared in the manifest (e.g., "com.android.chrome.FileProvider") and does not strip user info. Therefore, the query returns null, and isUriFromThisApp() incorrectly returns false (meaning “this URI does not belong to Chrome”).

2. Clipboard Defenses Bypass

In ui/android/java/src/org/chromium/ui/base/ClipboardImpl.java (lines 109-117):

if (UiAndroidFeatureMap.isEnabled(UiAndroidFeatures.CLIPBOARD_CONFUSED_DEPUTY_DEFENSE_TEXT)) {
    Uri uri = item.getUri();
    if (item.getText() == null && ContentUriUtils.isUriFromThisApp(uri)) {  // Evaluates to false; guard skipped
        return null;
    }
}
return item.coerceToText(mContext).toString();
  • Because isUriFromThisApp() returns false, the safety check is skipped.
  • The execution reaches item.coerceToText(mContext), where Android’s ContentResolver normalizes the authority by stripping the 0@ prefix. This successfully maps the URI back to Chrome’s own private, non-exported ChromeFileProvider.
  • Because Chrome is reading its own provider under its own UID, Android permits access, and the raw file contents are returned as plain text to the renderer process (and thus to the untrusted web page via clipboard APIs or paste events).

Similar bypasses apply to the image-pasting and filename-reading defense checks in ClipboardImpl.getPng(), getFilenames(), and hasFilenames().

3. File System Access Blocklist Bypass

In chrome/browser/file_system_access/chrome_file_system_access_permission_context.cc (lines 2174-2183):

if (path_info.path.IsContentUri()) {
  std::string decoded_path = base::UnescapeBinaryURLComponent(
      path_info.path.value(), base::UnescapeRule::NORMAL);
  std::move(callback).Run(base::StartsWith(
      decoded_path,
      base::StrCat({"content://", base::android::apk_info::package_name(), "."}),
      base::CompareCase::INSENSITIVE_ASCII));
  return;
}
  • A URI containing 0@ (e.g., content://0@com.android.chrome.FileProvider/...) does not start with content://com.android.chrome., bypassing this second-layer restriction.

Suggested Potential Steps to Reproduce

  1. Ensure a target file exists under a valid path in file_paths.xml (e.g., a .csv file under <app_cache>/passwords/).
  2. From a co-installed malicious app, write a crafted URI containing the primary user prefix 0@ to the system clipboard: content://0@<chrome_package>.FileProvider/passwords/<target>.csv.
  3. In Chrome, navigate to a page that reads the clipboard (e.g., via navigator.clipboard.readText() or a paste event handler).
  4. Observe that the clipboard validation is bypassed, and the private file contents are successfully read and exposed to the web origin.

Proposed Remediation

  1. In Java (ContentUriUtils.java): Strip any userId@ / userinfo@ prefix from the authority string before querying the PackageManager. For example:
    int idx = authority.indexOf('@');
    String normalizedAuthority = (idx == -1) ? authority : authority.substring(idx + 1);
    
  2. In C++ (chrome_file_system_access_permission_context.cc): Parse the content URI using a robust parser to extract the host/authority correctly instead of performing a raw string StartsWith prefix match, or normalize the URI to strip any userinfo prefix before comparison.

Evaluated with Chrome root at commit: 75203b87cbf6681eb7c7dda8e1d0bf781538c76a


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.

View on issue tracker