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.RestrictedBuildTest;
27 import android.platform.test.annotations.AsbSecurityTest;
28 
29 import com.android.ddmlib.Log;
30 import com.android.tradefed.device.DeviceNotAvailableException;
31 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
32 
33 import org.junit.Before;
34 import org.junit.Test;
35 import org.junit.runner.RunWith;
36 
37 import java.util.HashMap;
38 import java.util.Map;
39 
40 /**
41  * Set of tests that verify various security checks involving multiple apps are
42  * properly enforced.
43  */
44 @RunWith(DeviceJUnit4ClassRunner.class)
45 public class AppSecurityTests extends BaseAppSecurityTest {
46 
47     // testAppUpgradeDifferentCerts constants
48     private static final String SIMPLE_APP_APK = "CtsSimpleAppInstall.apk";
49     private static final String SIMPLE_APP_PKG = "com.android.cts.simpleappinstall";
50     private static final String SIMPLE_APP_DIFF_CERT_APK = "CtsSimpleAppInstallDiffCert.apk";
51 
52     // testAppFailAccessPrivateData constants
53     private static final String APP_WITH_DATA_APK = "CtsAppWithData.apk";
54     private static final String APP_WITH_DATA_PKG = "com.android.cts.appwithdata";
55     private static final String APP_WITH_DATA_CLASS =
56             "com.android.cts.appwithdata.CreatePrivateDataTest";
57     private static final String APP_WITH_DATA_CREATE_METHOD =
58             "testCreatePrivateData";
59     private static final String APP_WITH_DATA_CHECK_NOEXIST_METHOD =
60             "testEnsurePrivateDataNotExist";
61     private static final String APP_ACCESS_DATA_APK = "CtsAppAccessData.apk";
62     private static final String APP_ACCESS_DATA_PKG = "com.android.cts.appaccessdata";
63 
64     // testInstrumentationDiffCert constants
65     private static final String TARGET_INSTRUMENT_APK = "CtsTargetInstrumentationApp.apk";
66     private static final String TARGET_INSTRUMENT_PKG = "com.android.cts.targetinstrumentationapp";
67     private static final String INSTRUMENT_DIFF_CERT_APK = "CtsInstrumentationAppDiffCert.apk";
68     private static final String INSTRUMENT_DIFF_CERT_PKG =
69             "com.android.cts.instrumentationdiffcertapp";
70     private static final String INSTRUMENT_DIFF_CERT_CLASS =
71             "com.android.cts.instrumentationdiffcertapp.InstrumentationFailToRunTest";
72 
73     // testPermissionDiffCert constants
74     private static final String DECLARE_PERMISSION_APK = "CtsPermissionDeclareApp.apk";
75     private static final String DECLARE_PERMISSION_PKG = "com.android.cts.permissiondeclareapp";
76     private static final String DECLARE_PERMISSION_COMPAT_APK = "CtsPermissionDeclareAppCompat.apk";
77     private static final String DECLARE_PERMISSION_COMPAT_PKG = "com.android.cts.permissiondeclareappcompat";
78 
79     private static final String PERMISSION_DIFF_CERT_APK = "CtsUsePermissionDiffCert.apk";
80     private static final String PERMISSION_DIFF_CERT_PKG =
81         "com.android.cts.usespermissiondiffcertapp";
82 
83     private static final String DUPLICATE_DECLARE_PERMISSION_APK =
84             "CtsDuplicatePermissionDeclareApp.apk";
85     private static final String DUPLICATE_DECLARE_PERMISSION_PKG =
86             "com.android.cts.duplicatepermissiondeclareapp";
87 
88     private static final String LOG_TAG = "AppSecurityTests";
89 
90     @Before
setUp()91     public void setUp() throws Exception {
92         Utils.prepareSingleUser(getDevice());
93         assertNotNull(getBuild());
94     }
95 
96     /**
97      * Test that an app update cannot be installed over an existing app if it has a different
98      * certificate.
99      */
100     @Test
101     @AppModeFull(reason = "'full' portion of the hostside test")
testAppUpgradeDifferentCerts_full()102     public void testAppUpgradeDifferentCerts_full() throws Exception {
103         testAppUpgradeDifferentCerts(false);
104     }
105     @Test
106     @AppModeInstant(reason = "'instant' portion of the hostside test")
testAppUpgradeDifferentCerts_instant()107     public void testAppUpgradeDifferentCerts_instant() throws Exception {
108         testAppUpgradeDifferentCerts(true);
109     }
testAppUpgradeDifferentCerts(boolean instant)110     private void testAppUpgradeDifferentCerts(boolean instant) throws Exception {
111         Log.i(LOG_TAG, "installing app upgrade with different certs");
112         try {
113             getDevice().uninstallPackage(SIMPLE_APP_PKG);
114             getDevice().uninstallPackage(SIMPLE_APP_DIFF_CERT_APK);
115 
116             new InstallMultiple(instant).addFile(SIMPLE_APP_APK).run();
117             new InstallMultiple(instant).addFile(SIMPLE_APP_DIFF_CERT_APK)
118                     .runExpectingFailure("INSTALL_FAILED_UPDATE_INCOMPATIBLE");
119         } finally {
120             getDevice().uninstallPackage(SIMPLE_APP_PKG);
121             getDevice().uninstallPackage(SIMPLE_APP_DIFF_CERT_APK);
122         }
123     }
124 
125     /**
126      * Test that an app cannot access another app's private data.
127      */
128     @Test
129     @AppModeFull(reason = "'full' portion of the hostside test")
testAppFailAccessPrivateData_full()130     public void testAppFailAccessPrivateData_full() throws Exception {
131         testAppFailAccessPrivateData(false);
132     }
133     @Test
134     @AppModeInstant(reason = "'instant' portion of the hostside test")
testAppFailAccessPrivateData_instant()135     public void testAppFailAccessPrivateData_instant() throws Exception {
136         testAppFailAccessPrivateData(true);
137     }
testAppFailAccessPrivateData(boolean instant)138     private void testAppFailAccessPrivateData(boolean instant)
139             throws Exception {
140         Log.i(LOG_TAG, "installing app that attempts to access another app's private data");
141         try {
142             getDevice().uninstallPackage(APP_WITH_DATA_PKG);
143             getDevice().uninstallPackage(APP_ACCESS_DATA_PKG);
144 
145             new InstallMultiple().addFile(APP_WITH_DATA_APK).run();
146             runDeviceTests(APP_WITH_DATA_PKG, APP_WITH_DATA_CLASS, APP_WITH_DATA_CREATE_METHOD);
147 
148             new InstallMultiple(instant).addFile(APP_ACCESS_DATA_APK).run();
149             runDeviceTests(APP_ACCESS_DATA_PKG, null, null, instant);
150         } finally {
151             getDevice().uninstallPackage(APP_WITH_DATA_PKG);
152             getDevice().uninstallPackage(APP_ACCESS_DATA_PKG);
153         }
154     }
155 
156     /**
157      * Test that uninstall of an app removes its private data.
158      */
159     @Test
160     @AppModeFull(reason = "'full' portion of the hostside test")
testUninstallRemovesData_full()161     public void testUninstallRemovesData_full() throws Exception {
162         testUninstallRemovesData(false);
163     }
164     @Test
165     @AppModeInstant(reason = "'instant' portion of the hostside test")
testUninstallRemovesData_instant()166     public void testUninstallRemovesData_instant() throws Exception {
167         testUninstallRemovesData(true);
168     }
testUninstallRemovesData(boolean instant)169     private void testUninstallRemovesData(boolean instant) throws Exception {
170         Log.i(LOG_TAG, "Uninstalling app, verifying data is removed.");
171         try {
172             getDevice().uninstallPackage(APP_WITH_DATA_PKG);
173 
174             new InstallMultiple(instant).addFile(APP_WITH_DATA_APK).run();
175             runDeviceTests(
176                     APP_WITH_DATA_PKG, APP_WITH_DATA_CLASS, APP_WITH_DATA_CREATE_METHOD);
177 
178             getDevice().uninstallPackage(APP_WITH_DATA_PKG);
179 
180             new InstallMultiple(instant).addFile(APP_WITH_DATA_APK).run();
181             runDeviceTests(
182                     APP_WITH_DATA_PKG, APP_WITH_DATA_CLASS, APP_WITH_DATA_CHECK_NOEXIST_METHOD);
183         } finally {
184             getDevice().uninstallPackage(APP_WITH_DATA_PKG);
185         }
186     }
187 
188     /**
189      * Test that an app cannot instrument another app that is signed with different certificate.
190      */
191     // RestrictedBuildTest ensures the build only runs on user builds where the signature
192     // verification will be performed, but JUnit4TestNotRun reports the test will not be run because
193     // the method does not have the @Test annotation.
194     @SuppressWarnings("JUnit4TestNotRun")
195     @RestrictedBuildTest
196     @AppModeFull(reason = "'full' portion of the hostside test")
testInstrumentationDiffCert_full()197     public void testInstrumentationDiffCert_full() throws Exception {
198         testInstrumentationDiffCert(false, false);
199     }
200     @Test
201     @AppModeInstant(reason = "'instant' portion of the hostside test")
testInstrumentationDiffCert_instant()202     public void testInstrumentationDiffCert_instant() throws Exception {
203         testInstrumentationDiffCert(false, true);
204         testInstrumentationDiffCert(true, false);
205         testInstrumentationDiffCert(true, true);
206     }
testInstrumentationDiffCert(boolean targetInstant, boolean instrumentInstant)207     private void testInstrumentationDiffCert(boolean targetInstant, boolean instrumentInstant)
208             throws Exception {
209         Log.i(LOG_TAG, "installing app that attempts to instrument another app");
210         try {
211             // cleanup test app that might be installed from previous partial test run
212             getDevice().uninstallPackage(TARGET_INSTRUMENT_PKG);
213             getDevice().uninstallPackage(INSTRUMENT_DIFF_CERT_PKG);
214 
215             new InstallMultiple(targetInstant).addFile(TARGET_INSTRUMENT_APK).run();
216             new InstallMultiple(instrumentInstant).addFile(INSTRUMENT_DIFF_CERT_APK).run();
217 
218             // if we've installed either the instrumentation or target as an instant application,
219             // starting an instrumentation will just fail instead of throwing a security exception
220             // because neither the target nor instrumentation packages can see one another
221             final String methodName = (targetInstant|instrumentInstant)
222                     ? "testInstrumentationNotAllowed_fail"
223                     : "testInstrumentationNotAllowed_exception";
224             runDeviceTests(INSTRUMENT_DIFF_CERT_PKG, INSTRUMENT_DIFF_CERT_CLASS, methodName);
225         } finally {
226             getDevice().uninstallPackage(TARGET_INSTRUMENT_PKG);
227             getDevice().uninstallPackage(INSTRUMENT_DIFF_CERT_PKG);
228         }
229     }
230 
231     /**
232      * Test that an app cannot use a signature-enforced permission if it is signed with a different
233      * certificate than the app that declared the permission.
234      */
235     @Test
236     @AppModeFull(reason = "Only the platform can define permissions obtainable by instant applications")
237     @AsbSecurityTest(cveBugId = 111934948)
testPermissionDiffCert()238     public void testPermissionDiffCert() throws Exception {
239         Log.i(LOG_TAG, "installing app that attempts to use permission of another app");
240         try {
241             // cleanup test app that might be installed from previous partial test run
242             getDevice().uninstallPackage(DECLARE_PERMISSION_PKG);
243             getDevice().uninstallPackage(DECLARE_PERMISSION_COMPAT_PKG);
244             getDevice().uninstallPackage(PERMISSION_DIFF_CERT_PKG);
245 
246             new InstallMultiple().addFile(DECLARE_PERMISSION_APK).run();
247             new InstallMultiple().addFile(DECLARE_PERMISSION_COMPAT_APK).run();
248 
249             new InstallMultiple().addFile(PERMISSION_DIFF_CERT_APK).run();
250 
251             // Enable alert window permission so it can start activity in background
252             enableAlertWindowAppOp(DECLARE_PERMISSION_PKG);
253 
254             runDeviceTests(PERMISSION_DIFF_CERT_PKG, null);
255         } finally {
256             getDevice().uninstallPackage(DECLARE_PERMISSION_PKG);
257             getDevice().uninstallPackage(DECLARE_PERMISSION_COMPAT_PKG);
258             getDevice().uninstallPackage(PERMISSION_DIFF_CERT_PKG);
259         }
260     }
261 
262     /**
263      * Test that an app cannot set the installer package for an app with a different
264      * signature.
265      */
266     @Test
267     @AppModeFull(reason = "Only full apps can hold INSTALL_PACKAGES")
268     @AsbSecurityTest(cveBugId = 150857253)
testCrossPackageDiffCertSetInstaller()269     public void testCrossPackageDiffCertSetInstaller() throws Exception {
270         Log.i(LOG_TAG, "installing app that attempts to use permission of another app");
271         try {
272             // cleanup test app that might be installed from previous partial test run
273             getDevice().uninstallPackage(DECLARE_PERMISSION_PKG);
274             getDevice().uninstallPackage(DECLARE_PERMISSION_COMPAT_PKG);
275             getDevice().uninstallPackage(PERMISSION_DIFF_CERT_PKG);
276 
277             new InstallMultiple().addFile(DECLARE_PERMISSION_APK).run();
278             new InstallMultiple().addFile(DECLARE_PERMISSION_COMPAT_APK).run();
279             new InstallMultiple().addFile(PERMISSION_DIFF_CERT_APK).run();
280 
281             // Enable alert window permission so it can start activity in background
282             enableAlertWindowAppOp(DECLARE_PERMISSION_PKG);
283 
284             runCrossPackageInstallerDeviceTest(PERMISSION_DIFF_CERT_PKG, "assertBefore");
285             runCrossPackageInstallerDeviceTest(DECLARE_PERMISSION_PKG, "takeInstaller");
286             runCrossPackageInstallerDeviceTest(PERMISSION_DIFF_CERT_PKG, "attemptTakeOver");
287             runCrossPackageInstallerDeviceTest(DECLARE_PERMISSION_PKG, "clearInstaller");
288             runCrossPackageInstallerDeviceTest(PERMISSION_DIFF_CERT_PKG, "assertAfter");
289         } finally {
290             getDevice().uninstallPackage(DECLARE_PERMISSION_PKG);
291             getDevice().uninstallPackage(DECLARE_PERMISSION_COMPAT_PKG);
292             getDevice().uninstallPackage(PERMISSION_DIFF_CERT_PKG);
293         }
294     }
295 
296     /**
297      * Utility method to make actual test method easier to read.
298      */
runCrossPackageInstallerDeviceTest(String pkgName, String testMethodName)299     private void runCrossPackageInstallerDeviceTest(String pkgName, String testMethodName)
300             throws DeviceNotAvailableException {
301         Map<String, String> arguments = new HashMap<>();
302         arguments.put("runExplicit", "true");
303         runDeviceTests(getDevice(), null, pkgName, pkgName + ".ModifyInstallerCrossPackageTest",
304                 testMethodName, null, 10 * 60 * 1000L, 10 * 60 * 1000L, 0L, true, false, arguments);
305     }
306 
307     /**
308      * Test what happens if an app tried to take a permission away from another
309      */
310     @Test
rebootWithDuplicatePermission()311     public void rebootWithDuplicatePermission() throws Exception {
312         try {
313             new InstallMultiple(false).addFile(DECLARE_PERMISSION_APK).run();
314             new InstallMultiple(false).addFile(DUPLICATE_DECLARE_PERMISSION_APK).run();
315 
316             // Enable alert window permission so it can start activity in background
317             enableAlertWindowAppOp(DECLARE_PERMISSION_PKG);
318 
319             runDeviceTests(DUPLICATE_DECLARE_PERMISSION_PKG, null);
320 
321             // make sure behavior is preserved after reboot
322             getDevice().reboot();
323             waitForBootCompleted(getDevice());
324             runDeviceTests(DUPLICATE_DECLARE_PERMISSION_PKG, null);
325         } finally {
326             getDevice().uninstallPackage(DECLARE_PERMISSION_PKG);
327             getDevice().uninstallPackage(DUPLICATE_DECLARE_PERMISSION_PKG);
328         }
329     }
330 
331     /**
332      * Tests that an arbitrary file cannot be installed using the 'cmd' command.
333      */
334     @Test
335     @AppModeFull(reason = "'full' portion of the hostside test")
testAdbInstallFile_full()336     public void testAdbInstallFile_full() throws Exception {
337         testAdbInstallFile(false);
338     }
339     @Test
340     @AppModeInstant(reason = "'instant' portion of the hostside test")
testAdbInstallFile_instant()341     public void testAdbInstallFile_instant() throws Exception {
342         testAdbInstallFile(true);
343     }
testAdbInstallFile(boolean instant)344     private void testAdbInstallFile(boolean instant) throws Exception {
345         String output = getDevice().executeShellCommand(
346                 "cmd package install"
347                         + (instant ? " --instant" : " --full")
348                         + " -S 1024 /data/local/tmp/foo.apk");
349         assertTrue("Error text", output.contains("Error"));
350     }
351 
enableAlertWindowAppOp(String pkgName)352     private void enableAlertWindowAppOp(String pkgName) throws Exception {
353         getDevice().executeShellCommand(
354                 "appops set " + pkgName + " android:system_alert_window allow");
355         String result = "No operations.";
356         while (result.contains("No operations")) {
357             result = getDevice().executeShellCommand(
358                     "appops get " + pkgName + " android:system_alert_window");
359         }
360     }
361 }
362