Medium chrome Logic Error 📄 Reporter bug report 🔧 Commit mapped

Overview

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

Files Changed

  • chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabsConnection.java
  • chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabsConnectionServiceImpl.java
  • chrome/android/junit/src/org/chromium/chrome/browser/customtabs/CustomTabsConnectionUnitTest.java
From a34728fb28d14bc2c8fb1bf3f23d34b8e0763261 Mon Sep 17 00:00:00 2001
From: Mohamed Adel <[email protected]>
Date: Wed, 01 Jul 2026 16:29:53 -0700
Subject: [PATCH] [Android] Require a registered session for receiveFile

Unlike the other CustomTabsService entry points, receiveFile did not
check that the caller had completed first run or that the supplied
session token had been registered via newSession() before opening the
supplied URI and decoding it as a TWA splash image in
CustomTabsClientFileProcessor.

Bring it in line with warmup/mayLaunchUrl/updateVisuals etc:
* CustomTabsConnectionServiceImpl.receiveFile now returns false
  before first run completes.
* CustomTabsConnection.receiveFile now requires the session to
  be known to ClientManager before any file is opened.

Add Robolectric coverage for receiveFile that exercises both the
rejected (unregistered session) and accepted (registered session) paths.

TAG=agy
CONV=aba571cb-6002-44d9-a887-67df13c38ac3

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

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 5688cf5b..bf36da7d 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
@@ -2155,8 +2155,10 @@
             logCall("receiveFile()", false);
             return false;
         }
+        SessionHolder<?> session = new SessionHolder<>(sessionToken);
+        if (!mClientManager.isSessionValid(session)) return false;
         return CustomTabsClientFileProcessor.getInstance()
