blob: 3722dd2867b69c0cb04a7bf25c054426bede545a [file] [log] [blame]
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.appsecurity.cts;
import com.android.tradefed.util.RunUtil;
import static android.appsecurity.cts.Utils.waitForBootCompleted;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assume.assumeThat;
import static org.junit.Assume.assumeTrue;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.log.LogUtil;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.HashMap;
import java.util.Map;
/**
* Set of tests that verify app data isolation works.
*/
@RunWith(DeviceJUnit4ClassRunner.class)
public class AppDataIsolationTests extends BaseAppSecurityTest {
private static final String APPA_APK = "CtsAppDataIsolationAppA.apk";
private static final String APP_SHARED_A_APK = "CtsAppDataIsolationAppSharedA.apk";
private static final String APP_DIRECT_BOOT_A_APK = "CtsAppDataIsolationAppDirectBootA.apk";
private static final String APP_API29_A_APK = "CtsAppDataIsolationAppApi29A.apk";
private static final String APPA_PKG = "com.android.cts.appdataisolation.appa";
private static final String APPA_CLASS =
"com.android.cts.appdataisolation.appa.AppATests";
private static final String APPA_METHOD_CREATE_CE_DE_DATA = "testCreateCeDeAppData";
private static final String APPA_METHOD_DELETE_EXTERNAL_DIRS = "testDeleteExternalDirs";
private static final String APPA_METHOD_CHECK_CE_DATA_EXISTS = "testAppACeDataExists";
private static final String APPA_METHOD_CHECK_CE_DATA_DOES_NOT_EXIST =
"testAppACeDataDoesNotExist";
private static final String APPA_METHOD_CHECK_DE_DATA_EXISTS = "testAppADeDataExists";
private static final String APPA_METHOD_CHECK_DE_DATA_DOES_NOT_EXIST =
"testAppADeDataDoesNotExist";
private static final String APPA_METHOD_CHECK_CUR_PROFILE_ACCESSIBLE =
"testAppACurProfileDataAccessible";
private static final String APPA_METHOD_CHECK_REF_PROFILE_ACCESSIBLE =
"testAppARefProfileDataAccessible";
private static final String APPA_METHOD_UNLOCK_DEVICE_AND_VERIFY_CE_DE_EXTERNAL_EXIST =
"testAppAUnlockDeviceAndVerifyCeDeExternalDataExist";
private static final String APPA_METHOD_CANNOT_ACCESS_APPB_DIR = "testCannotAccessAppBDataDir";
private static final String APPA_METHOD_TEST_UNLOCK_DEVICE =
"testUnlockDevice";
private static final String APPB_APK = "CtsAppDataIsolationAppB.apk";
private static final String APP_SHARED_B_APK = "CtsAppDataIsolationAppSharedB.apk";
private static final String APPB_PKG = "com.android.cts.appdataisolation.appb";
private static final String APPB_CLASS =
"com.android.cts.appdataisolation.appb.AppBTests";
private static final String APPB_METHOD_CAN_NOT_ACCESS_APPA_DIR = "testCanNotAccessAppADataDir";
private static final String APPB_METHOD_CAN_ACCESS_APPA_DIR = "testCanAccessAppADataDir";
private static final String APPA_METHOD_CREATE_EXTERNAL_DIRS = "testCreateExternalDirs";
private static final String APPA_METHOD_TEST_ISOLATED_PROCESS = "testIsolatedProcess";
private static final String APPA_METHOD_TEST_APP_ZYGOTE_ISOLATED_PROCESS =
"testAppZygoteIsolatedProcess";
private static final String APPB_METHOD_CAN_NOT_ACCESS_APPA_EXTERNAL_DIRS =
"testCanNotAccessAppAExternalDirs";
private static final String APPB_METHOD_CAN_ACCESS_APPA_EXTERNAL_DIRS =
"testCanAccessAppAExternalDirs";
private static final String APPA_METHOD_CHECK_EXTERNAL_DIRS_DO_NOT_EXIST =
"testAppAExternalDirsDoNotExist";
private static final String APPA_METHOD_CHECK_EXTERNAL_DIRS_DO_EXIST =
"testAppAExternalDirsDoExist";
private static final String APPA_METHOD_CHECK_EXTERNAL_DIRS_UNAVAILABLE =
"testAppAExternalDirsUnavailable";
private static final String APPA_METHOD_TEST_OTHER_USER_DIRS_NOT_PRESENT =
"testOtherUserDirsNotPresent";
private static final String APPA_METHOD_TEST_OTHER_USER_DIRS_NOT_ACCESSIBLE =
"testOtherUserDirsNotAccessible";
private int mOtherUser = -1;
@Before
public void setUp() throws Exception {
Utils.prepareSingleUser(getDevice());
getDevice().uninstallPackage(APPA_PKG);
getDevice().uninstallPackage(APPB_PKG);
}
@After
public void tearDown() throws Exception {
if (mOtherUser != -1) {
getDevice().removeUser(mOtherUser);
}
getDevice().uninstallPackage(APPA_PKG);
getDevice().uninstallPackage(APPB_PKG);
}
@Test
public void testAppAbleToAccessItsDataAfterForceStop() throws Exception {
// Install AppA and verify no data stored
new InstallMultiple().addFile(APPA_APK).run();
runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_CHECK_CE_DATA_DOES_NOT_EXIST);
runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_CHECK_DE_DATA_DOES_NOT_EXIST);
runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_CHECK_EXTERNAL_DIRS_DO_NOT_EXIST);
// Create data in CE, DE and external storage
runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_CREATE_CE_DE_DATA);
runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_CREATE_EXTERNAL_DIRS);
// Verify CE, DE and external storage contains data, cur profile is accessible and ref
// profile is not accessible
runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_CHECK_CE_DATA_EXISTS);
runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_CHECK_DE_DATA_EXISTS);
runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_CHECK_EXTERNAL_DIRS_DO_EXIST);
runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_CHECK_CUR_PROFILE_ACCESSIBLE);
runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_CHECK_REF_PROFILE_ACCESSIBLE);
// Force stop and verify CE, DE and external storage contains data, cur profile is
// accessible and ref profile is not accessible, to confirm it's binding back the same data
// directory, not binding to a wrong one / create a new one.
forceStopPackage(APPA_PKG);
runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_CHECK_CE_DATA_EXISTS);
runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_CHECK_DE_DATA_EXISTS);
runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_CHECK_EXTERNAL_DIRS_DO_EXIST);
runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_CHECK_CUR_PROFILE_ACCESSIBLE);
runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_CHECK_REF_PROFILE_ACCESSIBLE);
}
@Test
public void testAppAbleToAccessItsDataAfterReboot() throws Exception {
// Install AppA and verify no data stored
new InstallMultiple().addFile(APPA_APK).run();
runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_CHECK_CE_DATA_DOES_NOT_EXIST);
runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_CHECK_DE_DATA_DOES_NOT_EXIST);
runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_CHECK_EXTERNAL_DIRS_DO_NOT_EXIST);
// Create data in CE, DE and external storage
runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_CREATE_CE_DE_DATA);
runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_CREATE_EXTERNAL_DIRS);
// Verify CE, DE and external storage contains data, cur profile is accessible and ref
// profile is not accessible
runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_CHECK_CE_DATA_EXISTS);
runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_CHECK_DE_DATA_EXISTS);
runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_CHECK_EXTERNAL_DIRS_DO_EXIST);
runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_CHECK_CUR_PROFILE_ACCESSIBLE);
runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_CHECK_REF_PROFILE_ACCESSIBLE);
// Reboot and verify CE, DE and external storage contains data, cur profile is accessible
// and ref profile is not accessible
reboot();
runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_CHECK_CE_DATA_EXISTS);
runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_CHECK_DE_DATA_EXISTS);
runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_CHECK_EXTERNAL_DIRS_DO_EXIST);
runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_CHECK_CUR_PROFILE_ACCESSIBLE);
runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_CHECK_REF_PROFILE_ACCESSIBLE);
}
@Test
public void testDirectBootModeWorks() throws Exception {
if (!"file".equals(getDevice().getProperty("ro.crypto.type"))) {
LogUtil.CLog.d("Device is NOT encrypted with file-based encryption. skipping test");
return;
}
assumeTrue("Screen lock is not supported so skip direct boot test",
hasDeviceFeature("android.software.secure_lock_screen"));
// Install AppA and verify no data stored
new InstallMultiple().addFile(APP_DIRECT_BOOT_A_APK).run();
new InstallMultiple().addFile(APPB_APK).run();
runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_CHECK_CE_DATA_DOES_NOT_EXIST);
runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_CHECK_DE_DATA_DOES_NOT_EXIST);
runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_CHECK_EXTERNAL_DIRS_DO_NOT_EXIST);
// Create data in CE, DE and external storage
runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_CREATE_CE_DE_DATA);
runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_CREATE_EXTERNAL_DIRS);
// Verify CE, DE and external storage contains data, cur profile is accessible and ref
// profile is not accessible
runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_CHECK_CE_DATA_EXISTS);
runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_CHECK_DE_DATA_EXISTS);
runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_CHECK_EXTERNAL_DIRS_DO_EXIST);
runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_CHECK_CUR_PROFILE_ACCESSIBLE);
runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_CHECK_REF_PROFILE_ACCESSIBLE);
try {
// Setup screenlock
getDevice().executeShellCommand("locksettings set-disabled false");
String response = getDevice().executeShellCommand("locksettings set-pin 1234");
if (!response.contains("1234")) {
// This seems to fail occasionally. Try again once, then give up.
RunUtil.getDefault().sleep(500);
response = getDevice().executeShellCommand("locksettings set-pin 1234");
assumeTrue("Test requires setting a pin, which failed: " + response,
response.contains("1234"));
}
// Give enough time for vold to update keys
RunUtil.getDefault().sleep(15000);
// Follow DirectBootHostTest, reboot system into known state with keys ejected
getDevice().rebootUntilOnline();
waitForBootCompleted(getDevice());
// Verify DE data is still readable and writeable, while CE and external data are not
// accessible
runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_CHECK_DE_DATA_EXISTS);
runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_CHECK_CE_DATA_DOES_NOT_EXIST);
runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_CHECK_EXTERNAL_DIRS_UNAVAILABLE);
runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_CHECK_CUR_PROFILE_ACCESSIBLE);
runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_CHECK_REF_PROFILE_ACCESSIBLE);
// Verify cannot access other apps data
runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_CANNOT_ACCESS_APPB_DIR);
// Unlock device and verify CE, DE and external data still exist, without killing the
// process, as test process usually will be killed after the test
runDeviceTests(APPA_PKG, APPA_CLASS,
APPA_METHOD_UNLOCK_DEVICE_AND_VERIFY_CE_DE_EXTERNAL_EXIST);
// Restart test app and verify CE, DE and external storage contains data, cur profile is
// accessible and ref profile is not accessible
runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_CHECK_CE_DATA_EXISTS);
runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_CHECK_DE_DATA_EXISTS);
runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_CHECK_EXTERNAL_DIRS_DO_EXIST);
runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_CHECK_CUR_PROFILE_ACCESSIBLE);
runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_CHECK_REF_PROFILE_ACCESSIBLE);
} finally {
try {
// Always try to unlock first, then clear screenlock setting
try {
runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_TEST_UNLOCK_DEVICE);
} catch (Exception e) {}
try {
runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_DELETE_EXTERNAL_DIRS);
} catch (Exception e) {}
getDevice().executeShellCommand("locksettings clear --old 1234");
getDevice().executeShellCommand("locksettings set-disabled true");
} finally {
// Get ourselves back into a known-good state
getDevice().rebootUntilOnline();
getDevice().waitForDeviceAvailable();
}
}
}
@Test
public void testAppNotAbleToAccessItsDataAfterReinstall() throws Exception {
// Install AppA create CE DE data
new InstallMultiple().addFile(APPA_APK).run();
runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_CREATE_CE_DE_DATA);
runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_CREATE_EXTERNAL_DIRS);
// Reinstall AppA
getDevice().uninstallPackage(APPA_PKG);
new InstallMultiple().addFile(APPA_APK).run();
// Verify CE, DE and external data are removed
runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_CHECK_CE_DATA_DOES_NOT_EXIST);
runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_CHECK_DE_DATA_DOES_NOT_EXIST);
runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_CHECK_EXTERNAL_DIRS_DO_NOT_EXIST);
}
@Test
public void testNormalProcessCannotAccessOtherAppDataDir() throws Exception {
new InstallMultiple().addFile(APPA_APK).run();
new InstallMultiple().addFile(APPB_APK).run();
runDeviceTests(APPB_PKG, APPB_CLASS, APPB_METHOD_CAN_NOT_ACCESS_APPA_DIR);
}
@Test
public void testSharedAppAbleToAccessOtherAppDataDir() throws Exception {
new InstallMultiple().addFile(APP_SHARED_A_APK).run();
new InstallMultiple().addFile(APP_SHARED_B_APK).run();
runDeviceTests(APPB_PKG, APPB_CLASS, APPB_METHOD_CAN_ACCESS_APPA_DIR);
}
@Test
public void testNormalProcessCannotAccessOtherAppExternalDataDir() throws Exception {
assumeThatFuseDataIsolationIsEnabled(getDevice());
new InstallMultiple().addFile(APPA_APK).run();
new InstallMultiple().addFile(APPB_APK).run();
runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_CREATE_EXTERNAL_DIRS);
runDeviceTests(APPB_PKG, APPB_CLASS, APPB_METHOD_CAN_NOT_ACCESS_APPA_EXTERNAL_DIRS);
}
@Test
public void testSharedAppAbleToAccessOtherAppExternalDataDir() throws Exception {
new InstallMultiple().addFile(APP_SHARED_A_APK).run();
new InstallMultiple().addFile(APP_SHARED_B_APK).run();
runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_CREATE_EXTERNAL_DIRS);
runDeviceTests(APPB_PKG, APPB_CLASS, APPB_METHOD_CAN_ACCESS_APPA_EXTERNAL_DIRS);
}
@Test
public void testIsolatedProcess() throws Exception {
new InstallMultiple().addFile(APPA_APK).run();
new InstallMultiple().addFile(APPB_APK).run();
runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_TEST_ISOLATED_PROCESS);
}
@Test
public void testAppZygoteIsolatedProcess() throws Exception {
new InstallMultiple().addFile(APPA_APK).run();
new InstallMultiple().addFile(APPB_APK).run();
runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_TEST_APP_ZYGOTE_ISOLATED_PROCESS);
}
@Test
public void testAppUnableToAccessOtherUserAppDataDir() throws Exception {
assumeCanCreateUser();
mOtherUser = getDevice().createUser("other_user");
// For targetSdk > 29, directories related to other users are not visible at all.
new InstallMultiple().addFile(APPA_APK).run();
new InstallMultiple().addFile(APPB_APK).run();
getDevice().startUser(mOtherUser, true /* wait */);
installExistingAppAsUser(APPB_PKG, mOtherUser);
runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_TEST_OTHER_USER_DIRS_NOT_PRESENT,
makeOtherUserIdArgs(mOtherUser));
}
@Test
public void testAppUnableToAccessOtherUserAppDataDirApi29() throws Exception {
assumeCanCreateUser();
mOtherUser = getDevice().createUser("other_user");
// For targetSdk <= 29, directories related to other users are visible but we cannot
// access anything within them.
new InstallMultiple().addFile(APP_API29_A_APK).run();
new InstallMultiple().addFile(APPB_APK).run();
getDevice().startUser(mOtherUser, true /* wait */);
installExistingAppAsUser(APPB_PKG, mOtherUser);
runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_TEST_OTHER_USER_DIRS_NOT_ACCESSIBLE,
makeOtherUserIdArgs(mOtherUser));
}
private void assumeCanCreateUser() throws DeviceNotAvailableException {
assumeTrue("Test requires multi-user support", mSupportsMultiUser);
// If we're already at the user limit, e.g. when running the test in a secondary user,
// then we can't create another one.
int currentUserCount = getDevice().listUsers().size();
assumeTrue("Test requires creating another user",
getDevice().getMaxNumberOfUsersSupported() > currentUserCount);
}
private void runDeviceTests(String pkgName, String testClassName, String testMethodName,
Map<String, String> instrumentationArgs) throws DeviceNotAvailableException {
runDeviceTests(getDevice(), null, pkgName, testClassName, testMethodName, null,
10 * 60 * 1000L, 10 * 60 * 1000L, 0L, true, false, instrumentationArgs);
}
private Map<String, String> makeOtherUserIdArgs(int otherUser) {
Map<String, String> args = new HashMap<>();
args.put("other_user_id", Integer.toString(otherUser));
return args;
}
private void forceStopPackage(String packageName) throws Exception {
getDevice().executeShellCommand("am force-stop " + packageName);
}
private void reboot() throws Exception {
getDevice().reboot();
waitForBootCompleted(getDevice());
}
private void installExistingAppAsUser(String packageName, int userId) throws Exception {
final String installString =
"Package " + packageName + " installed for user: " + userId + "\n";
assertEquals(installString, getDevice().executeShellCommand(
"cmd package install-existing --full"
+ " --user " + Integer.toString(userId)
+ " " + packageName));
}
private static void assumeThatFuseDataIsolationIsEnabled(ITestDevice device)
throws DeviceNotAvailableException {
assumeThat(device.executeShellCommand(
"getprop persist.sys.vold_app_data_isolation_enabled").trim(),
is("true"));
}
}