CE 与 DE 存储
本文承接 用户空间隔离 和 多用户安装,把 CE(Credential Encrypted)与 DE(Device Encrypted)放回真实调用链中。重点不是解释加密算法,而是说明 UserManagerService 何时准备哪种 storage、AppDataHelper 何时创建哪种 app data,以及为什么 direct boot 阶段只能依赖 DE。
Android 17 的关键不变量是:用户创建和启动阶段先准备 DE;用户解锁前由 onBeforeUnlockUser 准备 CE;包级数据准备还要同时满足 CE key 已解锁、CE storage 已 prepared 和 user state 允许创建数据。删除用户时,先处理 lock settings,再销毁 CE/DE key,随后由 PMS 和 UserDataPreparer 清理状态和目录。
1. 两种存储
源码文件:frameworks/base/core/java/android/os/storage/StorageManager.java,符号:storage flags
public static final int FLAG_STORAGE_DE = 1 << 0;
public static final int FLAG_STORAGE_CE = 1 << 1;DE 数据在用户凭据尚未输入时也可访问,供 Direct Boot 阶段的系统和 directBootAware 组件使用;CE 数据依赖用户凭据派生的 key,只有解锁后才允许处理。flags 是 PMS/UMS/installd 之间的结构化契约,不是路径名称的别名。
源码文件:frameworks/base/core/java/android/os/Environment.java,符号:user/system/misc directories
public static File getDataUserDeDirectory(
String volumeUuid, int userId) {
return newFilePrep(
getDataUserDeDirectory(volumeUuid),
String.valueOf(userId));
}
public static File getDataSystemCeDirectory(int userId) {
return buildPathPrep(getDataDirectory(),
"system_ce", String.valueOf(userId));
}
public static File getDataMiscDeDirectory(int userId) {
return buildPathPrep(getDataDirectory(),
"misc_de", String.valueOf(userId));
}应用 DE、应用 CE、system DE/CE、misc DE/CE 是不同目录族。UserDataPreparer 和 StorageManagerInternal 管理根用户存储,AppDataHelper/installd 管理包目录;不能用“用户解锁了”替代对每个目录族的 flags 判断。
2. 用户创建
源码文件:frameworks/base/services/core/java/com/android/server/pm/UserManagerService.java,符号:创建用户的 storage 调用
getStorageManagerInternal().createUserStorageKeys(
userId, userInfo.isEphemeral());
// CE storage is prepared later, when the user is unlocked.
mUserDataPreparer.prepareUserData(
userInfo, StorageManager.FLAG_STORAGE_DE);
getLockSettingsInternal().createNewUser(
userId, userInfo.serialNumber);
mPm.createNewUser(userId, installablePackages,
disallowedPackages);创建用户先生成 CE/DE key,再只准备 DE。CE 被延迟不是因为没有目录 API,而是要确保 CE key 已经保存并且不会在用户尚未解锁时暴露。PMS 的包状态创建发生在 DE 准备和 LockSettings 初始化之后。
3. 用户启动
源码文件:frameworks/base/services/core/java/com/android/server/pm/UserManagerService.java,符号:onBeforeStartUser
public void onBeforeStartUser(int userId) {
UserInfo userInfo = getUserInfo(userId);
if (userInfo == null) {
return;
}
boolean migrateAppsData =
!PackagePartitions.FINGERPRINT.equals(
userInfo.lastLoggedInFingerprint);
mUserDataPreparer.prepareUserData(
userInfo, StorageManager.FLAG_STORAGE_DE);
getPackageManagerInternal().reconcileAppsData(
userId, StorageManager.FLAG_STORAGE_DE,
migrateAppsData);
if (userId != UserHandle.USER_SYSTEM) {
synchronized (mRestrictionsLock) {
applyUserRestrictionsLR(userId);
}
}
}用户启动的屏障只处理 DE。reconcileAppsData 会检查已安装包的 DE 目录、owner、label 和缺失数据;OTA fingerprint 变化时还可能迁移 app data。此时不能把 CE app data 当作已经可用。
4. 用户解锁
源码文件:frameworks/base/services/core/java/com/android/server/pm/UserManagerService.java,符号:onBeforeUnlockUser
public void onBeforeUnlockUser(int userId) {
UserInfo userInfo = getUserInfo(userId);
if (userInfo == null) {
return;
}
boolean migrateAppsData =
!PackagePartitions.FINGERPRINT.equals(
userInfo.lastLoggedInFingerprint);
mUserDataPreparer.prepareUserData(
userInfo, StorageManager.FLAG_STORAGE_CE);
getStorageManagerInternal()
.markCeStoragePrepared(userId);
getPackageManagerInternal().reconcileAppsData(
userId, StorageManager.FLAG_STORAGE_CE,
migrateAppsData);
}CE key 已由 storage layer 解锁后,UMS 才准备 CE 根目录;随后标记 markCeStoragePrepared,最后由 PMS reconcile CE app data。prepareUserData(CE) 成功不等于所有 app 目录已检查,后续 reconcile 仍是必要步骤。
5. 用户级根目录准备
源码文件:frameworks/base/services/core/java/com/android/server/pm/UserDataPreparer.java,符号:prepareUserDataLI
private void prepareUserDataLI(String volumeUuid,
UserInfo userInfo, int flags,
boolean allowRecover) {
final int userId = userInfo.id;
final int userSerial = userInfo.serialNumber;
getStorageManagerInternal().prepareUserStorage(
volumeUuid, userId, flags);
if ((flags & StorageManager.FLAG_STORAGE_DE) != 0) {
enforceSerialNumber(
getDataUserDeDirectory(volumeUuid, userId),
userSerial);
}
if ((flags & StorageManager.FLAG_STORAGE_CE) != 0) {
enforceSerialNumber(
getDataUserCeDirectory(volumeUuid, userId),
userSerial);
}
mInstaller.createUserData(
volumeUuid, userId, userSerial, flags);
}StorageManager/vold 负责 key 和挂载状态,UserDataPreparer 负责 serial 校验与 orchestration,installd 负责用户 app-data 根结构。相同方法通过 flags 处理 DE 或 CE,避免为两种加密类型复制一套生命周期代码。
6. 包级 flags 选择
源码文件:frameworks/base/services/core/java/com/android/server/pm/AppDataHelper.java,符号:prepareAppDataPostCommitLIF
for (int userId : userIds) {
final int flags;
if (StorageManager.isCeStorageUnlocked(userId)
&& smInternal.isCeStoragePrepared(userId)) {
flags = StorageManager.FLAG_STORAGE_DE
| StorageManager.FLAG_STORAGE_CE;
} else if (umInternal.isUserRunning(userId)) {
flags = StorageManager.FLAG_STORAGE_DE;
} else {
continue;
}
prepareAppData(batch, ps, previousAppId,
userId, flags);
}包级数据准备有三个分支:CE 已解锁且 prepared 时同时准备 DE+CE;用户正在运行但 CE 不可用时只准备 DE;用户未运行时跳过。这个分支是 direct boot 与 app install 时机的真实连接点。
源码文件:frameworks/base/services/core/java/com/android/server/pm/AppDataHelper.java,符号:创建结果写回
CreateAppDataResult result =
mInstaller.createAppData(args);
if ((flags & StorageManager.FLAG_STORAGE_CE) != 0) {
synchronized (mPm.mLock) {
if (result.ceDataInode != -1) {
ps.setCeDataInode(
result.ceDataInode, userId);
}
}
}
if ((flags & StorageManager.FLAG_STORAGE_DE) != 0) {
synchronized (mPm.mLock) {
if (result.deDataInode != -1) {
ps.setDeDataInode(
result.deDataInode, userId);
}
}
}CE/DE app data 创建结果分别写回 PackageUserState 的 inode。后续清理、reconcile 和包删除可以依据具体 storage flags 与 inode 执行,不需要假设两个目录同时存在。
7. Direct Boot
Direct Boot 的关键不是 PMS 自己解密 CE,而是组件和服务在用户未解锁时只能使用 DE 可访问数据。PMS 在解析/查询组件时结合用户状态、direct boot flags 和 ApplicationInfo 的 aware 属性决定哪些组件可被查到;应用若要在该阶段运行,需要将必要状态放在 DE,并声明 directBootAware。
源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java,符号:用户查询入口中的 user state/flags 检查
final PackageUserStateInternal userState =
packageState.getUserStateOrDefault(userId);
if (!userState.isInstalled()) {
return null;
}安装状态是第一层门槛;组件是否 direct-boot aware、查询是否包含 MATCH_DIRECT_BOOT_AWARE/MATCH_DIRECT_BOOT_UNAWARE 是第二层。即使包 installed=true,CE 数据未解锁时也不能推导任意组件都能启动。
8. 数据调和
源码文件:frameworks/base/services/core/java/com/android/server/pm/AppDataHelper.java,符号:reconcileAppsData
public void reconcileAppsData(int userId,
@StorageManager.StorageFlags int flags,
boolean migrateAppsData) {
StorageManager storage =
mInjector.getSystemService(StorageManager.class);
for (VolumeInfo vol
: storage.getWritablePrivateVolumes()) {
try (PackageManagerTracedLock lock =
mPm.mInstallLock.acquireLock()) {
reconcileAppsDataLI(vol.getFsUuid(),
userId, flags, migrateAppsData);
}
}
}reconcile 不是只“补目录”:它会清理不应存在的 package dirs、检查 owner/label,并在 OTA fingerprint 改变时执行迁移。DE 和 CE 分开 reconcile,避免在 CE 锁定时误触碰凭据保护目录。
9. 用户删除顺序
源码文件:frameworks/base/services/core/java/com/android/server/pm/UserManagerService.java,符号:removeUserState
getLockSettingsInternal().removeUser(userId);
try {
getStorageManagerInternal()
.destroyUserStorageKeys(userId);
} catch (IllegalStateException e) {
Slog.i(LOG_TAG,
"Destroying storage keys failed, continuing", e);
}
mPm.cleanUpUser(this, userId);
mUserDataPreparer.destroyUserData(userId,
StorageManager.FLAG_STORAGE_DE
| StorageManager.FLAG_STORAGE_CE);LockSettings 必须在 key 销毁前清理;key 销毁后 CE/DE 变为不可访问,PMS 清理 per-user 包状态,再由 UserDataPreparer 清目录。这里的顺序防止系统在 user metadata 尚未清理时继续把已销毁 key 当作可用。
10. 失败定位
| 现象 | 应检查 |
|---|---|
| Direct Boot 组件启动失败 | DE app data、onBeforeStartUser、directBootAware 和查询 flags |
| 解锁后 CE 数据缺失 | CE key、markCeStoragePrepared、CE reconcile |
| 安装包只有 DE 目录 | 用户未解锁或 CE storage 未 prepared |
| OTA 后数据异常 | lastLoggedInFingerprint 与 migrateAppsData |
| 删除用户后目录残留 | key 销毁、destroyUserData、mounted volumes |
| CE/DE inode 不一致 | AppDataHelper flags、installd 返回值、PackageUserState |
11. 阅读检查
复述两条主线:用户启动时 onBeforeStartUser → DE prepare → DE reconcile;用户解锁时 onBeforeUnlockUser → CE prepare → markCeStoragePrepared → CE reconcile。再回答:用户创建时为什么只准备 DE?setInstalled(true) 是否意味着 CE 目录已存在?删除用户为什么先处理 LockSettings 和 storage keys?
