Revert "Revert "Add tests for cross-user data isolation.""

This reverts commit afc09388544350bc94661ddf911401ed0eb3a938, which in
turn reverted commit f7a3466217ee3b26771b259d06c8dec4e3bbe010.

Apps running in one user should not be able to probe directories
relating to other users.

This is enforced by the app data isolation feature for apps targeting
API level 30, and by SELinux rules for older apps, leading to slightly
different tests.

Also did some minor refactoring & added extra assertions to existing
tests.

Reason for revert: Re-landing with fixes.

Bug: 141677108
Test: atest AppDataIsolationTests
Test: atest --user_type secondary_user AppDataIsolationTests
Change-Id: I79b5f2d9d2a461473122c98bd72efa36edce36aa
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/AppDataIsolationTests.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/AppDataIsolationTests.java
index 08581f6..8749080 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/AppDataIsolationTests.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/AppDataIsolationTests.java
@@ -18,10 +18,10 @@
 
 import static android.appsecurity.cts.Utils.waitForBootCompleted;
 
-import static com.google.common.truth.Truth.assertThat;
-
 import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeThat;
 import static org.junit.Assume.assumeTrue;
 
 import com.android.tradefed.device.DeviceNotAvailableException;
@@ -30,11 +30,13 @@
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 
 import org.junit.After;
-import org.junit.Assume;
 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.
  */
@@ -44,6 +46,7 @@
     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";
@@ -76,8 +79,6 @@
     private static final String FBE_MODE_NATIVE = "native";
     private static final String FBE_MODE_EMULATED = "emulated";
 
-    private static final String CHECK_IF_FUSE_DATA_ISOLATION_IS_ENABLED_COMMANDLINE =
-            "getprop persist.sys.vold_app_data_isolation_enabled";
     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 =
@@ -92,6 +93,12 @@
             "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 {
@@ -102,19 +109,13 @@
 
     @After
     public void tearDown() throws Exception {
+        if (mOtherUser != -1) {
+            getDevice().removeUser(mOtherUser);
+        }
         getDevice().uninstallPackage(APPA_PKG);
         getDevice().uninstallPackage(APPB_PKG);
     }
 
-    private void forceStopPackage(String packageName) throws Exception {
-        getDevice().executeShellCommand("am force-stop " + packageName);
-    }
-
-    private void reboot() throws Exception {
-        getDevice().reboot();
-        waitForBootCompleted(getDevice());
-    }
-
     @Test
     public void testAppAbleToAccessItsDataAfterForceStop() throws Exception {
         // Install AppA and verify no data stored
@@ -176,17 +177,6 @@
         runDeviceTests(APPA_PKG, APPA_CLASS, APPA_METHOD_CHECK_REF_PROFILE_NOT_ACCESSIBLE);
     }
 
-    private boolean isFbeModeEmulated() throws Exception {
-        String mode = getDevice().executeShellCommand("sm get-fbe-mode").trim();
-        if (mode.equals(FBE_MODE_EMULATED)) {
-            return true;
-        } else if (mode.equals(FBE_MODE_NATIVE)) {
-            return false;
-        }
-        fail("Unknown FBE mode: " + mode);
-        return false;
-    }
-
     @Test
     public void testDirectBootModeWorks() throws Exception {
         assumeTrue("Screen lock is not supported so skip direct boot test",
@@ -332,13 +322,6 @@
         runDeviceTests(APPB_PKG, APPB_CLASS, APPB_METHOD_CAN_ACCESS_APPA_EXTERNAL_DIRS);
     }
 
-    private static void assumeThatFuseDataIsolationIsEnabled(ITestDevice device)
-            throws DeviceNotAvailableException {
-        Assume.assumeThat(device.executeShellCommand(
-                CHECK_IF_FUSE_DATA_ISOLATION_IS_ENABLED_COMMANDLINE).trim(),
-                is("true"));
-    }
-
     @Test
     public void testIsolatedProcess() throws Exception {
         new InstallMultiple().addFile(APPA_APK).run();
@@ -352,4 +335,92 @@
         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"));
+    }
+
+    private boolean isFbeModeEmulated() throws Exception {
+        String mode = getDevice().executeShellCommand("sm get-fbe-mode").trim();
+        if (mode.equals(FBE_MODE_EMULATED)) {
+            return true;
+        } else if (mode.equals(FBE_MODE_NATIVE)) {
+            return false;
+        }
+        fail("Unknown FBE mode: " + mode);
+        return false;
+    }
 }
