Skip to content

权限调试

通过真实源码解读 pm 权限命令、dumpsys permissionmgr 和权限状态排查路径。

AndroidPMSPackageManager权限调试

权限调试 ​

权限问题的难点不在命令名称,而在于命令查看的是哪一层状态。pm grant 修改 runtime permission flags,dumpsys permissionmgr 展示 AccessCheckingService 的系统与用户状态,pm list permissions 查看注册定义,而 AppOps 又是另一张表。本文沿 Android 17 源码把这些输出对应起来。

1. 调试对象分层 ​

现象应查看的状态入口
权限是否注册systemState.permissionspm list permissions
包是否获授appIdPermissionFlagsdumpsys permissionmgr --package
AppOps 是否放行appId/package AppOp modedumpsys permissionmgr
特权权限失败allowlist 与 protection policy白名单查询、启动日志
GID 未更新getGidsForUid 与 UID 生命周期permission service

2. 授予撤销入口 ​

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

java
private int runGrantRevokePermission(boolean grant) throws RemoteException {
    int userId = UserHandle.USER_SYSTEM;
    String opt;
    boolean allPermissions = false;
    while ((opt = getNextOption()) != null) {
        if (opt.equals("--user")) {
            userId = UserHandle.parseUserArg(getNextArgRequired());
        }
        if (opt.equals("--all-permissions")) {
            allPermissions = true;
        }
    }
    String pkg = getNextArg();
    if (!allPermissions && pkg == null) {
        getErrPrintWriter().println("Error: no package specified");
        return 1;
    }
    String perm = getNextArg();
    if (!allPermissions && perm == null) {
        getErrPrintWriter().println("Error: no permission specified");
        return 1;
    }
    if (allPermissions && perm != null) {
        getErrPrintWriter().println("Error: permission specified but not expected");
        return 1;
    }
    final UserHandle translatedUser = UserHandle.of(translateUserId(
            userId, UserHandle.USER_NULL, "runGrantRevokePermission"));

默认用户是 system user;--user 先经过 translateUserId。参数错误在 shell 层返回 1,不会进入权限策略。--all-permissions 不是一个独立的“授予全部”后端接口,而是先枚举包,再逐个调用单权限 API。

java
List<PackageInfo> packageInfos;
PackageManager pm = mContext.createContextAsUser(
        translatedUser, 0).getPackageManager();
if (pkg == null) {
    packageInfos = pm.getInstalledPackages(PackageManager.GET_PERMISSIONS);
} else {
    try {
        packageInfos = Collections.singletonList(
                pm.getPackageInfo(pkg, PackageManager.GET_PERMISSIONS));
    } catch (NameNotFoundException e) {
        getErrPrintWriter().println("Error: package not found");
        getOutPrintWriter().println("Failure [package not found]");
        return 1;
    }
}

for (PackageInfo packageInfo : packageInfos) {
    List<String> permissions = Collections.singletonList(perm);
    if (allPermissions) {
        permissions = getRequestedRuntimePermissions(packageInfo);
    }
    for (String permission : permissions) {
        if (grant) {
            mPermissionManager.grantRuntimePermission(
                    packageInfo.packageName, permission, translatedUser);
        } else {
            mPermissionManager.revokeRuntimePermission(
                    packageInfo.packageName, permission, translatedUser, null);
        }
    }
}
return 0;

指定包模式异常会返回调用失败;批量模式对单个权限异常记录 warning 后继续。因此批量命令返回 0 不等于每个权限都成功。

3. 批量筛选 ​

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

java
private List<String> getRequestedRuntimePermissions(PackageInfo info) {
    if (info.requestedPermissions == null) {
        return new ArrayList<>();
    }
    List<String> result = new ArrayList<>();
    PackageManager pm = mContext.getPackageManager();
    for (String permission : info.requestedPermissions) {
        PermissionInfo pi;
        try {
            pi = pm.getPermissionInfo(permission, 0);
        } catch (NameNotFoundException e) {
            continue;
        }
        if (pi.getProtection() != PermissionInfo.PROTECTION_DANGEROUS) {
            continue;
        }
        result.add(permission);
    }
    return result;
}

--all-permissions 只保留 dangerous 权限。normal、signature、privileged 权限不应通过该参数推断授予结果,而应回到各自 protection policy 排查。

4. 状态修改 ​

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

kotlin
override fun grantRuntimePermission(
    packageName: String,
    permissionName: String,
    deviceId: String,
    userId: Int,
) {
    setRuntimePermissionGranted(
        packageName, userId, permissionName, deviceId, isGranted = true)
}

override fun revokeRuntimePermission(
    packageName: String,
    permissionName: String,
    deviceId: String,
    userId: Int,
    reason: String?,
) {
    setRuntimePermissionGranted(
        packageName, userId, permissionName, deviceId, isGranted = false,
        reason = reason)
}

两个 Binder 接口共享 setRuntimePermissionGranted,区别只是目标状态。调用方可以是 shell 或 PermissionController,消费者是 appId/user/device 维度的 flags。

5. dump 输出 ​

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

kotlin
override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>?) {
    if (!DumpUtils.checkDumpPermission(context, LOG_TAG, pw)) return
    val writer = IndentingPrintWriter(pw, "  ")
    if (args.isNullOrEmpty() || args[0] == "-a") {
        service.getState {
            writer.dumpSystemState(state)
            getAllAppIdPackageNames(state).forEachIndexed { _, appId, names ->
                if (appId != Process.INVALID_UID) {
                    writer.dumpAppIdState(appId, state, names)
                }
            }
        }
    } else if (args[0] == "--app-id" && args.size == 2) {
        val appId = args[1].toInt()
        service.getState {
            val names = getAllAppIdPackageNames(state)
            if (appId in names) writer.dumpAppIdState(appId, state, names[appId])
            else writer.println("Unknown app ID $appId.")
        }
    } else if (args[0] == "--package" && args.size == 2) {
        val packageName = args[1]
        service.getState {
            val packageState = state.externalState.packageStates[packageName]
            if (packageState != null) {
                writer.dumpAppIdState(
                    packageState.appId, state, indexedSetOf(packageName))
            } else writer.println("Unknown package $packageName.")
        }
    } else {
        writer.println("Usage: dumpsys permissionmgr [--app-id <APP_ID>] " +
            "[--package <PACKAGE_NAME>]")
    }
}

