Medium chrome Logic Error 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactMissing authorization in CustomTabs
DescriptionMissing authorization in CustomTabs
ComponentCustomTabs
Bug ClassLogic Error
Tracker511804361
Fix commitf7e33e945ab5 (chromium/src) +134/-0
CISA KEVNot listed
CreditedGoogle
Disclosed2026-08-25

Files Changed

  • chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabsConnection.java
  • chrome/android/junit/src/org/chromium/chrome/browser/customtabs/CustomTabsConnectionUnitTest.java
From f7e33e945ab5509c38b18b0d6feb18d59600123f Mon Sep 17 00:00:00 2001
From: Mohamed Adel <[email protected]>
Date: Wed, 01 Jul 2026 14:47:26 -0700
Subject: [PATCH] [CCT] Check caller URI permission in receiveFile()

CustomTabsConnection.receiveFile() opens a client-supplied URI on behalf
of the bound client app to load a TWA splash image. Ensure the bound
client itself holds read access to that URI before opening it under the
browser's identity.

Adds Robolectric coverage for the granted and denied cases.

Bug: 511804361
Fixed: 511804361
Change-Id: Ic1fcaa0e3d4a40fa0897d479dff2d6b102b711f8
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8031565
Reviewed-by: Joel Tan-Aristy <[email protected]>
Auto-Submit: Moe Adel <[email protected]>
Commit-Queue: Joel Tan-Aristy <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1655765}
---

diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabsConnection.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabsConnection.java
index e82fd80..5688cf5b 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabsConnection.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabsConnection.java
@@ -2145,6 +2145,16 @@
 
     public boolean receiveFile(
             CustomTabsSessionToken sessionToken, Uri uri, int purpose, @Nullable Bundle extras) {
+        if (ContextUtils.getApplicationContext()
+                        .checkUriPermission(
+                                uri,
+                                Binder.getCallingPid(),
+                                Binder.getCallingUid(),
+                                Intent.FLAG_GRANT_READ_URI_PERMISSION)
+                != PackageManager.PERMISSION_GRANTED) {
+            logCall("receiveFile()", false);
+            return false;
+        }
         return CustomTabsClientFileProcessor.getInstance()
                 .processFile(new SessionHolder<>(sessionToken), uri, purpose, extras);
     }
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/CustomTabsConnectionUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/CustomTabsConnectionUnitTest.java
index 94d4582..b63618d2 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/CustomTabsConnectionUnitTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/customtabs/CustomTabsConnectionUnitTest.java
@@ -8,6 +8,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
@@ -28,13 +29,23 @@
 import static org.chromium.chrome.browser.customtabs.CustomTabsConnection.ON_ACTIVITY_LAYOUT_TOP_EXTRA;
 
 import android.app.PendingIntent;
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.ContextWrapper;
 import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ProviderInfo;
+import android.database.Cursor;
 import android.net.Network;
+import android.net.Uri;
 import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
 import android.os.Process;
 
 import androidx.browser.customtabs.CustomTabsCallback;
 import androidx.browser.customtabs.CustomTabsIntent;
+import androidx.browser.customtabs.CustomTabsService;
 import androidx.browser.customtabs.CustomTabsSessionToken;
 import androidx.browser.customtabs.EngagementSignalsCallback;
 import androidx.browser.customtabs.PostMessageServiceConnection;
@@ -43,14 +54,17 @@
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
+import org.robolectric.Robolectric;
 import org.robolectric.RuntimeEnvironment;
 import org.robolectric.annotation.Config;
 import org.robolectric.shadows.ShadowProcess;
 
+import org.chromium.base.ContextUtils;
 import org.chromium.base.test.BaseRobolectricTestRunner;
 import org.chromium.base.test.util.Batch;
 import org.chromium.base.test.util.Features.DisableFeatures;
@@ -59,11 +73,17 @@
 import org.chromium.chrome.browser.browserservices.SessionDataHolder;
 import org.chromium.chrome.browser.browserservices.SessionHandler;
 import org.chromium.chrome.browser.browserservices.intents.SessionHolder;
+import org.chromium.chrome.browser.browserservices.ui.splashscreen.trustedwebactivity.SplashImageHolder;
 import org.chromium.chrome.browser.customtabs.content.EngagementSignalsHandler;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.privacy.settings.PrivacyPreferencesManagerImpl;
 import org.chromium.chrome.browser.tab.Tab;
 
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
 /** Tests for some parts of {@link CustomTabsConnection}. */
 @RunWith(BaseRobolectricTestRunner.class)
 @Batch(Batch.UNIT_TESTS)
@@ -71,6 +91,7 @@
 public class CustomTabsConnectionUnitTest {
 
     @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+    @Rule public final TemporaryFolder mTemporaryFolder = new TemporaryFolder();
     @Mock private SessionHandler mSessionHandler;
 
     @Mock private CustomTabsCallback mCallback;
@@ -263,5 +284,108 @@
         assertNull(mConnection.extractTargetNetwork(intent, mSessionHolder));
     }
 
+    /** Content provider that records whether it was opened. */
+    public static class TestSplashImageContentProvider extends ContentProvider {
+        static boolean sOpened;
+        static File sImageFile;
+
+        @Override
+        public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
+            sOpened = true;
+            return ParcelFileDescriptor.open(sImageFile, ParcelFileDescriptor.MODE_READ_ONLY);
+        }
+
+        @Override
+        public boolean onCreate() {
+            return false;
+        }
+
+        @Override
+        public Cursor query(
+                Uri uri,
+                String[] projection,
+                String selection,
+                String[] selectionArgs,
+                String sortOrder) {
+            return null;
+        }
+
+        @Override
+        public String getType(Uri uri) {
+            return null;
+        }
+
+        @Override
+        public Uri insert(Uri uri, ContentValues values) {
+            return null;
+        }
+
+        @Override
+        public int delete(Uri uri, String selection, String[] selectionArgs) {
+            return 0;
+        }
+
+        @Override
+        public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+            return 0;
+        }
+    }
+
+    private Uri registerSplashImageProvider() throws IOException {
+        TestSplashImageContentProvider.sOpened = false;
+        TestSplashImageContentProvider.sImageFile = mTemporaryFolder.newFile("splash.png");
+        try (FileOutputStream stream =
+                new FileOutputStream(TestSplashImageContentProvider.sImageFile)) {
+            stream.write("non-empty".getBytes());
+        }
+        ProviderInfo info = new ProviderInfo();
+        info.authority = "org.chromium.test.splash";
+        Robolectric.buildContentProvider(TestSplashImageContentProvider.class).create(info);
+        return Uri.parse("content://org.chromium.test.splash/splash.png");
+    }
+
+    private void setCallerUriPermission(int result) {
+        Context context =
+                new ContextWrapper(ContextUtils.getApplicationContext()) {
+                    @Override
+                    public int checkUriPermission(Uri uri, int pid, int uid, int modeFlags) {
+                        return result;
+                    }
+                };
+        ContextUtils.initApplicationContextForTests(context);
+    }
+
+    @Config(sdk = {BaseRobolectricTestRunner.MIN_SDK, 35})
+    @Test
Loading diff…

Original Bug Report

The reporter's bug is still restricted on the tracker. Chrome de-restricts security bugs ~30–90 days after the fix ships; a later run will backfill it here.