Skip to content

权限白名单

分析特权、签名和 OEM 权限白名单的分区读取、三值状态与授予判定。

基于android-17.0.0_r1
AndroidPMSPackageManager权限白名单

权限白名单 ​

Android 的“权限白名单”不是一个只存包名的列表。Android 17 的实现同时保存特权权限、签名权限和 OEM 权限,并且为 system、vendor、product、system_ext、APEX 分别保留来源。最终是否授予权限,要经过 XML 解析、包所在分区、签名匹配、系统启动阶段和更新系统应用兼容逻辑共同决定。

本文沿着真实调用链阅读:SystemConfig 读取配置,PermissionAllowlist 保存配置,AccessCheckingService 把配置交给权限策略,AppIdPermissionPolicy 在权限重算时消费它。

1. 白名单是多张表 ​

1.1 数据字段 ​

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

java
public final class PermissionAllowlist {
    private final ArrayMap<String, ArrayMap<String, Boolean>> mOemAppAllowlist =
            new ArrayMap<>();
    private final ArrayMap<String, ArrayMap<String, Boolean>> mPrivilegedAppAllowlist =
            new ArrayMap<>();
    private final ArrayMap<String, ArrayMap<String, Boolean>> mVendorPrivilegedAppAllowlist =
            new ArrayMap<>();
    private final ArrayMap<String, ArrayMap<String, Boolean>> mProductPrivilegedAppAllowlist =
            new ArrayMap<>();
    private final ArrayMap<String, ArrayMap<String, Boolean>> mSystemExtPrivilegedAppAllowlist =
            new ArrayMap<>();
    private final ArrayMap<String, ArrayMap<String, ArrayMap<String, Boolean>>>
            mApexPrivilegedAppAllowlists = new ArrayMap<>();

    private final ArrayMap<String, ArrayMap<String, Boolean>> mSignatureAppAllowlist =
            new ArrayMap<>();
    private final ArrayMap<String, ArrayMap<String, Boolean>> mVendorSignatureAppAllowlist =
            new ArrayMap<>();
    private final ArrayMap<String, ArrayMap<String, Boolean>> mProductSignatureAppAllowlist =
            new ArrayMap<>();
    private final ArrayMap<String, ArrayMap<String, Boolean>> mSystemExtSignatureAppAllowlist =
            new ArrayMap<>();
    private final ArrayMap<String, ArrayMap<String, Boolean>> mApexSignatureAppAllowlist =
            new ArrayMap<>();
}

每个普通分区表的形状都是 包名 -> (权限名 -> Boolean)。APEX 特权表多一层模块名:APEX 模块名 -> 包名 -> (权限名 -> Boolean)。这层不是装饰:同一个 APK 包名可能来自不同 APEX,模块名是把配置绑定到发布它的模块的关键。

1.2 查询结果是三值 ​

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

java
@Nullable
public Boolean getPrivilegedAppAllowlistState(
        @NonNull String packageName, @NonNull String permissionName) {
    ArrayMap<String, Boolean> permissions = mPrivilegedAppAllowlist.get(packageName);
    if (permissions == null) {
        return null;
    }
    return permissions.get(permissionName);
}

@Nullable
public Boolean getApexPrivilegedAppAllowlistState(
        @NonNull String moduleName, @NonNull String packageName,
        @NonNull String permissionName) {
    ArrayMap<String, ArrayMap<String, Boolean>> allowlist =
            mApexPrivilegedAppAllowlists.get(moduleName);
    if (allowlist == null) {
        return null;
    }
    ArrayMap<String, Boolean> permissions = allowlist.get(packageName);
    if (permissions == null) {
        return null;
    }
    return permissions.get(permissionName);
}

null 表示没有这条配置,TRUE 表示明确允许,FALSE 表示明确拒绝。策略层不能把 null 当作 false 直接返回,因为未配置时还要根据调试版本、启动阶段、更新系统应用等规则决定是记录违规还是拒绝授予。

2. XML 分区路由 ​

