前言
Android 11 起开始存在 data_mirror 机制,通过 mount tmpfs 的方式将常见的应用目录(如 /data/data
、/data/user_de
)使得对应用而言仅可访问和发现自己的存储目录,进而避免 stat /data/data/<package_name>
的方式探测用户安装的其他应用的存在。
分析
本文将基于 Android 13 源码,分析应用目录隔离机制。
初始化
Android 最初由这个提交引入应用目录隔离机制,在根下添加了一个目录 data_mirror
作为数据镜像。
Android 13 中,系统启动时会进行如下操作:
system/core/rootdir/init.rc
# A tmpfs directory, which will contain all apps CE DE data directory that
# bind mount from the original source.
mount tmpfs tmpfs /data_mirror nodev noexec nosuid mode=0700,uid=0,gid=1000
restorecon /data_mirror
mkdir /data_mirror/data_ce 0700 root root
mkdir /data_mirror/data_de 0700 root root
# Create CE and DE data directory for default volume
mkdir /data_mirror/data_ce/null 0700 root root
mkdir /data_mirror/data_de/null 0700 root root
# Bind mount CE and DE data directory to mirror's default volume directory.
# The 'slave' option (MS_SLAVE) is needed to cause the later bind mount of
# /data/data onto /data/user/0 to propagate to /data_mirror/data_ce/null/0.
mount none /data/user /data_mirror/data_ce/null bind rec slave
mount none /data/user_de /data_mirror/data_de/null bind rec
# Create mirror directory for jit profiles
mkdir /data_mirror/cur_profiles 0700 root root
mount none /data/misc/profiles/cur /data_mirror/cur_profiles bind rec
mkdir /data_mirror/ref_profiles 0700 root root
mount none /data/misc/profiles/ref /data_mirror/ref_profiles bind rec
mount tmpfs
到 /data_mirror
,并创建 data_ce
、data_de
目录,以及内部存储卷目录 null
。
除此之外,还会创建两个目录 cur_profiles
、ref_profiles
,用于隔离 JIT 配置文件目录。
Zygote 启动参数
startProcess
AMS 通过 com.android.server.am.ProcessList.startProcess
方法进行启动应用进程的操作。
private Process.ProcessStartResult startProcess(HostingRecord hostingRecord, String entryPoint,
ProcessRecord app, int uid, int[] gids, int runtimeFlags, int zygotePolicyFlags,
int mountExternal, String seInfo, String requiredAbi, String instructionSet,
String invokeWith, long startTime) {
<--implementation-->
}
与目录隔离有关的部分如下:首先,创建两个 map pkgDataInfoMap
和 allowlistedAppDataInfoMap
,分别表示目标应用的应用目录信息和允许访问的应用目录信息,其中 key
为包名,value
为存储卷的 uuid 和真实目录的 inode(之后会介绍为什么需要 inode)。接着是两个控制变量 bindMountAppStorageDirs
和 bindMountAppsData
,分别表示是否隔离 Android/obb
、Android/data
目录和是否隔离 CE、DE 及 JIT profile 目录。
满足以下条件会启用 AppStorageDirs
隔离:
- 隔离功能已启用:由系统属性
persist.sys.vold_app_data_isolation_enabled
决定 - 要启动的应用是用户应用,且不是
ExternalStorageService
要启动的应用存储挂载模式不是以下几种之一
Zygote.MOUNT_EXTERNAL_ANDROID_WRITABLE
:仍然使用分区存储,但Android/
可写Zygote.MOUNT_EXTERNAL_PASS_THROUGH
:旧的文件系统,需要直接 bind mount 到外部存储Zygote.MOUNT_EXTERNAL_INSTALLER
:安装器 App,可以访问自身目录和Android/obb
文件夹Zygote.MOUNT_EXTERNAL_NONE
:不挂载外部存储
满足以下条件会启用 AppsData
隔离:
- 隔离功能已启用:由系统属性
persist.zygote.app_data_isolation
决定 - 要启动的应用是用户应用,或是 isolated process
- 要启动的应用 target API >= 29
- 要启动的应用存在数据目录
Map<String, Pair<String, Long>> pkgDataInfoMap;
Map<String, Pair<String, Long>> allowlistedAppDataInfoMap;
boolean bindMountAppStorageDirs = false;
boolean bindMountAppsData = mAppDataIsolationEnabled
&& (UserHandle.isApp(app.uid) || UserHandle.isIsolated(app.uid))
&& mPlatformCompat.isChangeEnabled(APP_DATA_DIRECTORY_ISOLATION, app.info);
接下来,获取所有属于同一 shared uid 的包名以及目标包名列表。如果应用存在 shared uid,则 targetPackagesList
即是 sharedPackages
,否则 targetPackagesList
为应用的包名,sharedPackages
为空。
然后,通过 getPackageAppDataInfoMap
方法获取 pkgDataInfoMap
和 allowlistedAppDataInfoMap
。其中处于隔离白名单的包名列表由 SystemConfig
的 app-data-isolation-whitelisted-app
决定。如果两个 map 有一个为空,则停用目录隔离(但实际上这还是个 todo,截止 Android 13 getPackageAppDataInfoMap
永远不为空)。
// 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;
final boolean hasAppStorage = hasAppStorage(pmInt, app.info.packageName);
pkgDataInfoMap = getPackageAppDataInfoMap(pmInt, targetPackagesList, uid);
if (pkgDataInfoMap == null) {
// TODO(b/152760674): Handle inode == 0 case properly, now we just give it a
// tmp free pass.
bindMountAppsData = false;
}
// Remove all packages in pkgDataInfoMap from mAppDataIsolationAllowlistedApps, so
// it won't be mounted twice.
final Set<String> allowlistedApps = new ArraySet<>(mAppDataIsolationAllowlistedApps);
for (String pkg : targetPackagesList) {
allowlistedApps.remove(pkg);
}
allowlistedAppDataInfoMap = getPackageAppDataInfoMap(pmInt,
allowlistedApps.toArray(new String[0]), uid);
if (allowlistedAppDataInfoMap == null) {
// TODO(b/152760674): Handle inode == 0 case properly, now we just give it a
// tmp free pass.
bindMountAppsData = false;
}
下面深入getPackageAppDataInfoMap
的实现:通过 android.content.pm.PackageManagerInternal.getPackageStateInternal
方法获取目标包名的 packageState
,进而由这个 packageState
获取存储卷 uuid 和应用目录 inode。
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) {
final PackageStateInternal packageState = pmInt.getPackageStateInternal(packageName);
if (packageState == null) {
Slog.w(TAG, "Unknown package:" + packageName);
continue;
}
String volumeUuid = packageState.getVolumeUuid();
long inode = packageState.getUserStateOrDefault(userId).getCeDataInode();
if (inode == 0) {
Slog.w(TAG, packageName + " inode == 0 (b/152760674)");
return null;
}
result.put(packageName, Pair.create(volumeUuid, inode));
}
return result;
}
PackageManagerInternal.getPackageStateInternal
方法由 com.android.server.pm.PackageManagerInternalBase
覆盖,实际上由 Computer
代理实现。
com.android.server.pm.Computer
是 Android 13 PMS 新增的一个接口,用于 live data 和 snapshot data,这里不再细究其具体用途(也许用来替换 IPackageManager
?不少 IPackageManager
方法被标上了 @Deprecated
)。
com.android.server.pm.Computer.getPackageStateInternal
方法又是由 com.android.server.pm.ComputerEngine
覆盖,然后接下来又是套各种代理套很多层,由于偏移了本文讨论内容,就不再继续深入。
public abstract class PackageManagerInternal {
...
@NonNull
private final PackageManagerService mService;
@Override
public final Computer snapshot() {
return mService.snapshotComputer();
}
@Nullable
@Override
@Deprecated
public final PackageStateInternal getPackageStateInternal(String packageName) {
return snapshot().getPackageStateInternal(packageName);
}
...
}
回到 startProcess
,取得两个 map 后,接下来判断如果要启动的应用不存在数据目录,则将 bindMountAppsData
置为 false
,并置空两个 map(说实话我不明白它为啥要先获取了再置空)。除此之外,如果要启动的应用是 isolated process,则它连自己的目录也不能访问,因此将两个 map 置空(所以我也不明白为啥这儿也是先获取了再置空)。
接着,通过 needsStorageDataIsolation
方法判断是否需要启用 AppStorageDirs
隔离。
if (!hasAppStorage) {
bindMountAppsData = false;
pkgDataInfoMap = null;
allowlistedAppDataInfoMap = null;
}
int userId = UserHandle.getUserId(uid);
StorageManagerInternal storageManagerInternal = LocalServices.getService(
StorageManagerInternal.class);
if (needsStorageDataIsolation(storageManagerInternal, app)) {
// We will run prepareStorageDirs() after we trigger zygote fork, so it won't
// slow down app starting speed as those dirs might not be cached.
if (pkgDataInfoMap != null && storageManagerInternal.isFuseMounted(userId)) {
bindMountAppStorageDirs = true;
} else {
// Fuse is not mounted or inode == 0,
// so we won't mount it in zygote, but resume the mount after unlocking device.
app.setBindMountPending(true);
bindMountAppStorageDirs = false;
}
}
// If it's an isolated process, it should not even mount its own app data directories,
// since it has no access to them anyway.
if (app.isolated) {
pkgDataInfoMap = null;
allowlistedAppDataInfoMap = null;
}
needsStorageDataIsolation
实现如下,具体要求上面已经提到,就不再细说。
private boolean needsStorageDataIsolation(StorageManagerInternal storageManagerInternal,
ProcessRecord app) {
final int mountMode = app.getMountMode();
return mVoldAppDataIsolationEnabled && UserHandle.isApp(app.uid)
&& !storageManagerInternal.isExternalStorageService(app.uid)
// Special mounting mode doesn't need to have data isolation as they won't
// access /mnt/user anyway.
&& mountMode != Zygote.MOUNT_EXTERNAL_ANDROID_WRITABLE
&& mountMode != Zygote.MOUNT_EXTERNAL_PASS_THROUGH
&& mountMode != Zygote.MOUNT_EXTERNAL_INSTALLER
&& mountMode != Zygote.MOUNT_EXTERNAL_NONE;
}
至此,目录隔离的参数构造已经完成,接下来便是通知 zygote 创建应用进程。
最后,如果启用 AppStorageDirs
隔离,如果存储目录尚不存在,则创建一个。
// This runs after Process.start() as this method may block app process starting time
// if dir is not cached. Running this method after Process.start() can make it
// cache the dir asynchronously, so zygote can use it without waiting for it.
if (bindMountAppStorageDirs) {
storageManagerInternal.prepareStorageDirs(userId, pkgDataInfoMap.keySet(),
app.processName);
}
startViaZygote
android.os.Process.start
方法调用 android.os.ZygoteProcess.start
,进而调用android.os.ZygoteProcess.startViaZygote
,最终在这里将参数序列化,发送给 zygote 创建应用进程。
目录隔离
处理参数
native 函数 com_android_internal_os_Zygote_nativeForkAndSpecialize
完成 fork 后,子进程通过 SpecializeCommon
函数处理处理 zygote 参数并继续启动应用。
com_android_internal_os_Zygote.cpp
static jint com_android_internal_os_Zygote_nativeForkAndSpecialize(
JNIEnv* env, jclass, jint uid, jint gid, jintArray gids, 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,
jobjectArray pkg_data_info_list, jobjectArray allowlisted_data_info_list,
jboolean mount_data_dirs, jboolean mount_storage_dirs) {
...
pid_t pid = zygote::ForkCommon(env, /* is_system_server= */ false, fds_to_close, fds_to_ignore, true);
if (pid == 0) {
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, pkg_data_info_list,
allowlisted_data_info_list, mount_data_dirs == JNI_TRUE,
mount_storage_dirs == JNI_TRUE);
}
return pid;
}
在 SpecializeCommon
函数中,通过 mount_data_dirs
参数判断是否进行目录隔离。如果需要隔离,则调用 isolateAppData
和 isolateJitProfile
函数执行隔离。
// 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, jlong permitted_capabilities,
jlong effective_capabilities, 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, jobjectArray pkg_data_info_list,
jobjectArray allowlisted_data_info_list, bool mount_data_dirs,
bool mount_storage_dirs) {
...
if (mount_data_dirs) {
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);
}
...
}
执行隔离
isolateAppData
isolateAppData
函数负责隔离应用数据目录。
static void isolateAppData(JNIEnv* env, jobjectArray pkg_data_info_list,
jobjectArray allowlisted_data_info_list, uid_t uid,
const char* process_name, jstring managed_nice_name, fail_fn_t fail_fn) {
std::vector<std::string> merged_data_info_list;
insertPackagesToMergedList(env, merged_data_info_list, pkg_data_info_list, process_name,
managed_nice_name, fail_fn);
insertPackagesToMergedList(env, merged_data_info_list, allowlisted_data_info_list, process_name,
managed_nice_name, fail_fn);
isolateAppData(env, merged_data_info_list, uid, process_name, managed_nice_name, fail_fn);
}
首先,创建一个 vector,其中按包名、卷 uuid、包名目录 inode 组成三元组依次存储。
其次,通过 insertPackagesToMergedList
函数,将 pkg_data_info_list
和 allowlisted_data_info_list
中的数据转换为 std::string
并将数据存入 vector 中。
static void insertPackagesToMergedList(JNIEnv* env,
std::vector<std::string>& merged_data_info_list,
jobjectArray data_info_list, const char* process_name,
jstring managed_nice_name, fail_fn_t fail_fn) {
auto extract_fn = std::bind(ExtractJString, env, process_name, managed_nice_name, _1);
int size = (data_info_list != nullptr) ? env->GetArrayLength(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 data_info_list size %d", size));
}
for (int i = 0; i < size; i += 3) {
jstring package_str = (jstring) (env->GetObjectArrayElement(data_info_list, i));
std::string packageName = extract_fn(package_str).value();
merged_data_info_list.push_back(packageName);
jstring vol_str = (jstring) (env->GetObjectArrayElement(data_info_list, i + 1));
std::string volUuid = extract_fn(vol_str).value();
merged_data_info_list.push_back(volUuid);
jstring inode_str = (jstring) (env->GetObjectArrayElement(data_info_list, i + 2));
std::string inode = extract_fn(inode_str).value();
merged_data_info_list.push_back(inode);
}
}
接下来便是隔离的真正实现部分。
/**
* Hide the CE and DE data directories of non-related apps.
*
* Without this, apps can detect if any app is installed by trying to "touch" the app's CE
* or DE data directory, e.g. /data/data/com.whatsapp. This fails with EACCES if the app
* is installed, or ENOENT if it's not. Traditional file permissions or SELinux can only
* block accessing those directories but can't fix fingerprinting like this.
*
* Instead, we hide non-related apps' data directories from the filesystem entirely by
* mounting tmpfs instances over their parent directories and bind-mounting in just the
* needed app data directories. This is done in a private mount namespace.
*
* Steps:
* (1) Collect a list of all related apps (apps with same uid and allowlisted apps) data info
* (package name, data stored volume uuid, and inode number of its CE data directory)
* (2) Mount tmpfs on /data/data and /data/user{,_de}, and on /mnt/expand/$volume/user{,_de}
* for all adoptable storage volumes. This hides all app data directories.
* (3) For each related app, create stubs for its data directories in the relevant tmpfs
* instances, then bind mount in the actual directories from /data_mirror. This works
* for both the CE and DE directories. DE storage is always unlocked, whereas the
* app's CE directory can be found via inode number if CE storage is locked.
*
* Example assuming user 0, app "com.android.foo", no shared uid, and no adoptable storage:
* (1) Info = ["com.android.foo", "null" (volume uuid "null"=default), "123456" (inode number)]
* (2) Mount tmpfs on /data/data, /data/user, and /data/user_de.
* (3) For DE storage, create a directory /data/user_de/0/com.android.foo and bind mount
* /data_mirror/data_de/0/com.android.foo onto it.
* (4) Do similar for CE storage. But if the device is in direct boot mode, then CE
* storage will be locked, so the app's CE data directory won't exist at the usual
* path /data_mirror/data_ce/0/com.android.foo. It will still exist in
* /data_mirror/data_ce/0, but its filename will be an unpredictable no-key name. In
* this case, we use the inode number to find the right directory instead. Note that
* the bind-mounted app CE data directory will remain locked. It will be unlocked
* automatically if/when the user's CE storage is unlocked, since adding an encryption
* key takes effect on a whole filesystem instance including all its mounts.
*/
static void isolateAppData(JNIEnv* env, const std::vector<std::string>& merged_data_info_list,
uid_t uid, const char* process_name,
jstring managed_nice_name, fail_fn_t fail_fn) {
<-- implementation -->
}
获取当前用户 userId,保存下 vector 大小。
const userid_t userId = multiuser_get_user_id(uid);
int size = merged_data_info_list.size();
数据原始目录,包括内置存储和 SD 卡。
// 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");
保存 SELinux context。
// Get the "u:object_r:system_userdir_file:s0" security context. This can be
// gotten from several different places; we use /data/user.
char* dataUserdirContext = nullptr;
if (getfilecon(internalCePath, &dataUserdirContext) < 0) {
fail_fn(CREATE_ERROR("Unable to getfilecon on %s %s", internalCePath,
strerror(errno)));
}
// Get the "u:object_r:system_data_file:s0" security context. This can be
// gotten from several different places; we use /data/misc.
char* dataFileContext = nullptr;
if (getfilecon("/data/misc", &dataFileContext) < 0) {
fail_fn(CREATE_ERROR("Unable to getfilecon on /data/misc %s", strerror(errno)));
}
挂载 tmpfs 到原始目录。
MountAppDataTmpFs(internalLegacyCePath, fail_fn);
MountAppDataTmpFs(internalCePath, fail_fn);
MountAppDataTmpFs(internalDePath, fail_fn);
// Mount tmpfs on all external vols DE and CE storage
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 cePath = StringPrintf("%s/user", volPath.c_str());
auto dePath = StringPrintf("%s/user_de", volPath.c_str());
// Wait until dir user is created.
WaitUntilDirReady(cePath.c_str(), fail_fn);
MountAppDataTmpFs(cePath.c_str(), fail_fn);
// Wait until dir user_de is created.
WaitUntilDirReady(dePath.c_str(), fail_fn);
MountAppDataTmpFs(dePath.c_str(), fail_fn);
}
closedir(dir);
用户 0 的目录对应用而言始终存在,因此将 /data/data
软链接到 /data/user/0
,并创建 /data/user_de/0
目录(当然,这里这么做实际上是有一个历史遗留原因)。
// 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);
接下来,读取之前保存的 vector
中的包名目录信息,对其中的每个包名,为 tmpfs 覆盖后的原始目录创建对应的目录。当然由于历史遗留问题,可以看到 user_ce
对用户 0 和其他用户的处理方式略有区别:用户 0 的原始目录在 /data/data
,而其他用户在 /data/user/<userId>
。
之后,调用函数 isolateAppDataPerPackage
处理每一个包名目录。
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);
}
通过 volume_uuid
、package_name
、ce_data_inode
字段,组合出在 /data_mirror
中的真实目录,将其 bind mount 到原始目录。
// 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,
true /*call_fail_fn*/);
std::string ce_data_path = getAppDataDirName(mirrorCePath, package_name, ce_data_inode, fail_fn);
if (!createAndMountAppData(package_name, ce_data_path, mirrorCePath, actualCePath, fail_fn,
false /*call_fail_fn*/)) {
// CE might unlocks and the name is decrypted
// get the name and mount again
ce_data_path=getAppDataDirName(mirrorCePath, package_name, ce_data_inode, fail_fn);
mountAppData(package_name, ce_data_path, mirrorCePath, actualCePath, fail_fn);
}
}
需要注意的是,由于在系统启动阶段,data_ce
尚未解密,包名对应的真实目录名并非包名本身,但是目录的 inode 是确定的。因此 Android 系统采用的方式是遍历加密目录,根据提供的 inode 找到真实目录。
// 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
std::unique_ptr<DIR, decltype(&closedir)> dir(opendir(parent_path.data()), closedir);
if (dir == nullptr) {
fail_fn(CREATE_ERROR("Failed to opendir %s", parent_path.data()));
}
struct dirent* ent;
while ((ent = readdir(dir.get()))) {
if (ent->d_ino == ce_data_inode) {
return ent->d_name;
}
}
}
// 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) {
std::unique_ptr<DIR, decltype(&closedir)> dir(opendir(parent_path.data()), closedir);
if (dir == nullptr) {
fail_fn(CREATE_ERROR("Failed to opendir %s", parent_path.data()));
}
struct dirent* ent;
while ((ent = readdir(dir.get()))) {
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);
return ent->d_name;
}
}
}
// Fallback done
fail_fn(CREATE_ERROR("Unable to find %s:%lld in %s", package_name.data(),
ce_data_inode, parent_path.data()));
return nullptr;
}
}
所有目录处理完后,重设 SELinux context,完成隔离。
// 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.
// Relabel dir under /data/user, including /data/user/0
relabelSubdirs(internalCePath, dataFileContext, fail_fn);
// Relabel /data/user
relabelDir(internalCePath, dataUserdirContext, fail_fn);
// Relabel /data/data
relabelDir(internalLegacyCePath, dataFileContext, fail_fn);
// Relabel subdirectories of /data/user_de
relabelSubdirs(internalDePath, dataFileContext, fail_fn);
// Relabel /data/user_de
relabelDir(internalDePath, dataUserdirContext, 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 cePath = StringPrintf("%s/user", volPath.c_str());
auto dePath = StringPrintf("%s/user_de", volPath.c_str());
relabelSubdirs(cePath.c_str(), dataFileContext, fail_fn);
relabelDir(cePath.c_str(), dataUserdirContext, fail_fn);
relabelSubdirs(dePath.c_str(), dataFileContext, fail_fn);
relabelDir(dePath.c_str(), dataUserdirContext, fail_fn);
}
closedir(dir);
freecon(dataUserdirContext);
freecon(dataFileContext);
isolateJitProfile
isolateJitProfile
函数负责隔离 JIT 配置目录,其大致方法与 isolateAppData
差不多。
/**
* Like isolateAppData(), isolate jit profile directories, so apps don't see what
* other apps are installed by reading content inside /data/misc/profiles/cur.
*
* The implementation is similar to isolateAppData(), it creates a tmpfs
* on /data/misc/profiles/cur, and bind mounts related package profiles to it.
*/
static void isolateJitProfile(JNIEnv* env, jobjectArray pkg_data_info_list,
uid_t uid, const char* process_name, jstring managed_nice_name,
fail_fn_t fail_fn) {
<-- implementation -->
}
获取当前用户 userId,保存下 pkg_data_info_list
大小。
auto extract_fn = std::bind(ExtractJString, env, process_name, managed_nice_name, _1);
const userid_t user_id = multiuser_get_user_id(uid);
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));
}
将 tmpfs 挂载到原始目录(/data/misc/profiles/cur
和 /data/misc/profiles/ref
)。
// Mount (namespace) tmpfs on profile directory, so apps no longer access
// the original profile directory anymore.
MountAppDataTmpFs(kCurProfileDirPath, fail_fn);
MountAppDataTmpFs(kRefProfileDirPath, fail_fn);
之后,处理每个该应用的包名目录,由于其原理和实现与 isolateAppData
相似,就不再具体展开。
// 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);
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();
std::string actualCurPackageProfile = StringPrintf("%s/%s", actualCurUserProfile.c_str(),
packageName.c_str());
std::string mirrorCurPackageProfile = StringPrintf("/data_mirror/cur_profiles/%d/%s",
user_id, packageName.c_str());
std::string actualRefPackageProfile = StringPrintf("%s/%s", kRefProfileDirPath,
packageName.c_str());
std::string mirrorRefPackageProfile = StringPrintf("/data_mirror/ref_profiles/%s",
packageName.c_str());
if (access(mirrorCurPackageProfile.c_str(), F_OK) != 0) {
ALOGW("Can't access app profile directory: %s", mirrorCurPackageProfile.c_str());
continue;
}
if (access(mirrorRefPackageProfile.c_str(), F_OK) != 0) {
ALOGW("Can't access app profile directory: %s", mirrorRefPackageProfile.c_str());
continue;
}
PrepareDir(actualCurPackageProfile, DEFAULT_DATA_DIR_PERMISSION, uid, uid, fail_fn);
BindMount(mirrorCurPackageProfile, actualCurPackageProfile, fail_fn);
PrepareDir(actualRefPackageProfile, DEFAULT_DATA_DIR_PERMISSION, uid, uid, fail_fn);
BindMount(mirrorRefPackageProfile, actualRefPackageProfile, fail_fn);
}