Skip to content

授予运行时权限

追踪 Android 17 grantRuntimePermission 的调用者校验、状态写入、限制判断和结果生效路径。

基于android-17.0.0_r1
AndroidPermissionServicegrantRuntimePermissionRuntime Permission源码阅读

授予运行时权限 ​

本文承接 运行时权限请求、权限类型 和 权限服务架构。上一文解释用户如何点击授权界面;本文从点击之后的服务端方法 grantRuntimePermission 开始,逐层追踪 caller UID、用户和设备、包可见性、permission 类型、fixed/restricted flags、AccessState 写入和 metrics。这里不讨论 PermissionController 的 UI 文案,也不把安装阶段的 PackageInstalledParams 当作 runtime grant API。

1. 调用链 ​

1.1 门面与实现 ​

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

java
@Override
public void grantRuntimePermission(String packageName,
        String permissionName, String persistentDeviceId,
        int userId) {
    mPermissionManagerServiceImpl.grantRuntimePermission(
            packageName, permissionName,
            persistentDeviceId, userId);
}

Java 门面不直接写 flags,真实实现由 PermissionManagerServiceInterface 提供。在 Android 17 中,该接口由 PermissionService 实现,状态由 AccessCheckingService 的 AccessState 持有。

1.2 三类 caller ​

调用者典型场景进入接口
PermissionController用户在授权 UI 点击允许Binder IPermissionManager
系统策略默认权限、设备策略、角色LocalService/implementation
Shell/测试pm grant 或测试 APIBinder,受 shell restriction

所有调用者最终经过 PermissionService 的 caller permission、包可见性和 permission 类型检查;PermissionController 并不因为是系统组件就跳过目标包验证。

2. 调用者校验 ​

2.1 用户与 shell ​

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

