Medium chrome Logic Error 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInsufficient validation of untrusted input in CustomTabs
DescriptionInsufficient validation of untrusted input in CustomTabs
ComponentCustomTabs
Bug ClassLogic Error
Tracker496012495
Fix commitf98f0f4e339c (chromium/src) +28/-9
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-30

Changed Functions

FunctionChangeNotes
for
chrome/android/java/src/org/chromium/chrome/browser/customtabs/content/WebAppLaunchHandler.java
modified

Files Changed

  • chrome/android/java/src/org/chromium/chrome/browser/customtabs/content/WebAppLaunchHandler.java
  • chrome/android/junit/src/org/chromium/chrome/browser/customtabs/content/WebAppLaunchHandlerTest.java
From f98f0f4e339caeadfee03ffacdb1f1473b75b38c Mon Sep 17 00:00:00 2001
From: Nate Chapin <[email protected]>
Date: Fri, 15 May 2026 16:59:27 -0700
Subject: [PATCH] Remove grantUriPermission() permission call in WebAppLaunchHandler.launchNewIntent()

The package should already have the permissions it needs.

Fixed: 496012495
Change-Id: I8a757f148d8823be348b2a530f8373e999129775
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7779798
Reviewed-by: Jinsuk Kim <[email protected]>
Commit-Queue: Nate Chapin <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1631646}
---

diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/content/WebAppLaunchHandler.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/content/WebAppLaunchHandler.java
index c896c8e..6e1d5f06 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/content/WebAppLaunchHandler.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/content/WebAppLaunchHandler.java
@@ -260,13 +260,6 @@
         /* This method can be called for file handling intent as well. In this case we need to send
         a file data extras as well. Also we need to grant file permissions */
         if (fileData != null && !fileData.uris.isEmpty()) {
-            for (Uri uri : fileData.uris) {
-                mActivity.grantUriPermission(
-                        packageName,
-                        uri,
-                        Intent.FLAG_GRANT_READ_URI_PERMISSION
-                                | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
-            }
             newIntent.putExtra(EXTRA_FILE_HANDLING_DATA, fileData.toBundle());
         }
 
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/content/WebAppLaunchHandlerTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/content/WebAppLaunchHandlerTest.java
index 5030e78..e9f8d83 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/content/WebAppLaunchHandlerTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/content/WebAppLaunchHandlerTest.java
@@ -13,6 +13,7 @@
 import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoInteractions;
@@ -21,6 +22,7 @@
 
 import android.app.Activity;
 import android.content.Intent;
+import android.content.pm.PackageManager;
 import android.net.Uri;
 import android.os.Looper;
 
@@ -372,12 +374,36 @@
         mExpectedFileList = new String[] {CONTENT_URI};
         doTestNavigateNewNewIntent(
                 LaunchHandlerClientMode.NAVIGATE_NEW, /* expectedStartActivityTimes= */ 1);
-        verify(mActivityMock, times(1))
+        verify(mActivityMock, never())
                 .grantUriPermission(
                         eq(TEST_PACKAGE_NAME), eq(mFileHandlingData.uris.get(0)), anyInt());
     }
 
     @Test
