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 6e1d5f067f908..d853da368a447 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 @@ -15,7 +15,10 @@ import static org.chromium.build.NullUtil.assumeNonNull; import android.app.Activity; import android.content.ActivityNotFoundException; +import android.content.ContentResolver; import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ProviderInfo; import android.net.Uri; import android.text.TextUtils; @@ -40,6 +43,7 @@ import org.chromium.content_public.browser.NavigationHandle; import org.chromium.content_public.browser.WebContents; import org.chromium.content_public.browser.WebContentsObserver; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -136,7 +140,7 @@ public class WebAppLaunchHandler extends WebContentsObserver { } else { WebAppLaunchHandlerHistogram.logFileHandling(FileHandlingAction.MULTIPLE_FILES); } - fileUris = fileHandlingData.uris; + fileUris = getAccessibleFileUris(fileHandlingData.uris, packageName); } else { WebAppLaunchHandlerHistogram.logFileHandling(FileHandlingAction.NO_FILES); } @@ -144,6 +148,49 @@ public class WebAppLaunchHandler extends WebContentsObserver { return new WebAppLaunchParams(newNavigationStarted, targetUrl, packageName, fileUris); } + /** + * Returns the subset of {@code uris} that the client app is permitted to access. Only content + * URIs are eligible; a content URI is accepted if the client app owns the backing content + * provider or holds an explicit URI grant for it. + */ + private List getAccessibleFileUris(List uris, String packageName) { + PackageManager pm = mActivity.getPackageManager(); + int clientUid; + try { + clientUid = pm.getPackageUid(packageName, 0); + } catch (PackageManager.NameNotFoundException e) { + Log.w(TAG, "Client package not found: " + packageName); + return new ArrayList<>(); + } + List result = new ArrayList<>(uris.size()); + for (Uri uri : uris) { + if (canPackageAccessUri(pm, packageName, clientUid, uri)) { + result.add(uri); + } else { + Log.w(TAG, "Dropping file URI not accessible by client app: " + uri); + } + } + return result; + } + + private boolean canPackageAccessUri( + PackageManager pm, String packageName, int clientUid, @Nullable Uri uri) { + if (uri == null || !ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) { + return false; + } + String authority = uri.getAuthority(); + if (authority == null || authority.isEmpty()) { + return false; + } + ProviderInfo info = pm.resolveContentProvider(authority, 0); + if (info != null && packageName.equals(info.packageName)) { + return true; + } + return mActivity.checkUriPermission( + uri, /* pid= */ -1, clientUid, Intent.FLAG_GRANT_READ_URI_PERMISSION) + == PackageManager.PERMISSION_GRANTED; + } + /** * Handles an intent that triggers a TWA creation. It doesn't trigger a url loading because in * the case of initial intent it has been triggered earlier. Passes launch params to a launch 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 e9f8d83eb318e..d9fd185a05f41 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 @@ -23,6 +23,7 @@ import static org.robolectric.Shadows.shadowOf; import android.app.Activity; import android.content.Intent; import android.content.pm.PackageManager; +import android.content.pm.ProviderInfo; import android.net.Uri; import android.os.Looper; @@ -62,6 +63,7 @@ public class WebAppLaunchHandlerTest { public static final String OTHER_URL = JUnitTestGURLs.EXAMPLE_URL.getSpec(); public static final String CONTENT_URI = "content://com.a.b.c/a"; public static final String TEST_PACKAGE_NAME = "com.test"; + public static final int TEST_UID = 12345; private FileHandlingData mFileHandlingData; private String[] mExpectedFileList = new String[0]; @@ -71,10 +73,11 @@ public class WebAppLaunchHandlerTest { @Mock Verifier mVerifierMock; @Mock CurrentPageVerifier mCurrentPageVerifierMock; @Mock Activity mActivityMock; + @Mock PackageManager mPackageManagerMock; @Mock WebAppLaunchHandler.Natives mWebAppLaunchHandlerJniMock; @Before - public void setUp() { + public void setUp() throws PackageManager.NameNotFoundException { WebAppLaunchHandlerJni.setInstanceForTesting(mWebAppLaunchHandlerJniMock); when(mVerifierMock.verify(any())).thenReturn(Promise.fulfilled(true)); @@ -82,6 +85,12 @@ public class WebAppLaunchHandlerTest { .thenReturn( new CurrentPageVerifier.VerificationState( "", "", CurrentPageVerifier.VerificationStatus.SUCCESS)); + + when(mActivityMock.getPackageManager()).thenReturn(mPackageManagerMock); + when(mPackageManagerMock.getPackageUid(eq(TEST_PACKAGE_NAME), anyInt())) + .thenReturn(TEST_UID); + when(mActivityMock.checkUriPermission(any(), anyInt(), eq(TEST_UID), anyInt())) + .thenReturn(PackageManager.PERMISSION_GRANTED); } @Test @@ -163,13 +172,15 @@ public class WebAppLaunchHandlerTest { eq(url), eq(TEST_PACKAGE_NAME), eq(mExpectedFileList)); - verifyNoInteractions(mActivityMock); + verify(mActivityMock, never()).startActivity(any()); } else { verify(mWebAppLaunchHandlerJniMock, times(0)) .notifyLaunchQueue(any(), anyBoolean(), eq(url), any(), any()); } - if (clientMode != LaunchHandlerClientMode.NAVIGATE_NEW) verifyNoInteractions(mActivityMock); + if (clientMode != LaunchHandlerClientMode.NAVIGATE_NEW) { + verify(mActivityMock, never()).startActivity(any()); + } } @Test @@ -276,8 +287,8 @@ public class WebAppLaunchHandlerTest { @Test public void multipleFilePaths() { - final String secondUri = "second_uri.com"; - final String thirdUri = "third_uri.com"; + final String secondUri = "content://com.a.b.c/b"; + final String thirdUri = "content://com.a.b.c/c"; mFileHandlingData = new FileHandlingData( Arrays.asList( @@ -295,6 +306,64 @@ public class WebAppLaunchHandlerTest { /* expectedNotifyQueue= */ true); } + @Test + public void filePath_clientLacksUriPermission() { + when(mActivityMock.checkUriPermission(any(), anyInt(), eq(TEST_UID), anyInt())) + .thenReturn(PackageManager.PERMISSION_DENIED); + + final String otherAppUri = "content://com.other.app.documents/document/1"; + mFileHandlingData = + new FileHandlingData( + Arrays.asList(Uri.parse(CONTENT_URI), Uri.parse(otherAppUri))); + mExpectedFileList = new String[0]; + doTestHandleIntent( + LaunchHandlerClientMode.AUTO, + INITIAL_URL, + /* expectedLoadUrl= */ false, + /* expectedNotifyQueue= */ true); + doTestHandleIntent( + LaunchHandlerClientMode.FOCUS_EXISTING, + OTHER_URL, + /* expectedLoadUrl= */ false, + /* expectedNotifyQueue= */ true); + } + + @Test + public void filePath_clientOwnsProvider() { + when(mActivityMock.checkUriPermission(any(), anyInt(), eq(TEST_UID), anyInt())) + .thenReturn(PackageManager.PERMISSION_DENIED); + + final String ownUri = "content://com.test.files/document/1"; + ProviderInfo info = new ProviderInfo(); + info.packageName = TEST_PACKAGE_NAME; + when(mPackageManagerMock.resolveContentProvider(eq("com.test.files"), anyInt())) + .thenReturn(info); + + mFileHandlingData = + new FileHandlingData(Arrays.asList(Uri.parse(ownUri), Uri.parse(CONTENT_URI))); + mExpectedFileList = new String[] {ownUri}; + doTestHandleIntent( + LaunchHandlerClientMode.AUTO, + INITIAL_URL, + /* expectedLoadUrl= */ false, + /* expectedNotifyQueue= */ true); + } + + @Test + public void filePath_nonContentUri() { + mFileHandlingData = + new FileHandlingData( + Arrays.asList( + Uri.parse("file:///data/local/tmp/a"), + Uri.parse("/data/local/tmp/a"))); + mExpectedFileList = new String[0]; + doTestHandleIntent( + LaunchHandlerClientMode.AUTO, + INITIAL_URL, + /* expectedLoadUrl= */ false, + /* expectedNotifyQueue= */ true); + } + void doTestNavigateNewInitialIntent(Integer clientMode) { CustomTabIntentDataProvider dataProvider = createIntentDataProvider(clientMode, INITIAL_URL); @@ -302,7 +371,7 @@ public class WebAppLaunchHandlerTest { launchHandler.handleInitialIntent(dataProvider); shadowOf(Looper.getMainLooper()).idle(); - verifyNoInteractions(mActivityMock); + verify(mActivityMock, never()).startActivity(any()); verifyNoInteractions(mNavigationControllerMock); verify(mWebAppLaunchHandlerJniMock, times(1)) .notifyLaunchQueue(any(), anyBoolean(), any(), any(), any());