Skip to content

platform.xml 权限映射

解释 platform.xml 如何声明 GID、系统 UID 权限和 split-permission,并进入 Android 17 权限状态。

AndroidPMSPackageManager权限platform.xml

platform.xml 权限映射 ​

platform.xml 不是一张单一的权限表。Android 17 的 SystemConfig 将系统权限 XML 分成三条链:<permission><group> 描述权限对应的 Linux GID,<assign-permission> 为系统 UID 建立直接权限集合,<split-permission> 为权限策略提供兼容性来源。三类状态的 owner、消费者和生效时机都不同。

1. 三类声明 ​

标签内存状态消费者结果
<permission><group>PermissionEntry.gids/perUserPermissionService.getGidsForUidsupplementary GID
<assign-permission>mSystemPermissions[uid]checkUidPermission系统 UID 直接授权
<split-permission>mSplitPermissionsAccessCheckingService隐式来源索引

权限有 GID 映射不代表所有声明它的应用都自动获得 GID;getGidsForUid 仍要检查 permission flags。系统 UID 的 assign 也不会授给普通 APK。split 规则只提供重算输入,不直接设置 runtime grant。

2. 配置入口 ​

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

java
switch (tagName) {
    case "permission":
        readPermission(parser, permFile, allowPermissions);
        break;
    case "assign-permission":
        readAssignPermission(parser, permFile, allowPermissions,
                allowVendorAssignPermissions);
        break;
    case "split-permission":
        readSplitPermission(parser, permFile, allowPermissions);
        break;
}

readPermissionsFromXml 先决定文件所在分区能否声明标签;不允许时记录 logNotAllowedInPartition 并跳过当前标签。解析结果保存在 SystemConfig,后续服务消费内存快照,不会在每次权限检查时重新读文件。

3. 权限与 GID ​

3.1 PermissionEntry ​

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

java
public static final class PermissionEntry {
    public final String name;
    public int[] gids;
    public boolean perUser;

    PermissionEntry(String name, boolean perUser) {
        this.name = name;
        this.perUser = perUser;
    }
}

final ArrayMap<String, PermissionEntry> mPermissions = new ArrayMap<>();

没有 <group> 的权限也会生成 entry,只是 gids 为空。它表示“配置存在但没有 Linux 组映射”,不表示权限不存在。

3.2 读取 group ​

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

java
void readPermission(XmlPullParser parser, String name)
        throws IOException, XmlPullParserException {
    if (mPermissions.containsKey(name)) {
        throw new IllegalStateException(
                "Duplicate permission definition for " + name);
    }
    final boolean perUser = XmlUtils.readBooleanAttribute(
            parser, "perUser", false);
    final PermissionEntry perm = new PermissionEntry(name, perUser);
    mPermissions.put(name, perm);
    int outerDepth = parser.getDepth();
    int type;
    while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
            && (type != XmlPullParser.END_TAG
            || parser.getDepth() > outerDepth)) {
        if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
            continue;
        }
        if ("group".equals(parser.getName())) {
            String gidStr = parser.getAttributeValue(null, "gid");
            if (gidStr != null) {
                int gid = Process.getGidForName(gidStr);
                if (gid != -1) {
                    perm.gids = appendInt(perm.gids, gid);
                } else {
                    Slog.w(TAG, "<group> with unknown gid \"" + gidStr);
                }
            } else {
                Slog.w(TAG, "<group> without gid");
            }
        }
        XmlUtils.skipCurrentTag(parser);
    }
}

重复权限定义直接抛出异常;未知 GID 只记录 warning,不写入数组;perUser 默认为 false。典型配置 <permission name="android.permission.INTERNET"><group gid="inet"/></permission> 只建立映射,不直接授予 INTERNET。

4. GID 计算 ​

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

kotlin
inline val hasGids: Boolean
    get() = gids.isNotEmpty()

fun getGidsForUser(userId: Int): IntArray =
    if (areGidsPerUser) {
        IntArray(gids.size) { i -> UserHandle.getUid(userId, gids[i]) }
    } else {
        gids.copyOf()
    }

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

