1 /*
2  * Copyright (C) 2009 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.appsecurity.cts;
18 
19 import static android.appsecurity.cts.Utils.waitForBootCompleted;
20 
21 import static org.junit.Assert.assertNotNull;
22 import static org.junit.Assert.assertTrue;
23 
24 import android.platform.test.annotations.AppModeFull;
25 import android.platform.test.annotations.AppModeInstant;
26 import android.platform.test.annotations.AsbSecurityTest;
27 import android.platform.test.annotations.Presubmit;
28 import android.platform.test.annotations.RestrictedBuildTest;
29 
30 import com.android.ddmlib.Log;
31 import com.android.tradefed.device.DeviceNotAvailableException;
32 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
33 
34 import org.junit.Before;
35 import org.junit.Test;
36 import org.junit.runner.RunWith;
37 
38 import java.util.HashMap;
39 import java.util.Map;
40 
41 /**
42  * Set of tests that verify various security checks involving multiple apps are
43  * properly enforced.
44  */
45 @Presubmit
46 @RunWith(DeviceJUnit4ClassRunner.class)
47 public class AppSecurityTests extends BaseAppSecurityTest {
48 
49     // testAppUpgradeDifferentCerts constants
50     private static final String SIMPLE_APP_APK = "CtsSimpleAppInstall.apk";
51     private static final String SIMPLE_APP_PKG = "com.android.cts.simpleappinstall";
52     private static final String SIMPLE_APP_DIFF_CERT_APK = "CtsSimpleAppInstallDiffCert.apk";
53 
54     // testAppFailAccessPrivateData constants
55     private static final String APP_WITH_DATA_APK = "CtsAppWithData.apk";
56     private static final String APP_WITH_DATA_PKG = "com.android.cts.appwithdata";
57     private static final String APP_WITH_DATA_CLASS =
58             "com.android.cts.appwithdata.CreatePrivateDataTest";
59     private static final String APP_WITH_DATA_CREATE_METHOD =
60             "testCreatePrivateData";
61     private static final String APP_WITH_DATA_CHECK_NOEXIST_METHOD =
62             "testEnsurePrivateDataNotExist";
63     private static final String APP_ACCESS_DATA_APK = "CtsAppAccessData.apk";
64     private static final String APP_ACCESS_DATA_PKG = "com.android.cts.appaccessdata";
65 
66     // testInstrumentationDiffCert constants
67     private static final String TARGET_INSTRUMENT_APK = "CtsTargetInstrumentationApp.apk";
68     private static final String TARGET_INSTRUMENT_PKG = "com.android.cts.targetinstrumentationapp";
69     private static final String INSTRUMENT_DIFF_CERT_APK = "CtsInstrumentationAppDiffCert.apk";
70     private static final String INSTRUMENT_DIFF_CERT_PKG =
71             "com.android.cts.instrumentationdiffcertapp";
72     private static final String INSTRUMENT_DIFF_CERT_CLASS =
73             "com.android.cts.instrumentationdiffcertapp.InstrumentationFailToRunTest";
74 
75     // testPermissionDiffCert constants
76     private static final String DECLARE_PERMISSION_APK = "CtsPermissionDeclareApp.apk";
77     private static final String DECLARE_PERMISSION_PKG = "com.android.cts.permissiondeclareapp";
78     private static final String DECLARE_PERMISSION_COMPAT_APK = "CtsPermissionDeclareAppCompat.apk";
79     private static final String DECLARE_PERMISSION_COMPAT_PKG = "com.android.cts.permissiondeclareappcompat";
80 
81     private static final String PERMISSION_DIFF_CERT_APK = "CtsUsePermissionDiffCert.apk";
82     private static final String PERMISSION_DIFF_CERT_PKG =
83         "com.android.cts.usespermissiondiffcertapp";
84 
85     private static final String DUPLICATE_DECLARE_PERMISSION_APK =
86             "CtsDuplicatePermissionDeclareApp.apk";
87     private static final String DUPLICATE_DECLARE_PERMISSION_PKG =
88             "com.android.cts.duplicatepermissiondeclareapp";
89 
90     private static final String DUPLICATE_PERMISSION_DIFFERENT_PROTECTION_LEVEL_APK =
91             "CtsDuplicatePermissionDeclareApp_DifferentProtectionLevel.apk";
92     private static final String DUPLICATE_PERMISSION_DIFFERENT_PROTECTION_LEVEL_PKG =
93             "com.android.cts.duplicatepermission.differentprotectionlevel";
94     private static final String DUPLICATE_PERMISSION_SAME_PROTECTION_LEVEL_APK =
95             "CtsDuplicatePermissionDeclareApp_SameProtectionLevel.apk";
96     private static final String DUPLICATE_PERMISSION_SAME_PROTECTION_LEVEL_PKG =
97             "com.android.cts.duplicatepermission.sameprotectionlevel";
98 
99     private static final String DUPLICATE_PERMISSION_DIFFERENT_PERMISSION_GROUP_APK =
100             "CtsMalformedDuplicatePermission_DifferentPermissionGroup.apk";
101     private static final String DUPLICATE_PERMISSION_DIFFERENT_PERMISSION_GROUP_PKG =
102             "com.android.cts.duplicatepermission.differentpermissiongroup";
103     private static final String DUPLICATE_PERMISSION_SAME_PERMISSION_GROUP_APK =
104             "CtsDuplicatePermission_SamePermissionGroup.apk";
105     private static final String DUPLICATE_PERMISSION_SAME_PERMISSION_GROUP_PKG =
106             "com.android.cts.duplicatepermission.samepermissiongroup";
107 
108     private static final String LOG_TAG = "AppSecurityTests";
109 
110     @Before
setUp()111     public void setUp() throws Exception {
112         Utils.prepareSingleUser(getDevice());
113         assertNotNull(getBuild());
114     }
115 
116     /**
117      * Test that an app update cannot be installed over an existing app if it has a different
118      * certificate.
119      */
120     @Test
121     @AppModeFull(reason = "'full' portion of the hostside test")
testAppUpgradeDifferentCerts_full()122     public void testAppUpgradeDifferentCerts_full() throws Exception {
123         testAppUpgradeDifferentCerts(false);
124     }
125     @Test
126     @AppModeInstant(reason = "'instant' portion of the hostside test")
testAppUpgradeDifferentCerts_instant()127     public void testAppUpgradeDifferentCerts_instant() throws Exception {
128         testAppUpgradeDifferentCerts(true);
129     }
testAppUpgradeDifferentCerts(boolean instant)130     private void testAppUpgradeDifferentCerts(boolean instant) throws Exception {
131         Log.i(LOG_TAG, "installing app upgrade with different certs");
132         try {
133             getDevice().uninstallPackage(SIMPLE_APP_PKG);
134             getDevice().uninstallPackage(SIMPLE_APP_DIFF_CERT_APK);
135 
136             new InstallMultiple(instant).addFile(SIMPLE_APP_APK).run();
137             new InstallMultiple(instant).addFile(SIMPLE_APP_DIFF_CERT_APK)
138                     .runExpectingFailure("INSTALL_FAILED_UPDATE_INCOMPATIBLE");
139         } finally {
140             getDevice().uninstallPackage(SIMPLE_APP_PKG);
141             getDevice().uninstallPackage(SIMPLE_APP_DIFF_CERT_APK);
142         }
143     }
144 
145     /**
146      * Test that an app cannot access another app's private data.
147      */
148     @Test
149     @AppModeFull(reason = "'full' portion of the hostside test")
testAppFailAccessPrivateData_full()150     public void testAppFailAccessPrivateData_full() throws Exception {
151         testAppFailAccessPrivateData(false);
152     }
153     @Test
154     @AppModeInstant(reason = "'instant' portion of the hostside test")
testAppFailAccessPrivateData_instant()155     public void testAppFailAccessPrivateData_instant() throws Exception {
156         testAppFailAccessPrivateData(true);
157     }
testAppFailAccessPrivateData(boolean instant)158     private void testAppFailAccessPrivateData(boolean instant)
159             throws Exception {
160         Log.i(LOG_TAG, "installing app that attempts to access another app's private data");
161         try {
162             getDevice().uninstallPackage(APP_WITH_DATA_PKG);
163             getDevice().uninstallPackage(APP_ACCESS_DATA_PKG);
164 
165             new InstallMultiple().addFile(APP_WITH_DATA_APK).run();
166             runDeviceTests(APP_WITH_DATA_PKG, APP_WITH_DATA_CLASS, APP_WITH_DATA_CREATE_METHOD);
167 
168             new InstallMultiple(instant).addFile(APP_ACCESS_DATA_APK).run();
169             runDeviceTests(APP_ACCESS_DATA_PKG, null, null, instant);
170         } finally {
171             getDevice().uninstallPackage(APP_WITH_DATA_PKG);
172             getDevice().uninstallPackage(APP_ACCESS_DATA_PKG);
173         }
174     }
175 
176     /**
177      * Test that uninstall of an app removes its private data.
178      */
179     @Test
180     @AppModeFull(reason = "'full' portion of the hostside test")
testUninstallRemovesData_full()181     public void testUninstallRemovesData_full() throws Exception {
182         testUninstallRemovesData(false);
183     }
184     @Test
185     @AppModeInstant(reason = "'instant' portion of the hostside test")
testUninstallRemovesData_instant()186     public void testUninstallRemovesData_instant() throws Exception {
187         testUninstallRemovesData(true);
188     }
testUninstallRemovesData(boolean instant)189     private void testUninstallRemovesData(boolean instant) throws Exception {
190         Log.i(LOG_TAG, "Uninstalling app, verifying data is removed.");
191         try {
192             getDevice().uninstallPackage(APP_WITH_DATA_PKG);
193 
194             new InstallMultiple(instant).addFile(APP_WITH_DATA_APK).run();
195             runDeviceTests(
196                     APP_WITH_DATA_PKG, APP_WITH_DATA_CLASS, APP_WITH_DATA_CREATE_METHOD);
197 
198             getDevice().uninstallPackage(APP_WITH_DATA_PKG);
199 
200             new InstallMultiple(instant).addFile(APP_WITH_DATA_APK).run();
201             runDeviceTests(
202                     APP_WITH_DATA_PKG, APP_WITH_DATA_CLASS, APP_WITH_DATA_CHECK_NOEXIST_METHOD);
203         } finally {
204             getDevice().uninstallPackage(APP_WITH_DATA_PKG);
205         }
206     }
207 
208     /**
209      * Test that an app cannot instrument another app that is signed with different certificate.
210      */
211     // RestrictedBuildTest ensures the build only runs on user builds where the signature
212     // verification will be performed, but JUnit4TestNotRun reports the test will not be run because
213     // the method does not have the @Test annotation.
214     @SuppressWarnings("JUnit4TestNotRun")
215     @RestrictedBuildTest
216     @AppModeFull(reason = "'full' portion of the hostside test")
testInstrumentationDiffCert_full()217     public void testInstrumentationDiffCert_full() throws Exception {
218         testInstrumentationDiffCert(false, false);
219     }
220     @Test
221     @AppModeInstant(reason = "'instant' portion of the hostside test")
testInstrumentationDiffCert_instant()222     public void testInstrumentationDiffCert_instant() throws Exception {
223         testInstrumentationDiffCert(false, true);
224         testInstrumentationDiffCert(true, false);
225         testInstrumentationDiffCert(true, true);
226     }
testInstrumentationDiffCert(boolean targetInstant, boolean instrumentInstant)227     private void testInstrumentationDiffCert(boolean targetInstant, boolean instrumentInstant)
228             throws Exception {
229         Log.i(LOG_TAG, "installing app that attempts to instrument another app");
230         try {
231             // cleanup test app that might be installed from previous partial test run
232             getDevice().uninstallPackage(TARGET_INSTRUMENT_PKG);
233             getDevice().uninstallPackage(INSTRUMENT_DIFF_CERT_PKG);
234 
235             new InstallMultiple(targetInstant).addFile(TARGET_INSTRUMENT_APK).run();
236             new InstallMultiple(instrumentInstant).addFile(INSTRUMENT_DIFF_CERT_APK).run();
237 
238             // if we've installed either the instrumentation or target as an instant application,
239             // starting an instrumentation will just fail instead of throwing a security exception
240             // because neither the target nor instrumentation packages can see one another
241             final String methodName = (targetInstant|instrumentInstant)
242                     ? "testInstrumentationNotAllowed_fail"
243                     : "testInstrumentationNotAllowed_exception";
244             runDeviceTests(INSTRUMENT_DIFF_CERT_PKG, INSTRUMENT_DIFF_CERT_CLASS, methodName);
245         } finally {
246             getDevice().uninstallPackage(TARGET_INSTRUMENT_PKG);
247             getDevice().uninstallPackage(INSTRUMENT_DIFF_CERT_PKG);
248         }
249     }
250 
251     /**
252      * Test that an app cannot use a signature-enforced permission if it is signed with a different
253      * certificate than the app that declared the permission.
254      */
255     @Test
256     @AppModeFull(reason = "Only the platform can define permissions obtainable by instant applications")
257     @AsbSecurityTest(cveBugId = 111934948)
testPermissionDiffCert()258     public void testPermissionDiffCert() throws Exception {
259         Log.i(LOG_TAG, "installing app that attempts to use permission of another app");
260         try {
261             // cleanup test app that might be installed from previous partial test run
262             getDevice().uninstallPackage(DECLARE_PERMISSION_PKG);
263             getDevice().uninstallPackage(DECLARE_PERMISSION_COMPAT_PKG);
264             getDevice().uninstallPackage(PERMISSION_DIFF_CERT_PKG);
265 
266             new InstallMultiple().addFile(DECLARE_PERMISSION_APK).run();
267             new InstallMultiple().addFile(DECLARE_PERMISSION_COMPAT_APK).run();
268 
269             new InstallMultiple().addFile(PERMISSION_DIFF_CERT_APK).run();
270 
271             // Enable alert window permission so it can start activity in background
272             enableAlertWindowAppOp(DECLARE_PERMISSION_PKG);
273 
274             runDeviceTests(PERMISSION_DIFF_CERT_PKG, null);
275         } finally {
276             getDevice().uninstallPackage(DECLARE_PERMISSION_PKG);
277             getDevice().uninstallPackage(DECLARE_PERMISSION_COMPAT_PKG);
278             getDevice().uninstallPackage(PERMISSION_DIFF_CERT_PKG);
279         }
280     }
281 
282     /**
283      * Test that an app cannot set the installer package for an app with a different
284      * signature.
285      */
286     @Test
287     @AppModeFull(reason = "Only full apps can hold INSTALL_PACKAGES")
288     @AsbSecurityTest(cveBugId = 150857253)
testCrossPackageDiffCertSetInstaller()289     public void testCrossPackageDiffCertSetInstaller() throws Exception {
290         Log.i(LOG_TAG, "installing app that attempts to use permission of another app");
291         try {
292             // cleanup test app that might be installed from previous partial test run
293             getDevice().uninstallPackage(DECLARE_PERMISSION_PKG);
294             getDevice().uninstallPackage(DECLARE_PERMISSION_COMPAT_PKG);
295             getDevice().uninstallPackage(PERMISSION_DIFF_CERT_PKG);
296 
297             new InstallMultiple().addFile(DECLARE_PERMISSION_APK).run();
298             new InstallMultiple().addFile(DECLARE_PERMISSION_COMPAT_APK).run();
299             new InstallMultiple().addFile(PERMISSION_DIFF_CERT_APK).run();
300 
301             // Enable alert window permission so it can start activity in background
302             enableAlertWindowAppOp(DECLARE_PERMISSION_PKG);
303 
304             runCrossPackageInstallerDeviceTest(PERMISSION_DIFF_CERT_PKG, "assertBefore");
305             runCrossPackageInstallerDeviceTest(DECLARE_PERMISSION_PKG, "takeInstaller");
306             runCrossPackageInstallerDeviceTest(PERMISSION_DIFF_CERT_PKG, "attemptTakeOver");
307             runCrossPackageInstallerDeviceTest(DECLARE_PERMISSION_PKG, "clearInstaller");
308             runCrossPackageInstallerDeviceTest(PERMISSION_DIFF_CERT_PKG, "assertAfter");
309         } finally {
310             getDevice().uninstallPackage(DECLARE_PERMISSION_PKG);
311             getDevice().uninstallPackage(DECLARE_PERMISSION_COMPAT_PKG);
312             getDevice().uninstallPackage(PERMISSION_DIFF_CERT_PKG);
313         }
314     }
315 
316     /**
317      * Utility method to make actual test method easier to read.
318      */
runCrossPackageInstallerDeviceTest(String pkgName, String testMethodName)319     private void runCrossPackageInstallerDeviceTest(String pkgName, String testMethodName)
320             throws DeviceNotAvailableException {
321         Map<String, String> arguments = new HashMap<>();
322         arguments.put("runExplicit", "true");
323         runDeviceTests(getDevice(), null, pkgName, pkgName + ".ModifyInstallerCrossPackageTest",
324                 testMethodName, null, 10 * 60 * 1000L, 10 * 60 * 1000L, 0L, true, false, arguments);
325     }
326 
327     /**
328      * Test what happens if an app tried to take a permission away from another
329      */
330     @Test
rebootWithDuplicatePermission()331     public void rebootWithDuplicatePermission() throws Exception {
332         try {
333             new InstallMultiple(false).addFile(DECLARE_PERMISSION_APK).run();
334             new InstallMultiple(false).addFile(DUPLICATE_DECLARE_PERMISSION_APK).run();
335 
336             // Enable alert window permission so it can start activity in background
337             enableAlertWindowAppOp(DECLARE_PERMISSION_PKG);
338 
339             runDeviceTests(DUPLICATE_DECLARE_PERMISSION_PKG, null);
340 
341             // make sure behavior is preserved after reboot
342             getDevice().reboot();
343             waitForBootCompleted(getDevice());
344             runDeviceTests(DUPLICATE_DECLARE_PERMISSION_PKG, null);
345         } finally {
346             getDevice().uninstallPackage(DECLARE_PERMISSION_PKG);
347             getDevice().uninstallPackage(DUPLICATE_DECLARE_PERMISSION_PKG);
348         }
349     }
350 
351     /**
352      * Tests that an arbitrary file cannot be installed using the 'cmd' command.
353      */
354     @Test
355     @AppModeFull(reason = "'full' portion of the hostside test")
testAdbInstallFile_full()356     public void testAdbInstallFile_full() throws Exception {
357         testAdbInstallFile(false);
358     }
359 
360     @Test
361     @AppModeInstant(reason = "'instant' portion of the hostside test")
testAdbInstallFile_instant()362     public void testAdbInstallFile_instant() throws Exception {
363         testAdbInstallFile(true);
364     }
365 
testAdbInstallFile(boolean instant)366     private void testAdbInstallFile(boolean instant) throws Exception {
367         String output = getDevice().executeShellCommand(
368                 "cmd package install"
369                         + (instant ? " --instant" : " --full")
370                         + " -S 1024 /data/local/tmp/foo.apk");
371         assertTrue("Error text", output.contains("Error"));
372     }
373 
enableAlertWindowAppOp(String pkgName)374     private void enableAlertWindowAppOp(String pkgName) throws Exception {
375         getDevice().executeShellCommand(
376                 "appops set " + pkgName + " android:system_alert_window allow");
377         String result = "No operations.";
378         while (result.contains("No operations")) {
379             result = getDevice().executeShellCommand(
380                     "appops get " + pkgName + " android:system_alert_window");
381         }
382     }
383 
384     /**
385      * Tests that a single APK declaring duplicate permissions with different protection levels
386      * cannot be installed.
387      */
388     @Test
testInstallDuplicatePermission_differentProtectionLevel_fail()389     public void testInstallDuplicatePermission_differentProtectionLevel_fail() throws Exception {
390         try {
391             new InstallMultiple(false /* instant */)
392                     .addFile(DUPLICATE_PERMISSION_DIFFERENT_PROTECTION_LEVEL_APK)
393                     .runExpectingFailure("INSTALL_PARSE_FAILED_MANIFEST_MALFORMED");
394         } finally {
395             getDevice().uninstallPackage(DUPLICATE_PERMISSION_DIFFERENT_PROTECTION_LEVEL_PKG);
396         }
397     }
398 
399     /**
400      * Tests that a single APK declaring duplicate permissions with the same protection level
401      * can be installed.
402      */
403     @Test
testInstallDuplicatePermission_sameProtectionLevel_success()404     public void testInstallDuplicatePermission_sameProtectionLevel_success() throws Exception {
405         try {
406             new InstallMultiple(false /* instant */)
407                     .addFile(DUPLICATE_PERMISSION_SAME_PROTECTION_LEVEL_APK)
408                     .run(true /* expectingSuccess */);
409         } finally {
410             getDevice().uninstallPackage(DUPLICATE_PERMISSION_SAME_PROTECTION_LEVEL_PKG);
411         }
412     }
413 
414     /**
415      * Tests that a single APK declaring duplicate permissions with different permission group
416      * cannot be installed.
417      */
418     @Test
testInstallDuplicatePermission_differentPermissionGroup_fail()419     public void testInstallDuplicatePermission_differentPermissionGroup_fail() throws Exception {
420         try {
421             new InstallMultiple(false /* instant */)
422                     .addFile(DUPLICATE_PERMISSION_DIFFERENT_PERMISSION_GROUP_APK)
423                     .runExpectingFailure("INSTALL_PARSE_FAILED_MANIFEST_MALFORMED");
424         } finally {
425             getDevice().uninstallPackage(DUPLICATE_PERMISSION_DIFFERENT_PERMISSION_GROUP_PKG);
426         }
427     }
428 
429     /**
430      * Tests that a single APK declaring duplicate permissions with the same permission group
431      * can be installed.
432      */
433     @Test
testInstallDuplicatePermission_samePermissionGroup_success()434     public void testInstallDuplicatePermission_samePermissionGroup_success() throws Exception {
435         try {
436             new InstallMultiple(false /* instant */)
437                     .addFile(DUPLICATE_PERMISSION_SAME_PERMISSION_GROUP_APK)
438                     .run(true /* expectingSuccess */);
439         } finally {
440             getDevice().uninstallPackage(DUPLICATE_PERMISSION_SAME_PERMISSION_GROUP_PKG);
441         }
442     }
443 }
444