Skip to content

安装来源与更新所有权

追踪 Android 17 安装来源身份、更新所有权、denylist、持久化和查询脱敏规则。

基于android-17.0.0_r1
AndroidPMSInstallSourceUpdateOwnershipPackageInstaller

安装来源与更新所有权 ​

本文面向已经读过 安装后注册、应用更新流程 和 Package Visibility 过滤 的读者。本文不重复 PackageInstaller session 的创建,而是追踪安装来源进入 PackageSetting 后如何演变:initiator、originator、installer、update owner 分别代表谁,installer 被卸载时哪些字段保留,以及查询 InstallSourceInfo 时为什么不同调用者会看到不同答案。

1. 四种身份 ​

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

字段含义可信边界
mInitiatingPackageName创建安装请求的包session/PMS 验证调用身份,可保留其签名
mOriginatingPackageNameinitiator 声称代表的下载来源源码明确说明框架不验证
mInstallerPackageNameinstaller of record可被后续管理 API 修改
mUpdateOwnerPackageName请求独占后续更新的包受 install flag、denylist 和更新规则限制

mOriginalInstallerUid 保存最初创建 session 的 UID,mInstallerPackageUid 对应当前 installer;二者在 installer 被替换或卸载后可能不同。mPackageSource 只描述来源分类,不取代上述身份。

2. 不可变对象 ​

InstallSource 是不可变类。setInstallerPackage、setUpdateOwnerPackageName 和 setIsOrphaned 不修改原对象,而是返回一个新实例;字段相同则直接返回 this。包名通过 String.intern() 复用,常见空状态还共享 EMPTY 和 EMPTY_ORPHANED。

java
InstallSource setUpdateOwnerPackageName(
        @Nullable String updateOwnerPackageName) {
    if (Objects.equals(updateOwnerPackageName,
            mUpdateOwnerPackageName)) {
        return this;
    }
    return createInternal(mInitiatingPackageName,
            mOriginatingPackageName, mInstallerPackageName,
            mInstallerPackageUid, mOriginalInstallerUid,
            intern(updateOwnerPackageName), ...);
}

因此 PackageSetting.setInstallSource() 才是替换当前来源状态的提交点。调用者如果只调用 installSource.setUpdateOwnerPackageName() 而不保存返回值,状态不会变化。

3. 初次安装 ​

源码文件:frameworks/base/services/core/java/com/android/server/pm/InstallPackageHelper.java;符号:扫描结果提交中的 InstallSource 处理。

标准 session 安装会携带非空 InstallSource。PMS 根据 initiating package 查找 PackageSetting,不是 ADB 发起时保存其 PackageSignatures,随后把更新后的不可变对象写入新 package setting。

java
if (!isInstalledByAdb(installSource.mInitiatingPackageName)) {
    PackageSetting ips = mPm.mSettings.getPackageLPr(
            installSource.mInitiatingPackageName);
    if (ips != null) {
        installSource = installSource
                .setInitiatingPackageSignatures(ips.getSignatures());
    }
}
pkgSetting.setInstallSource(installSource);

initiator 签名用于 initiator 被卸载后的受控查询;originator 没有对应签名证明,因为它本来就是 initiator 提供的未验证声明。

4. 请求更新所有权 ​

普通 APK 只有在初次安装携带 INSTALL_REQUEST_UPDATE_OWNERSHIP 时,installer 才可能成为 update owner。以下条件会清除请求:目标包在 denylist、installer 自己是 denylist provider、已有包却未启用 ownership、或新 installer 与旧 owner 不同。

java
boolean requestOwnership = (request.getInstallFlags()
        & INSTALL_REQUEST_UPDATE_OWNERSHIP) != 0;
boolean sameOwner = TextUtils.equals(oldUpdateOwner,
        installSource.mInstallerPackageName);

if (!isUpdate) {
    if (!requestOwnership || isUpdateOwnershipDenylisted
            || isInstallerUpdateOwnerDenylistProvider) {
        installSource = installSource
                .setUpdateOwnerPackageName(null);
    } else if ((!isUpdateOwnershipEnabled && pkgAlreadyExists)
            || (isUpdateOwnershipEnabled && !sameOwner)) {
        installSource = installSource
                .setUpdateOwnerPackageName(null);
    }
} else if (!sameOwner || !isUpdateOwnershipEnabled) {
    installSource = installSource
            .setUpdateOwnerPackageName(null);
}

更新所有权不是“最后安装者自动拥有”。它必须在初装显式请求,并在后续更新中保持 installer 与 owner 一致;用户允许其他 installer 更新后,ownership 会被清除,而不是转移给新 installer。

5. 系统包 owner ​

非 session 的 system scan 没有 InstallSource,PMS 从 sysconfig 获取 system app update owner。首次发现 system package 时写入该 owner;OTA 后如果旧 owner 与新 sysconfig 不一致则清除,避免旧配置继续控制新系统镜像。

APEX 不走这一 APK ownership 分支。system app 从普通包变成系统包时,也只有旧 owner 与 sysconfig 一致才保留。

6. Denylist 模型 ​

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

合法 denylist provider 必须是 system/updated-system package,声明 PROPERTY_LEGACY_UPDATE_OWNERSHIP_DENYLIST,并请求 INSTALL_PACKAGES 或 INSTALL_PACKAGE_UPDATES。XML 中每个 <deny-ownership> 文本是一个不允许启用 update ownership 的目标包,最多读取 500 项。