diff --git a/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/Android.bp b/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/Android.bp
index e7254c2..8a6e06b 100644
--- a/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/Android.bp
@@ -70,6 +70,25 @@
 }
 
 android_test_helper_app {
+    name: "CtsAppDataIsolationAppApi29A",
+    defaults: ["cts_support_defaults"],
+    srcs: ["common/src/**/*.java", "AppA/src/**/*.java", "AppA/aidl/**/*.aidl"],
+    sdk_version: "test_current",
+    static_libs: ["androidx.test.rules", "truth-prebuilt", "testng", "ub-uiautomator", "compatibility-device-util-axt"],
+    libs: ["android.test.base"],
+    // tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    certificate: ":cts-testkey1",
+    dex_preopt: {
+        enabled: false,
+    },
+    manifest: "AppA/AndroidManifest_api29.xml",
+}
+
+android_test_helper_app {
     name: "CtsAppDataIsolationAppB",
     defaults: ["cts_support_defaults"],
     srcs: ["common/src/**/*.java", "AppB/src/**/*.java"],
diff --git a/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/AppA/AndroidManifest_api29.xml b/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/AppA/AndroidManifest_api29.xml
new file mode 100644
index 0000000..15c3ce1
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/AppA/AndroidManifest_api29.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+       package="com.android.cts.appdataisolation.appa">
+
+    <uses-sdk android:targetSdkVersion="29" />
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+        <service android:name=".IsolatedService"
+                 android:process=":Isolated"
+                 android:isolatedProcess="true"/>
+        <service android:name=".AppZygoteIsolatedService"
+                 android:process=":Isolated2"
+                 android:isolatedProcess="true"
+                 android:useAppZygote="true"/>
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.cts.appdataisolation.appa"
+                     android:label="Test app data isolation."/>
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/AppA/src/com/android/cts/appdataisolation/appa/AppATests.java b/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/AppA/src/com/android/cts/appdataisolation/appa/AppATests.java
index c25d3d9..bdd02b5 100644
--- a/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/AppA/src/com/android/cts/appdataisolation/appa/AppATests.java
+++ b/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/AppA/src/com/android/cts/appdataisolation/appa/AppATests.java
@@ -16,10 +16,12 @@
 
 package com.android.cts.appdataisolation.appa;
 
+import static com.android.cts.appdataisolation.common.FileUtils.APPA_PKG;
 import static com.android.cts.appdataisolation.common.FileUtils.APPB_PKG;
 import static com.android.cts.appdataisolation.common.FileUtils.CE_DATA_FILE_NAME;
 import static com.android.cts.appdataisolation.common.FileUtils.DE_DATA_FILE_NAME;
 import static com.android.cts.appdataisolation.common.FileUtils.EXTERNAL_DATA_FILE_NAME;
+import static com.android.cts.appdataisolation.common.FileUtils.NOT_INSTALLED_PKG;
 import static com.android.cts.appdataisolation.common.FileUtils.OBB_FILE_NAME;
 import static com.android.cts.appdataisolation.common.FileUtils.assertDirDoesNotExist;
 import static com.android.cts.appdataisolation.common.FileUtils.assertDirIsAccessible;
@@ -29,6 +31,7 @@
 import static com.android.cts.appdataisolation.common.FileUtils.touchFile;
 import static com.android.cts.appdataisolation.common.UserUtils.getCurrentUserId;
 
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
@@ -39,19 +42,24 @@
 import android.content.IntentFilter;
 import android.content.ServiceConnection;
 import android.content.pm.ApplicationInfo;
+import android.os.Bundle;
 import android.os.IBinder;
 import android.support.test.uiautomator.UiDevice;
+import android.util.Log;
 import android.view.KeyEvent;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 
+import com.android.compatibility.common.util.SystemUtil;
 import com.android.cts.appdataisolation.common.FileUtils;
 
 import org.junit.Before;
 import org.junit.Test;
 
+import java.io.BufferedReader;
 import java.io.File;
+import java.io.InputStreamReader;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
@@ -287,4 +295,48 @@
             mContext.unbindService(mServiceConnection);
         }
     }
