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