diff options
author | 2019-12-18 13:07:02 +0000 | |
---|---|---|
committer | 2019-12-18 13:07:02 +0000 | |
commit | 32e4875f6b9a23f95d1ef38f9fd7c3efe575883b (patch) | |
tree | 44048928a2df7f019af3ec5448cc5b2ed056cc1c | |
parent | f95cac8b0a0ac81a1c306cc1be1e717f7258c2d8 (diff) | |
parent | 4482ab53d17140dfeb756ff595b66fb641f1addf (diff) |
Merge changes from topic "mount_isolate_apps_data"
* changes:
App data directory isolation
Pass app visible packages data directory info to zygote
11 files changed, 488 insertions, 24 deletions
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java index ebb2071ead7e..6ae188a8fc3d 100644 --- a/core/java/android/os/Process.java +++ b/core/java/android/os/Process.java @@ -22,10 +22,13 @@ import android.annotation.TestApi; import android.annotation.UnsupportedAppUsage; import android.system.Os; import android.system.OsConstants; +import android.util.Pair; import android.webkit.WebViewZygote; import dalvik.system.VMRuntime; +import java.util.Map; + /** * Tools for managing OS processes. */ @@ -521,6 +524,8 @@ public class Process { * @param isTopApp whether the process starts for high priority application. * @param disabledCompatChanges null-ok list of disabled compat changes for the process being * started. + * @param pkgDataInfoMap Map from related package names to private data directory + * volume UUID and inode number. * @param zygoteArgs Additional arguments to supply to the zygote process. * @return An object that describes the result of the attempt to start the process. * @throws RuntimeException on fatal start failure @@ -541,11 +546,14 @@ public class Process { @Nullable String packageName, boolean isTopApp, @Nullable long[] disabledCompatChanges, + @Nullable Map<String, Pair<String, Long>> + pkgDataInfoMap, @Nullable String[] zygoteArgs) { return ZYGOTE_PROCESS.start(processClass, niceName, uid, gid, gids, runtimeFlags, mountExternal, targetSdkVersion, seInfo, abi, instructionSet, appDataDir, invokeWith, packageName, - /*useUsapPool=*/ true, isTopApp, disabledCompatChanges, zygoteArgs); + /*useUsapPool=*/ true, isTopApp, disabledCompatChanges, + pkgDataInfoMap, zygoteArgs); } /** @hide */ @@ -563,10 +571,13 @@ public class Process { @Nullable String packageName, @Nullable long[] disabledCompatChanges, @Nullable String[] zygoteArgs) { + // Webview zygote can't access app private data files, so doesn't need to know its data + // info. return WebViewZygote.getProcess().start(processClass, niceName, uid, gid, gids, runtimeFlags, mountExternal, targetSdkVersion, seInfo, abi, instructionSet, appDataDir, invokeWith, packageName, - /*useUsapPool=*/ false, /*isTopApp=*/ false, disabledCompatChanges, zygoteArgs); + /*useUsapPool=*/ false, /*isTopApp=*/ false, disabledCompatChanges, + /* pkgDataInfoMap */ null, zygoteArgs); } /** diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java index d17a5e026880..d32bd26c2bb2 100644 --- a/core/java/android/os/ZygoteProcess.java +++ b/core/java/android/os/ZygoteProcess.java @@ -23,6 +23,7 @@ import android.content.pm.ApplicationInfo; import android.net.LocalSocket; import android.net.LocalSocketAddress; import android.util.Log; +import android.util.Pair; import android.util.Slog; import com.android.internal.annotations.GuardedBy; @@ -39,6 +40,7 @@ import java.util.Arrays; import java.util.Base64; import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.UUID; /*package*/ class ZygoteStartFailedEx extends Exception { @@ -310,6 +312,8 @@ public class ZygoteProcess { * started. * @param zygoteArgs Additional arguments to supply to the zygote process. * @param isTopApp Whether the process starts for high priority application. + * @param pkgDataInfoMap Map from related package names to private data directory + * volume UUID and inode number. * * @return An object that describes the result of the attempt to start the process. * @throws RuntimeException on fatal start failure @@ -328,6 +332,8 @@ public class ZygoteProcess { boolean useUsapPool, boolean isTopApp, @Nullable long[] disabledCompatChanges, + @Nullable Map<String, Pair<String, Long>> + pkgDataInfoMap, @Nullable String[] zygoteArgs) { // TODO (chriswailes): Is there a better place to check this value? if (fetchUsapPoolEnabledPropWithMinInterval()) { @@ -338,7 +344,8 @@ public class ZygoteProcess { return startViaZygote(processClass, niceName, uid, gid, gids, runtimeFlags, mountExternal, targetSdkVersion, seInfo, abi, instructionSet, appDataDir, invokeWith, /*startChildZygote=*/ false, - packageName, useUsapPool, isTopApp, disabledCompatChanges, zygoteArgs); + packageName, useUsapPool, isTopApp, disabledCompatChanges, + pkgDataInfoMap, zygoteArgs); } catch (ZygoteStartFailedEx ex) { Log.e(LOG_TAG, "Starting VM process through Zygote failed"); @@ -539,6 +546,8 @@ public class ZygoteProcess { * @param packageName null-ok the name of the package this process belongs to. * @param isTopApp Whether the process starts for high priority application. * @param disabledCompatChanges a list of disabled compat changes for the process being started. + * @param pkgDataInfoMap Map from related package names to private data directory volume UUID + * and inode number. * @param extraArgs Additional arguments to supply to the zygote process. * @return An object that describes the result of the attempt to start the process. * @throws ZygoteStartFailedEx if process start failed for any reason @@ -559,6 +568,8 @@ public class ZygoteProcess { boolean useUsapPool, boolean isTopApp, @Nullable long[] disabledCompatChanges, + @Nullable Map<String, Pair<String, Long>> + pkgDataInfoMap, @Nullable String[] extraArgs) throws ZygoteStartFailedEx { ArrayList<String> argsForZygote = new ArrayList<>(); @@ -635,6 +646,24 @@ public class ZygoteProcess { if (isTopApp) { argsForZygote.add(Zygote.START_AS_TOP_APP_ARG); } + if (pkgDataInfoMap != null && pkgDataInfoMap.size() > 0) { + StringBuilder sb = new StringBuilder(); + sb.append(Zygote.PKG_DATA_INFO_MAP); + sb.append("="); + boolean started = false; + for (Map.Entry<String, Pair<String, Long>> entry : pkgDataInfoMap.entrySet()) { + if (started) { + sb.append(','); + } + started = true; + sb.append(entry.getKey()); + sb.append(','); + sb.append(entry.getValue().first); + sb.append(','); + sb.append(entry.getValue().second); + } + argsForZygote.add(sb.toString()); + } if (disabledCompatChanges != null && disabledCompatChanges.length > 0) { StringBuilder sb = new StringBuilder(); @@ -1182,12 +1211,14 @@ public class ZygoteProcess { Process.ProcessStartResult result; try { + // As app zygote is for generating isolated process, at the end it can't access + // apps data, so doesn't need to its data info. result = startViaZygote(processClass, niceName, uid, gid, gids, runtimeFlags, 0 /* mountExternal */, 0 /* targetSdkVersion */, seInfo, abi, instructionSet, null /* appDataDir */, null /* invokeWith */, true /* startChildZygote */, null /* packageName */, false /* useUsapPool */, false /* isTopApp */, - null /* disabledCompatChanges */, extraArgs); + null /* disabledCompatChanges */, null /* pkgDataInfoMap */, extraArgs); } catch (ZygoteStartFailedEx ex) { throw new RuntimeException("Starting child-zygote through Zygote failed", ex); } diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java index 2b988c155412..c390a518bb22 100644 --- a/core/java/com/android/internal/os/Zygote.java +++ b/core/java/com/android/internal/os/Zygote.java @@ -151,6 +151,9 @@ public final class Zygote { /** Make the new process have top application priority. */ public static final String START_AS_TOP_APP_ARG = "--is-top-app"; + /** List of packages with the same uid, and its app data info: volume uuid and inode. */ + public static final String PKG_DATA_INFO_MAP = "--pkg-data-info-map"; + /** * An extraArg passed when a zygote process is forking a child-zygote, specifying a name * in the abstract socket namespace. This socket name is what the new child zygote @@ -254,6 +257,8 @@ public final class Zygote { * @param instructionSet null-ok the instruction set to use. * @param appDataDir null-ok the data directory of the app. * @param isTopApp true if the process is for top (high priority) application. + * @param pkgDataInfoList A list that stores related packages and its app data + * info: volume uuid and inode. * * @return 0 if this is the child, pid of the child * if this is the parent, or -1 on error. @@ -261,12 +266,13 @@ public final class Zygote { static int forkAndSpecialize(int uid, int gid, int[] gids, int runtimeFlags, int[][] rlimits, int mountExternal, String seInfo, String niceName, int[] fdsToClose, int[] fdsToIgnore, boolean startChildZygote, String instructionSet, String appDataDir, - int targetSdkVersion, boolean isTopApp) { + int targetSdkVersion, boolean isTopApp, String[] pkgDataInfoList) { ZygoteHooks.preFork(); int pid = nativeForkAndSpecialize( uid, gid, gids, runtimeFlags, rlimits, mountExternal, seInfo, niceName, fdsToClose, - fdsToIgnore, startChildZygote, instructionSet, appDataDir, isTopApp); + fdsToIgnore, startChildZygote, instructionSet, appDataDir, isTopApp, + pkgDataInfoList); // Enable tracing as soon as possible for the child process. if (pid == 0) { Zygote.disableExecuteOnly(targetSdkVersion); @@ -286,7 +292,7 @@ public final class Zygote { private static native int nativeForkAndSpecialize(int uid, int gid, int[] gids, int runtimeFlags, int[][] rlimits, int mountExternal, String seInfo, String niceName, int[] fdsToClose, int[] fdsToIgnore, boolean startChildZygote, String instructionSet, - String appDataDir, boolean isTopApp); + String appDataDir, boolean isTopApp, String[] pkgDataInfoList); /** * Specialize an unspecialized app process. The current VM must have been started @@ -309,12 +315,16 @@ public final class Zygote { * @param instructionSet null-ok The instruction set to use. * @param appDataDir null-ok The data directory of the app. * @param isTopApp True if the process is for top (high priority) application. + * @param pkgDataInfoList A list that stores related packages and its app data + * info: volume uuid and inode. */ private static void specializeAppProcess(int uid, int gid, int[] gids, int runtimeFlags, int[][] rlimits, int mountExternal, String seInfo, String niceName, - boolean startChildZygote, String instructionSet, String appDataDir, boolean isTopApp) { + boolean startChildZygote, String instructionSet, String appDataDir, boolean isTopApp, + String[] pkgDataInfoList) { nativeSpecializeAppProcess(uid, gid, gids, runtimeFlags, rlimits, mountExternal, seInfo, - niceName, startChildZygote, instructionSet, appDataDir, isTopApp); + niceName, startChildZygote, instructionSet, appDataDir, isTopApp, + pkgDataInfoList); // Enable tracing as soon as possible for the child process. Trace.setTracingEnabled(true, runtimeFlags); @@ -336,7 +346,8 @@ public final class Zygote { private static native void nativeSpecializeAppProcess(int uid, int gid, int[] gids, int runtimeFlags, int[][] rlimits, int mountExternal, String seInfo, String niceName, - boolean startChildZygote, String instructionSet, String appDataDir, boolean isTopApp); + boolean startChildZygote, String instructionSet, String appDataDir, boolean isTopApp, + String[] pkgDataInfoList); /** * Called to do any initialization before starting an application. @@ -665,7 +676,8 @@ public final class Zygote { specializeAppProcess(args.mUid, args.mGid, args.mGids, args.mRuntimeFlags, rlimits, args.mMountExternal, args.mSeInfo, args.mNiceName, args.mStartChildZygote, - args.mInstructionSet, args.mAppDataDir, args.mIsTopApp); + args.mInstructionSet, args.mAppDataDir, args.mIsTopApp, + args.mPkgDataInfoList); disableExecuteOnly(args.mTargetSdkVersion); diff --git a/core/java/com/android/internal/os/ZygoteArguments.java b/core/java/com/android/internal/os/ZygoteArguments.java index 54b2a2063451..d3499541a3a3 100644 --- a/core/java/com/android/internal/os/ZygoteArguments.java +++ b/core/java/com/android/internal/os/ZygoteArguments.java @@ -221,6 +221,12 @@ class ZygoteArguments { long[] mDisabledCompatChanges = null; /** + * A list that stores all related packages and its data info: volume uuid and inode. + * Null if it does need to do app data isolation. + */ + String[] mPkgDataInfoList; + + /** * Constructs instance and parses args * * @param args zygote command-line args @@ -437,6 +443,8 @@ class ZygoteArguments { for (int i = 0; i < length; i++) { mDisabledCompatChanges[i] = Long.parseLong(params[i]); } + } else if (arg.startsWith(Zygote.PKG_DATA_INFO_MAP)) { + mPkgDataInfoList = getAssignmentList(arg); } else { break; } diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java index 3111b6ff0f72..9c6a288372c6 100644 --- a/core/java/com/android/internal/os/ZygoteConnection.java +++ b/core/java/com/android/internal/os/ZygoteConnection.java @@ -258,7 +258,7 @@ class ZygoteConnection { parsedArgs.mRuntimeFlags, rlimits, parsedArgs.mMountExternal, parsedArgs.mSeInfo, parsedArgs.mNiceName, fdsToClose, fdsToIgnore, parsedArgs.mStartChildZygote, parsedArgs.mInstructionSet, parsedArgs.mAppDataDir, parsedArgs.mTargetSdkVersion, - parsedArgs.mIsTopApp); + parsedArgs.mIsTopApp, parsedArgs.mPkgDataInfoList); try { if (pid == 0) { diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp index 0ca0dc857545..df5b02c22a2d 100644 --- a/core/jni/com_android_internal_os_Zygote.cpp +++ b/core/jni/com_android_internal_os_Zygote.cpp @@ -31,6 +31,8 @@ // sys/mount.h has to come before linux/fs.h due to redefinition of MS_RDONLY, MS_BIND, etc #include <sys/mount.h> #include <linux/fs.h> +#include <sys/types.h> +#include <dirent.h> #include <array> #include <atomic> @@ -40,6 +42,7 @@ #include <sstream> #include <string> #include <string_view> +#include <unordered_set> #include <android/fdsan.h> #include <arpa/inet.h> @@ -51,6 +54,7 @@ #include <mntent.h> #include <paths.h> #include <signal.h> +#include <stdio.h> #include <stdlib.h> #include <sys/capability.h> #include <sys/cdefs.h> @@ -159,6 +163,17 @@ static std::atomic_uint32_t gUsapPoolCount = 0; */ static int gUsapPoolEventFD = -1; +static constexpr int DEFAULT_DATA_DIR_PERMISSION = 0751; + +/** + * Property to control if app data isolation is enabled. + */ +static const std::string ANDROID_APP_DATA_ISOLATION_ENABLED_PROPERTY = + "persist.zygote.app_data_isolation"; + +static constexpr const uint64_t UPPER_HALF_WORD_MASK = 0xFFFF'FFFF'0000'0000; +static constexpr const uint64_t LOWER_HALF_WORD_MASK = 0x0000'0000'FFFF'FFFF; + /** * The maximum value that the gUSAPPoolSizeMax variable may take. This value * is a mirror of ZygoteServer.USAP_POOL_SIZE_MAX_LIMIT @@ -663,7 +678,7 @@ static int UnmountTree(const char* path) { return 0; } -static void CreateDir(const std::string& dir, mode_t mode, uid_t uid, gid_t gid, +static void PrepareDir(const std::string& dir, mode_t mode, uid_t uid, gid_t gid, fail_fn_t fail_fn) { if (fs_prepare_dir(dir.c_str(), mode, uid, gid) != 0) { fail_fn(CREATE_ERROR("fs_prepare_dir failed on %s: %s", @@ -671,6 +686,16 @@ static void CreateDir(const std::string& dir, mode_t mode, uid_t uid, gid_t gid, } } +static void PrepareDirIfNotPresent(const std::string& dir, mode_t mode, uid_t uid, gid_t gid, + fail_fn_t fail_fn) { + struct stat sb; + if (TEMP_FAILURE_RETRY(stat(dir.c_str(), &sb)) != -1) { + // Directory exists already + return; + } + PrepareDir(dir, mode, uid, gid, fail_fn); +} + static void BindMount(const std::string& source_dir, const std::string& target_dir, fail_fn_t fail_fn) { if (TEMP_FAILURE_RETRY(mount(source_dir.c_str(), target_dir.c_str(), nullptr, @@ -680,6 +705,15 @@ static void BindMount(const std::string& source_dir, const std::string& target_d } } +static void MountAppDataTmpFs(const std::string& target_dir, + fail_fn_t fail_fn) { + if (TEMP_FAILURE_RETRY(mount("tmpfs", target_dir.c_str(), "tmpfs", + MS_NOSUID | MS_NODEV | MS_NOEXEC, "uid=0,gid=0,mode=0751")) == -1) { + fail_fn(CREATE_ERROR("Failed to mount tmpfs to %s: %s", + target_dir.c_str(), strerror(errno))); + } +} + // Create a private mount namespace and bind mount appropriate emulated // storage for the given user. static void MountEmulatedStorage(uid_t uid, jint mount_mode, @@ -712,7 +746,7 @@ static void MountEmulatedStorage(uid_t uid, jint mount_mode, const std::string pass_through_source = StringPrintf("/mnt/pass_through/%d", user_id); bool isFuse = GetBoolProperty(kPropFuse, false); - CreateDir(user_source, 0751, AID_ROOT, AID_ROOT, fail_fn); + PrepareDir(user_source, DEFAULT_DATA_DIR_PERMISSION, AID_ROOT, AID_ROOT, fail_fn); if (isFuse) { if (mount_mode == MOUNT_EXTERNAL_PASS_THROUGH || mount_mode == @@ -1016,6 +1050,231 @@ static pid_t ForkCommon(JNIEnv* env, bool is_system_server, return pid; } +// Create an app data directory over tmpfs overlayed CE / DE storage, and bind mount it +// from the actual app data directory in data mirror. +static void createAndMountAppData(std::string_view package_name, + std::string_view mirror_pkg_dir_name, std::string_view mirror_data_path, + std::string_view actual_data_path, fail_fn_t fail_fn) { + + char mirrorAppDataPath[PATH_MAX]; + char actualAppDataPath[PATH_MAX]; + snprintf(mirrorAppDataPath, PATH_MAX, "%s/%s", mirror_data_path.data(), + mirror_pkg_dir_name.data()); + snprintf(actualAppDataPath, PATH_MAX, "%s/%s", actual_data_path.data(), package_name.data()); + + PrepareDir(actualAppDataPath, 0700, AID_ROOT, AID_ROOT, fail_fn); + + // Bind mount from original app data directory in mirror. + BindMount(mirrorAppDataPath, actualAppDataPath, fail_fn); +} + +// Get the directory name stored in /data/data. If device is unlocked it should be the same as +// package name, otherwise it will be an encrypted name but with same inode number. +static std::string getAppDataDirName(std::string_view parent_path, std::string_view package_name, + long long ce_data_inode, fail_fn_t fail_fn) { + // Check if directory exists + char tmpPath[PATH_MAX]; + snprintf(tmpPath, PATH_MAX, "%s/%s", parent_path.data(), package_name.data()); + struct stat s; + int err = stat(tmpPath, &s); + if (err == 0) { + // Directory exists, so return the directory name + return package_name.data(); + } else { + if (errno != ENOENT) { + fail_fn(CREATE_ERROR("Unexpected error in getAppDataDirName: %s", strerror(errno))); + return nullptr; + } + // Directory doesn't exist, try to search the name from inode + DIR* dir = opendir(parent_path.data()); + if (dir == nullptr) { + fail_fn(CREATE_ERROR("Failed to opendir %s", parent_path.data())); + } + struct dirent* ent; + while ((ent = readdir(dir))) { + if (ent->d_ino == ce_data_inode) { + closedir(dir); + return ent->d_name; + } + } + closedir(dir); + + // Fallback due to b/145989852, ce_data_inode stored in package manager may be corrupted + // if ino_t is 32 bits. + ino_t fixed_ce_data_inode = 0; + if ((ce_data_inode & UPPER_HALF_WORD_MASK) == UPPER_HALF_WORD_MASK) { + fixed_ce_data_inode = ce_data_inode & LOWER_HALF_WORD_MASK; + } else if ((ce_data_inode & LOWER_HALF_WORD_MASK) == LOWER_HALF_WORD_MASK) { + fixed_ce_data_inode = ((ce_data_inode >> 32) & LOWER_HALF_WORD_MASK); + } + if (fixed_ce_data_inode != 0) { + dir = opendir(parent_path.data()); + if (dir == nullptr) { + fail_fn(CREATE_ERROR("Failed to opendir %s", parent_path.data())); + } + while ((ent = readdir(dir))) { + if (ent->d_ino == fixed_ce_data_inode) { + long long d_ino = ent->d_ino; + ALOGW("Fallback success inode %lld -> %lld", ce_data_inode, d_ino); + closedir(dir); + return ent->d_name; + } + } + closedir(dir); + } + // Fallback done + + fail_fn(CREATE_ERROR("Unable to find %s:%lld in %s", package_name.data(), + ce_data_inode, parent_path.data())); + return nullptr; + } +} + +// Isolate app's data directory, by mounting a tmpfs on CE DE storage, +// and create and bind mount app data in related_packages. +static void isolateAppDataPerPackage(int userId, std::string_view package_name, + std::string_view volume_uuid, long long ce_data_inode, std::string_view actualCePath, + std::string_view actualDePath, fail_fn_t fail_fn) { + + char mirrorCePath[PATH_MAX]; + char mirrorDePath[PATH_MAX]; + char mirrorCeParent[PATH_MAX]; + snprintf(mirrorCeParent, PATH_MAX, "/data_mirror/data_ce/%s", volume_uuid.data()); + snprintf(mirrorCePath, PATH_MAX, "%s/%d", mirrorCeParent, userId); + snprintf(mirrorDePath, PATH_MAX, "/data_mirror/data_de/%s/%d", volume_uuid.data(), userId); + + createAndMountAppData(package_name, package_name, mirrorDePath, actualDePath, fail_fn); + + std::string ce_data_path = getAppDataDirName(mirrorCePath, package_name, ce_data_inode, fail_fn); + createAndMountAppData(package_name, ce_data_path, mirrorCePath, actualCePath, fail_fn); +} + +/** + * Make other apps data directory not visible in CE, DE storage. + * + * Apps without app data isolation can detect if another app is installed on system, + * by "touching" other apps data directory like /data/data/com.whatsapp, if it returns + * "Permission denied" it means apps installed, otherwise it returns "File not found". + * Traditional file permissions or SELinux can only block accessing those directories but + * can't fix fingerprinting like this. + * We fix it by "overlaying" data directory, and only relevant app data packages exists + * in data directories. + * + * Steps: + * 1). Collect a list of all related apps (apps with same uid and whitelisted apps) data info + * (package name, data stored volume uuid, and inode number of its CE data directory) + * 2). Mount tmpfs on /data/data, /data/user(_de) and /mnt/expand, so apps no longer + * able to access apps data directly. + * 3). For each related app, create its app data directory and bind mount the actual content + * from apps data mirror directory. This works on both CE and DE storage, as DE storage + * is always available even storage is FBE locked, while we use inode number to find + * the encrypted DE directory in mirror so we can still bind mount it successfully. + * + * Example: + * 0). Assuming com.android.foo CE data is stored in /data/data and no shared uid + * 1). Mount a tmpfs on /data/data, /data/user, /data/user_de, /mnt/expand + * List = ["com.android.foo", "null" (volume uuid "null"=default), + * 123456 (inode number)] + * 2). On DE storage, we create a directory /data/user_de/0/com.com.android.foo, and bind + * mount (in the app's mount namespace) it from /data_mirror/data_de/0/com.android.foo. + * 3). We do similar for CE storage. But in direct boot mode, as /data_mirror/data_ce/0/ is + * encrypted, we can't find a directory with name com.android.foo on it, so we will + * use the inode number to find the right directory instead, which that directory content will + * be decrypted after storage is decrypted. + * + */ +static void isolateAppData(JNIEnv* env, jobjectArray pkg_data_info_list, + uid_t uid, const char* process_name, jstring managed_nice_name, + fail_fn_t fail_fn) { + + const userid_t userId = multiuser_get_user_id(uid); + + auto extract_fn = std::bind(ExtractJString, env, process_name, managed_nice_name, _1); + + int size = (pkg_data_info_list != nullptr) ? env->GetArrayLength(pkg_data_info_list) : 0; + // Size should be a multiple of 3, as it contains list of <package_name, volume_uuid, inode> + if ((size % 3) != 0) { + fail_fn(CREATE_ERROR("Wrong pkg_inode_list size %d", size)); + } + + // Mount tmpfs on all possible data directories, so app no longer see the original apps data. + char internalCePath[PATH_MAX]; + char internalLegacyCePath[PATH_MAX]; + char internalDePath[PATH_MAX]; + char externalPrivateMountPath[PATH_MAX]; + + snprintf(internalCePath, PATH_MAX, "/data/user"); + snprintf(internalLegacyCePath, PATH_MAX, "/data/data"); + snprintf(internalDePath, PATH_MAX, "/data/user_de"); + snprintf(externalPrivateMountPath, PATH_MAX, "/mnt/expand"); + + MountAppDataTmpFs(internalLegacyCePath, fail_fn); + MountAppDataTmpFs(internalCePath, fail_fn); + MountAppDataTmpFs(internalDePath, fail_fn); + MountAppDataTmpFs(externalPrivateMountPath, fail_fn); + + for (int i = 0; i < size; i += 3) { + jstring package_str = (jstring) (env->GetObjectArrayElement(pkg_data_info_list, i)); + std::string packageName = extract_fn(package_str).value(); + + jstring vol_str = (jstring) (env->GetObjectArrayElement(pkg_data_info_list, i + 1)); + std::string volUuid = extract_fn(vol_str).value(); + + jstring inode_str = (jstring) (env->GetObjectArrayElement(pkg_data_info_list, i + 2)); + std::string inode = extract_fn(inode_str).value(); + std::string::size_type sz; + long long ceDataInode = std::stoll(inode, &sz); + + std::string actualCePath, actualDePath; + if (volUuid.compare("null") != 0) { + // Volume that is stored in /mnt/expand + char volPath[PATH_MAX]; + char volCePath[PATH_MAX]; + char volDePath[PATH_MAX]; + char volCeUserPath[PATH_MAX]; + char volDeUserPath[PATH_MAX]; + + snprintf(volPath, PATH_MAX, "/mnt/expand/%s", volUuid.c_str()); + snprintf(volCePath, PATH_MAX, "%s/user", volPath); + snprintf(volDePath, PATH_MAX, "%s/user_de", volPath); + snprintf(volCeUserPath, PATH_MAX, "%s/%d", volCePath, userId); + snprintf(volDeUserPath, PATH_MAX, "%s/%d", volDePath, userId); + + PrepareDirIfNotPresent(volPath, DEFAULT_DATA_DIR_PERMISSION, AID_ROOT, AID_ROOT, fail_fn); + PrepareDirIfNotPresent(volCePath, DEFAULT_DATA_DIR_PERMISSION, AID_ROOT, AID_ROOT, fail_fn); + PrepareDirIfNotPresent(volDePath, DEFAULT_DATA_DIR_PERMISSION, AID_ROOT, AID_ROOT, fail_fn); + PrepareDirIfNotPresent(volCeUserPath, DEFAULT_DATA_DIR_PERMISSION, AID_ROOT, AID_ROOT, + fail_fn); + PrepareDirIfNotPresent(volDeUserPath, DEFAULT_DATA_DIR_PERMISSION, AID_ROOT, AID_ROOT, + fail_fn); + + actualCePath = volCeUserPath; + actualDePath = volDeUserPath; + } else { + // Internal volume that stored in /data + char internalCeUserPath[PATH_MAX]; + char internalDeUserPath[PATH_MAX]; + snprintf(internalCeUserPath, PATH_MAX, "/data/user/%d", userId); + snprintf(internalDeUserPath, PATH_MAX, "/data/user_de/%d", userId); + // If it's user 0, create a symlink /data/user/0 -> /data/data, + // otherwise create /data/user/$USER + if (userId == 0) { + symlink(internalLegacyCePath, internalCeUserPath); + actualCePath = internalLegacyCePath; + } else { + PrepareDirIfNotPresent(internalCeUserPath, DEFAULT_DATA_DIR_PERMISSION, + AID_ROOT, AID_ROOT, fail_fn); + actualCePath = internalCeUserPath; + } + PrepareDirIfNotPresent(internalDeUserPath, DEFAULT_DATA_DIR_PERMISSION, + AID_ROOT, AID_ROOT, fail_fn); + actualDePath = internalDeUserPath; + } + isolateAppDataPerPackage(userId, packageName, volUuid, ceDataInode, + actualCePath, actualDePath, fail_fn); + } +} + // Utility routine to specialize a zygote child process. static void SpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, @@ -1023,7 +1282,8 @@ static void SpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray gids, jint mount_external, jstring managed_se_info, jstring managed_nice_name, bool is_system_server, bool is_child_zygote, jstring managed_instruction_set, - jstring managed_app_data_dir, bool is_top_app) { + jstring managed_app_data_dir, bool is_top_app, + jobjectArray pkg_data_info_list) { const char* process_name = is_system_server ? "system_server" : "zygote"; auto fail_fn = std::bind(ZygoteFailure, env, process_name, managed_nice_name, _1); auto extract_fn = std::bind(ExtractJString, env, process_name, managed_nice_name, _1); @@ -1059,6 +1319,16 @@ static void SpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray gids, MountEmulatedStorage(uid, mount_external, use_native_bridge, fail_fn); + // System services, isolated process, webview/app zygote, old target sdk app, should + // give a null in same_uid_pkgs and private_volumes so they don't need app data isolation. + // Isolated process / webview / app zygote should be gated by SELinux and file permission + // so they can't even traverse CE / DE directories. + if (pkg_data_info_list != nullptr + && GetBoolProperty(ANDROID_APP_DATA_ISOLATION_ENABLED_PROPERTY, false)) { + isolateAppData(env, pkg_data_info_list, uid, process_name, managed_nice_name, + fail_fn); + } + // If this zygote isn't root, it won't be able to create a process group, // since the directory is owned by root. if (!is_system_server && getuid() == 0) { @@ -1425,7 +1695,8 @@ static jint com_android_internal_os_Zygote_nativeForkAndSpecialize( jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name, jintArray managed_fds_to_close, jintArray managed_fds_to_ignore, jboolean is_child_zygote, - jstring instruction_set, jstring app_data_dir, jboolean is_top_app) { + jstring instruction_set, jstring app_data_dir, jboolean is_top_app, + jobjectArray pkg_data_info_list) { jlong capabilities = CalculateCapabilities(env, uid, gid, gids, is_child_zygote); if (UNLIKELY(managed_fds_to_close == nullptr)) { @@ -1457,7 +1728,7 @@ static jint com_android_internal_os_Zygote_nativeForkAndSpecialize( capabilities, capabilities, mount_external, se_info, nice_name, false, is_child_zygote == JNI_TRUE, instruction_set, app_data_dir, - is_top_app == JNI_TRUE); + is_top_app == JNI_TRUE, pkg_data_info_list); } return pid; } @@ -1481,10 +1752,13 @@ static jint com_android_internal_os_Zygote_nativeForkSystemServer( fds_to_ignore, true); if (pid == 0) { + // System server prcoess does not need data isolation so no need to + // know pkg_data_info_list. SpecializeCommon(env, uid, gid, gids, runtime_flags, rlimits, permitted_capabilities, effective_capabilities, MOUNT_EXTERNAL_DEFAULT, nullptr, nullptr, true, - false, nullptr, nullptr, /* is_top_app= */ false); + false, nullptr, nullptr, /* is_top_app= */ false, + /* pkg_data_info_list */ nullptr); } else if (pid > 0) { // The zygote process checks whether the child process has died or not. ALOGI("System server process %d has been created", pid); @@ -1607,14 +1881,15 @@ static void com_android_internal_os_Zygote_nativeSpecializeAppProcess( JNIEnv* env, jclass, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name, - jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir, jboolean is_top_app) { + jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir, jboolean is_top_app, + jobjectArray pkg_data_info_list) { jlong capabilities = CalculateCapabilities(env, uid, gid, gids, is_child_zygote); SpecializeCommon(env, uid, gid, gids, runtime_flags, rlimits, capabilities, capabilities, mount_external, se_info, nice_name, false, is_child_zygote == JNI_TRUE, instruction_set, app_data_dir, - is_top_app == JNI_TRUE); + is_top_app == JNI_TRUE, pkg_data_info_list); } /** @@ -1775,7 +2050,7 @@ static void com_android_internal_os_Zygote_nativeBoostUsapPriority(JNIEnv* env, static const JNINativeMethod gMethods[] = { { "nativeForkAndSpecialize", - "(II[II[[IILjava/lang/String;Ljava/lang/String;[I[IZLjava/lang/String;Ljava/lang/String;Z)I", + "(II[II[[IILjava/lang/String;Ljava/lang/String;[I[IZLjava/lang/String;Ljava/lang/String;Z[Ljava/lang/String;)I", (void *) com_android_internal_os_Zygote_nativeForkAndSpecialize }, { "nativeForkSystemServer", "(II[II[[IJJ)I", (void *) com_android_internal_os_Zygote_nativeForkSystemServer }, @@ -1788,7 +2063,7 @@ static const JNINativeMethod gMethods[] = { { "nativeForkUsap", "(II[IZ)I", (void *) com_android_internal_os_Zygote_nativeForkUsap }, { "nativeSpecializeAppProcess", - "(II[II[[IILjava/lang/String;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/String;Z)V", + "(II[II[[IILjava/lang/String;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/String;Z[Ljava/lang/String;)V", (void *) com_android_internal_os_Zygote_nativeSpecializeAppProcess }, { "nativeInitNativeState", "(Z)V", (void *) com_android_internal_os_Zygote_nativeInitNativeState }, diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java index 49046b244bb4..d2f1113ec5a9 100644 --- a/services/core/java/android/content/pm/PackageManagerInternal.java +++ b/services/core/java/android/content/pm/PackageManagerInternal.java @@ -133,6 +133,12 @@ public abstract class PackageManagerInternal { @PackageInfoFlags int flags, int filterCallingUid, int userId); /** + * Retrieve CE data directory inode number of an application. + * Return 0 if there's error. + */ + public abstract long getCeDataInode(String packageName, int userId); + + /** * Return a List of all application packages that are installed on the * device, for a specific user. If flag GET_UNINSTALLED_PACKAGES has been * set, a list of all applications including those deleted with diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index 5a78036911a8..22fa8ff4a0fa 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -144,6 +144,7 @@ import com.android.internal.util.HexDump; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.Preconditions; import com.android.internal.widget.LockPatternUtils; +import com.android.server.pm.Installer; import com.android.server.storage.AppFuseBridge; import com.android.server.storage.StorageSessionController; import com.android.server.storage.StorageSessionController.ExternalStorageServiceException; @@ -367,6 +368,8 @@ class StorageManagerService extends IStorageManager.Stub private volatile int mCurrentUserId = UserHandle.USER_SYSTEM; + private final Installer mInstaller; + /** Holding lock for AppFuse business */ private final Object mAppFuseLock = new Object(); @@ -1245,6 +1248,13 @@ class StorageManagerService extends IStorageManager.Stub vol.state = newState; onVolumeStateChangedLocked(vol, oldState, newState); } + try { + if (vol.type == VolumeInfo.TYPE_PRIVATE && state == VolumeInfo.STATE_MOUNTED) { + mInstaller.onPrivateVolumeMounted(vol.getFsUuid()); + } + } catch (Installer.InstallerException e) { + Slog.i(TAG, "Failed when private volume mounted " + vol, e); + } } } @@ -1290,6 +1300,13 @@ class StorageManagerService extends IStorageManager.Stub if (vol != null) { mStorageSessionController.onVolumeRemove(vol); + try { + if (vol.type == VolumeInfo.TYPE_PRIVATE) { + mInstaller.onPrivateVolumeRemoved(vol.getFsUuid()); + } + } catch (Installer.InstallerException e) { + Slog.i(TAG, "Failed when private volume unmounted " + vol, e); + } } } }; @@ -1601,6 +1618,9 @@ class StorageManagerService extends IStorageManager.Stub mStorageSessionController = new StorageSessionController(mContext, mIsFuseEnabled); + mInstaller = new Installer(mContext); + mInstaller.onStart(); + // Initialize the last-fstrim tracking if necessary File dataDir = Environment.getDataDirectory(); File systemDir = new File(dataDir, "system"); @@ -1974,6 +1994,13 @@ class StorageManagerService extends IStorageManager.Stub try { mVold.unmount(vol.id); mStorageSessionController.onVolumeUnmount(vol); + try { + if (vol.type == VolumeInfo.TYPE_PRIVATE) { + mInstaller.onPrivateVolumeRemoved(vol.getFsUuid()); + } + } catch (Installer.InstallerException e) { + Slog.e(TAG, "Failed unmount mirror data", e); + } } catch (Exception e) { Slog.wtf(TAG, e); } diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index ee064191f97a..557def44dc66 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -59,6 +59,7 @@ import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; +import android.content.pm.PackageManagerInternal; import android.content.res.Resources; import android.graphics.Point; import android.os.AppZygote; @@ -78,6 +79,7 @@ import android.os.Trace; import android.os.UserHandle; import android.os.storage.StorageManager; import android.os.storage.StorageManagerInternal; +import android.provider.DeviceConfig; import android.system.ErrnoException; import android.system.Os; import android.system.OsConstants; @@ -85,6 +87,7 @@ import android.text.TextUtils; import android.util.ArrayMap; import android.util.EventLog; import android.util.LongSparseArray; +import android.util.Pair; import android.util.Slog; import android.util.SparseArray; import android.util.SparseBooleanArray; @@ -117,6 +120,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.BitSet; import java.util.List; +import java.util.Map; /** * Activity manager code dealing with processes. @@ -124,6 +128,13 @@ import java.util.List; public final class ProcessList { static final String TAG = TAG_WITH_CLASS_NAME ? "ProcessList" : TAG_AM; + // A device config to control the minimum target SDK to enable app data isolation + static final String ANDROID_APP_DATA_ISOLATION_ENABLED_PROPERTY = + "persist.zygote.app_data_isolation"; + + // A device config to control the minimum target SDK to enable app data isolation + static final String ANDROID_APP_DATA_ISOLATION_MIN_SDK = "android_app_data_isolation_min_sdk"; + // The minimum time we allow between crashes, for us to consider this // application to be bad and stop and its services and reject broadcasts. static final int MIN_CRASH_INTERVAL = 60 * 1000; @@ -337,6 +348,8 @@ public final class ProcessList { private boolean mOomLevelsSet = false; + private boolean mAppDataIsolationEnabled = false; + /** * Temporary to avoid allocations. Protected by main lock. */ @@ -621,6 +634,10 @@ public final class ProcessList { mService = service; mActiveUids = activeUids; mPlatformCompat = platformCompat; + // Get this after boot, and won't be changed until it's rebooted, as we don't + // want some apps enabled while some apps disabled + mAppDataIsolationEnabled = + SystemProperties.getBoolean(ANDROID_APP_DATA_ISOLATION_ENABLED_PROPERTY, false); if (sKillHandler == null) { sKillThread = new ServiceThread(TAG + ":kill", @@ -1853,6 +1870,32 @@ public final class ProcessList { } } + private boolean shouldIsolateAppData(ProcessRecord app) { + if (!mAppDataIsolationEnabled) { + return false; + } + if (!UserHandle.isApp(app.uid)) { + return false; + } + final int minTargetSdk = DeviceConfig.getInt(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + ANDROID_APP_DATA_ISOLATION_MIN_SDK, Build.VERSION_CODES.R); + return app.info.targetSdkVersion >= minTargetSdk; + } + + private Map<String, Pair<String, Long>> getPackageAppDataInfoMap(PackageManagerInternal pmInt, + String[] packages, int uid) { + Map<String, Pair<String, Long>> result = new ArrayMap<>(packages.length); + int userId = UserHandle.getUserId(uid); + for (String packageName : packages) { + String volumeUuid = pmInt.getPackage(packageName).getVolumeUuid(); + long inode = pmInt.getCeDataInode(packageName, userId); + if (inode != 0) { + result.put(packageName, Pair.create(volumeUuid, inode)); + } + } + return result; + } + private Process.ProcessStartResult startProcess(HostingRecord hostingRecord, String entryPoint, ProcessRecord app, int uid, int[] gids, int runtimeFlags, int mountExternal, String seInfo, String requiredAbi, String instructionSet, String invokeWith, @@ -1869,6 +1912,21 @@ public final class ProcessList { app.setHasForegroundActivities(true); } + final Map<String, Pair<String, Long>> pkgDataInfoMap; + + if (shouldIsolateAppData(app)) { + // Get all packages belongs to the same shared uid. sharedPackages is empty array + // if it doesn't have shared uid. + final PackageManagerInternal pmInt = mService.getPackageManagerInternalLocked(); + final String sharedUserId = pmInt.getSharedUserIdForPackage(app.info.packageName); + final String[] sharedPackages = pmInt.getPackagesForSharedUserId(sharedUserId, + app.userId); + pkgDataInfoMap = getPackageAppDataInfoMap(pmInt, sharedPackages.length == 0 + ? new String[]{app.info.packageName} : sharedPackages, uid); + } else { + pkgDataInfoMap = null; + } + final Process.ProcessStartResult startResult; if (hostingRecord.usesWebviewZygote()) { startResult = startWebView(entryPoint, @@ -1884,13 +1942,13 @@ public final class ProcessList { app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet, app.info.dataDir, null, app.info.packageName, /*useUsapPool=*/ false, isTopApp, app.mDisabledCompatChanges, - new String[]{PROC_START_SEQ_IDENT + app.startSeq}); + pkgDataInfoMap, new String[]{PROC_START_SEQ_IDENT + app.startSeq}); } else { startResult = Process.start(entryPoint, app.processName, uid, uid, gids, runtimeFlags, mountExternal, app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet, app.info.dataDir, invokeWith, app.info.packageName, isTopApp, - app.mDisabledCompatChanges, + app.mDisabledCompatChanges, pkgDataInfoMap, new String[]{PROC_START_SEQ_IDENT + app.startSeq}); } checkSlow(startTime, "startProcess: returned from zygote!"); diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java index 26cd42daa9f8..eb4b5939aaad 100644 --- a/services/core/java/com/android/server/pm/Installer.java +++ b/services/core/java/com/android/server/pm/Installer.java @@ -580,6 +580,30 @@ public class Installer extends SystemService { } } + /** + * Bind mount private volume CE and DE mirror storage. + */ + public void onPrivateVolumeMounted(String volumeUuid) throws InstallerException { + if (!checkBeforeRemote()) return; + try { + mInstalld.onPrivateVolumeMounted(volumeUuid); + } catch (Exception e) { + throw InstallerException.from(e); + } + } + + /** + * Unmount private volume CE and DE mirror storage. + */ + public void onPrivateVolumeRemoved(String volumeUuid) throws InstallerException { + if (!checkBeforeRemote()) return; + try { + mInstalld.onPrivateVolumeRemoved(volumeUuid); + } catch (Exception e) { + throw InstallerException.from(e); + } + } + public boolean prepareAppProfile(String pkg, @UserIdInt int userId, @AppIdInt int appId, String profileName, String codePath, String dexMetadataPath) throws InstallerException { if (!checkBeforeRemote()) return false; diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 99d5e4a21df4..cb362b0f0a23 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -22626,6 +22626,18 @@ public class PackageManagerService extends IPackageManager.Stub } @Override + public long getCeDataInode(String packageName, int userId) { + synchronized (mLock) { + final PackageSetting ps = mSettings.mPackages.get(packageName); + if (ps != null) { + return ps.getCeDataInode(userId); + } + Slog.e(TAG, "failed to find package " + packageName); + return 0; + } + } + + @Override public Bundle getSuspendedPackageLauncherExtras(String packageName, int userId) { synchronized (mLock) { final PackageSetting ps = mSettings.mPackages.get(packageName); |