dump 先经过 DumpUtils.checkDumpPermission。--package 由 package state 找 appId 后输出用户状态;shared UID 下可能看到同 UID 其他包的影响。--app-id 适合排查共享 UID,-a 才输出系统定义和全部 appId。

5.1 system state ​

kotlin
println("Permissions:")
state.systemState.permissions.forEachIndexed { _, _, permission ->
    val level = PermissionInfo.protectionToString(permission.protectionLevel)
    println("${permission.name}: " +
        "type=${Permission.typeToString(permission.type)}, " +
        "packageName=${permission.packageName}, appId=${permission.appId}, " +
        "gids=${permission.gids.contentToString()}, " +
        "protectionLevel=[$level]")
}

这段输出用于确认注册定义:type、owner、appId、GID 和 protection level。它不表示某个用户已经授予权限。

5.2 appId/user state ​

kotlin
userState.appIdPermissionFlags[appId]?.forEachIndexed {
    _, permissionName, flags ->
    val granted = PermissionFlags.isPermissionGranted(flags)
    println("$permissionName: granted=$granted, flags=" +
        PermissionFlags.toString(flags))
}
userState.appIdAppOpModes[appId]?.forEachIndexed {
    _, opName, mode ->
    println("$opName: mode=${AppOpsManager.modeToName(mode)}")
}

granted=true 只表示 permission flags 的 granted 位;AppOps 仍可能是 MODE_IGNORED 或 MODE_ERRORED,访问失败时必须同时查看两段。

6. 定义查询 ​

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

java
List<PermissionInfo> ps = mPermissionManager.queryPermissionsByGroup(
        groupList.get(i), 0);
for (PermissionInfo pi : ps) {
    int base = pi.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE;
    if (base < startProtectionLevel || base > endProtectionLevel) continue;
    pw.println(prefix + "permission:" + pi.name);
}

pm list permissions 适合确认权限是否注册、所属 group 和 protection;不适合确认包是否已授予。-d 筛选 dangerous,-g 按组,-f 增加元数据,-s 输出摘要。查询结果还受 package visibility 过滤,instant app 不能使用这些查询路径。

7. flags 修改命令 ​

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

java
static {
    SUPPORTED_PERMISSION_FLAGS_LIST = List.of(
            "review-required", "revoked-compat",
            "revoke-when-requested", "user-fixed", "user-set");
}

