Skip to content

SM成为context_manager

追踪 ServiceManager 通过 BINDER_SET_CONTEXT_MGR_EXT 注册 context manager、驱动保存 binder_node、handle 0 路由及进程退出清理。

基于android-17.0.0_r1
AndroidBinderServiceManagercontext manager源码阅读

SM成为context_manager ​

ServiceManager 能接收系统服务查询,并不是因为名字叫 servicemanager,而是因为它通过 Binder ioctl 成为某个 Binder 设备 context 的管理者。这个动作建立了一条跨进程约束:客户端把目标 handle 写成 0 时,驱动不从客户端的 refs_by_desc 查普通引用,而是读取该设备 binder_context 中保存的 binder_context_mgr_node。

本文面向已经读过 ServiceManager启动、Binder事务发送 和 Binder远程引用 的读者。前文解释了 main() 为什么调用 becomeContextManager();本文继续向下追踪这个调用如何变成 ioctl、驱动如何做安全和 UID 检查、为什么扩展命令失败会回退,以及 handle 0 在事务目标解析和进程退出时分别发生什么。本文不展开 addService() 的服务名校验和服务表更新,那是下一篇的独立问题。

读完后,读者应能从 ProcessState::becomeContextManager() 找到 ioctl 入口,判断一次注册失败属于“已经有人注册”“UID 不匹配”“SELinux 拒绝”还是“节点分配失败”,并沿 binder_transaction() 解释 handle 0 为什么只能指向当前 Binder device 的 context manager。

1. 角色边界 ​

1.1 三个对象 ​

这里有三个容易混淆的对象。ServiceManager 是用户态的 BBinder 本地对象;binder_node 是驱动为本地 Binder 对象维护的内核节点;binder_context_mgr_node 是每个 Binder context 保存的特殊 node 指针。前两个描述“谁能执行 onTransact”,后一个描述“handle 0 应该路由给谁”。

对象所有者读取者生效时机
ServiceManagerservicemanager 进程IPCThreadStatesetTheContextObject() 后收到本地分发
binder_nodeBinder 驱动事务路由、引用管理binder_new_node() 成功后
binder_context_mgr_node某个 Binder device 的 binder_contextbinder_transaction()ioctl 注册成功后

1.2 设备隔离 ​

源码文件:kernel/common/drivers/android/binder_internal.h

相关类型:binder_context、binder_device

c
struct binder_context {
    struct binder_node *binder_context_mgr_node;
    struct mutex context_mgr_node_lock;
    kuid_t binder_context_mgr_uid;
    const char *name;
};

struct binder_device {
    struct hlist_node hlist;
    struct miscdevice miscdev;
    struct binder_context context;
    struct inode *binderfs_inode;
    refcount_t ref;
};

binder_context 嵌在 binder_device 中,所以 context manager 不是跨所有 Binder 设备的单例。/dev/binder、/dev/hwbinder 等设备各自拥有 context、锁和 UID 记录;本文讨论的 servicemanager 默认打开 /dev/binder。因此,“handle 0 指向 ServiceManager”必须补上设备前提,不能写成所有 Binder 设备都共享一个全局服务管理器。

2. 用户注册 ​

2.1 扩展命令 ​

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

相关函数:ProcessState::becomeContextManager()

cpp
bool ProcessState::becomeContextManager()
{
    std::unique_lock<std::mutex> _l(mLock);

    flat_binder_object obj {
        .flags = FLAT_BINDER_FLAG_TXN_SECURITY_CTX,
    };

    int result = ioctl(mDriverFD, BINDER_SET_CONTEXT_MGR_EXT, &obj);

    // fallback to original method
    if (result != 0) {
        android_errorWriteLog(0x534e4554, "121035042");
        int unused = 0;
        result = ioctl(mDriverFD, BINDER_SET_CONTEXT_MGR, &unused);
    }

    if (result == -1) {
        ALOGE("Binder ioctl to become context manager failed: %s\\n", strerror(errno));
    }
    return result == 0;
}

扩展命令传入一个只设置 flags 的 flat_binder_object。这里的 flags 请求事务带上 security context;它不是把 ServiceManager 地址传给驱动。驱动通过当前打开的 file 找到 binder_proc,再由 binder_new_node(proc, fbo) 建立或复用本地 node。扩展命令失败后,用户态无条件尝试旧命令;因此“扩展命令失败”本身不等于注册失败,只有两次 ioctl 都没有返回 0,main() 才会把它当作 fatal 条件。

2.2 调用方顺序 ​

源码文件:frameworks/native/cmds/servicemanager/main.cpp

相关函数:main()

cpp
IPCThreadState::self()->setTheContextObject(manager);
if (!ps->becomeContextManager()) {
    LOG(FATAL) << "Could not become context manager";
}

setTheContextObject() 和 ioctl 是两个方向的准备。前者把收到的 context-manager transaction 交给本进程的 manager 对象;后者把驱动的全局目标指针设为该进程的 node。前者不改变 handle 0 路由,后者也不替代用户态对象分发。启动顺序把它们连续放置,是因为二者共同构成“驱动找到进程后,线程能找到本地对象”的完整链路。

