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 com.android.cts.appsecurity; 18 19 import com.android.cts.tradefed.build.CtsBuildHelper; 20 import com.android.ddmlib.Log; 21 import com.android.ddmlib.testrunner.RemoteAndroidTestRunner; 22 import com.android.tradefed.build.IBuildInfo; 23 import com.android.tradefed.device.DeviceNotAvailableException; 24 import com.android.tradefed.device.ITestDevice; 25 import com.android.tradefed.result.CollectingTestListener; 26 import com.android.tradefed.result.TestRunResult; 27 import com.android.tradefed.testtype.DeviceTestCase; 28 import com.android.tradefed.testtype.IBuildReceiver; 29 30 import java.io.File; 31 import java.io.FileNotFoundException; 32 33 /** 34 * Set of tests that verify various security checks involving multiple apps are properly enforced. 35 */ 36 public class AppSecurityTests extends DeviceTestCase implements IBuildReceiver { 37 38 // testSharedUidDifferentCerts constants 39 private static final String SHARED_UI_APK = "CtsSharedUidInstall.apk"; 40 private static final String SHARED_UI_PKG = "com.android.cts.shareuidinstall"; 41 private static final String SHARED_UI_DIFF_CERT_APK = "CtsSharedUidInstallDiffCert.apk"; 42 private static final String SHARED_UI_DIFF_CERT_PKG = 43 "com.android.cts.shareuidinstalldiffcert"; 44 45 // testAppUpgradeDifferentCerts constants 46 private static final String SIMPLE_APP_APK = "CtsSimpleAppInstall.apk"; 47 private static final String SIMPLE_APP_PKG = "com.android.cts.simpleappinstall"; 48 private static final String SIMPLE_APP_DIFF_CERT_APK = "CtsSimpleAppInstallDiffCert.apk"; 49 50 // testAppFailAccessPrivateData constants 51 private static final String APP_WITH_DATA_APK = "CtsAppWithData.apk"; 52 private static final String APP_WITH_DATA_PKG = "com.android.cts.appwithdata"; 53 private static final String APP_WITH_DATA_CLASS = 54 "com.android.cts.appwithdata.CreatePrivateDataTest"; 55 private static final String APP_WITH_DATA_CREATE_METHOD = 56 "testCreatePrivateData"; 57 private static final String APP_WITH_DATA_CHECK_NOEXIST_METHOD = 58 "testEnsurePrivateDataNotExist"; 59 private static final String APP_ACCESS_DATA_APK = "CtsAppAccessData.apk"; 60 private static final String APP_ACCESS_DATA_PKG = "com.android.cts.appaccessdata"; 61 62 // External storage constants 63 private static final String EXTERNAL_STORAGE_APP_APK = "CtsExternalStorageApp.apk"; 64 private static final String EXTERNAL_STORAGE_APP_PKG = "com.android.cts.externalstorageapp"; 65 private static final String EXTERNAL_STORAGE_APP_CLASS = EXTERNAL_STORAGE_APP_PKG 66 + ".ExternalStorageTest"; 67 private static final String WRITE_EXTERNAL_STORAGE_APP_APK = "CtsWriteExternalStorageApp.apk"; 68 private static final String 69 WRITE_EXTERNAL_STORAGE_APP_PKG = "com.android.cts.writeexternalstorageapp"; 70 private static final String WRITE_EXTERNAL_STORAGE_APP_CLASS = WRITE_EXTERNAL_STORAGE_APP_PKG 71 + ".WriteExternalStorageTest"; 72 73 // testInstrumentationDiffCert constants 74 private static final String TARGET_INSTRUMENT_APK = "CtsTargetInstrumentationApp.apk"; 75 private static final String TARGET_INSTRUMENT_PKG = "com.android.cts.targetinstrumentationapp"; 76 private static final String INSTRUMENT_DIFF_CERT_APK = "CtsInstrumentationAppDiffCert.apk"; 77 private static final String INSTRUMENT_DIFF_CERT_PKG = 78 "com.android.cts.instrumentationdiffcertapp"; 79 80 // testPermissionDiffCert constants 81 private static final String DECLARE_PERMISSION_APK = "CtsPermissionDeclareApp.apk"; 82 private static final String DECLARE_PERMISSION_PKG = "com.android.cts.permissiondeclareapp"; 83 private static final String PERMISSION_DIFF_CERT_APK = "CtsUsePermissionDiffCert.apk"; 84 private static final String PERMISSION_DIFF_CERT_PKG = 85 "com.android.cts.usespermissiondiffcertapp"; 86 87 private static final String READ_EXTERNAL_STORAGE = "android.permission.READ_EXTERNAL_STORAGE"; 88 89 private static final String LOG_TAG = "AppSecurityTests"; 90 91 private CtsBuildHelper mCtsBuild; 92 93 /** 94 * {@inheritDoc} 95 */ 96 @Override 97 public void setBuild(IBuildInfo buildInfo) { 98 mCtsBuild = CtsBuildHelper.createBuildHelper(buildInfo); 99 } 100 101 private File getTestAppFile(String fileName) throws FileNotFoundException { 102 return mCtsBuild.getTestApp(fileName); 103 } 104 105 @Override 106 protected void setUp() throws Exception { 107 super.setUp(); 108 // ensure build has been set before test is run 109 assertNotNull(mCtsBuild); 110 } 111 112 /** 113 * Test that an app that declares the same shared uid as an existing app, cannot be installed 114 * if it is signed with a different certificate. 115 */ 116 public void testSharedUidDifferentCerts() throws Exception { 117 Log.i(LOG_TAG, "installing apks with shared uid, but different certs"); 118 try { 119 // cleanup test apps that might be installed from previous partial test run 120 getDevice().uninstallPackage(SHARED_UI_PKG); 121 getDevice().uninstallPackage(SHARED_UI_DIFF_CERT_PKG); 122 123 String installResult = getDevice().installPackage(getTestAppFile(SHARED_UI_APK), 124 false); 125 assertNull(String.format("failed to install shared uid app, Reason: %s", installResult), 126 installResult); 127 installResult = getDevice().installPackage(getTestAppFile(SHARED_UI_DIFF_CERT_APK), 128 false); 129 assertNotNull("shared uid app with different cert than existing app installed " + 130 "successfully", installResult); 131 assertEquals("INSTALL_FAILED_SHARED_USER_INCOMPATIBLE", installResult); 132 } 133 finally { 134 getDevice().uninstallPackage(SHARED_UI_PKG); 135 getDevice().uninstallPackage(SHARED_UI_DIFF_CERT_PKG); 136 } 137 } 138 139 /** 140 * Test that an app update cannot be installed over an existing app if it has a different 141 * certificate. 142 */ 143 public void testAppUpgradeDifferentCerts() throws Exception { 144 Log.i(LOG_TAG, "installing app upgrade with different certs"); 145 try { 146 // cleanup test app that might be installed from previous partial test run 147 getDevice().uninstallPackage(SIMPLE_APP_PKG); 148 149 String installResult = getDevice().installPackage(getTestAppFile(SIMPLE_APP_APK), 150 false); 151 assertNull(String.format("failed to install simple app. Reason: %s", installResult), 152 installResult); 153 installResult = getDevice().installPackage(getTestAppFile(SIMPLE_APP_DIFF_CERT_APK), 154 true /* reinstall */); 155 assertNotNull("app upgrade with different cert than existing app installed " + 156 "successfully", installResult); 157 assertEquals("INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES", installResult); 158 } 159 finally { 160 getDevice().uninstallPackage(SIMPLE_APP_PKG); 161 } 162 } 163 164 /** 165 * Test that an app cannot access another app's private data. 166 */ 167 public void testAppFailAccessPrivateData() throws Exception { 168 Log.i(LOG_TAG, "installing app that attempts to access another app's private data"); 169 try { 170 // cleanup test app that might be installed from previous partial test run 171 getDevice().uninstallPackage(APP_WITH_DATA_PKG); 172 getDevice().uninstallPackage(APP_ACCESS_DATA_PKG); 173 174 String installResult = getDevice().installPackage(getTestAppFile(APP_WITH_DATA_APK), 175 false); 176 assertNull(String.format("failed to install app with data. Reason: %s", installResult), 177 installResult); 178 // run appwithdata's tests to create private data 179 assertTrue("failed to create app's private data", runDeviceTests(APP_WITH_DATA_PKG, 180 APP_WITH_DATA_CLASS, APP_WITH_DATA_CREATE_METHOD)); 181 182 installResult = getDevice().installPackage(getTestAppFile(APP_ACCESS_DATA_APK), 183 false); 184 assertNull(String.format("failed to install app access data. Reason: %s", 185 installResult), installResult); 186 // run appaccessdata's tests which attempt to access appwithdata's private data 187 assertTrue("could access app's private data", runDeviceTests(APP_ACCESS_DATA_PKG)); 188 } 189 finally { 190 getDevice().uninstallPackage(APP_WITH_DATA_PKG); 191 getDevice().uninstallPackage(APP_ACCESS_DATA_PKG); 192 } 193 } 194 195 /** 196 * Test behavior when 197 * {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} is unenforced. 198 */ 199 public void testReadExternalStorageUnenforced() throws Exception { 200 try { 201 getDevice().uninstallPackage(EXTERNAL_STORAGE_APP_PKG); 202 getDevice().uninstallPackage(WRITE_EXTERNAL_STORAGE_APP_PKG); 203 204 // stage test file on external storage 205 getDevice().pushString("CAEK", "/sdcard/meow"); 206 207 // mark permission as not enforced 208 setPermissionEnforced(getDevice(), READ_EXTERNAL_STORAGE, false); 209 210 // install apps and run test 211 assertNull(getDevice() 212 .installPackage(getTestAppFile(EXTERNAL_STORAGE_APP_APK), false)); 213 assertNull(getDevice() 214 .installPackage(getTestAppFile(WRITE_EXTERNAL_STORAGE_APP_APK), false)); 215 216 // normal app should be able to read 217 assertTrue("Normal app unable to read external storage", runDeviceTests( 218 EXTERNAL_STORAGE_APP_PKG, EXTERNAL_STORAGE_APP_CLASS, 219 "testReadExternalStorage")); 220 221 // WRITE_EXTERNAL app should be able to read and write 222 assertTrue("WRITE_EXTERNAL app unable to read external storage", runDeviceTests( 223 WRITE_EXTERNAL_STORAGE_APP_PKG, WRITE_EXTERNAL_STORAGE_APP_CLASS, 224 "testReadExternalStorage")); 225 assertTrue("WRITE_EXTERNAL app unable to write external storage", runDeviceTests( 226 WRITE_EXTERNAL_STORAGE_APP_PKG, WRITE_EXTERNAL_STORAGE_APP_CLASS, 227 "testWriteExternalStorage")); 228 229 } finally { 230 getDevice().uninstallPackage(EXTERNAL_STORAGE_APP_PKG); 231 getDevice().uninstallPackage(WRITE_EXTERNAL_STORAGE_APP_PKG); 232 } 233 } 234 235 /** 236 * Test behavior when 237 * {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} is enforced. 238 */ 239 public void testReadExternalStorageEnforced() throws Exception { 240 try { 241 getDevice().uninstallPackage(EXTERNAL_STORAGE_APP_PKG); 242 getDevice().uninstallPackage(WRITE_EXTERNAL_STORAGE_APP_PKG); 243 244 // stage test file on external storage 245 getDevice().pushString("CAEK", "/sdcard/meow"); 246 247 // mark permission as enforced 248 setPermissionEnforced(getDevice(), READ_EXTERNAL_STORAGE, true); 249 250 // install apps and run test 251 assertNull(getDevice() 252 .installPackage(getTestAppFile(EXTERNAL_STORAGE_APP_APK), false)); 253 assertNull(getDevice() 254 .installPackage(getTestAppFile(WRITE_EXTERNAL_STORAGE_APP_APK), false)); 255 256 // normal app should not be able to read 257 assertTrue("Normal app able to read external storage", runDeviceTests( 258 EXTERNAL_STORAGE_APP_PKG, EXTERNAL_STORAGE_APP_CLASS, 259 "testFailReadExternalStorage")); 260 261 // WRITE_EXTERNAL app should be able to read and write 262 assertTrue("WRITE_EXTERNAL app unable to read external storage", runDeviceTests( 263 WRITE_EXTERNAL_STORAGE_APP_PKG, WRITE_EXTERNAL_STORAGE_APP_CLASS, 264 "testReadExternalStorage")); 265 assertTrue("WRITE_EXTERNAL app unable to write external storage", runDeviceTests( 266 WRITE_EXTERNAL_STORAGE_APP_PKG, WRITE_EXTERNAL_STORAGE_APP_CLASS, 267 "testWriteExternalStorage")); 268 269 } finally { 270 getDevice().uninstallPackage(EXTERNAL_STORAGE_APP_PKG); 271 getDevice().uninstallPackage(WRITE_EXTERNAL_STORAGE_APP_PKG); 272 } 273 } 274 275 /** 276 * Test that uninstall of an app removes its private data. 277 */ 278 public void testUninstallRemovesData() throws Exception { 279 Log.i(LOG_TAG, "Uninstalling app, verifying data is removed."); 280 try { 281 // cleanup test app that might be installed from previous partial test run 282 getDevice().uninstallPackage(APP_WITH_DATA_PKG); 283 284 String installResult = getDevice().installPackage(getTestAppFile(APP_WITH_DATA_APK), 285 false); 286 assertNull(String.format("failed to install app with data. Reason: %s", installResult), 287 installResult); 288 // run appwithdata's tests to create private data 289 assertTrue("failed to create app's private data", runDeviceTests(APP_WITH_DATA_PKG, 290 APP_WITH_DATA_CLASS, APP_WITH_DATA_CREATE_METHOD)); 291 292 getDevice().uninstallPackage(APP_WITH_DATA_PKG); 293 294 installResult = getDevice().installPackage(getTestAppFile(APP_WITH_DATA_APK), 295 false); 296 assertNull(String.format("failed to install app with data second time. Reason: %s", 297 installResult), installResult); 298 // run appwithdata's 'check if file exists' test 299 assertTrue("app's private data still exists after install", runDeviceTests( 300 APP_WITH_DATA_PKG, APP_WITH_DATA_CLASS, APP_WITH_DATA_CHECK_NOEXIST_METHOD)); 301 302 } 303 finally { 304 getDevice().uninstallPackage(APP_WITH_DATA_PKG); 305 } 306 } 307 308 /** 309 * Test that an app cannot instrument another app that is signed with different certificate. 310 */ 311 public void testInstrumentationDiffCert() throws Exception { 312 Log.i(LOG_TAG, "installing app that attempts to instrument another app"); 313 try { 314 // cleanup test app that might be installed from previous partial test run 315 getDevice().uninstallPackage(TARGET_INSTRUMENT_PKG); 316 getDevice().uninstallPackage(INSTRUMENT_DIFF_CERT_PKG); 317 318 String installResult = getDevice().installPackage( 319 getTestAppFile(TARGET_INSTRUMENT_APK), false); 320 assertNull(String.format("failed to install target instrumentation app. Reason: %s", 321 installResult), installResult); 322 323 // the app will install, but will get error at runtime when starting instrumentation 324 installResult = getDevice().installPackage(getTestAppFile(INSTRUMENT_DIFF_CERT_APK), 325 false); 326 assertNull(String.format( 327 "failed to install instrumentation app with diff cert. Reason: %s", 328 installResult), installResult); 329 // run INSTRUMENT_DIFF_CERT_PKG tests 330 // this test will attempt to call startInstrumentation directly and verify 331 // SecurityException is thrown 332 assertTrue("running instrumentation with diff cert unexpectedly succeeded", 333 runDeviceTests(INSTRUMENT_DIFF_CERT_PKG)); 334 } 335 finally { 336 getDevice().uninstallPackage(TARGET_INSTRUMENT_PKG); 337 getDevice().uninstallPackage(INSTRUMENT_DIFF_CERT_PKG); 338 } 339 } 340 341 /** 342 * Test that an app cannot use a signature-enforced permission if it is signed with a different 343 * certificate than the app that declared the permission. 344 */ 345 public void testPermissionDiffCert() throws Exception { 346 Log.i(LOG_TAG, "installing app that attempts to use permission of another app"); 347 try { 348 // cleanup test app that might be installed from previous partial test run 349 getDevice().uninstallPackage(DECLARE_PERMISSION_PKG); 350 getDevice().uninstallPackage(PERMISSION_DIFF_CERT_PKG); 351 352 String installResult = getDevice().installPackage( 353 getTestAppFile(DECLARE_PERMISSION_APK), false); 354 assertNull(String.format("failed to install declare permission app. Reason: %s", 355 installResult), installResult); 356 357 // the app will install, but will get error at runtime 358 installResult = getDevice().installPackage(getTestAppFile(PERMISSION_DIFF_CERT_APK), 359 false); 360 assertNull(String.format("failed to install permission app with diff cert. Reason: %s", 361 installResult), installResult); 362 // run PERMISSION_DIFF_CERT_PKG tests which try to access the permission 363 assertTrue("unexpected result when running permission tests", 364 runDeviceTests(PERMISSION_DIFF_CERT_PKG)); 365 } 366 finally { 367 getDevice().uninstallPackage(DECLARE_PERMISSION_PKG); 368 getDevice().uninstallPackage(PERMISSION_DIFF_CERT_PKG); 369 } 370 } 371 372 /** 373 * Helper method that will the specified packages tests on device. 374 * 375 * @param pkgName Android application package for tests 376 * @return <code>true</code> if all tests passed. 377 * @throws DeviceNotAvailableException if connection to device was lost. 378 */ 379 private boolean runDeviceTests(String pkgName) throws DeviceNotAvailableException { 380 return runDeviceTests(pkgName, null, null); 381 } 382 383 /** 384 * Helper method that will the specified packages tests on device. 385 * 386 * @param pkgName Android application package for tests 387 * @return <code>true</code> if all tests passed. 388 * @throws DeviceNotAvailableException if connection to device was lost. 389 */ 390 private boolean runDeviceTests(String pkgName, String testClassName, String testMethodName) 391 throws DeviceNotAvailableException { 392 TestRunResult runResult = doRunTests(pkgName, testClassName, testMethodName); 393 return !runResult.hasFailedTests(); 394 } 395 396 /** 397 * Helper method to run tests and return the listener that collected the results. 398 * 399 * @param pkgName Android application package for tests 400 * @return the {@link TestRunResult} 401 * @throws DeviceNotAvailableException if connection to device was lost. 402 */ 403 private TestRunResult doRunTests(String pkgName, String testClassName, 404 String testMethodName) throws DeviceNotAvailableException { 405 406 RemoteAndroidTestRunner testRunner = new RemoteAndroidTestRunner(pkgName, 407 getDevice().getIDevice()); 408 if (testClassName != null && testMethodName != null) { 409 testRunner.setMethodName(testClassName, testMethodName); 410 } 411 CollectingTestListener listener = new CollectingTestListener(); 412 getDevice().runInstrumentationTests(testRunner, listener); 413 return listener.getCurrentRunResults(); 414 } 415 416 private static void setPermissionEnforced( 417 ITestDevice device, String permission, boolean enforced) 418 throws DeviceNotAvailableException { 419 device.executeShellCommand("pm set-permission-enforced " + permission + " " 420 + Boolean.toString(enforced)); 421 } 422 } 423