java
private final ArrayMap<String, ArraySet<String>>
        mUpdateOwnerOptOutsToOwners = new ArrayMap<>(200);

map 的 key 是被 denylist 的目标包,value 是贡献该条目的 provider 集合。只有所有 provider 都移除后,目标包才不再 denylisted。provider 包删除时,removeUpdateOwnerDenyList 遍历并移除其贡献。

读取失败返回 null,不会把部分解析结果加入内存;超过 500 项时保留前面的集合并写 warning。

7. Denylist 生效 ​

安装/扫描一个合法 provider 后,PMS 在 handler 上读取资源并注册 denylist。对于列表中已有 update owner、但 sysconfig 没有指定 owner 的包,PMS会清除其 owner。

java
for (String unownedPackage : listItems) {
    PackageSetting setting =
            mPm.mSettings.getPackageLPr(unownedPackage);
    if (setting != null
            && mPm.getSystemAppUpdateOwnerPackageName(
                    unownedPackage) == null) {
        setting.setUpdateOwnerPackage(null);
    }
}

denylist 因而不仅影响未来初装请求,也会撤销已有的非-sysconfig ownership。provider 的资源解析与 owner 清理是异步 handler 工作,包提交成功和 denylist 完全生效之间存在时间差。

8. Installer 被卸载 ​

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

同一个被卸载包可能同时扮演多个角色,清理规则不同:

java
if (packageName.equals(mInitiatingPackageName)) {
    isInitiatingPackageUninstalled = true;
}
if (packageName.equals(originatingPackageName)) {
    originatingPackageName = null;
}
if (packageName.equals(installerPackageName)) {
    installerPackageName = null;
    installerPackageUid = INVALID_UID;
    isOrphaned = true;
}
if (packageName.equals(updateOwnerPackageName)) {
    updateOwnerPackageName = null;
}

initiator 名称和签名被保留,并标记 uninstalled;originator 直接清除;installer 清除并把记录标成 orphaned;update owner 清除。installer attribution tag 也在产生新对象时清空。

9. 持久化 ​

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

Settings 在 package 节点保存 installer、installer UID、original installer UID、update owner、attribution tag、package source、orphaned、initiator 和 initiator-uninstalled。启动读取后调用 InstallSource.create() 恢复不可变对象。

xml
<package
    installer="com.example.store"
    installerUid="10123"
    originalInstallerUid="10123"
    updateOwner="com.example.store"
    installInitiator="com.example.downloader"
    packageSource="1" />

originating package 和 initiating signatures 也由对应 Settings 结构持久化。磁盘记录只恢复来源事实;包是否仍可见、installer 是否还安装,要在查询时重新判断。

10. 查询脱敏 ​

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

公开查询先检查目标包可见性,再分别过滤每个来源字段:installer、initiator、originator 和 update owner 不可见时置 null。originator 还要求调用者拥有 INSTALL_PACKAGES,因为它是更敏感且未经框架验证的信息。

managed user/profile 中,非 system 且非 update owner 的调用者看不到 update owner;源码认为在这些用户里不强制 ownership,暴露该字段没有意义。

initiator 已卸载时,普通 visibility 无法执行。此时只有目标 app 自己且不是 instant app 才能看到 initiator 名称和保存的签名。

11. 主动放弃 owner ​

relinquishUpdateOwnership 允许 system、shell 或当前 update owner 清除目标包的 owner。PMS 先验证目标包对调用者可见,再比较 caller appId,最后通过 PackageStateMutator 写 null 并安排 Settings 写盘。

这项操作不可撤销地清除当前 ownership;后续普通更新不会自动恢复,只有符合初装/sysconfig规则的流程才能再次建立 owner。

12. 失败定位 ​

现象检查点解释
安装器请求 owner 但结果为 nullinstall flag、denylist、已有包、old ownerowner 只在满足全部初装条件时建立
更新后 owner 消失installer 是否与旧 owner 相同非 owner 更新会清除而非转移 ownership
InstallSourceInfo 字段为 nullvisibility、权限、managed profile内部 InstallSource 可能仍保存原值
installer 卸载后 initiator 仍显示mIsInitiatingPackageUninstalledinitiator 名称/签名有意保留给目标 app
denylist provider 删除后仍 denylisted其他 provider contributorvalue 是 provider 集合,不是单个 owner
系统包 OTA 后 owner 消失新旧 sysconfig owner 不一致PMS 清除陈旧 system ownership

13. 源码练习 ​

  1. 画出 downloader、Package Installer 和应用商店分别作为 originator、initiator、installer 时的字段,并指出哪一个由框架明确不验证。
  2. 构造初装请求带 ownership flag,但目标包在两个 provider 的 denylist 中;依次卸载 provider,判断何时允许新 ownership。
  3. 从 removeInstallerPackage 追踪同一个包同时是 initiator 和 installer 时的字段结果,说明 orphaned 与 initiator-uninstalled 的区别。
  4. 给定内部 Settings 有完整 InstallSource、普通应用查询只看到 installer,沿 ComputerEngine.getInstallSourceInfo 判断其他字段被哪条规则隐藏。