图中 FD 不是独立的服务对象,而是用户态 ioctl 与驱动 binder_ioctl 之间的边界。binder_context 只有在所有检查和 node 创建成功后才写入特殊指针。

3. 驱动校验 ​

3.1 命令分派 ​

源码文件:kernel/common/drivers/android/binder.c

相关函数:binder_ioctl()

c
case BINDER_SET_CONTEXT_MGR_EXT: {
    struct flat_binder_object fbo;
    if (copy_from_user(&fbo, ubuf, sizeof(fbo))) {
        ret = -EFAULT;
        break;
    }
    ret = binder_ioctl_set_ctx_mgr(filp, &fbo);
    break;
}
case BINDER_SET_CONTEXT_MGR:
    ret = binder_ioctl_set_ctx_mgr(filp, NULL);
    break;

扩展命令先复制用户态结构体,原始命令不携带结构体,最终都进入同一个核心函数。filp->private_data 是驱动在打开设备时关联的 binder_proc,因此注册目标由调用者打开的设备决定,而不是由命令参数决定。

3.2 单次占用 ​

源码文件:kernel/common/drivers/android/binder.c

相关函数:binder_ioctl_set_ctx_mgr()

c
static int binder_ioctl_set_ctx_mgr(struct file *filp,
                                    struct flat_binder_object *fbo)
{
    struct binder_proc *proc = filp->private_data;
    struct binder_context *context = proc->context;
    kuid_t curr_euid = current_euid();

    guard(mutex)(&context->context_mgr_node_lock);
    if (context->binder_context_mgr_node) {
        pr_err("BINDER_SET_CONTEXT_MGR already set\\n");
        return -EBUSY;
    }
    ...
}

锁保护的是“是否已经占用”这一 context 级状态。第一次调用看到空指针,才会继续安全和 UID 检查;第二个并发或后续调用直接返回 -EBUSY。这不是用户态 ServiceManager 服务表的重复名称错误,而是驱动角色本身只能有一个当前 node。

3.3 权限与 UID ​

c
ret = security_binder_set_context_mgr(proc->cred);
if (ret < 0)
    return ret;
if (uid_valid(context->binder_context_mgr_uid)) {
    if (!uid_eq(context->binder_context_mgr_uid, curr_euid)) {
        pr_err("BINDER_SET_CONTEXT_MGR bad uid ...\\n");
        return -EPERM;
    }
} else {
    context->binder_context_mgr_uid = curr_euid;
}

安全检查先于 UID 记录。首次成功注册把当前有效 UID 写入 context;之后即使特殊 node 已被清理,只要 context 中 UID 仍有效,新的注册者也必须匹配这个 UID。源码没有把 UID 规则表达为“任何 root 都可以替换”,也没有在该函数中直接实现 SELinux 策略;security_binder_set_context_mgr() 把权限判断交给 LSM。

4. 节点保存 ​

4.1 创建节点 ​

源码文件:kernel/common/drivers/android/binder.c

相关函数:binder_ioctl_set_ctx_mgr()、binder_new_node()

c
new_node = binder_new_node(proc, fbo);
if (!new_node)
    return -ENOMEM;
binder_node_lock(new_node);
new_node->local_weak_refs++;
new_node->local_strong_refs++;
new_node->has_strong_ref = 1;
new_node->has_weak_ref = 1;
context->binder_context_mgr_node = new_node;
binder_node_unlock(new_node);
binder_put_node(new_node);

binder_new_node() 失败时特殊指针保持为空,用户态得到失败返回;成功后,驱动先给 node 建立本地强、弱引用状态,再写入 context。最后的 binder_put_node() 只释放本次临时获取的 node 引用,不会把刚保存的 context 指针立即变成悬空指针,因为 node 的本地引用和 context 所属进程生命周期仍然存在。

4.2 引用描述符 ​

源码文件:kernel/common/drivers/android/binder.c

相关函数:get_ref_desc_olocked()

c
/* 0 is reserved for the context manager */
offset = (node == proc->context->binder_context_mgr_node) ? 0 : 1;

当客户端为某个 node 分配 handle 时,驱动把 context manager node 的搜索起点设为 0;普通 node 从 1 开始。这段代码解释了“handle 0 是保留值”的实现边界:它不是客户端自行约定的 service name,也不是 manager 字符串的别名,而是驱动引用分配对特殊 node 的保留槽位。

5. 事务路由 ​

5.1 普通句柄 ​

源码文件:kernel/common/drivers/android/binder.c

相关函数:binder_transaction()

c
if (tr->target.handle) {
    struct binder_ref *ref;
    binder_proc_lock(proc);
    ref = binder_get_ref_olocked(proc, tr->target.handle, true);
    if (ref) {
        target_node = binder_get_node_refs_for_txn(
                ref->node, &target_proc, &return_error);
    } else {
        return_error = BR_FAILED_REPLY;
    }
    binder_proc_unlock(proc);
}