-                .processFile(new SessionHolder<>(sessionToken), uri, purpose, extras);
+                .processFile(session, uri, purpose, extras);
     }
 
     public void setCustomTabIsInForeground(
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabsConnectionServiceImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabsConnectionServiceImpl.java
index ffceaff..a1ab4a9d 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabsConnectionServiceImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabsConnectionServiceImpl.java
@@ -131,6 +131,7 @@
     @Override
     protected boolean receiveFile(
             CustomTabsSessionToken sessionToken, Uri uri, int purpose, @Nullable Bundle extras) {
+        if (!isFirstRunDone()) return false;
         return mConnection.receiveFile(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 b63618d2..eb39946e 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
@@ -374,6 +374,7 @@
     @Config(sdk = {BaseRobolectricTestRunner.MIN_SDK, 35})
     @Test
     public void receiveFile_CallerWithUriPermission() throws IOException {
+        initSession();
         Uri uri = registerSplashImageProvider();
         setCallerUriPermission(PackageManager.PERMISSION_GRANTED);
 
@@ -387,5 +388,20 @@
         assertNotNull(SplashImageHolder.getInstance().takeImage(mSessionHolder));
     }
 
+    @Config(sdk = {BaseRobolectricTestRunner.MIN_SDK, 35})
+    @Test
+    public void receiveFile_SessionNotRegistered() throws IOException {
+        Uri uri = registerSplashImageProvider();
+        setCallerUriPermission(PackageManager.PERMISSION_GRANTED);
+
+        assertFalse(
+                mConnection.receiveFile(
+                        mSession,
+                        uri,
+                        CustomTabsService.FILE_PURPOSE_TRUSTED_WEB_ACTIVITY_SPLASH_IMAGE,
+                        Bundle.EMPTY));
+        assertFalse(TestSplashImageContentProvider.sOpened);
+        assertNull(SplashImageHolder.getInstance().takeImage(mSessionHolder));
+    }
     // TODO(https://crrev.com/c/4118209) Add more tests for Feature enabling/disabling.
 }
Loading diff…

Original Bug Report

reported by [email protected]

Potential Sandbox Bypass / Rule of 2 Violation via CustomTabsService.receiveFile

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

Overview: The exported CustomTabsConnectionService allows any installed Android app to trigger image decoding of an attacker-controlled URI directly in the browser process. This bypasses the sandboxed DecoderService and violates the Rule of 2 by parsing untrusted input using memory-unsafe native code (Skia) in a highly privileged process.

Affected files:

  • chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabsConnection.java
  • chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabsClientFileProcessor.java
  • chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabsConnectionServiceImpl.java
  • chrome/android/java/src/org/chromium/chrome/browser/base/SplitCompatCustomTabsService.java
  • base/android/java/src/org/chromium/base/FileUtils.java

Estimated timestamp from git blame: 2025-09-08

Description

Chrome for Android exposes the CustomTabsConnectionService, which is exported in the manifest without requiring any permissions to bind. This service exposes the receiveFile AIDL method. Any installed application can connect to this service and invoke receiveFile with an arbitrary URI.

When receiveFile is called with the purpose FILE_PURPOSE_TRUSTED_WEB_ACTIVITY_SPLASH_IMAGE, the following call chain occurs entirely within the highly privileged browser process:

  1. SplitCompatCustomTabsService.receiveFile delegates to CustomTabsConnectionServiceImpl.receiveFile.
  2. CustomTabsConnection.receiveFile routes the request to CustomTabsClientFileProcessor.processFile without validating the caller’s UID or checking if the caller is a verified Trusted Web Activity (TWA).
  3. CustomTabsClientFileProcessor.receiveTwaSplashImage is invoked, which calls FileUtils.queryBitmapFromContentProvider(context, uri).
  4. FileUtils.queryBitmapFromContentProvider opens a ParcelFileDescriptor from the attacker-supplied URI and directly calls the Android framework API BitmapFactory.decodeFileDescriptor.

Security Impact

BitmapFactory is a Java facade that relies on the native Android Skia library (written in C++) to parse and decode image formats. Because CustomTabsConnectionService runs in the main browser process, this architecture forces the browser process to parse untrusted, attacker-controlled data using a memory-unsafe language.

This violates Chromium’s “Rule of 2” and bypasses Chrome’s existing sandboxing mechanisms for image decoding (such as DecoderService, which uses seccomp-bpf to safely process untrusted images).

If an attacker provides a malformed image designed to exploit a memory corruption vulnerability (N-day or 0-day) in the Android Skia library, they could achieve arbitrary code execution within the browser process. This would lead to a full sandbox escape, granting the malicious app access to cookies, saved passwords, and other sensitive user data.

Potential Reproduction Steps

Note: These are suggested steps; we do not currently have a weaponized proof-of-concept.

  1. Create a malicious Android application with a ContentProvider that serves a malformed image (e.g., a crafted WebP or PNG known to trigger a Skia vulnerability).
  2. Have the malicious app bind to Chrome’s CustomTabsConnectionService using the intent action android.support.customtabs.action.CustomTabsService.
  3. Invoke the receiveFile method on the ICustomTabsService binder, providing a dummy session token, the URI to the malicious ContentProvider, and setting purpose to 1 (FILE_PURPOSE_TRUSTED_WEB_ACTIVITY_SPLASH_IMAGE).
  4. Chrome’s browser process will open the URI and pass the file descriptor to BitmapFactory.decodeFileDescriptor, triggering the vulnerability in the browser process heap.

Suggested Fix

  1. Sandbox the Decoding: Do not use BitmapFactory.decodeFileDescriptor in the browser process. Instead, route the image decoding through a sandboxed utility process, such as Chrome’s DecoderService, which is specifically designed to safely parse untrusted image data.
  2. Caller Validation: Implement proper validation in CustomTabsConnection.receiveFile to ensure that the calling UID (Binder.getCallingUid()) matches the UID associated with the session, and verify that the caller is a legitimate TWA before processing the file.

Evaluated with Chrome root at commit: a9cbf6e8b275fe4147435aa905f3b7f5a656f5f0


Results from 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