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