+
+    @Test
+    public void testOtherUserDirsNotPresent() throws Exception {
+        final Bundle arguments = InstrumentationRegistry.getArguments();
+        final int otherUserId = Integer.parseInt(arguments.getString("other_user_id"));
+
+        final String ceDataRoot = "/data/user/" + otherUserId;
+        final String deDataRoot = "/data/user_de/" + otherUserId;
+        final String profileRoot = "/data/misc/profiles/cur/" + otherUserId;
+
+        assertDirDoesNotExist(ceDataRoot);
+        assertDirDoesNotExist(deDataRoot);
+        assertDirDoesNotExist(profileRoot);
+    }
+
+    @Test
+    public void testOtherUserDirsNotAccessible() throws Exception {
+        final Bundle arguments = InstrumentationRegistry.getArguments();
+        final int otherUserId = Integer.parseInt(arguments.getString("other_user_id"));
+
+        final String ceDataRoot = "/data/user/" + otherUserId;
+        final String deDataRoot = "/data/user_de/" + otherUserId;
+        final String profileRoot = "/data/misc/profiles/cur/" + otherUserId;
+
+        // APPA (this app) is installed in this user but not the other one.
+        // APPB is installed in this user and the other one.
+        // NOT_INSTALLED_PKG isn't installed anywhere.
+        // We must get the same answer for all of them, so we can't infer if any of them are or
+        // are not installed in the other user.
+        assertDirIsNotAccessible(ceDataRoot);
+        assertDirIsNotAccessible(ceDataRoot + "/" + APPA_PKG);
+        assertDirIsNotAccessible(ceDataRoot + "/" + APPB_PKG);
+        assertDirIsNotAccessible(ceDataRoot + "/" + NOT_INSTALLED_PKG);
+
+        assertDirIsNotAccessible(deDataRoot);
+        assertDirIsNotAccessible(deDataRoot + "/" + APPA_PKG);
+        assertDirIsNotAccessible(deDataRoot + "/" + APPB_PKG);
+        assertDirIsNotAccessible(deDataRoot + "/" + NOT_INSTALLED_PKG);
+
+        assertDirIsNotAccessible(profileRoot);
+        assertDirIsNotAccessible(profileRoot + "/" + APPA_PKG);
+        assertDirIsNotAccessible(profileRoot + "/" + APPB_PKG);
+        assertDirIsNotAccessible(profileRoot + "/" + NOT_INSTALLED_PKG);
+    }
 }
diff --git a/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/common/src/com/android/cts/appdataisolation/common/FileUtils.java b/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/common/src/com/android/cts/appdataisolation/common/FileUtils.java
index 80f2f77..15450f9 100644
--- a/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/common/src/com/android/cts/appdataisolation/common/FileUtils.java
+++ b/hostsidetests/appsecurity/test-apps/AppDataIsolationTestApp/common/src/com/android/cts/appdataisolation/common/FileUtils.java
@@ -59,6 +59,8 @@
         });
         assertThat(exception.getMessage()).contains(JAVA_FILE_PERMISSION_DENIED_MSG);
         assertThat(exception.getMessage()).doesNotContain(JAVA_FILE_NOT_FOUND_MSG);
+
+        assertThat(new File(path).canExecute()).isFalse();
     }
 
     public static void assertDirDoesNotExist(String path) {
@@ -66,7 +68,7 @@
         // Trying to access a file/directory that does exist, but is not visible to the caller, it
         // should return file not found.
         Exception exception = expectThrows(FileNotFoundException.class, () -> {
-            new FileInputStream(new File(path));
+            new FileInputStream(directory);
         });
         assertThat(exception.getMessage()).contains(JAVA_FILE_NOT_FOUND_MSG);
         assertThat(exception.getMessage()).doesNotContain(JAVA_FILE_PERMISSION_DENIED_MSG);
@@ -85,6 +87,8 @@
         } catch (ErrnoException e) {
             assertEquals(e.errno, OsConstants.EACCES, "Error on path: " + path);
         }
+
+        assertThat(directory.exists()).isFalse();
     }
 
     public static void assertDirIsAccessible(String path) {
@@ -92,6 +96,8 @@
         // if app has search permission to that directory, it should return file not found
         // and not security exception.
         assertFileDoesNotExist(path, "FILE_DOES_NOT_EXIST");
+
+        assertThat(new File(path).canExecute()).isTrue();
     }
 
     public static void assertFileIsAccessible(String path) {