kotlin
private fun setRuntimePermissionGranted(
    packageName: String,
    userId: Int,
    permissionName: String,
    deviceId: String,
    isGranted: Boolean,
    skipKillUid: Boolean = false,
    revokeReason: String? = null,
) {
    val methodName = if (isGranted) {
        "grantRuntimePermission"
    } else "revokeRuntimePermission"

    if (!userManagerInternal.exists(userId)) {
        Slog.w(LOG_TAG, "$methodName: Unknown user $userId")
        return
    }
    permissionEnforcer.enforceCallingOrSelfCrossUserPermission(
        userId,
        enforceFullPermission = true,
        enforceShellRestriction = true,
        methodName,
    )
    val enforcedPermissionName = if (isGranted) {
        Manifest.permission.GRANT_RUNTIME_PERMISSIONS
    } else Manifest.permission.REVOKE_RUNTIME_PERMISSIONS
    context.enforceCallingOrSelfPermission(
        enforcedPermissionName, methodName)

授予与撤销使用不同的 manifest permission;跨用户检查还会对 shell 应用 restrictions。未知用户直接返回,不创建权限状态。deviceId 已由门面转换为 persistent ID,后续 flags 以设备维度读取。

2.2 目标包快照 ​

kotlin
val packageState: PackageState?
val permissionControllerPackageState: PackageState?
packageManagerLocal.withUnfilteredSnapshot().use { snapshot ->
    packageState = snapshot.filtered(callingUid, userId)
        .use { it.getPackageState(packageName) }
    permissionControllerPackageState = snapshot.getPackageState(
        permissionControllerPackageName)
}
val androidPackage = packageState?.androidPackage
if (androidPackage == null) {
    // 不区分“不存在”和“调用者不可见”,避免泄露包存在性
    Slog.w(LOG_TAG, "$methodName: Unknown package $packageName")
    return
}

PermissionService 通过 filtered snapshot 查询目标包;不可见包和不存在包都返回 unknown,防止权限 API 成为包枚举通道。PermissionController 的 appId 另从 unfiltered snapshot 取得,用于 role permission 管理判断。

3. 权限类型 ​

3.1 可变类型 ​

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

kotlin
service.mutateState {
    setRuntimePermissionGranted(
        packageState,
        userId,
        permissionName,
        deviceId,
        isGranted,
        canManageRolePermission,
        overridePolicyFixed,
        reportError = true,
        methodName,
    )
}

真正写入发生在 service.mutateState 事务中。事务内的 setRuntimePermissionGranted 允许 development、role 和 runtime permission,其他 protection 类型进入不可变分支。

3.2 目标检查 ​

kotlin
val permission = with(policy) {
    getPermissions()[permissionName]
}
if (permission == null) {
    if (reportError) {
        throw IllegalArgumentException(
            "Unknown permission $permissionName")
    }
    return
}

when {
    permission.isDevelopment -> Unit
    permission.isRole -> {
        if (!canManageRolePermission) {
            if (reportError) {
                throw SecurityException(
                    "Permission $permissionName is managed by role")
            }
            return
        }
    }
    permission.isRuntime -> {
        if (androidPackage.targetSdkVersion < Build.VERSION_CODES.M) {
            return
        }
    }
    else -> {
        if (reportError) {
            throw SecurityException(
                "Permission $permissionName is not changeable")
        }
        return
    }
}

grantRuntimePermission 名称并不意味着所有 permission 都可变。development、role、dangerous/runtime 才能进入该函数;normal、signature、internal 等应在安装/策略评估阶段决定。targetSdk < M 的 legacy 包直接返回,因为其权限模型不是 runtime grant。

3.3 Instant app ​

kotlin
if (isGranted &&
    packageState.getUserStateOrDefault(userId).isInstantApp &&
    !permission.isInstant
) {
    throw SecurityException(
        "Cannot grant non-instant permission $permissionName " +
            "to package $packageName")
}

instant app 只能获得带 PROTECTION_FLAG_INSTANT 的权限。即使 caller 拥有 grant permission,目标包的用户状态仍会限制可授予集合。

4. 请求与 fixed ​

4.1 请求声明 ​

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

kotlin
val appId = packageState.appId
val oldFlags = getPermissionFlagsWithPolicy(
    appId, userId, permissionName, deviceId)

if (permissionName !in androidPackage.requestedPermissions
    && oldFlags == 0
) {
    if (reportError) {
        Slog.e(LOG_TAG,
            "Permission $permissionName isn't requested by " +
                "package $packageName")
    }
    return
}

目标 APK 未在 Manifest 请求该 permission 且没有既有状态时,grant 被忽略。保留旧 flags 的包在某些迁移/兼容场景仍可处理,但不能把任意权限写入任意 appId。

4.2 固定状态 ​

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

kotlin
if (oldFlags.hasBits(PermissionFlags.SYSTEM_FIXED)) {
    if (reportError) {
        Slog.e(LOG_TAG,
            "$methodName: Cannot change system fixed permission " +
                "$permissionName for package $packageName")
    }
    return
}

if (oldFlags.hasBits(PermissionFlags.POLICY_FIXED)
    && !overridePolicyFixed
) {
    if (reportError) {
        Slog.e(LOG_TAG,
            "$methodName: Cannot change policy fixed permission " +
                "$permissionName for package $packageName")
    }
    return
}

SYSTEM_FIXED 永远不能被该 API 修改;POLICY_FIXED 只有 caller 同时具备 ADJUST_RUNTIME_PERMISSIONS_POLICY 时才能覆盖。返回通常是无异常的 no-op,并由 PermissionController 根据后续查询显示最终状态。

5. restricted 权限 ​

5.1 授予阻断 ​

kotlin
if (isGranted &&
    oldFlags.hasBits(PermissionFlags.RESTRICTION_REVOKED)
) {
    if (reportError) {
        Slog.e(LOG_TAG,
            "$methodName: Cannot grant hard-restricted " +
                "non-exempt permission $permissionName")
    }
    return
}
if (isGranted &&
    oldFlags.hasBits(PermissionFlags.SOFT_RESTRICTED)
) {
    if (reportError) {
        Slog.e(LOG_TAG,
            "$methodName: Cannot grant soft-restricted " +
                "non-exempt permission $permissionName")
    }
    return
}

hard/soft restricted permission 没有 exemption 时,即使是 runtime 类型也不能由普通 grant API 强行授予。exemption 通常来自 installer allowlist、系统策略或预装身份,先由安装/策略阶段写入 flags。

5.2 更新 flags ​

kotlin
val newFlags = PermissionFlags.updateRuntimePermissionGranted(
    oldFlags, isGranted)
if (oldFlags == newFlags) {
    return
}
setPermissionFlagsWithPolicy(
    appId, userId, permissionName,
    deviceId, newFlags)

updateRuntimePermissionGranted 只改变 runtime grant 来源位,保留 user-set、policy-fixed、restriction 等其他 bits。新旧 flags 相同则不写盘、不发变更通知,避免重复 grant 产生无意义的 UID 操作。

6. 生效与进程 ​

6.1 状态提交 ​

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

kotlin
internal inline fun mutateState(
    crossinline action: MutateStateScope.() -> Unit
) {
    synchronized(stateLock) {
        val oldState = state
        val newState = oldState.toMutable()
        MutateStateScope(oldState, newState).action()
        persistence.write(newState)
        state = newState
        with(policy) {
            GetStateScope(newState).onStateMutated()
        }
    }
}

事务完成后才替换 volatile state 引用,读者不会看到半写入 flags。persistence.write 位于引用替换前,表示该实现把持久化纳入 mutation 临界区;具体文件写入方式由 AccessPersistence 决定。

6.2 metrics 与 UID ​

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

kotlin
if (permission.isRuntime) {
    val action = if (isGranted) {
        MetricsProto.MetricsEvent.ACTION_PERMISSION_GRANTED
    } else {
        MetricsProto.MetricsEvent.ACTION_PERMISSION_REVOKED
    }
    val log = LogMaker(action).apply {
        setPackageName(packageName)
        addTaggedData(
            MetricsProto.MetricsEvent.FIELD_PERMISSION,
            permissionName,
        )
    }
    metricsLogger.write(log)
}

Android 17 的 PermissionService 在 flags 变化后记录 metrics。进程是否被杀死由 onPermissionFlagsChangedListener 和 revoke 路径策略处理;测试专用 revokePostNotificationPermissionWithoutKillForTest 可跳过 kill,普通 revoke 不能据此推断进程仍可使用旧能力。

7. 设备与 AppOps ​

7.1 设备 ID ​

Binder 门面把 numeric deviceId 转成 persistent device ID;PermissionService 以该字符串读取 permission flags。相同 appId/user 在不同虚拟设备上的授权可以独立存在,默认物理设备使用 default persistent ID。

7.2 AppOp permission ​

如果 permission 带 PROTECTION_FLAG_APPOP,grant runtime flags 只完成 permission 层授予;真实 API 还要由 PermissionCheckerService 查询 AppOps mode。MODE_ALLOWED、MODE_IGNORED 和 MODE_ERRORED 可以让同一个 permission grant 得到不同的数据访问结果。

8. 失败路径 ​

8.1 典型失败 ​

条件行为状态是否改变
用户不存在记录 warning 并返回否
目标包不可见/不存在返回 unknown package否
权限未定义IllegalArgumentException否
未声明权限记录错误并返回否
system fixed记录错误并返回否
policy fixed 且无 policy 权限记录错误并返回否
hard/soft restricted 无 exemption记录错误并返回否
instant app 请求非 instant 权限SecurityException否

错误发生在 setPermissionFlagsWithPolicy 之前,因此失败不会产生新的 runtime grant。调用方若只看到 API 返回而不重新查询 flags,可能把 no-op 误认为成功。

8.2 角色权限 ​

role permission 只有 root/system 或 PermissionController appId 能管理。其他 caller 即使拥有 GRANT_RUNTIME_PERMISSIONS,也会因 role owner 检查被拒绝;角色变化应通过 RoleManager/PermissionController 处理。

9. 验证方法 ​

9.1 正常授予 ​

安装 targetSdk >= 23、声明 CAMERA 的测试包,用 PermissionController 授予后调用 checkSelfPermission。断言 user 0 的 runtime flags 出现 grant,另一个用户不变;对未声明权限授予应无状态变化。

9.2 固定限制 ​

为权限设置 system-fixed、policy-fixed 和 restriction revoked flags,分别调用 grant。预期 system-fixed 永不改变,policy-fixed 只有 policy caller 可改,restricted 无 exemption 时 grant 被拒绝。检查日志中的 methodName 和 package/permission,确认失败发生在 flags 写入前。

9.3 device-aware ​

在两个虚拟设备上授予同一包权限,分别用 deviceId 查询。断言 persistent device ID 对应的状态互不覆盖;默认设备仍使用 default ID。

9.4 AppOps ​

保持 permission grant,分别设置关联 AppOp 为 allowed、ignored、errored,调用真实受保护 API 和 PermissionChecker。对比静态 checkSelfPermission 与数据访问结果,验证 grant 不是最终资源访问保证。

9.5 进程生效 ​

在应用进程运行时撤销权限,观察 UID 是否被终止并重新启动;使用测试专用 no-kill API 对照。授予路径不应依赖杀进程才能让后续检查看到新 flags。

bash
adb shell pm grant <package.name> <permission>
adb shell pm revoke <package.name> <permission>
adb shell dumpsys package <package.name>
adb shell appops get <package.name>

10. 源码路线 ​

  1. PermissionManagerService.grantRuntimePermission:确认 Binder 门面。
  2. PermissionService.setRuntimePermissionGranted:读取完整 caller、包和用户检查。
  3. permission type/fixed/restricted 分支:理解哪些请求会 no-op 或抛异常。
  4. PermissionFlags.updateRuntimePermissionGranted:跟踪 bits 如何变化。
  5. AccessCheckingService.mutateState:理解 copy-on-write、持久化和可见性。
  6. PermissionCheckerService:连接 AppOps 和数据访问。
  7. killUid/permission change listener:确认进程生效与撤销时机。

grantRuntimePermission 的核心不是把一个 bit 设为 1,而是先证明 caller 有权为目标 user/device 的目标包改变目标 permission,再在 AccessState 事务中只更新允许变化的 runtime 来源位。fixed、restricted、instant、role、AppOps 和包声明都会截断这条路径;只有把“请求、状态写入、进程生效、真实资源检查”分开,才能准确解释授予 API 返回后应用仍被拒绝的情况。