2.1 顶层标签的入口 ​

源码文件:frameworks/base/services/core/java/com/android/server/SystemConfig.java

java
case "privapp-permissions":
    readPrivappPermissions(parser, permFile, allowPrivappPermissions);
    break;
case "signature-permissions":
    readSignaturePermissions(parser, permFile, allowSignaturePermissions);
    break;
case "oem-permissions":
    readOemPermissions(parser, permFile, allowOemPermissions);
    break;

readPermissionsFromXml 先依据文件所在目录计算该文件属于哪个分区,再把解析任务交给对应方法。allowPrivappPermissions、allowSignaturePermissions 和 allowOemPermissions 是读取权限,而不是 XML 内容里的开关;不允许的分区会记录日志并跳过标签。

2.2 特权分区选择 ​

源码文件:frameworks/base/services/core/java/com/android/server/SystemConfig.java

java
private void readPrivappPermissions(
        XmlPullParser parser, File permFile, boolean allow)
        throws IOException, XmlPullParserException {
    if (allow) {
        boolean vendor = permFile.toPath().startsWith(
                Environment.getVendorDirectory().toPath() + "/")
                || permFile.toPath().startsWith(
                Environment.getOdmDirectory().toPath() + "/");
        boolean product = permFile.toPath().startsWith(
                Environment.getProductDirectory().toPath() + "/");
        boolean systemExt = permFile.toPath().startsWith(
                Environment.getSystemExtDirectory().toPath() + "/");
        boolean apex = permFile.toPath().startsWith(
                Environment.getApexDirectory().toPath() + "/");

        if (vendor) {
            readPrivAppPermissions(parser,
                    mPermissionAllowlist.getVendorPrivilegedAppAllowlist());
        } else if (product) {
            readPrivAppPermissions(parser,
                    mPermissionAllowlist.getProductPrivilegedAppAllowlist());
        } else if (systemExt) {
            readPrivAppPermissions(parser,
                    mPermissionAllowlist.getSystemExtPrivilegedAppAllowlist());
        } else if (apex) {
            readApexPrivAppPermissions(parser, permFile,
                    Environment.getApexDirectory().toPath());
        } else {
            readPrivAppPermissions(parser,
                    mPermissionAllowlist.getPrivilegedAppAllowlist());
        }
    } else {
        logNotAllowedInPartition("privapp-permissions", permFile, parser);
        XmlUtils.skipCurrentTag(parser);
    }
}

这里的 owner 是 SystemConfig,消费者是 PermissionAllowlist。vendor/odm、product、system_ext 和 system 使用不同 inner map,防止一个分区的配置意外授权另一个分区的特权应用。APEX 走专门的读取函数,因为它必须从文件路径推导模块名并写入三层 map。

2.3 签名分区隔离 ​

源码文件:frameworks/base/services/core/java/com/android/server/SystemConfig.java

java
private void readSignaturePermissions(
        XmlPullParser parser, File permFile, boolean allow)
        throws IOException, XmlPullParserException {
    if (allow) {
        boolean vendor = permFile.toPath().startsWith(
                Environment.getVendorDirectory().toPath() + "/")
                || permFile.toPath().startsWith(
                Environment.getOdmDirectory().toPath() + "/");
        boolean product = permFile.toPath().startsWith(
                Environment.getProductDirectory().toPath() + "/");
        boolean systemExt = permFile.toPath().startsWith(
                Environment.getSystemExtDirectory().toPath() + "/");
        boolean apex = permFile.toPath().startsWith(
                Environment.getApexDirectory().toPath() + "/");
        if (vendor) {
            readSignaturePermissions(parser,
                    mPermissionAllowlist.getVendorSignatureAppAllowlist());
        } else if (product) {
            readSignaturePermissions(parser,
                    mPermissionAllowlist.getProductSignatureAppAllowlist());
        } else if (systemExt) {
            readSignaturePermissions(parser,
                    mPermissionAllowlist.getSystemExtSignatureAppAllowlist());
        } else if (apex) {
            readSignaturePermissions(parser,
                    mPermissionAllowlist.getApexSignatureAppAllowlist());
        } else {
            readSignaturePermissions(parser,
                    mPermissionAllowlist.getSignatureAppAllowlist());
        }
    } else {
        logNotAllowedInPartition("signature-permissions", permFile, parser);
        XmlUtils.skipCurrentTag(parser);
    }
}

