Skip to content

checkCallingPermission

追踪 checkCallingPermission 从 Binder caller UID/PID、PermissionCache 到 PermissionController 的真实调用链和身份边界。

基于android-17.0.0_r1
AndroidBinder权限

checkCallingPermission ​

checkCallingPermission 横跨 caller identity、PermissionCache、权限控制服务和重连状态;它不是驱动 SELinux 检查的替代品。下图固定 framework permission 的调用链。

本文面向已读过 Binder调用者身份 和 Binder服务注册 的读者。checkCallingPermission 不是驱动 ioctl 的单次布尔判断:它先从当前线程的 Binder 入站上下文取得 caller PID/UID,再通过 Native permission controller 或缓存执行检查。本文追踪身份来源、缓存 key、controller 重连和 clear identity 后的语义,不展开 SELinux 强制访问控制(见后续 BD096)。

1. 调用入口 ​

源码文件:frameworks/native/libs/binder/IServiceManager.cpp

cpp
bool checkCallingPermission(const String16& permission, int32_t* outPid, int32_t* outUid) {
    IPCThreadState* ipcState = IPCThreadState::self();
    pid_t pid = ipcState->getCallingPid();
    uid_t uid = ipcState->getCallingUid();
    if (outPid) *outPid = pid;
    if (outUid) *outUid = uid;
    return checkPermission(permission, pid, uid);
}

权限接口的 owner 是当前 Binder 线程的 IPCThreadState,不是 getpid/getuid 系统调用。非入站事务时 Native getter 会回退到自身身份;服务若需要强制“必须由远端调用”,应先检查调用上下文而非直接信任返回值。

2. PermissionCache ​

源码文件:frameworks/native/libs/binder/PermissionCache.cpp

缓存 key 只有 permission name 和 UID;源码明确“不需要保存 pid,因为 permission check 实际不使用 pid”。root UID 和本进程 PID 直接放行,其余请求先查 mCache,命中则返回布尔值,未命中才调用 android::checkPermission(permission, pid, uid) 并写入缓存。

这意味着同一 UID 的不同进程共享缓存结果,权限变更后必须调用 PermissionCache::purgeCache,否则短时间内可能继续使用旧结果。

3. Controller路径 ​

源码文件:frameworks/native/libs/binder/IServiceManager.cpp

checkPermission 缓存一个 IPermissionController Binder 代理。controller 存在时调用 pc->checkPermission(permission, pid, uid);若返回 false,代码再检查 controller 是否存活,以区分真实拒绝与 controller 死亡。controller 不可用时循环等待 service manager 重新获取,避免把暂时服务重启误判成永久权限拒绝。

4. 失败日志 ​

若 controller 存活且返回 false,logPermissionFailure 为 true 时记录 permission、UID、PID;若 controller 死亡则释放代理、重新查找。排查“权限失败”时要确认日志是否来自真实 denial,还是 controller 不可用后的重试耗时。

5. PID与UID ​

权限缓存只按 UID 复用,但底层 android::checkPermission 仍接收 PID/UID;PID 可用于调试或特定策略,不能作为稳定身份。caller UID 来自 Binder 驱动 sender_euid,不由客户端在 Parcel 中自报;客户端传入一个“uid 参数”不能改变 checkCallingPermission 的 caller。

6. clear identity ​

服务线程调用 clearCallingIdentity 后,后续本地权限检查看到的是服务自身 UID/PID;恢复 token 后才回到原 caller。若服务要代表自己访问本地对象,这是正确用法;若忘记恢复,后续同一事务中的检查会绕过 caller 权限,形成授权漏洞。应使用 Java finally 或 Native RAII 约束清理。

7. Java边界 ​

Java framework 常直接使用 Context.checkPermission(permission, Binder.getCallingPid(), Binder.getCallingUid()) 或 PermissionEnforcer。这些 API 的 caller 参数仍来自 Binder 线程状态;checkCallingOrSelfPermission 则在没有远端 caller 时使用自身身份,不能与严格的 checkCallingPermission 混用。

8. 并发与锁 ​

PermissionCache 使用 Mutex 保护缓存数组;IServiceManager 的 controller 代理使用独立 mutex 快照。权限 RPC 不在 cache mutex 内执行,避免 controller Binder 调用重入时阻塞缓存锁;权限检查热点应观察 cache 命中率和 controller RPC 延迟,而不是只增加 Binder 线程。

9. 测试与边界 ​

源码文件:frameworks/native/libs/binder/tests/binderIPCThreadStateUnitTest.cpp

单元测试覆盖没有 service、calling UID 非特权组件时的判断;真实服务测试还验证 getCallingUid 与服务自身 euid 分离。权限缓存 root/self 快速放行路径则由 PermissionCache::checkPermission 的显式条件保证。

10. 排查顺序 ​

先确认当前代码是否在入站 Binder 线程;再记录 caller UID/PID、是否 clear identity;检查 PermissionCache 是否命中/是否 purge;最后检查 PermissionController 存活、重连和真实 checkPermission 结果。若 UID 正确但仍失败,再进入 package/permission 数据和后续 SELinux/AppOps 层,而不是修改 Binder caller 字段。

11. 阅读检查 ​

给定三种调用:远端 UID 10000、服务自身本地调用、服务 clearCallingIdentity 后的本地调用,分别指出 checkCallingPermission 的 PID/UID、缓存 key、controller 调用和最终结果来源。再说明为什么缓存不保存 PID,以及 controller 死亡时为何不能立即返回 denial。