Merge "Isolate sdk sandbox data" into tm-qpr-dev

This commit is contained in:
Sanjana Sunil
2022-11-15 18:36:55 +00:00
committed by Android (Google) Code Review
2 changed files with 264 additions and 68 deletions

View File

@@ -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<std::string>& 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<std::string>& 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<std::string>& 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);

View File

@@ -2207,16 +2207,25 @@ public final class ProcessList {
Map<String, Pair<String, Long>> 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;