签名白名单和特权白名单的读取隔离,但 XML 的 package/permission 结构相同。区别在于它们最终由不同的 protection flag 分支消费,不能因为 XML 形状相同就混为一谈。

3. XML 三值写入 ​

源码文件:frameworks/base/services/core/java/com/android/server/SystemConfig.java

java
private static void readPermissionAllowlist(
        @NonNull XmlPullParser parser,
        @NonNull ArrayMap<String, ArrayMap<String, Boolean>> allowlist,
        @NonNull String tagName)
        throws IOException, XmlPullParserException {
    final String packageName = parser.getAttributeValue(null, "package");
    if (TextUtils.isEmpty(packageName)) {
        Slog.w(TAG, "package is required for <" + tagName + ">");
        return;
    }

    ArrayMap<String, Boolean> permissions = allowlist.get(packageName);
    if (permissions == null) {
        permissions = new ArrayMap<>();
    }

    final int depth = parser.getDepth();
    while (XmlUtils.nextElementWithin(parser, depth)) {
        final String name = parser.getName();
        if ("permission".equals(name)) {
            final String permissionName = parser.getAttributeValue(null, "name");
            if (TextUtils.isEmpty(permissionName)) {
                Slog.w(TAG, "name is required");
                continue;
            }
            permissions.put(permissionName, Boolean.TRUE);
        } else if ("deny-permission".equals(name)) {
            String permissionName = parser.getAttributeValue(null, "name");
            if (TextUtils.isEmpty(permissionName)) {
                Slog.w(TAG, "name is required");
                continue;
            }
            permissions.put(permissionName, Boolean.FALSE);
        }
    }
    allowlist.put(packageName, permissions);
}

这段代码有三个值得在调试时记住的行为:

  1. <permission> 写入 Boolean.TRUE,<deny-permission> 写入 Boolean.FALSE;拒绝是显式状态,不是“没有配置”的别名。
  2. 同一个 package 再次出现时复用原来的 inner map,因此多个 XML 文件可以合并配置。
  3. 缺少 package 或 name 只输出 warning 并放弃该条目,不会生成一个半有效的配置。

示例配置:

xml
<permissions>
    <privapp-permissions package="com.example.systemui">
        <permission name="android.permission.STATUS_BAR_SERVICE"/>
        <deny-permission name="android.permission.READ_LOGS"/>
    </privapp-permissions>
</permissions>

上例在 map 中得到:STATUS_BAR_SERVICE -> TRUE、READ_LOGS -> FALSE。如果完全没有 READ_LOGS 条目,查询结果是 null,策略层仍可能在宽松构建中保留授予或仅记录违规。

4. 配置进入策略 ​

源码文件:frameworks/base/services/permission/java/com/android/server/permission/access/AccessCheckingService.kt

kotlin
fun initialize() {
    packageManagerInternal = LocalServices.getService(PackageManagerInternal::class.java)
    packageManagerLocal =
        LocalManagerRegistry.getManagerOrThrow(PackageManagerLocal::class.java)
    userManagerService = UserManagerService.getInstance()
    systemConfig = SystemConfig.getInstance()

    val configPermissions = systemConfig.permissions
    val privilegedPermissionAllowlistPackages =
        systemConfig.privilegedPermissionAllowlistPackages
    val permissionAllowlist = systemConfig.permissionAllowlist

    val state = MutableAccessState()
    policy.initialize(
        state,
        userIds,
        packageStates,
        disabledSystemPackageStates,
        knownPackages,
        isLeanback,
        configPermissions,
        privilegedPermissionAllowlistPackages,
        permissionAllowlist,
        implicitToSourcePermissions,
    )
    persistence.initialize()
    persistence.read(state)
    this.state = state
}