kotlin
override fun getGidsForUid(uid: Int): IntArray {
    val appId = UserHandle.getAppId(uid)
    val userId = UserHandle.getUserId(uid)
    val globalGids = systemConfig.globalGids
    service.getState {
        val permissionFlags =
            with(policy) { getUidPermissionFlags(appId, userId) }
            ?: return globalGids.copyOf()
        val gids = GrowingIntArray.wrap(globalGids)
        permissionFlags.forEachIndexed { _, permissionName, flags ->
            if (!PermissionFlags.isPermissionGranted(flags)) {
                return@forEachIndexed
            }
            val permission =
                with(policy) { getPermissions()[permissionName] }
                ?: return@forEachIndexed
            gids.addAll(permission.getGidsForUser(userId))
        }
        return gids.toArray()
    }
}

完整 UID 先拆成 appId/userId。无权限状态时仍返回 global GID;每个权限必须同时满足“已授予、能找到 Permission、Permission 有 GID”才进入结果。perUser 决定 GID 是否按 userId 转换。

5. GID 变化 ​

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

kotlin
if (permission.hasGids &&
    (wasPermissionGranted != isPermissionGranted)
) {
    gidsChangedUids += uid
}

gidsChangedUids.forEachIndexed { _, uid ->
    if (!handler.hasMessages(uid, 0)) {
        handler.obtainMessage(uid, 0).setCallback {
            killUid(uid, PermissionManager.KILL_APP_REASON_GIDS_CHANGED)
        }.sendToTarget()
    }
}
gidsChangedUids.clear()

只有具有关联 GID 且 granted 真值发生变化时,UID 才进入集合。状态 mutation 完成后异步 kill,确保进程不会继续使用旧 supplementary groups;消息去重避免一次批量变更重复 kill。

6. UID 权限 ​

6.1 解析 UID 权限 ​

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

java
private void readAssignPermission(XmlPullParser parser, File permFile,
        boolean isVendor) throws IOException, XmlPullParserException {
    if (AconfigFlags.getInstance().skipCurrentElement(null, parser, true)) {
        XmlUtils.skipCurrentTag(parser);
        return;
    }
    String perm = parser.getAttributeValue(null, "name");
    if (perm == null) {
        Slog.w(TAG, "<assign-permission> without name in " + permFile);
        XmlUtils.skipCurrentTag(parser);
        return;
    }
    if (isVendor && !VENDOR_ASSIGNABLE_PERMISSIONS.contains(perm)) {
        Slog.w(TAG, "<assign-permission> for vendor unavailable permission " + perm);
        XmlUtils.skipCurrentTag(parser);
        return;
    }
    String uidStr = parser.getAttributeValue(null, "uid");
    if (uidStr == null) {
        XmlUtils.skipCurrentTag(parser);
        return;
    }
    int uid = Process.getUidForName(uidStr);
    if (uid < 0) {
        XmlUtils.skipCurrentTag(parser);
        return;
    }
    ArraySet<String> perms = mSystemPermissions.get(uid);
    if (perms == null) {
        perms = new ArraySet<>();
        mSystemPermissions.put(uid, perms);
    }
    perms.add(perm.intern());
    XmlUtils.skipCurrentTag(parser);
}

vendor 文件有 VENDOR_ASSIGNABLE_PERMISSIONS 限制;缺少属性、未知 UID、Aconfig 禁用条目都不会写入表。同一 UID 的多条声明合并到 ArraySet。

6.2 UID 直接命中 ​

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

kotlin
private fun isSystemUidPermissionGranted(
    uid: Int, permissionName: String
): Boolean {
    val uidPermissions = systemConfig.systemPermissions[uid]
        ?: return false
    if (permissionName in uidPermissions) return true
    val fullerPermissionName = FULLER_PERMISSIONS[permissionName]
    return fullerPermissionName != null &&
        fullerPermissionName in uidPermissions
}

assign 表是 checkUidPermission 的直接授权来源。查询 key 是完整 UID,只有精确权限或 FULLER_PERMISSIONS 兼容映射命中才返回 true。

7. split-permission ​

7.1 解析规则 ​

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