int flagMask = 0;
String flagName = getNextArg();
while (flagName != null) {
    if (!SUPPORTED_PERMISSION_FLAGS.containsKey(flagName)) {
        getErrPrintWriter().println(
                "Error: specified flag " + flagName + " is not one of " +
                SUPPORTED_PERMISSION_FLAGS_LIST);
        return 1;
    }
    flagMask |= SUPPORTED_PERMISSION_FLAGS.get(flagName);
    flagName = getNextArg();
}
int flagSet = setFlags ? flagMask : 0;
mPermissionManager.updatePermissionFlags(
        pkg, perm, flagMask, flagSet, translatedUser);

set 与 clear 都是 mask 操作;clear 不是把所有 flags 清零。排查自动撤销、用户固定或兼容性撤销时,应先记录原 flags,再只操作目标位。

8. 完整排查流程 ​

9. 显式权限检查 ​

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

kotlin
override fun checkUidPermission(
    uid: Int,
    permissionName: String,
): Int {
    if (UserHandle.getAppId(uid) >= Process.FIRST_APPLICATION_UID) {
        val packageState = packageManagerLocal.withUnfilteredSnapshot().use {
            it.getPackageStateByUid(uid)
        }
        if (packageState == null) return PackageManager.PERMISSION_DENIED
        val granted = service.getState {
            isPermissionGranted(
                packageState, UserHandle.getUserId(uid), permissionName,
                VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT)
        }
        return if (granted) PackageManager.PERMISSION_GRANTED
        else PackageManager.PERMISSION_DENIED
    }
    return if (isSystemUidPermissionGranted(uid, permissionName)) {
        PackageManager.PERMISSION_GRANTED
    } else {
        PackageManager.PERMISSION_DENIED
    }
}

这段代码解释了一个常见误区:应用 UID 走 package state、用户和设备维度的 permission flags;系统 UID 则走 systemPermissions。因此排查 shell、system_server 或 media UID 时,不能只 dump 某个 APK。

10. AppOps 对照 ​

权限检查成功后,调用方还可能经过 PermissionChecker 和 AppOps。dumpsys permissionmgr 输出的 appIdAppOpModes、packageAppOpModes 只能说明 PMS 保存的模式;实际数据访问还可能受 AppOpsService 的 note/start 结果影响。

java
// frameworks/base/core/java/android/app/AppOpsManager.java
public static String modeToName(int mode) {
    switch (mode) {
        case MODE_ALLOWED: return "allow";
        case MODE_IGNORED: return "ignore";
        case MODE_ERRORED: return "deny";
        case MODE_DEFAULT: return "default";
        case MODE_FOREGROUND: return "foreground";
        default: return "mode=" + mode;
    }
}

若 dump 显示 granted=true 但访问仍失败,下一步应检查对应 op 的 mode、调用是否为 data delivery,以及是否存在前台限制,而不是重复执行 pm grant。

11. 调试命令示例 ​

bash
adb shell pm list permissions -d -f
adb shell dumpsys permissionmgr --package com.example.app
adb shell dumpsys permissionmgr --app-id 10123
adb shell pm grant --user 0 com.example.app android.permission.CAMERA
adb shell pm revoke --user 0 com.example.app android.permission.CAMERA
adb shell pm set-permission-flags com.example.app android.permission.CAMERA user-fixed
adb shell pm clear-permission-flags com.example.app android.permission.CAMERA user-fixed

命令顺序对应源码层次:先确认定义,再查看 user flags,再检查 AppOps,最后才修改状态。--app-id 的值应来自包状态或 UID 查询;错误的 appId 会得到 Unknown app ID,不会自动猜测包名。

12. 源码阅读路线 ​

  1. PackageManagerShellCommand.runGrantRevokePermission:参数、用户转换和批量异常语义。
  2. PermissionService.grantRuntimePermission/revokeRuntimePermission:最终修改的 flags。
  3. PermissionService.dump:--package、--app-id 和 -a 的状态范围。
  4. dumpSystemState 与 dumpAppIdState:定义状态、用户 flags 和 AppOps 的对应关系。
  5. queryPermissionsByGroup:定义查询与 visibility 过滤。
  6. flags 命令解析:mask 修改与错误码。

权限调试必须建立“定义 → flags → AppOps → GID/进程”的证据链。只看 pm grant 退出码,或只看 manifest 中的 uses-permission,都无法解释 Android 17 中权限访问失败的完整原因。