SystemConfig.getInstance() 产生的白名单对象没有在这里复制成另一套格式,而是作为 externalState.permissionAllowlist 的一部分交给 policy。这样启动时读到的配置和后续权限重算使用的是同一个快照;白名单文件本身不会在每次 checkPermission 调用时重新解析。

5. 特权授予判定 ​

5.1 重算组合条件 ​

源码文件:frameworks/base/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt

kotlin
val mayGrantByPrivileged =
    !permission.isPrivileged ||
        requestingPackageStates.anyIndexed { _, it ->
            checkPrivilegedPermissionAllowlistIfNeeded(it, permission)
        }
val shouldGrantBySignature =
    permission.isSignature &&
        requestingPackageStates.anyIndexed { _, it ->
            shouldGrantPermissionBySignature(it, permission)
        }
val shouldGrantByProtectionFlags =
    requestingPackageStates.anyIndexed { _, it ->
        shouldGrantPermissionByProtectionFlags(it, permission)
    }
if (mayGrantByPrivileged &&
    (shouldGrantBySignature || shouldGrantByProtectionFlags)
) {
    PermissionFlags.PROTECTION_GRANTED
} else {
    0
}

这里的白名单不是单独的“授予 API”。它参与权限状态重算:特权权限先通过 mayGrantByPrivileged,再与签名匹配或 protection flag 条件组合,最后写入 PROTECTION_GRANTED。调用方是权限状态 reconcile 流程,消费者是每个 appId/userId 的 permission flags。

5.2 特权白名单检查 ​

源码文件:frameworks/base/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt

kotlin
private fun MutateStateScope.checkPrivilegedPermissionAllowlistIfNeeded(
    packageState: PackageState,
    permission: Permission,
): Boolean {
    if (RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_DISABLE) {
        return true
    }
    if (packageState.packageName == PLATFORM_PACKAGE_NAME) {
        return true
    }
    if (!(packageState.isSystem && packageState.isPrivileged)) {
        return true
    }
    if (
        permission.packageName !in
            newState.externalState.privilegedPermissionAllowlistPackages
    ) {
        return true
    }
    val allowlistState =
        getPrivilegedPermissionAllowlistState(packageState, permission.name)
    if (allowlistState != null) {
        return allowlistState
    }
    if (packageState.isUpdatedSystemApp) {
        return true
    }
    if (!newState.externalState.isSystemReady) {
        if (!packageState.isApkInUpdatedApex) {
            Slog.w(
                LOG_TAG,
                "Privileged permission ${permission.name} for package" +
                    " ${packageState.packageName} (${packageState.path}) not in" +
                    " privileged permission allowlist",
            )
            if (RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE) {
                privilegedPermissionAllowlistViolations +=
                    "${packageState.packageName}" +
                        " (${packageState.path}): ${permission.name}"
            }
        }
    }
    return !RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_ENFORCE
}

判定顺序决定了很多“看似例外”的行为:

  • 关闭控制开关、平台包、非 system/privileged 包,直接返回 true,表示不需要白名单检查。
  • permission 声明包不在 privilegedPermissionAllowlistPackages 中时,也不进入强制检查。这个集合是“哪些权限需要执行白名单”的门槛,不是包白名单本身。
  • TRUE 返回允许,FALSE 返回拒绝;只有 null 才继续走更新包、启动阶段和 enforce 开关。
  • 白名单只在 isSystemReady == false 的启动阶段收集违规。宽松构建返回 true,强制构建返回 false,因此同一缺失配置在 userdebug 与 user 构建上的结果可能不同。
  • updated system app 和 updated APEX 中的 APK 有专门豁免,避免更新包在恢复或迁移阶段被错误撤销。

6. 分区与 APEX 查询 ​

源码文件:frameworks/base/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt

