Skip to content

ProtectedPackages 数据保护

追踪 Android 17 受保护包的 owner 来源、用户范围、状态修改和数据清理拒绝路径。

基于android-17.0.0_r1
AndroidPMSProtectedPackagesDeviceOwnerPackageData

ProtectedPackages 数据保护 ​

本文面向已经了解设备所有者、Profile Owner 和 PMS 卸载/数据清理入口的读者。它聚焦一个经常被误判的边界:为什么 system 或 privileged app 仍然不能禁用、隐藏或清除某些包。本文不重新讲设备策略服务如何选出 owner,而是从 Android 17 的 ProtectedPackages 到 PMS 的真实调用点,说明“受保护”究竟保护了什么、按哪个 user 生效,以及调用失败时在哪一层返回。

1. Owner 数据 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/ProtectedPackages.java。

ProtectedPackages 自身不持久化 owner 信息,类注释明确留下了 TODO;它接收 DevicePolicyManager 侧同步来的包名,并在内存中按 user 保存:

java
@GuardedBy("this")
private int mDeviceOwnerUserId;
@GuardedBy("this")
private String mDeviceOwnerPackage;
@GuardedBy("this")
private SparseArray<String> mProfileOwnerPackages;
@GuardedBy("this")
private SparseArray<String> mDevicePolicyControllerPackages;
@GuardedBy("this")
private final SparseArray<Set<String>> mOwnerProtectedPackages =
        new SparseArray<>();

受保护来源有五类:device/profile owner 包、DPC 包、系统设备 provisioning 包、owner 额外设置的 protected package,以及 supervision role holder。它们最终汇入 isProtectedPackage,但不共享同一个配置来源。

2. 两种保护 ​

isPackageStateProtected(userId, packageName) 和 isPackageDataProtected(userId, packageName) 当前实现都返回相同的 owner/protected 判断,但调用语义不同。前者用于改变 enabled/hidden 等包状态;后者用于清除 app data。保留两个 API 让 PMS 在未来可以独立扩展状态保护和数据保护。

java
public boolean isPackageStateProtected(int userId, String packageName) {
    return hasDeviceOwnerOrProfileOwner(userId, packageName)
            || isProtectedPackage(userId, packageName);
}

public boolean isPackageDataProtected(int userId, String packageName) {
    return hasDeviceOwnerOrProfileOwner(userId, packageName)
            || isProtectedPackage(userId, packageName);
}

类注释给出的安全含义是:除包 owner 自己外,system 或 privileged app 也不能修改受保护包的数据或 package state。这个条件不是普通 MANAGE_DEVICE_ADMINS 权限的替代品,而是 PMS 在具体目标包上增加的一道拒绝检查。

3. 同步入口 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java;符号:setDeviceAndProfileOwnerPackages、setDevicePolicyControllerPackages。

java
public void setDeviceAndProfileOwnerPackages(
        int deviceOwnerUserId, String deviceOwnerPackage,
        SparseArray<String> profileOwnerPackages) {
    mProtectedPackages.setDeviceAndProfileOwnerPackages(
            deviceOwnerUserId, deviceOwnerPackage, profileOwnerPackages);
    ...
}

public void setDevicePolicyControllerPackages(
        SparseArray<String> packages) {
    mProtectedPackages.setDevicePolicyControllerPackages(packages);
    ...
}

两个同步方法都在 PackageManagerInternal 暴露给系统内部调用。ProtectedPackages 对传入的 SparseArray 做 clone,避免调用方随后修改原对象而绕过服务的状态边界;setOwnerProtectedPackages 对 package list 构造新的 ArraySet。

同步后 PMS 还会移除对应 user 的非系统 package suspension。这是策略联动,不是保护判断本身:DPC 成为 owner 后,旧的普通挂起状态不能继续阻止系统管理该用户的包。

4. 清数据拒绝 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java;符号:IPackageManagerImpl.clearApplicationUserData。

java
snapshot.enforceCrossUserPermission(callingUid, userId,
        true /* requireFullPermission */, false /* checkShell */,
        "clear application data");

if (snapshot.getPackageStateForInstalledAndFiltered(
        packageName, callingUid, userId) == null) {
    // 对调用者不可见或未安装,异步回调 false
    return;
}
if (mProtectedPackages.isPackageDataProtected(userId, packageName)) {
    throw new SecurityException(
            "Cannot clear data for a protected package: " + packageName);
}

