CVE-2026-76039
Overview
Files Changed
base/android/java/src/org/chromium/base/ContentUriUtils.javaui/android/junit/src/org/chromium/ui/base/ClipboardTest.java
Patch
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]);
Original Bug Report
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.javaui/android/java/src/org/chromium/ui/base/ClipboardImpl.javachrome/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 theuserinfoprefix). 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 returnsnull, andisUriFromThisApp()incorrectly returnsfalse(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()returnsfalse, the safety check is skipped. - The execution reaches
item.coerceToText(mContext), where Android’sContentResolvernormalizes the authority by stripping the0@prefix. This successfully maps the URI back to Chrome’s own private, non-exportedChromeFileProvider. - 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 withcontent://com.android.chrome., bypassing this second-layer restriction.
Suggested Potential Steps to Reproduce
- Ensure a target file exists under a valid path in
file_paths.xml(e.g., a.csvfile under<app_cache>/passwords/). - 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. - In Chrome, navigate to a page that reads the clipboard (e.g., via
navigator.clipboard.readText()or apasteevent handler). - Observe that the clipboard validation is bypassed, and the private file contents are successfully read and exposed to the web origin.
Proposed Remediation
- In Java (
ContentUriUtils.java): Strip anyuserId@/userinfo@prefix from the authority string before querying thePackageManager. For example:int idx = authority.indexOf('@'); String normalizedAuthority = (idx == -1) ? authority : authority.substring(idx + 1); - 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 stringStartsWithprefix 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.