Skip to content

CE 与 DE 存储

追踪 Android 17 用户创建、启动、解锁和删除时 CE/DE 存储、应用数据 flags 与密钥生命周期。

基于android-17.0.0_r1
AndroidPMSMultiUserStorageEncryption

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

java
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

java
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 调用

java
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

java
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

java
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

java
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

java
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,符号:创建结果写回

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 检查

java
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

java
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

java
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?