检查顺序有实际意义:先做跨用户权限和可见性检查,再判断 protected。一个不可见包不会通过异常内容泄露“它是否是受保护包”。保护命中后不会创建 PackageFreezer,也不会进入 installer 的数据删除调用。

5. 状态修改拒绝 ​

组件 enabled/disabled 修改在 PMS 内部先验证调用者是否是目标 app 或拥有管理权限,再检查 protected state:

java
if (!isCallerTargetApp
        && mProtectedPackages.isPackageStateProtected(userId, packageName)) {
    throw new SecurityException(
            "Cannot disable a protected package: " + packageName);
}

隐藏包的路径使用不同策略:只有受保护包自己可以请求隐藏;其他调用者即使是 system,也会记录 warning 并返回 false。这个“自己可隐藏、别人不可修改”的例外只属于 hide 操作,不能外推到 clear data 或 disable component。

6. Owner 特殊包 ​

hasDeviceOwnerOrProfileOwner 只按给定 userId 比较 device owner user 和 profile owner map。device owner 包在其他 user 上不会因为“设备上存在 device owner”而自动成为那个 user 的 protected package。

isProtectedPackage 还检查:

  • 当前 user 的 DPC package;
  • config_deviceProvisioningPackage 指定的 provisioning 包;
  • setOwnerProtectedPackages 传入的 user 或 USER_ALL 集合;
  • supervision role 在该 user 是否启用且包是否为 role holder。

supervision 检查通过 Binder.withCleanCallingIdentity 查询 RoleManager,避免把调用者身份带入系统角色查询。若 SupervisionManager 或 RoleManager 不可用,源码分别记录 warning 并按“不属于 supervision protected”继续。

7. 安装现有包 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/InstallPackageHelper.java;符号:installExistingPackageAsUser。

安装现有包为某 user 时,protected state 参与 instant app 约束:系统不会把 system、updated system、device admin 或 protected package 作为 instant app 安装目标。这里的保护不是拒绝普通 full app 安装,而是阻止改变 package 的 instant/full 身份。

java
boolean isProtectedPackage = mPm.mProtectedPackages != null
        && mPm.mProtectedPackages.isPackageStateProtected(userId, packageName);
if (instantApp && (pkgSetting.isSystem()
        || pkgSetting.isUpdatedSystemApp()
        || isPackageDeviceAdmin || isProtectedPackage)) {
    return Pair.create(INSTALL_FAILED_INVALID_URI, intentSender);
}

因此“安装 existing 失败”要先区分 install flags;同一个包用 full-app 路径可能成功,而 instant-app 路径会被保护规则拒绝。

8. 用户维度 ​

保护状态属于 user。PMS 的 PackageManagerInternal.isPackageStateProtected(packageName, userId) 会先执行 cross-user permission,再检查调用者是否为 system/root 或拥有 MANAGE_DEVICE_ADMINS;普通应用不能用这个内部查询接口枚举保护状态。

工作资料、克隆用户或受限用户的问题,不能只看 device owner 包名;必须把调用 userId、profile owner map 和 owner protected list 一起打印。

9. 失败定位 ​

现象首先检查结论边界
清数据抛 SecurityExceptionisPackageDataProtected 调用前的 user、owner、DPC保护命中时不会进入 freezer/installer
禁用组件失败isPackageStateProtected 与调用者是否目标 app目标 app 自己与管理调用者路径不同
hide 返回 falsehide 路径的 protected 分支这是 warning + false,不等同于 SecurityException
instant app 安装失败installExistingPackageAsUser 的 flagsprotected 只限制 instant 身份变更
保护状态看起来过期owner 同步方法、SparseArray clone、userIdProtectedPackages 不自行持久化,需追同步 owner
system UID 也无法清理PMS 保护检查system/privileged 不会自动绕过 protected package

10. 源码练习 ​

  1. 给定 device owner 位于 user 0、目标包安装在 user 10,按照 hasDeviceOwnerOrProfileOwner 判断 user 10 是否自动受保护,并说明 profile owner 的差异。
  2. 追踪 clearApplicationUserData 的检查顺序,解释为什么不可见包不会暴露 protected 状态。
  3. 对比 disable、hide 和 clear data 三条路径,列出各自的异常类型、返回值和“目标 app 自己”例外。
  4. 构造一个 protected package 走 installExistingPackageAsUser(INSTALL_INSTANT_APP) 的输入,指出 INSTALL_FAILED_INVALID_URI 在哪一层返回,以及为什么 full app 路径不能据此判定也会失败。