Skip to content

clearCallingIdentity

追踪 clearCallingIdentity 的 token 编码、线程身份清除、SID 丢失、JNI 桥接与异常安全恢复。

基于android-17.0.0_r1
AndroidBinder权限

clearCallingIdentity ​

clearCallingIdentity 修改的是当前 IPCThreadState 保存的调用身份,并返回只能在同一线程按配对顺序恢复的 opaque token。它不会修改驱动中的原始 transaction sender,也不会撤销 SELinux 检查结果。

本文承接 getCallingUid与getCallingPid 和 checkCallingPermission。clearCallingIdentity 不是修改 Linux 进程 UID,也不是全局权限提升;它只替换当前 Binder 线程的 caller 状态,使后续本地调用按服务自身身份执行,并返回一个只能用于恢复的 token。本文追踪 token 位布局、JNI、Java finally 和嵌套身份边界。

1. 清除动作 ​

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

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;
}

调用先把当前 UID/PID 和显式标志打包,再 clearCaller:PID 变为 getpid(),UID optional 清空,SID 置空。mHasExplicitIdentity=true 让后续 getCallingUidOrThrow 知道这是显式清除后的合法上下文。

2. Token布局 ​

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

token 为 64 位:高 32 位保存 caller UID,低 32 位保存 caller PID,并借用 PID 未使用的第 30 位保存 hasExplicitIdentity。unpackCallingPid 特别处理负 PID(如 -1),因此 token 不能被当作普通 UID/PID 拼接字符串或自行修改。

3. JNI桥接 ​

源码文件:frameworks/base/core/jni/android_util_Binder.cpp

cpp
static jlong android_os_Binder_clearCallingIdentity(CRITICAL_JNI_PARAMS) {
    return IPCThreadState::self()->clearCallingIdentity();
}
static void android_os_Binder_restoreCallingIdentity(..., jlong token) {
    IPCThreadState::self()->restoreCallingIdentity(token);
}

Java Binder.clearCallingIdentity/restoreCallingIdentity 是 CriticalNative 直通,没有 Java 层 token 对象或全局栈;token 的有效性和嵌套顺序由调用者维护。

4. 恢复动作 ​

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

cpp
void IPCThreadState::restoreCallingIdentity(int64_t token) {
    mCallingUid = unpackCallingUid(token);
    mCallingSid = nullptr;
    mCallingPid = unpackCallingPid(token);
    mHasExplicitIdentity = unpackHasExplicitIdentity(token);
}

恢复只还原 UID、PID 和显式标志;SID 永远置空,因为 token 没有携带 SID。恢复后再次进行 SELinux caller SID 相关逻辑不能假设拿回了原 SID,应让新的 Binder 事务重新提供上下文。

5. 异常安全 ​

源码文件:frameworks/base/core/java/android/os/Binder.java

withCleanCallingIdentity 在 try 执行动作、finally 恢复 token,并在恢复后重新抛出原异常。PowerManager 的 callback listener 也采用同样模式:先清除 caller,再调度 app executor,最后恢复。恢复必须位于 finally,而不能只写在正常返回分支。

6. 嵌套清除 ​

若外层 caller A 进入服务,第一次 clear 得 token T1;随后服务又在清除状态下进入另一个 Binder 回调,再次 clear 得 T2。恢复必须按 T2→T1 的嵌套顺序,否则会把外层显式身份或 caller 状态覆盖。token 没有防篡改校验,跨线程传递或乱序恢复属于调用者 bug。

7. 本地调用边界 ​

清除身份的典型用途是服务在处理远端请求时调用本进程内的另一个 Binder 对象,让本地对象看到服务自身 UID,而不是原始 caller。对于真正跨进程的下一次调用,驱动会使用当前服务进程的 sender_euid;clear 不会伪造远端身份,也不会改变 Linux credential。

8. 权限影响 ​

clear 后 checkCallingPermission 读取的是自身 UID/PID,PermissionCache 也会以自身 UID 查缓存。若代码意图继续代表 caller 执行权限操作,不能在中间 clear;若意图代表系统服务执行,则必须把清除范围限制在最小临界区。

9. 线程与生命周期 ​

身份字段属于 IPCThreadState 线程局部状态。把 token 存到成员变量、跨线程传给 Handler 或异步 callback,会让恢复发生在错误线程;正确做法是在 Binder 回调线程内清除、调度已准备好的数据,再 finally 恢复。

10. 错误边界 ​

  • 非 Binder 上下文调用 clear:token 保存自身身份,restore 后仍是自身;它不会制造 caller。
  • token 被修改:UID/PID 可能变成任意值,Native 没有完整性校验。
  • 忘记 restore:当前线程后续本地权限检查持续使用自身身份,直到下一次入站事务覆盖。
  • restore 后读取 SID:SID 为 null,不能当作原调用者 SELinux 上下文。

11. 阅读检查 ​

画出 caller A→服务线程→本地对象的身份变化:clear 前、clear 后、restore 后分别是哪些 UID/PID/SID;再加入异常和嵌套 clear,指出 token 保存/恢复顺序以及为什么必须使用 finally。