Binder调用者身份
调用者身份不是 Java Binder 临时查询系统服务得到的值,而是驱动把事务 sender 字段交给接收线程,libbinder 在分发前保存到 IPCThreadState,Java/JNI getter 再读取同一线程状态。下面的图固定这条字段流转主线。
本文承接 Binder同步调用 和 Binder嵌套调用。getCallingUid() 读取的不是当前线程的 Linux uid,而是当前 Binder 入站事务携带的 sender identity;没有入站事务时才回退到进程自身身份。本文追踪 PID/UID/SID 的来源、线程状态覆盖与恢复、clearCallingIdentity 的显式身份,以及 oneway/死亡等边界。
1. 驱动字段
源码文件:kernel/common/drivers/android/binder.c
创建事务时保存发送进程的 euid:
t->sender_euid = task_euid(proc->tsk);向目标线程输出 BR_TRANSACTION 时填入:
trd->sender_euid = from_kuid(current_user_ns(), t->sender_euid);
t_from = binder_get_txn_from(t);
trd->sender_pid = t_from
? task_tgid_nr_ns(t_from->proc->tsk, task_active_pid_ns(current))
: 0;PID 通过原始事务的发送线程链计算;找不到来源(例如某些异步/回复路径)时为 0。UID 是事务创建时捕获的 euid,不会因发送进程之后 seteuid 改变而重新读取。
2. Native状态
源码文件:frameworks/native/libs/binder/IPCThreadState.cpp
处理 BR_TRANSACTION 前,当前线程保存旧身份,然后覆盖:
const pid_t origPid = mCallingPid;
const auto origUid = mCallingUid;
const bool origHasExplicitIdentity = mHasExplicitIdentity;
mCallingPid = tr.sender_pid;
mCallingSid = reinterpret_cast<const char*>(tr_secctx.secctx);
mCallingUid = tr.sender_euid;
mHasExplicitIdentity = false;服务 Stub 在这段 doTransactBinder 调用期间读取到入站调用者。事务结束后,Native 恢复 orig PID、UID、SID、显式身份、work source 和 strict mode;同一线程随后处理下一事务时不会泄漏前一个调用者。
3. 查询入口
源码文件:frameworks/native/libs/binder/IPCThreadState.cpp、frameworks/base/core/jni/android_util_Binder.cpp
pid_t IPCThreadState::getCallingPid() const {
checkContextIsBinderForUse(__func__);
return mCallingPid;
}
uid_t IPCThreadState::getCallingUid() const {
checkContextIsBinderForUse(__func__);
return mCallingUid.has_value() ? mCallingUid.value() : getuid();
}Java Binder.getCallingPid/getCallingUid 通过 JNI 直接转发到同一 IPCThreadState。因此 Java 服务和 Native 服务读取的是同一份驱动元数据,而不是 Java 层自行解析 Parcel 字段。
4. Java语义
源码文件:frameworks/base/core/java/android/os/Binder.java
Java 文档明确:当前线程不在入站事务中时,PID/UID 返回自身身份;oneway 不接收 PID,可能得到 0;PID 只适合调试,不能作为安全标识,因为可复用。权限检查应优先基于 UID、权限和 AppOps,而不是 PID。
getCallingUidOrThrow 进一步要求线程正在处理事务,或身份已经被 clearCallingIdentity 显式设置;这能把误在普通线程调用的安全逻辑变成显式异常。
5. 清除身份
源码文件:frameworks/native/libs/binder/IPCThreadState.cpp
int64_t IPCThreadState::clearCallingIdentity() {
uid_t callingUid = mCallingUid.has_value() ? mCallingUid.value() : getuid();
int64_t token = packCallingIdentity(mHasExplicitIdentity, callingUid, mCallingPid);
clearCaller();
mHasExplicitIdentity = true;
return token;
}clearCaller 把 PID 设为自身、清空 UID optional;token 保存原 UID/PID 和显式标志。restoreCallingIdentity 解包 token 恢复这些值,但 SID 无法从 token 恢复而被置空。服务在代表自身访问本地对象或系统资源时应使用 try/finally(Java)或 RAII 约束恢复。
6. 嵌套事务
当服务线程在处理 A 的事务期间同步调用 B,Native 线程会暂时离开当前 doTransactBinder 进入等待;收到 B 的回调时,mCallingPid/mCallingUid 被新的入站事务覆盖。内层结束后恢复外层身份。若业务代码在嵌套调用前 clearCallingIdentity,恢复顺序必须与调用层级一致,否则会把外层调用者错误地当成本进程。
7. oneway边界
驱动对 oneway 事务不保存 t->from,输出的 sender_pid 通常为 0;Java 文档因此警告即使接口声明同步,恶意或错误客户端也可能以异步方式发送。服务不能用 getCallingPid()==0 单独判断“系统调用”,应结合 UID、权限和接口 flags。
8. SID和上下文
启用安全上下文时,驱动把事务 security context 作为 BR_TRANSACTION_SEC_CTX 附加数据,Native 设置 mCallingSid。getCallingSid 仅在 Binder 处理上下文可用;清除身份会把 SID 置空,restore token 也不会恢复它。SELinux 判断应使用内核提供的 SID/策略接口,而不是解析 PID。
9. 测试边界
源码文件:frameworks/native/libs/binder/tests/binderLibTest.cpp
测试服务在入站 onTransact 中比较 getCallingUid() 与 getuid(),并覆盖 seteuid(1000) 后当前进程 euid 改变的场景;事务携带的 caller UID 不随服务自身 euid 改变,验证 caller identity 与本地身份分离。
源码文件:frameworks/base/core/java/android/os/Binder.java
clearCallingIdentity/restoreCallingIdentity 的公开契约要求 token 成对使用;PowerManager、Looper 等真实 framework 调用都在 finally 中恢复,说明身份覆盖是线程局部的临界区,不是全局权限切换。
10. 调试路径
遇到权限误判时,先确认代码运行在线程的 doTransactBinder 上,再看驱动 sender_euid/sender_pid、Native mCallingUid/mCallingPid 是否被 clear/restore 覆盖,最后检查 oneway、嵌套回调和进程死亡路径。不要只打印 Process.myUid() 或 getuid(),那只能说明服务自身身份。
11. 阅读检查
从驱动写入 sender_euid 到 Java Binder.getCallingUid() 画出字段流;再给出“服务 euid 改变但 caller UID 不变”“oneway caller PID 为 0”“clear 后本地调用”的三种结果,并指出每种结果对应的源码分支。
