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