非零 handle 必须在发送方自己的引用表中找到 binder_ref。因此普通服务调用依赖先前已经建立的引用,错误 handle 会进入 failed reply,而不会退回 context manager。

5.2 零句柄 ​

c
else {
    mutex_lock(&context->context_mgr_node_lock);
    target_node = context->binder_context_mgr_node;
    if (target_node)
        target_node = binder_get_node_refs_for_txn(
                target_node, &target_proc, &return_error);
    else
        return_error = BR_DEAD_REPLY;
    mutex_unlock(&context->context_mgr_node_lock);
}

零句柄分支完全绕过发送方的普通 binder_ref 查找,改为锁住当前设备的 context 并读取特殊 node。node 为空时返回 BR_DEAD_REPLY,所以“ServiceManager 没有注册某个服务”和“Binder context manager 已死亡”是两种不同故障:前者发生在 ServiceManager 的服务表逻辑,后者发生在事务目标解析之前。

5.3 自调用保护 ​

c
if (target_node && target_proc->pid == proc->pid) {
    binder_user_error("got transaction to context manager from process owning it\\n");
    return_error = BR_FAILED_REPLY;
    return_error_param = -EINVAL;
    goto err_invalid_target_handle;
}

驱动拒绝 context manager 进程自己通过目标 0 发起的事务。这个分支不能推导出“ServiceManager 不能调用任何 Binder 服务”;它只限制目标解析为自身 context-manager node 的这一路径,普通非零 handle 仍按常规引用规则处理。

6. 失败清理 ​

6.1 注册失败 ​

分支驱动结果binder_context_mgr_node用户态后果
已有 node-EBUSY保持原值becomeContextManager() 返回 false
SELinux 拒绝LSM 错误码保持空值进入 fatal
UID 不匹配-EPERM保持空值或原值进入 fatal
node 分配失败-ENOMEM保持空值进入 fatal
成功0指向新 node继续建立 Looper

失败路径的共同点是写入特殊指针发生在所有前置检查和 node 创建之后。不会出现“UID 检查失败但 context 已指向半成品 node”的中间状态。

6.2 进程退出 ​

源码文件:kernel/common/drivers/android/binder.c

相关函数:binder_deferred_release()

c
mutex_lock(&context->context_mgr_node_lock);
if (context->binder_context_mgr_node &&
    context->binder_context_mgr_node->proc == proc) {
    binder_debug(BINDER_DEBUG_DEAD_BINDER,
                 "%s: %d context_mgr_node gone\\n",
                 __func__, proc->pid);
    context->binder_context_mgr_node = NULL;
}
mutex_unlock(&context->context_mgr_node_lock);

进程释放时,驱动按所属 proc 判断是否清空 context manager node。清空后,后续目标 0 的事务会走 BR_DEAD_REPLY,而不是继续访问已经死亡的进程。这里没有把 binder_context_mgr_uid 重置为无效值,因此“node 被清空”与“下一进程可以用任意 UID 接管”不能互相推断。

状态图中的 DeadReply 不是 binder_context_mgr_node 的持久状态,而是 node 为空时某次事务得到的结果;这样画是为了区分“注册状态”与“请求观察到的错误”。

7. 读源码 ​

7.1 命令搜索 ​

读者可以在 AOSP 源码树中运行:

bash
rg -n "becomeContextManager|BINDER_SET_CONTEXT_MGR(_EXT)?" \
  frameworks/native/libs/binder/ProcessState.cpp \
  kernel/common/include/uapi/linux/android/binder.h \
  kernel/common/drivers/android/binder.c

rg -n "binder_ioctl_set_ctx_mgr|binder_context_mgr_node|binder_deferred_release" \
  kernel/common/drivers/android/binder.c \
  kernel/common/drivers/android/binder_internal.h

rg -n "target.handle|context_mgr_node|BR_DEAD_REPLY" \
  kernel/common/drivers/android/binder.c

第一组命令应能把用户态调用、UAPI 命令码和驱动分派连起来;第二组定位注册与清理 owner;第三组定位普通 handle 和 0 handle 的分叉。

7.2 判断练习 ​

假设 becomeContextManager() 返回 false,先按返回码和日志区分 -EBUSY、-EPERM、LSM 错误和 -ENOMEM,不要直接把所有失败归结为“Binder 没打开”。假设一个客户端得到 BR_DEAD_REPLY,应检查目标设备的 binder_context_mgr_node 是否已在 binder_deferred_release() 中清空;若 node 仍存在,则问题不在 context manager 进程退出路径,而应继续追踪事务目标和后续交付。

再反向复述这条主线:servicemanager 打开哪个设备,ProcessState 发出哪个 ioctl,驱动把哪个 node 写入哪个 context 字段,客户端的 0 handle 在哪里读取该字段,以及 node 为空时返回什么。能够沿这五个问题定位源码,就不会把 manager 服务名、用户态 context object 和驱动 handle 0 混成同一个状态。