+    public void navigateNewStartNewTask_fileData_chromePrivateData() {
+        doTestNavigateNewInitialIntent(LaunchHandlerClientMode.NAVIGATE_NEW);
+
+        final Uri pwCsv =
+                Uri.parse(
+                        "content://com.android.chrome.FileProvider/passwords/"
+                                + "Chrome%20Passwords.csv");
+
+        when(mActivityMock.checkCallingUriPermission(eq(pwCsv), anyInt()))
+                .thenReturn(PackageManager.PERMISSION_DENIED);
+
+        mFileHandlingData = new FileHandlingData(Arrays.asList(Uri.parse(CONTENT_URI), pwCsv));
+        mExpectedFileList = new String[] {CONTENT_URI};
+        doTestNavigateNewNewIntent(
+                LaunchHandlerClientMode.NAVIGATE_NEW, /* expectedStartActivityTimes= */ 1);
+        verify(mActivityMock, never())
+                .grantUriPermission(
+                        eq(TEST_PACKAGE_NAME), eq(mFileHandlingData.uris.get(0)), anyInt());
+        verify(mActivityMock, never())
+                .grantUriPermission(
+                        eq(TEST_PACKAGE_NAME), eq(mFileHandlingData.uris.get(1)), anyInt());
+    }
+
+    @Test
     public void currentPageVerifierFailed() {
         when(mCurrentPageVerifierMock.getState())
                 .thenReturn(
@@ -406,7 +432,7 @@
         mExpectedFileList = new String[] {CONTENT_URI};
         doTestNavigateNewNewIntent(
                 LaunchHandlerClientMode.NAVIGATE_EXISTING, /* expectedStartActivityTimes= */ 1);
-        verify(mActivityMock, times(1))
+        verify(mActivityMock, never())
                 .grantUriPermission(
                         eq(TEST_PACKAGE_NAME), eq(mFileHandlingData.uris.get(0)), anyInt());
     }
Loading diff…

Original Bug Report

reported by [email protected]

Confused deputy in WebAppLaunchHandler grants attacker READ/WRITE access to Chrome's internal files

Project Fortify, an experimental security project, has identified the following potential security issue.

Overview: A potential confused deputy vulnerability exists where a malicious Android app can trick Chrome into granting it READ and WRITE access to Chrome’s internal FileProvider. By sending a crafted intent with the NAVIGATE_NEW mode and attacker-supplied URIs in EXTRA_FILE_HANDLING_DATA, the attacker bypasses Digital Asset Links verification. Chrome blindly grants permissions to these URIs, exposing sensitive private files like cached PDFs and network export logs.

Affected files:

  • chrome/android/java/src/org/chromium/chrome/browser/customtabs/content/WebAppLaunchHandler.java
  • chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabIntentDataProvider.java

Estimated timestamp from git blame: 2025-10-03

Overview

A potential confused deputy vulnerability has been identified in Chrome for Android’s handling of Trusted Web Activity (TWA) intents. A malicious application can obtain persistent READ and WRITE URI permissions for Chrome’s private FileProvider paths by exploiting WebAppLaunchHandler’s processing of the EXTRA_FILE_HANDLING_DATA bundle.

Vulnerability Details

When Chrome handles a TWA intent with the EXTRA_LAUNCH_HANDLER_CLIENT_MODE set to 3 (NAVIGATE_NEW), it invokes WebAppLaunchHandler.launchNewIntent(). This specific client mode bypasses the standard Digital Asset Links (DAL) verification check that normally restricts sensitive operations to verified origins.

Inside launchNewIntent(), Chrome extracts a list of URIs from the EXTRA_FILE_HANDLING_DATA bundle. This data is supplied by the calling intent and is not validated by Chrome before use. The handler iterates through these URIs and calls mActivity.grantUriPermission() for each one, granting the calling package (the attacker’s app) both READ and WRITE access.

// WebAppLaunchHandler.java
if (fileData != null && !fileData.uris.isEmpty()) {
    for (Uri uri : fileData.uris) {
        mActivity.grantUriPermission(
                packageName,
                uri,
                Intent.FLAG_GRANT_READ_URI_PERMISSION
                        | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
    }
    newIntent.putExtra(EXTRA_FILE_HANDLING_DATA, fileData.toBundle());
}

An attacker can provide URIs that point to Chrome’s own internal FileProvider (e.g., content://com.android.chrome.FileProvider/pdfs/...). Because Chrome is the owner of this FileProvider and its AndroidManifest.xml configures it with android:grantUriPermissions="true", the Android framework successfully processes Chrome’s request to grant these permissions to the attacker’s package.

Impact

By obtaining READ and WRITE access to Chrome’s FileProvider, a malicious app can exfiltrate sensitive user data stored in Chrome’s private cache directories. Paths exported in file_paths.xml include:

  • cache/pdfs/: Cached PDF files.
  • cache/net-export/: Network export logs.
  • cache/Offline Pages/archives/: Offline page archives.

The granted permissions persist until the device is rebooted.

Potential Attack Steps

(Note: These are suggested steps based on code analysis, as this agent cannot run live proofs-of-concept).

  1. A malicious application installed on the device binds to Chrome’s CustomTabsService and obtains a CustomTabsSessionToken via newSession(). This associates the session with the attacker’s package name.
  2. The app sends an initial ACTION_VIEW intent to Chrome with a valid HTTPS URL, the session token, and EXTRA_LAUNCH_AS_TRUSTED_WEB_ACTIVITY=true. This starts the TWA process.
  3. The app then sends a follow-up intent to the same activity (using FLAG_ACTIVITY_SINGLE_TOP) with the following extras:
    • EXTRA_LAUNCH_AS_TRUSTED_WEB_ACTIVITY = true
    • EXTRA_LAUNCH_HANDLER_CLIENT_MODE = 3 (NAVIGATE_NEW)
    • EXTRA_FILE_HANDLING_DATA = [Bundle containing target Chrome FileProvider URIs, e.g., content://com.android.chrome.FileProvider/pdfs/sensitive.pdf]
  4. BaseCustomTabActivity receives this new intent and routes it to WebAppLaunchHandler.handleNewIntent().
  5. Because clientMode == NAVIGATE_NEW, DAL verification is bypassed, and launchNewIntent() is called.
  6. Chrome iterates over the attacker-supplied URIs and calls grantUriPermission() for the attacker’s package name.
  7. The malicious app uses Android’s ContentResolver to read from or write to the granted URIs, successfully exfiltrating or modifying private data.

Suggested Fix

In WebAppLaunchHandler.launchNewIntent(), validate the URIs provided in EXTRA_FILE_HANDLING_DATA before calling grantUriPermission(). Specifically, Chrome should verify that the URIs do not belong to its own FileProvider authority (e.g., com.android.chrome.FileProvider). Additionally, consider whether grantUriPermission is even necessary if the URIs already belong to the calling package.

Evaluated with Chrome root at commit: 0eb4855bda702feaaa8b899336664f97e3df88b8


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. Please feel free to reach out to me if you have concerns or feedback.

View on issue tracker