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