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 c896c8e6550f4..eea03e39b0e26 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 @@ -16,6 +16,7 @@ import static org.chromium.build.NullUtil.assumeNonNull; import android.app.Activity; import android.content.ActivityNotFoundException; import android.content.Intent; +import android.content.pm.ProviderInfo; import android.net.Uri; import android.text.TextUtils; @@ -260,7 +261,30 @@ public class WebAppLaunchHandler extends WebContentsObserver { /* 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()) { + // SECURITY: |fileData.uris| comes directly from EXTRA_FILE_HANDLING_DATA on the + // incoming intent and is fully attacker-controlled. |packageName| is the + // Binder-verified caller package -- correct, but that means we're granting TO the + // attacker. A malicious app can supply content://.FileProvider/... URIs + // here and receive a READ+WRITE grant to Chrome's private cache (passwords/, + // net-export/, pdfs/, webapks/, Offline Pages/archives/) because ChromeFileProvider + // declares android:grantUriPermissions="true". + // + // Bandaid: refuse to grant any URI whose authority resolves to a provider in + // Chrome's own package. The legitimate file-handling use case never involves + // Chrome-owned URIs -- the TWA app shares URIs from its OWN provider for Chrome + // to forward back. (A proper fix should also gate this whole branch on Digital + // Asset Links verification having succeeded, and probably drop the WRITE flag.) + String selfPkg = mActivity.getPackageName(); for (Uri uri : fileData.uris) { + String auth = uri.getAuthority(); + if (auth != null) { + ProviderInfo pi = + mActivity.getPackageManager().resolveContentProvider(auth, 0); + if (pi != null && selfPkg.equals(pi.packageName)) { + Log.w(TAG, "Refusing to grant Chrome-owned URI to caller: " + uri); + continue; + } + } mActivity.grantUriPermission( packageName, uri,