kotlin
private fun MutateStateScope.getPrivilegedPermissionAllowlistState(
    packageState: PackageState,
    permissionName: String,
): Boolean? {
    val permissionAllowlist = newState.externalState.permissionAllowlist
    val packageName = packageState.packageName
    val nonApexAllowlistState =
        when {
            packageState.isVendor || packageState.isOdm ->
                permissionAllowlist.getVendorPrivilegedAppAllowlistState(
                    packageName, permissionName)
            packageState.isProduct ->
                permissionAllowlist.getProductPrivilegedAppAllowlistState(
                    packageName, permissionName)
            packageState.isSystemExt ->
                permissionAllowlist.getSystemExtPrivilegedAppAllowlistState(
                    packageName, permissionName)
            else ->
                permissionAllowlist.getPrivilegedAppAllowlistState(
                    packageName, permissionName)
        }
    val apexModuleName = packageState.apexModuleName
    return if (
        apexModuleName != null &&
            !(packageState.isVendor || packageState.isOdm || packageState.isSystemExt)
    ) {
        if (nonApexAllowlistState != null) {
            Slog.w(
                LOG_TAG,
                "Package $packageName is an APK in APEX but has permission" +
                    " allowlist on the system image, please bundle the allowlist in the" +
                    " $apexModuleName APEX instead",
            )
        }
        val apexAllowlistState =
            permissionAllowlist.getApexPrivilegedAppAllowlistState(
                apexModuleName, packageName, permissionName)
        apexAllowlistState ?: nonApexAllowlistState
    } else {
        nonApexAllowlistState
    }
}

普通 APK 只查它所属分区的表。APEX APK 先查模块表;如果模块表没有结果,才回退到非 APEX 表,同时对“把 APK-in-APEX 配置写在 system image”发出迁移警告。FALSE 不会因为回退而变成 null,而是保留显式拒绝。

7. 更新包与原包 ​

源码文件:frameworks/base/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt

kotlin
if ((permission.isPrivileged || permission.isOem) && packageState.isSystem) {
    val shouldGrant =
        if (packageState.isUpdatedSystemApp) {
            val disabledSystemPackageState =
                newState.externalState.disabledSystemPackageStates[packageName]
            val disabledSystemPackage = disabledSystemPackageState?.androidPackage
            disabledSystemPackage != null &&
                permission.name in disabledSystemPackage.requestedPermissions &&
                shouldGrantPrivilegedOrOemPermission(
                    disabledSystemPackageState, permission)
        } else {
            shouldGrantPrivilegedOrOemPermission(packageState, permission)
        }
    if (shouldGrant) {
        return true
    }
}

更新系统应用的 APK 位于 /data,但它替换的是系统镜像中的原包。策略因此从 disabledSystemPackageStates 取原包:只有原包曾请求该 permission,且原包通过 allowlist 判定,权限才可沿用。这样更新包不能通过新增 manifest 请求绕过出厂白名单。

这条路径的消费者仍然是 permission flags;原包状态只影响“是否允许进入授予条件”,不会把更新包自动变成系统特权包。

8. 签名权限链 ​

8.1 签名先行 ​

源码文件:frameworks/base/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt

kotlin
val hasCommonSigner =
    sourceSigningDetails?.hasCommonSignerWithCapability(
        packageSigningDetails,
        SigningDetails.CertCapabilities.PERMISSION,
    ) == true ||
        packageSigningDetails.hasAncestorOrSelf(platformSigningDetails) ||
        platformSigningDetails.checkCapability(
            packageSigningDetails,
            SigningDetails.CertCapabilities.PERMISSION,
        )
if (!Flags.signaturePermissionAllowlistEnabled()) {
    return hasCommonSigner
}
if (!hasCommonSigner) {
    return false
}

白名单开关启用后,签名不匹配会立即失败,白名单不能替代签名校验。只有通过签名能力检查,才继续处理平台签名权限的 allowlist。

8.2 平台签名条件 ​

源码文件:frameworks/base/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt

