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