Home | History | Annotate | Download | only in appsecurity
      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