kotlin
if (permission.packageName == PLATFORM_PACKAGE_NAME) {
    val isRequestedByFactoryApp =
        if (packageState.isSystem) {
            if (packageState.isUpdatedSystemApp) {
                val disabledSystemPackage =
                    newState.externalState.disabledSystemPackageStates[
                        packageState.packageName]?.androidPackage
                disabledSystemPackage != null &&
                    permission.name in disabledSystemPackage.requestedPermissions
            } else {
                true
            }
        } else {
            false
        }
    if (!(isRequestedByFactoryApp ||
        getSignaturePermissionAllowlistState(
            packageState, permission.name) == true)
    ) {
        Slog.w(
            LOG_TAG,
            "Signature permission ${permission.name} for package" +
                " ${packageState.packageName} (${packageState.path}) not in" +
                " signature permission allowlist",
        )
        if (!Build.isDebuggable() || isSignaturePermissionAllowlistForceEnforced) {
            return false
        }
    }
}

平台签名权限有两个允许来源:system 出厂包在原 manifest 中请求过,或包/权限在签名白名单中明确为 TRUE。缺少配置时,debuggable 构建默认只告警,user 构建或强制开关则拒绝。注意这里检查的是 == true:签名白名单中的 FALSE 和 null 都不能满足条件。

8.3 签名分区优先级 ​

源码文件:frameworks/base/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt

kotlin
return when {
    packageState.isVendor || packageState.isOdm ->
        permissionAllowlist.getVendorSignatureAppAllowlistState(
            packageName, permissionName)
    packageState.isProduct ->
        apexModuleName?.let {
            permissionAllowlist.getApexSignatureAppAllowlistState(
                packageName, permissionName)
        } ?: permissionAllowlist.getProductSignatureAppAllowlistState(
            packageName, permissionName)
    packageState.isSystemExt ->
        permissionAllowlist.getSystemExtSignatureAppAllowlistState(
            packageName, permissionName)
    else ->
        permissionAllowlist.getApexSignatureAppAllowlistState(
            packageName, permissionName)
            ?: permissionAllowlist.getProductSignatureAppAllowlistState(
                packageName, permissionName)
            ?: permissionAllowlist.getVendorSignatureAppAllowlistState(
                packageName, permissionName)
            ?: permissionAllowlist.getSystemExtSignatureAppAllowlistState(
                packageName, permissionName)
            ?: permissionAllowlist.getSignatureAppAllowlistState(
                packageName, permissionName)
}

vendor/odm 和 system_ext 使用固定分区表;product 中的 APEX 包优先使用 APEX 表。其他包按 APEX、product、vendor、system_ext、system 顺序回退。这个顺序是源码定义的优先级,不能用“最后一个文件覆盖前一个文件”的直觉替代。

9. OEM 保护标志 ​

源码文件:frameworks/base/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt

kotlin
permission.isOem -> {
    if (packageState.isOem) {
        val allowlistState =
            newState.externalState.permissionAllowlist
                .getOemAppAllowlistState(packageName, permissionName)
        checkNotNull(allowlistState) {
            "OEM permission $permissionName for package $packageName " +
                "must be explicitly declared"
        }
        return allowlistState
    }
}

OEM permission 的语义更严格:包必须被识别为 OEM 包,而且配置必须存在;null 会触发 checkNotNull,不是宽松地继续。配置为 FALSE 时返回明确拒绝。它与 privileged allowlist 的“启动阶段记录违规”策略不同,调试时要看清 permission 的 protection flag。

10. 查询和诊断路径 ​

Android 17 的 PackageManagerShellCommand 暴露了读取白名单的只读命令。源码文件:frameworks/base/services/core/java/com/android/server/pm/PackageManagerShellCommand.java

java
private int runGetPrivappPermissions() {
    final String pkg = getNextArg();
    if (pkg == null) {
        getErrPrintWriter().println("Error: no package specified.");
        return 1;
    }
    getOutPrintWriter().println(getPrivAppPermissionsString(pkg, true));
    return 0;
}

