From 4b0e57f6295607c4f7873403d2b452d25d1c1a16 Mon Sep 17 00:00:00 2001 From: Sanjana Sunil Date: Wed, 6 Apr 2022 15:35:26 +0000 Subject: [PATCH] Isolate sdk sandbox data Similar to app data isolation, sdk sandbox data isolation is done to prevent the sandbox from checking the existence of other apps via paths containing the app package name like: * Sandbox data paths such as /data/misc_ce/0/sdksandbox/ * Regular app data paths * JIT profile data paths and checking if EACCESS or ENOENT error comes up. This is done by mounting tmpfs on each of these data paths in a separate mount namespace and then bind mounting the required data for that process from the data mirror. For example, in the case of an sdk sandbox process, tmpfs is mounted on misc_ce, misc_de storage, app data paths and JIT profile paths. Then, a sandbox data path is created and data for that process is bind mounted from the mirror. In the case of app processes, access to sdk sandbox storage is restricted through selinux. Bug: 214241165 Test: atest GtsSdkSandboxInprocessTests Change-Id: I79fd5967b157c711cc75e340da7411f2b2f3bf00 Merged-In: I79fd5967b157c711cc75e340da7411f2b2f3bf00 --- core/jni/com_android_internal_os_Zygote.cpp | 311 ++++++++++++++---- .../com/android/server/am/ProcessList.java | 21 +- 2 files changed, 264 insertions(+), 68 deletions(-) diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp index 21bbac0b0a7d8..c8b85c314acaf 100644 --- a/core/jni/com_android_internal_os_Zygote.cpp +++ b/core/jni/com_android_internal_os_Zygote.cpp @@ -1176,6 +1176,11 @@ static void relabelAllDirs(const char* path, const char* context, fail_fn_t fail closedir(dir); } +static bool is_sdk_sandbox_uid(uid_t uid) { + appid_t appId = multiuser_get_app_id(uid); + return appId >= AID_SDK_SANDBOX_PROCESS_START && appId <= AID_SDK_SANDBOX_PROCESS_END; +} + /** * Make other apps data directory not visible in CE, DE storage. * @@ -1262,68 +1267,77 @@ static void isolateAppData(JNIEnv* env, const std::vector& merged_d } closedir(dir); - // Prepare default dirs for user 0 as user 0 always exists. - int result = symlink("/data/data", "/data/user/0"); - if (result != 0) { - fail_fn(CREATE_ERROR("Failed to create symlink /data/user/0 %s", strerror(errno))); - } - PrepareDirIfNotPresent("/data/user_de/0", DEFAULT_DATA_DIR_PERMISSION, - AID_ROOT, AID_ROOT, fail_fn); - - for (int i = 0; i < size; i += 3) { - std::string const & packageName = merged_data_info_list[i]; - std::string const & volUuid = merged_data_info_list[i + 1]; - std::string const & inode = merged_data_info_list[i + 2]; - - 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 not user 0, create /data/user/$USER. - if (userId == 0) { - actualCePath = internalLegacyCePath; - } else { - PrepareDirIfNotPresent(internalCeUserPath, DEFAULT_DATA_DIR_PERMISSION, - AID_ROOT, AID_ROOT, fail_fn); - actualCePath = internalCeUserPath; + // No bind mounting of app data should occur in the case of a sandbox process since SDK sandboxes + // should not be able to read app data. Tmpfs was mounted however since a sandbox should not have + // access to app data. + if (!is_sdk_sandbox_uid(uid)) { + // Prepare default dirs for user 0 as user 0 always exists. + int result = symlink("/data/data", "/data/user/0"); + if (result != 0) { + fail_fn(CREATE_ERROR("Failed to create symlink /data/user/0 %s", strerror(errno))); + } + PrepareDirIfNotPresent("/data/user_de/0", DEFAULT_DATA_DIR_PERMISSION, AID_ROOT, AID_ROOT, + fail_fn); + + for (int i = 0; i < size; i += 3) { + std::string const& packageName = merged_data_info_list[i]; + std::string const& volUuid = merged_data_info_list[i + 1]; + std::string const& inode = merged_data_info_list[i + 2]; + + 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 not user 0, create /data/user/$USER. + if (userId == 0) { + 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); } - PrepareDirIfNotPresent(internalDeUserPath, DEFAULT_DATA_DIR_PERMISSION, - AID_ROOT, AID_ROOT, fail_fn); - actualDePath = internalDeUserPath; - } - isolateAppDataPerPackage(userId, packageName, volUuid, ceDataInode, - actualCePath, actualDePath, fail_fn); } + // We set the label AFTER everything is done, as we are applying // the file operations on tmpfs. If we set the label when we mount // tmpfs, SELinux will not happy as we are changing system_data_files. @@ -1363,6 +1377,167 @@ static void isolateAppData(JNIEnv* env, const std::vector& merged_d freecon(dataDataContext); } +/** + * Without sdk sandbox data isolation, the sandbox could detect if another app is installed on the + * system by "touching" other data directories like /data/misc_ce/0/sdksandbox/com.whatsapp, similar + * to apps without app data isolation (see {@link #isolateAppData()}). + * + * To prevent this, tmpfs is mounted onto misc_ce and misc_de directories on all possible volumes in + * a separate mount namespace. The sandbox directory path is then created containing the name of the + * client app package associated with the sdk sandbox. The contents for this (sdk level storage and + * shared sdk storage) are bind mounted from the sandbox data mirror. + */ +static void isolateSdkSandboxData(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); + + int size = (pkg_data_info_list != nullptr) ? env->GetArrayLength(pkg_data_info_list) : 0; + // The sandbox should only have information of one associated client app (package, uuid, inode) + if (size != 3) { + fail_fn(CREATE_ERROR( + "Unable to isolate sandbox data, incorrect associated app information")); + } + + auto extract_fn = [env, process_name, managed_nice_name, + pkg_data_info_list](int info_list_idx) { + jstring jstr = (jstring)(env->GetObjectArrayElement(pkg_data_info_list, info_list_idx)); + return ExtractJString(env, process_name, managed_nice_name, jstr).value(); + }; + std::string packageName = extract_fn(0); + std::string volUuid = extract_fn(1); + + char internalCePath[PATH_MAX]; + char internalDePath[PATH_MAX]; + char externalPrivateMountPath[PATH_MAX]; + snprintf(internalCePath, PATH_MAX, "/data/misc_ce"); + snprintf(internalDePath, PATH_MAX, "/data/misc_de"); + snprintf(externalPrivateMountPath, PATH_MAX, "/mnt/expand"); + + char ceUserPath[PATH_MAX]; + char deUserPath[PATH_MAX]; + if (volUuid != "null") { + snprintf(ceUserPath, PATH_MAX, "%s/%s/misc_ce/%d", externalPrivateMountPath, + volUuid.c_str(), userId); + snprintf(deUserPath, PATH_MAX, "%s/%s/misc_de/%d", externalPrivateMountPath, + volUuid.c_str(), userId); + } else { + snprintf(ceUserPath, PATH_MAX, "%s/%d", internalCePath, userId); + snprintf(deUserPath, PATH_MAX, "%s/%d", internalDePath, userId); + } + + char ceSandboxPath[PATH_MAX]; + char deSandboxPath[PATH_MAX]; + snprintf(ceSandboxPath, PATH_MAX, "%s/sdksandbox", ceUserPath); + snprintf(deSandboxPath, PATH_MAX, "%s/sdksandbox", deUserPath); + + // If the client app using the sandbox has been installed when the device is locked and the + // sandbox starts up when the device is locked, sandbox storage might not have been created. + // In that case, mount tmpfs for data isolation, but don't bind mount. + bool bindMountCeSandboxDataDirs = true; + bool bindMountDeSandboxDataDirs = true; + if (access(ceSandboxPath, F_OK) != 0) { + bindMountCeSandboxDataDirs = false; + } + if (access(deSandboxPath, F_OK) != 0) { + bindMountDeSandboxDataDirs = false; + } + + char* context = nullptr; + char* userContext = nullptr; + char* sandboxContext = nullptr; + if (getfilecon(internalDePath, &context) < 0) { + fail_fn(CREATE_ERROR("Unable to getfilecon on %s %s", internalDePath, strerror(errno))); + } + if (bindMountDeSandboxDataDirs) { + if (getfilecon(deUserPath, &userContext) < 0) { + fail_fn(CREATE_ERROR("Unable to getfilecon on %s %s", deUserPath, strerror(errno))); + } + if (getfilecon(deSandboxPath, &sandboxContext) < 0) { + fail_fn(CREATE_ERROR("Unable to getfilecon on %s %s", deSandboxPath, strerror(errno))); + } + } + + MountAppDataTmpFs(internalCePath, fail_fn); + MountAppDataTmpFs(internalDePath, fail_fn); + + // Mount tmpfs on all external volumes + DIR* dir = opendir(externalPrivateMountPath); + if (dir == nullptr) { + fail_fn(CREATE_ERROR("Failed to opendir %s", externalPrivateMountPath)); + } + struct dirent* ent; + while ((ent = readdir(dir))) { + if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0) continue; + if (ent->d_type != DT_DIR) { + fail_fn(CREATE_ERROR("Unexpected type: %d %s", ent->d_type, ent->d_name)); + } + auto volPath = StringPrintf("%s/%s", externalPrivateMountPath, ent->d_name); + auto externalCePath = StringPrintf("%s/misc_ce", volPath.c_str()); + auto externalDePath = StringPrintf("%s/misc_de", volPath.c_str()); + + WaitUntilDirReady(externalCePath.c_str(), fail_fn); + MountAppDataTmpFs(externalCePath.c_str(), fail_fn); + WaitUntilDirReady(externalDePath.c_str(), fail_fn); + MountAppDataTmpFs(externalDePath.c_str(), fail_fn); + } + closedir(dir); + + char mirrorCeSandboxPath[PATH_MAX]; + char mirrorDeSandboxPath[PATH_MAX]; + snprintf(mirrorCeSandboxPath, PATH_MAX, "/data_mirror/misc_ce/%s/%d/sdksandbox", + volUuid.c_str(), userId); + snprintf(mirrorDeSandboxPath, PATH_MAX, "/data_mirror/misc_de/%s/%d/sdksandbox", + volUuid.c_str(), userId); + + if (bindMountCeSandboxDataDirs) { + PrepareDir(ceUserPath, DEFAULT_DATA_DIR_PERMISSION, AID_ROOT, AID_ROOT, fail_fn); + PrepareDir(ceSandboxPath, DEFAULT_DATA_DIR_PERMISSION, AID_ROOT, AID_ROOT, fail_fn); + // TODO(b/231322885): Use inode numbers to find the correct app path when the device locked. + createAndMountAppData(packageName, packageName, mirrorCeSandboxPath, ceSandboxPath, fail_fn, + true /*call_fail_fn*/); + + relabelDir(ceSandboxPath, sandboxContext, fail_fn); + relabelDir(ceUserPath, userContext, fail_fn); + } + if (bindMountDeSandboxDataDirs) { + PrepareDir(deUserPath, DEFAULT_DATA_DIR_PERMISSION, AID_ROOT, AID_ROOT, fail_fn); + PrepareDir(deSandboxPath, DEFAULT_DATA_DIR_PERMISSION, AID_ROOT, AID_ROOT, fail_fn); + createAndMountAppData(packageName, packageName, mirrorDeSandboxPath, deSandboxPath, fail_fn, + true /*call_fail_fn*/); + + relabelDir(deSandboxPath, sandboxContext, fail_fn); + relabelDir(deUserPath, userContext, fail_fn); + } + + // We set the label AFTER everything is done, as we are applying + // the file operations on tmpfs. If we set the label when we mount + // tmpfs, SELinux will not happy as we are changing system_data_files. + relabelDir(internalCePath, context, fail_fn); + relabelDir(internalDePath, context, fail_fn); + + // Relabel CE and DE dirs under /mnt/expand + dir = opendir(externalPrivateMountPath); + if (dir == nullptr) { + fail_fn(CREATE_ERROR("Failed to opendir %s", externalPrivateMountPath)); + } + while ((ent = readdir(dir))) { + if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0) continue; + auto volPath = StringPrintf("%s/%s", externalPrivateMountPath, ent->d_name); + auto externalCePath = StringPrintf("%s/misc_ce", volPath.c_str()); + auto externalDePath = StringPrintf("%s/misc_de", volPath.c_str()); + relabelDir(externalCePath.c_str(), context, fail_fn); + relabelDir(externalDePath.c_str(), context, fail_fn); + } + closedir(dir); + + if (bindMountDeSandboxDataDirs) { + freecon(sandboxContext); + freecon(userContext); + } + freecon(context); +} + static void insertPackagesToMergedList(JNIEnv* env, std::vector& merged_data_info_list, jobjectArray data_info_list, const char* process_name, @@ -1428,6 +1603,12 @@ static void isolateJitProfile(JNIEnv* env, jobjectArray pkg_data_info_list, MountAppDataTmpFs(kCurProfileDirPath, fail_fn); MountAppDataTmpFs(kRefProfileDirPath, fail_fn); + // Sandbox processes do not have JIT profile, so no data needs to be bind mounted. However, it + // should still not have access to JIT profile, so tmpfs is mounted. + if (is_sdk_sandbox_uid(uid)) { + return; + } + // Create profile directory for this user. std::string actualCurUserProfile = StringPrintf("%s/%d", kCurProfileDirPath, user_id); PrepareDir(actualCurUserProfile, DEFAULT_DATA_DIR_PERMISSION, AID_ROOT, AID_ROOT, fail_fn); @@ -1580,9 +1761,15 @@ static void SpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray gids, // Make sure app is running in its own mount namespace before isolating its data directories. ensureInAppMountNamespace(fail_fn); - // Sandbox data and jit profile directories by overlaying a tmpfs on those dirs and bind - // mount all related packages separately. + // Isolate app data, jit profile and sandbox data directories by overlaying a tmpfs on those + // dirs and bind mount all related packages separately. if (mount_data_dirs) { + // Sdk sandbox data isolation does not need to occur for app processes since sepolicy + // prevents access to sandbox data anyway. + if (is_sdk_sandbox_uid(uid)) { + isolateSdkSandboxData(env, pkg_data_info_list, uid, process_name, managed_nice_name, + fail_fn); + } isolateAppData(env, pkg_data_info_list, allowlisted_data_info_list, uid, process_name, managed_nice_name, fail_fn); isolateJitProfile(env, pkg_data_info_list, uid, process_name, managed_nice_name, fail_fn); diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index 3eac4066dc866..9700e562b5183 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -2207,16 +2207,25 @@ public final class ProcessList { Map> allowlistedAppDataInfoMap; boolean bindMountAppStorageDirs = false; boolean bindMountAppsData = mAppDataIsolationEnabled - && (UserHandle.isApp(app.uid) || UserHandle.isIsolated(app.uid)) + && (UserHandle.isApp(app.uid) || UserHandle.isIsolated(app.uid) + || app.isSdkSandbox) && mPlatformCompat.isChangeEnabled(APP_DATA_DIRECTORY_ISOLATION, app.info); // Get all packages belongs to the same shared uid. sharedPackages is empty array // if it doesn't have shared uid. final PackageManagerInternal pmInt = mService.getPackageManagerInternal(); - final String[] sharedPackages = pmInt.getSharedUserPackagesForPackage( - app.info.packageName, app.userId); - final String[] targetPackagesList = sharedPackages.length == 0 - ? new String[]{app.info.packageName} : sharedPackages; + + // In the case of sdk sandbox, the pkgDataInfoMap of only the client app associated with + // the sandbox is required to handle app visibility restrictions for the sandbox. + final String[] targetPackagesList; + if (app.isSdkSandbox) { + targetPackagesList = new String[]{app.sdkSandboxClientAppPackage}; + } else { + final String[] sharedPackages = pmInt.getSharedUserPackagesForPackage( + app.info.packageName, app.userId); + targetPackagesList = sharedPackages.length == 0 + ? new String[]{app.info.packageName} : sharedPackages; + } final boolean hasAppStorage = hasAppStorage(pmInt, app.info.packageName); @@ -2242,7 +2251,7 @@ public final class ProcessList { bindMountAppsData = false; } - if (!hasAppStorage) { + if (!hasAppStorage && !app.isSdkSandbox) { bindMountAppsData = false; pkgDataInfoMap = null; allowlistedAppDataInfoMap = null;