Binder 安全:调用方身份与权限检查位置
2026/8/9大约 3 分钟权限与安全Android 14Binder安全getCallingUid
Binder 安全:调用方身份与权限检查位置
上一章看了身份背书。这一章回答:系统服务如何知道“谁在调用我”,权限检查在跨进程 调用中放在哪,为什么不能只信参数?
服务端必须验证真实调用方
| 常见误解 | 正确理解 |
|---|---|
| 服务端收到参数就能信 | 参数可能被伪造,必须校验调用方 UID/PID |
| getCallingUid 是应用传的 | 它由 Binder 内核层记录,客户端无法伪造 |
| 权限检查在客户端做 | 关键检查在服务端,按调用方身份判定 |
核心结论
- Binder 事务由内核携带调用方 UID/PID,服务端通过 getCallingUid/Pid 读取。
- 权限检查发生在服务端入口,依据是调用方身份。
clearCallingIdentity()临时清除身份用于内部调用,必须成对恢复。- SELinux 在内核层对 binder 事务做额外校验(source/target domain)。
- 设计安全服务的原则:入口校验身份,参数永远不可信。
身份校验主链
逐步解释:
- 事务进入内核:驱动记录发送方 UID/PID。
- 服务端取身份:onTransact 里读取调用方身份。
- 权限检查:按身份判定是否有权调用。
- SELinux 校验:内核再按 domain 规则确认。
- 放行/拒绝:任一环节不过都拒绝。
clearCallingIdentity 为什么危险
服务端有时要“以自己身份”调用其他服务(如 PMS 查询),此时临时清除调用方身份。 如果忘记恢复,后续权限检查会误判身份,造成越权或错乱。原则是:用完立即恢复, 绝不跨异步边界携带。
源码证据
阅读目标:确认身份获取与清除入口。
正文指针:Binder.java getCallingUid()/clearCallingIdentity(); BinderInternal.java 的内部实现。
// Binder.java(伪代码,行号以 r75 为准)
public static final int getCallingUid() {
// 读取当前事务的调用方 UID(内核记录)
}
public static final long clearCallingIdentity() { ... }
public static final void restoreCallingIdentity(long token) { ... }这段代码证明: 身份由内核事务携带,Java 层只负责读取与临时修改;这也解释了 为什么客户端无法伪造 getCallingUid。
验证与排障
环境:开发设备或模拟器;部分日志需要 root。
adb shell dumpsys activity service <服务名>
adb logcat -v threadtime | grep -E "SecurityException|Permission Denial"
adb shell su 0 dmesg | grep -iE "binder|avc"服务端拒绝通常抛 SecurityException 或记录 Permission Denial;先看拒绝日志中的 调用方 UID,再对照权限与 SELinux 规则。参数伪造类问题则要检查服务端是否校验身份。
常见误区
- “参数能证明调用者”:身份看内核记录,不信任参数。
- “客户端检查权限就够”:关键检查在服务端。
- “clearCallingIdentity 随时可用”:必须成对恢复。
- “Binder 安全只有 UID”:还有 SELinux 与权限模型叠加。
延伸问题
- getCallingUid 与 getCallingPid 各在什么场景使用?
- 异步回调里为什么不能用调用方身份?
- SELinux 的 binder 规则如何映射服务与调用方?
- 如何给自定义服务设计安全的权限校验?
源码入口
| 文件 | 关键位置 | 作用 |
|---|---|---|
Binder.java | getCallingUid() | 身份读取 |
Binder.java | clearCallingIdentity() | 身份临时清除 |
BinderInternal.java | 内部实现 | 身份管理 |
| 内核 binder 驱动 | 事务元数据 | UID/PID 记录 |
公共路径:frameworks/base/core/java/android/os/ 与内核 drivers/android/。 行号以 r75 检索为准。
遇到拒绝时的分层检查方法,见 安全问题排查。