Binder Token机制
“Binder token”至少有两种不同含义:Parcel interface token 是 descriptor 字符串校验;业务 Binder token 是一个具有内核身份和生命周期的 IBinder 对象。二者不能互相替代。
本文面向已读过 SELinux与Binder、Binder调用者身份 和 AIDL服务实战 的读者。“Binder token”在 Android 源码中不是单一机制:接口 token 是 Parcel 中的 descriptor 字符串;业务 token 通常是一个强 Binder 对象,服务端用对象身份和死亡通知关联会话。本文把两者分开,避免把字符串校验误称为不可伪造身份。
1. 接口Token
源码文件:frameworks/native/libs/binder/Parcel.cpp
status_t Parcel::writeInterfaceToken(const char16_t* str, size_t len) {
// kernel header: strict mode/work source/vendor header
return writeString16(str, len);
}Proxy 把接口 descriptor 写入 Parcel;Stub 通过 enforceInterface 读取并逐字比较。它能防止把错误接口的 transaction 误交给当前 Stub,但字符串来自调用者 Parcel,本身不是 caller 身份或权限凭证。
2. Binder对象Token
业务 token 通常由客户端创建本地 Binder/BBinder,通过 writeStrongBinder 传给服务。Parcel 记录 BINDER_TYPE_BINDER(本地对象)或 BINDER_TYPE_HANDLE(远端代理);驱动为目标进程建立 binder_ref/handle,接收端 readStrongBinder 得到指向同一 Binder 对象的代理,而不是可任意伪造的整数。
源码文件:frameworks/native/libs/binder/Parcel.cpp
writeStrongBinder/readStrongBinder 的对象转换由 Binder driver 完成;对象引用转移还受 SELinux binder transfer 权限约束(见 BD096)。服务端保存 sp<IBinder> 或 Java IBinder 后,可用对象相等性、handle 生命周期和 death recipient 关联会话。
3. 真实用法
源码文件:frameworks/native/libs/binder/IActivityManager.cpp
ActivityManager 的 observer API 把 IUidObserver Binder 传入注册请求,服务返回 observerToken,后续 add/remove 调用再次写入该 token。token 是服务分配/确认的 Binder 引用,用于匹配此前注册关系;调用者不能仅凭一个相同字符串替代它。
Java 服务中同样常见 IBinder token 作为 session/window/pending UI key。服务必须在注册时保存 token,并在后续调用用 equals/Binder identity 查找对应 session;token 的权限仍由 caller UID、Java permission 和 SELinux 独立决定。
4. 防伪造边界
Binder 对象 token 的不可伪造性来自驱动维护的 object/node/ref 关系:客户端可以创建新的 Binder 对象,但不能在 Parcel 中声明一个任意 handle 指向别人的 node;无效 handle、已死亡 node 或未授权 transfer 会被驱动拒绝。它不能阻止客户端把自己的合法 Binder 对象冒充“另一个会话”,所以服务必须绑定 token 与 caller UID、注册状态和业务 nonce/权限。
5. 生命周期
token 是强 Binder 引用,服务保存它会延长对应对象/进程关联;客户端进程死亡时,服务可通过 linkToDeath/death recipient 清理 session。服务只保存整数 hash 或字符串会丢失死亡通知能力,也可能产生 hash collision;应保存 IBinder 并在锁内维护映射。
6. 分层校验
enforceInterface 只验证 descriptor;checkCallingPermission 验证 caller UID 的 framework permission;SELinux Binder hook 验证 domain call/transfer;业务 token 验证“这个 Binder 对象是否是已注册 session”。四层都通过后,方法才同时满足协议、主体、域策略和会话状态。
7. 失败路径
- descriptor 不匹配:
enforceInterface返回 false,Stub 通常拒绝 transaction。 - 非 Binder/错误类型对象:
readStrongBinder返回失败或 null,服务应拒绝注册。 - token 未注册或属于另一用户/UID:业务层返回
PERMISSION_DENIED/IllegalArgumentException。 - Binder node 已死亡:后续调用
DEAD_OBJECT,death recipient 清理关联状态。 - SELinux transfer 被拒:驱动在对象进入目标 Parcel 前返回失败。
8. 检查顺序
服务收到 token 后先验证 Parcel/interface header,再读取 Binder 对象;随后用 caller UID/PID 做授权,最后在受保护的数据结构中查 token 是否已注册、是否属于当前用户和是否仍 alive。不要先把未经校验的 token 放进全局 map,也不要用 token 替代权限检查。
9. 测试边界
源码文件:frameworks/native/libs/binder/tests/binderParcelUnitTest.cpp
Parcel 单元测试写入多个强 Binder、null 和不同对象,再按顺序读取并断言对象身份保持一致;它验证对象序列化/反序列化,不证明业务服务会正确绑定 caller 权限。接口 token 测试则验证 descriptor 正确和错误字符串的 enforceInterface 结果。
10. 阅读检查
分别解释 descriptor 字符串 token、客户端自建 Binder token、服务返回 observer token 三者的 owner、验证方式、死亡路径和可伪造边界。再设计一个“合法 token 但无权限”和“有权限但未注册 token”的请求,指出它们分别在哪一层失败。