java
private void readSplitPermission(XmlPullParser parser, File permFile)
        throws IOException, XmlPullParserException {
    String featureFlag = parser.getAttributeValue(
            "http://schemas.android.com/apk/res/android", "featureFlag");
    if (featureFlag == null) {
        featureFlag = parser.getAttributeValue(null, "featureFlag");
    }
    boolean negated = featureFlag != null && featureFlag.strip().startsWith("!");
    String source = parser.getAttributeValue(null, "name");
    if (source == null) {
        XmlUtils.skipCurrentTag(parser);
        return;
    }
    String targetSdkStr = parser.getAttributeValue(null, "targetSdk");
    int targetSdk = Build.VERSION_CODES.CUR_DEVELOPMENT + 1;
    if (!TextUtils.isEmpty(targetSdkStr)) {
        try {
            targetSdk = Integer.parseInt(targetSdkStr);
        } catch (NumberFormatException e) {
            XmlUtils.skipCurrentTag(parser);
            return;
        }
    }
    final int depth = parser.getDepth();
    List<String> newPermissions = new ArrayList<>();
    while (XmlUtils.nextElementWithin(parser, depth)) {
        if ("new-permission".equals(parser.getName())) {
            String newName = parser.getAttributeValue(null, "name");
            if (!TextUtils.isEmpty(newName)) newPermissions.add(newName);
        } else {
            XmlUtils.skipCurrentTag(parser);
        }
    }
    if (!newPermissions.isEmpty()) {
        mSplitPermissions.add(new SplitPermissionInfo(
                source, newPermissions, targetSdk, featureFlag, negated));
    }
}

缺少 targetSdk 使用高于当前开发版本的默认值;feature flag 支持取反;没有有效目标权限时不保存规则。

7.2 反向索引 ​

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

kotlin
private val SystemConfig.implicitToSourcePermissions:
    IndexedMap<String, IndexedListSet<String>>
    get() = MutableIndexedMap<String, MutableIndexedListSet<String>>().apply {
        splitPermissions.forEach { info ->
            val source = info.splitPermission
            info.newPermissions.forEach { newPermission ->
                getOrPut(newPermission) { MutableIndexedListSet() } += source
            }
        }
    } as IndexedMap<String, IndexedListSet<String>>

XML 的 source → new 在策略输入中反转为 new → source。这样权限重算处理新权限时,可以直接反查旧权限来源,而不用遍历全部规则。

8. 查询和生效边界 ​

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

java
public ArrayList<SplitPermissionInfo> getSplitPermissions(
        boolean includeDisabled) {
    if (includeDisabled) return mSplitPermissions;
    ArrayList<SplitPermissionInfo> filtered = new ArrayList<>();
    for (int i = 0; i < mSplitPermissions.size(); i++) {
        SplitPermissionInfo info = mSplitPermissions.get(i);
        String flag = info.getFeatureFlag();
        if (flag == null || !AconfigFlags.getInstance().skip(
                null, flag, info.isFeatureFlagNegated())) {
            filtered.add(info);
        }
    }
    return filtered;
}

includeDisabled 只影响查询结果,不会从原始列表删除规则。修改 XML 也不会让运行中的进程自动获得新 GID;必须经过配置重新加载、权限状态重算和必要的 UID 重启。

9. 源码阅读路线 ​

  1. SystemConfig 顶层标签分发:确认权限、assign、split 的入口与分区限制。
  2. readPermission / PermissionEntry:确认 GID 名称到数字数组的转换。
  3. Permission.getGidsForUser / PermissionService.getGidsForUid:确认授予状态与 global/per-user GID 合并。
  4. PermissionService.gidsChangedUids:理解 GID 变化为何触发 UID kill。
  5. readAssignPermission / isSystemUidPermissionGranted:区分系统 UID 直接授权和普通包权限。
  6. readSplitPermission / getSplitPermissions / implicitToSourcePermissions:确认 target SDK、feature flag 和反向索引。

platform.xml 的真正作用是把配置分别接入 Linux GID 计算、系统 UID 快速授权和权限策略兼容性推导。只有沿各自消费者继续阅读,才能解释一条声明为什么生效、为什么被跳过,以及为什么权限变化会影响进程生命周期。