private int runGetPrivappDenyPermissions() {
    final String pkg = getNextArg();
    if (pkg == null) {
        getErrPrintWriter().println("Error: no package specified.");
        return 1;
    }
    getOutPrintWriter().println(getPrivAppPermissionsString(pkg, false));
    return 0;
}

对应的帮助文本包含 get-signature-permission-allowlist PARTITION,用于输出 system、vendor、product、system-ext 或 apex 表中的签名权限。特权权限查询还会先识别 APK 所属 APEX,再按 vendor/product/system_ext/system 选择表;因此诊断结果应与包实际路径对应,不能只 grep 一个 XML 文件。

java
private int runGetOemPermissions() {
    final String pkg = getNextArg();
    if (pkg == null) {
        getErrPrintWriter().println("Error: no package specified.");
        return 1;
    }
    final Map<String, Boolean> oemPermissions = SystemConfig.getInstance()
            .getPermissionAllowlist().getOemAppAllowlist().get(pkg);
    if (oemPermissions == null || oemPermissions.isEmpty()) {
        getOutPrintWriter().println("{}");
    } else {
        oemPermissions.forEach((permission, granted) ->
                getOutPrintWriter().println(permission + " granted:" + granted));
    }
    return 0;
}

缺少 package 参数返回错误码 1;没有配置返回 {}。这两个结果要区分:前者是命令输入错误,后者是查询成功但 map 中没有该包。

11. 排查清单 ​

遇到“特权权限没有授予”时,按源码顺序核对:

  1. 包是否真的位于 system/priv-app、vendor/priv-app、product/priv-app、system_ext/priv-app 或某个 APEX;packageState 的分区字段决定查询哪张表。
  2. permission 的 protection level 是否是 privileged、signature 或 OEM;不同分支使用不同白名单和失败策略。
  3. XML 是否写在允许的分区目录,package、name 是否非空;解析 warning 不会生成配置。
  4. 查询结果是 TRUE、FALSE 还是 null;只有 null 才会继续走启动阶段和构建类型逻辑。
  5. 包是否为 updated system app;若是,检查 disabled system 原包是否请求该权限,不能只看 /data 更新包 manifest。
  6. 包是否在 updated APEX 中;APEX 表优先,system image 上的旧配置会产生迁移警告。
  7. 构建是否设置 CONTROL_PRIVAPP_PERMISSIONS_ENFORCE,以及系统是否已经 isSystemReady;这决定缺失配置是记录还是拒绝。

12. 源码阅读路线 ​

建议按以下顺序在 Android 17 源码中跳转:

  1. SystemConfig.readPermissionsFromXml:确认文件路径如何映射到 privapp-permissions、signature-permissions 和 oem-permissions。
  2. SystemConfig.readPrivappPermissions、readSignaturePermissions、readPermissionAllowlist:确认分区 map、XML 标签和三值写入。
  3. PermissionAllowlist 的字段与 get*AllowlistState:确认普通分区和 APEX 的 key 层级。
  4. AccessCheckingService.initialize:确认白名单进入 externalState 的时机和 owner。
  5. AppIdPermissionPolicy.checkPrivilegedPermissionAllowlistIfNeeded、getPrivilegedPermissionAllowlistState:确认 privileged 的豁免、enforce 和 APEX 回退。
  6. AppIdPermissionPolicy.shouldGrantPermissionBySignature、getSignaturePermissionAllowlistState:确认签名能力检查和平台签名权限的 allowlist。
  7. PackageManagerShellCommand.runGetPrivappPermissions、runGetSignaturePermissionAllowlist、runGetOemPermissions:把内存中的最终 map 与设备诊断输出对应起来。

白名单的核心不是某个 XML 示例,而是“来源分区 -> 三值状态 -> 权限策略 -> permission flags”的闭环。掌握这条链后,才能解释为什么同一个权限在 userdebug、user、更新系统包和 APEX 场景下会得到不同结果。