Adds test for session referrer uri masking
This change ensures that only the owner of an install session may view
its referrer URI.
Test: atest SessionReferrerUriTest
Bug: 142125338
Merged-In: I0500e198f5e53aac5b90534c8e438b847d78191c
Change-Id: I0500e198f5e53aac5b90534c8e438b847d78191c
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/SessionReferrerUriTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/SessionReferrerUriTest.java
new file mode 100644
index 0000000..7477721
--- /dev/null
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/SessionReferrerUriTest.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.appsecurity.cts;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import android.platform.test.annotations.AppModeFull;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class SessionReferrerUriTest extends BaseAppSecurityTest {
+
+ private static final String SESSION_INSPECTOR_A_APK = "CtsSessionInspectorAppA.apk";
+ private static final String SESSION_INSPECTOR_B_APK = "CtsSessionInspectorAppB.apk";
+ private static final String SESSION_INSPECTOR_PKG_A = "com.android.cts.sessioninspector.a";
+ private static final String SESSION_INSPECTOR_PKG_B = "com.android.cts.sessioninspector.b";
+
+ @Before
+ public void setup() throws Exception {
+ new InstallMultiple().addApk(SESSION_INSPECTOR_A_APK).run();
+ new InstallMultiple().addApk(SESSION_INSPECTOR_B_APK).run();
+ }
+
+ @After
+ public void teardown() throws Exception {
+ getDevice().uninstallPackage(SESSION_INSPECTOR_PKG_A);
+ getDevice().uninstallPackage(SESSION_INSPECTOR_PKG_B);
+ }
+
+ @Test
+ @AppModeFull(reason = "Only full apps may install")
+ public void testSessionReferrerUriVisibleToOwner() throws DeviceNotAvailableException {
+ Utils.runDeviceTests(getDevice(), SESSION_INSPECTOR_PKG_A,
+ "com.android.cts.sessioninspector.SessionInspectorTest", "testOnlyOwnerCanSee");
+ Utils.runDeviceTests(getDevice(), SESSION_INSPECTOR_PKG_B,
+ "com.android.cts.sessioninspector.SessionInspectorTest", "testOnlyOwnerCanSee");
+ }
+}
diff --git a/hostsidetests/appsecurity/test-apps/SessionInspector/Android.bp b/hostsidetests/appsecurity/test-apps/SessionInspector/Android.bp
new file mode 100644
index 0000000..421c9de
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SessionInspector/Android.bp
@@ -0,0 +1,42 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+android_test_helper_app {
+ name: "CtsSessionInspectorAppA",
+ defaults: ["cts_support_defaults"],
+ manifest: "AndroidManifestA.xml",
+ sdk_version: "test_current",
+ srcs: ["src/**/*.java"],
+ static_libs: ["androidx.test.rules"],
+ // tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "vts",
+ "general-tests",
+ ],
+}
+android_test_helper_app {
+ name: "CtsSessionInspectorAppB",
+ defaults: ["cts_support_defaults"],
+ manifest: "AndroidManifestB.xml",
+ sdk_version: "test_current",
+ srcs: ["src/**/*.java"],
+ static_libs: ["androidx.test.rules"],
+ // tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "vts",
+ "general-tests",
+ ],
+}
diff --git a/hostsidetests/appsecurity/test-apps/SessionInspector/AndroidManifestA.xml b/hostsidetests/appsecurity/test-apps/SessionInspector/AndroidManifestA.xml
new file mode 100644
index 0000000..34aba10
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SessionInspector/AndroidManifestA.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.cts.sessioninspector.a">
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.cts.sessioninspector.a" />
+
+ <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ <activity android:name="com.android.cts.sessioninspector.SessionInspectorActivity"
+ android:exported="true"/>
+ </application>
+
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/SessionInspector/AndroidManifestB.xml b/hostsidetests/appsecurity/test-apps/SessionInspector/AndroidManifestB.xml
new file mode 100644
index 0000000..324aa4e
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SessionInspector/AndroidManifestB.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.cts.sessioninspector.b">
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.cts.sessioninspector.b" />
+
+ <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ <activity android:name="com.android.cts.sessioninspector.SessionInspectorActivity"
+ android:exported="true"/>
+ </application>
+
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/SessionInspector/OWNERS b/hostsidetests/appsecurity/test-apps/SessionInspector/OWNERS
new file mode 100644
index 0000000..9d4a924
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SessionInspector/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 533114
+patb@google.com
+toddke@google.com
+chiuwinson@google.com
diff --git a/hostsidetests/appsecurity/test-apps/SessionInspector/src/com/android/cts/sessioninspector/Constants.java b/hostsidetests/appsecurity/test-apps/SessionInspector/src/com/android/cts/sessioninspector/Constants.java
new file mode 100644
index 0000000..6957448
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SessionInspector/src/com/android/cts/sessioninspector/Constants.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.sessioninspector;
+
+import android.net.Uri;
+
+public class Constants {
+ private static final String PKG_BASE = "com.android.cts.sessioninspector.";
+
+ public static final String ACTION_CREATE_SESSION = PKG_BASE + "CREATE_SESSION";
+ public static final String ACTION_GET_SESSION = PKG_BASE + "GET_SESSION";
+ public static final String ACTION_ABANDON_SESSION = PKG_BASE + "ABANDON_SESSION";
+
+ public static final String PACKAGE_A = PKG_BASE + "a";
+ public static final String PACKAGE_B = PKG_BASE + "b";
+
+ public static final String ACTIVITY_NAME = PKG_BASE + "SessionInspectorActivity";
+
+ public static final Uri REFERRER_URI = Uri.parse("https://user-sensitive-domain.com/");
+}
diff --git a/hostsidetests/appsecurity/test-apps/SessionInspector/src/com/android/cts/sessioninspector/SessionInspectorActivity.java b/hostsidetests/appsecurity/test-apps/SessionInspector/src/com/android/cts/sessioninspector/SessionInspectorActivity.java
new file mode 100644
index 0000000..edf90f2
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SessionInspector/src/com/android/cts/sessioninspector/SessionInspectorActivity.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.sessioninspector;
+
+import static android.content.Intent.EXTRA_RESULT_RECEIVER;
+import static android.content.pm.PackageInstaller.EXTRA_SESSION;
+import static android.content.pm.PackageInstaller.EXTRA_SESSION_ID;
+import static android.content.pm.PackageInstaller.SessionInfo;
+import static android.content.pm.PackageInstaller.SessionParams;
+import static android.content.pm.PackageInstaller.SessionParams.MODE_FULL_INSTALL;
+
+import static com.android.cts.sessioninspector.Constants.ACTION_ABANDON_SESSION;
+import static com.android.cts.sessioninspector.Constants.ACTION_CREATE_SESSION;
+import static com.android.cts.sessioninspector.Constants.ACTION_GET_SESSION;
+import static com.android.cts.sessioninspector.Constants.REFERRER_URI;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.os.RemoteCallback;
+
+public class SessionInspectorActivity extends Activity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ RemoteCallback remoteCallback = getIntent().getParcelableExtra(EXTRA_RESULT_RECEIVER);
+ final Bundle result = new Bundle();
+ String action = getIntent().getAction();
+ try {
+ switch (action) {
+ case ACTION_CREATE_SESSION:
+ SessionParams params = new SessionParams(MODE_FULL_INSTALL);
+ params.setReferrerUri(REFERRER_URI);
+ final int session =
+ getPackageManager().getPackageInstaller().createSession(params);
+ result.putInt(EXTRA_SESSION_ID, session);
+ break;
+ case ACTION_GET_SESSION: {
+ final int sessionId = getIntent().getIntExtra(EXTRA_SESSION_ID, 0);
+ final SessionInfo sessionInfo =
+ getPackageManager().getPackageInstaller().getSessionInfo(sessionId);
+ result.putParcelable(EXTRA_SESSION, sessionInfo);
+ break;
+ }
+ case ACTION_ABANDON_SESSION: {
+ final int sessionId = getIntent().getIntExtra(EXTRA_SESSION_ID, 0);
+ getPackageManager().getPackageInstaller().abandonSession(sessionId);
+ break;
+ }
+ default:
+ throw new IllegalArgumentException("Unrecognized action: " + action);
+ }
+ } catch (Exception e) {
+ result.putSerializable("error", e);
+ }
+ remoteCallback.sendResult(result);
+ finish();
+ }
+}
diff --git a/hostsidetests/appsecurity/test-apps/SessionInspector/src/com/android/cts/sessioninspector/SessionInspectorTest.java b/hostsidetests/appsecurity/test-apps/SessionInspector/src/com/android/cts/sessioninspector/SessionInspectorTest.java
new file mode 100644
index 0000000..8afcdef
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SessionInspector/src/com/android/cts/sessioninspector/SessionInspectorTest.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.sessioninspector;
+
+import static com.android.cts.sessioninspector.Constants.ACTION_ABANDON_SESSION;
+import static com.android.cts.sessioninspector.Constants.ACTION_CREATE_SESSION;
+import static com.android.cts.sessioninspector.Constants.ACTION_GET_SESSION;
+import static com.android.cts.sessioninspector.Constants.ACTIVITY_NAME;
+import static com.android.cts.sessioninspector.Constants.REFERRER_URI;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageInstaller;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.ConditionVariable;
+import android.os.RemoteCallback;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(JUnit4.class)
+public class SessionInspectorTest {
+
+ @Test
+ public void testOnlyOwnerCanSee() throws Exception {
+ String myPackage = Constants.PACKAGE_A.equals(getContext().getPackageName())
+ ? Constants.PACKAGE_A : Constants.PACKAGE_B;
+ String otherPackage = Constants.PACKAGE_A.equals(myPackage) ? Constants.PACKAGE_B
+ : Constants.PACKAGE_A;
+
+ int sessionId = createSession(myPackage);
+
+ final PackageInstaller.SessionInfo sessionToMe = getSessionInfo(myPackage, sessionId);
+ final PackageInstaller.SessionInfo sessionToOther = getSessionInfo(otherPackage, sessionId);
+
+ abandonSession(myPackage, sessionId);
+
+ assertEquals(REFERRER_URI, sessionToMe.getReferrerUri());
+ assertNull(sessionToOther.getReferrerUri());
+ }
+
+ private Context getContext() {
+ return InstrumentationRegistry.getContext();
+ }
+
+ private int createSession(String packageName) throws Exception {
+ Bundle result = sendCommand(new Intent(ACTION_CREATE_SESSION).setComponent(
+ new ComponentName(packageName, ACTIVITY_NAME)));
+ return result.getInt(PackageInstaller.EXTRA_SESSION_ID, 0);
+ }
+
+ private PackageInstaller.SessionInfo getSessionInfo(String packageName, int session)
+ throws Exception {
+ Bundle result = sendCommand(new Intent(ACTION_GET_SESSION).putExtra(
+ PackageInstaller.EXTRA_SESSION_ID, session).setComponent(
+ new ComponentName(packageName, ACTIVITY_NAME)));
+ return result.getParcelable(PackageInstaller.EXTRA_SESSION);
+ }
+
+ private void abandonSession(String packageName, int sessionId) throws Exception {
+ sendCommand(new Intent(ACTION_ABANDON_SESSION).putExtra(PackageInstaller.EXTRA_SESSION_ID,
+ sessionId).setComponent(new ComponentName(packageName, ACTIVITY_NAME)));
+ }
+
+ private Bundle sendCommand(Intent intent) throws Exception {
+ ConditionVariable condition = new ConditionVariable();
+ final Bundle[] resultHolder = new Bundle[1];
+ RemoteCallback callback = new RemoteCallback(result -> {
+ resultHolder[0] = result;
+ condition.open();
+ });
+ intent.putExtra(Intent.EXTRA_RESULT_RECEIVER, callback);
+ intent.setData(Uri.parse("https://" + UUID.randomUUID()));
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ getContext().startActivity(intent);
+ condition.block(TimeUnit.SECONDS.toMillis(10));
+ Bundle result = resultHolder[0];
+ if (result.containsKey("error")) {
+ throw (Exception) result.getSerializable("error");
+ }
+ return result;
+ }
+}