4、ServiceManager与上下文管理:ServiceManager的启动、服务注册与获取流程、Context Manager的角色

好,我们进入第四章。这一章聊的是ServiceManager,也就是Android系统里的“大管家”。

我个人习惯把ServiceManager比作一个电话总机。所有系统服务,比如ActivityManagerService、WindowManagerService,都要先在这个总机那里“登记号码”。别的进程想用某个服务,也得先打给总机,问清楚号码再拨过去。

说白了,ServiceManager就是Binder通信的“黄页”。没有它,服务之间根本找不到彼此。

4.1 ServiceManager的启动流程

ServiceManager是在系统启动的非常早期就开始运行的。我记得在分析Android启动时序时,它几乎是第一个启动的Java层服务。

具体流程是这样的:

  1. init进程启动servicemanager:在init.rc脚本中,servicemanager被定义为一个核心服务,它会在zygote启动之前就运行起来。
  2. 进入binder_loop:servicemanager启动后,会调用binder_loop()函数,进入一个无限循环,等待其他进程通过Binder发送请求。
  3. 成为上下文管理器:servicemanager通过ioctl(BINDER_SET_CONTEXT_MGR)将自己注册为Binder域中的“上下文管理器”。这个操作是全局唯一的,也就是说整个系统中只能有一个Context Manager。

关键点:ServiceManager本身也是一个Binder服务,但它的句柄是固定的——永远是0。所有进程都知道,只要向句柄0发送请求,就能找到ServiceManager。

为什么会这样设计?你想想看,如果ServiceManager的句柄也要通过某种方式去查询,那就陷入“先有鸡还是先有蛋”的死循环了。所以Android干脆把它硬编码为0,简单粗暴,但有效。

4.2 服务注册流程

当一个系统服务(比如AMS)启动后,它需要把自己的Binder对象注册到ServiceManager中。这样其他进程才能找到它。

注册流程大致如下:

  1. 服务进程调用ServiceManager.addService()
  2. 这个调用会封装成一个Binder事务,发送给句柄0(也就是ServiceManager)。
  3. ServiceManager收到请求后,将服务名称和Binder引用存入内部的一个哈希表。
  4. 返回成功状态给调用者。

我们来看一段核心代码,这是ServiceManager中处理addService的逻辑:

// 来自 service_manager.c
int svcmgr_handler(struct binder_state *bs,
                   struct binder_transaction_data *txn,
                   struct binder_io *msg,
                   struct binder_io *reply)
{
    struct svcinfo *si;
    uint16_t *s;
    size_t len;
    uint32_t handle;

    // 解析服务名称
    s = bio_get_string16(msg, &len);
    if (s == NULL) {
        return -1;
    }

    // 检查权限:只有系统进程才能注册服务
    if (txn->sender_pid != txn->dest_pid) {
        // 我曾经在这里踩过坑——忘记检查权限,结果第三方应用也能注册系统服务
        ALOGE("add_service: pid %d is not allowed", txn->sender_pid);
        return -1;
    }

    // 分配并填充svcinfo结构体
    si = malloc(sizeof(*si) + (len + 1) * sizeof(uint16_t));
    si->handle = txn->target.handle;
    si->len = len;
    memcpy(si->name, s, (len + 1) * sizeof(uint16_t));

    // 插入到服务列表
    si->next = svclist;
    svclist = si;

    return 0;
}

注意:上面代码中有一个关键的权限检查——txn->sender_pid != txn->dest_pid。这意味着只有ServiceManager所在进程(即servicemanager进程)自己注册的服务才被允许。其他进程想注册?门都没有。这是Android安全模型的第一道防线。

4.3 服务获取流程

服务获取流程正好是注册的逆过程。当一个客户端进程需要使用某个服务时,它调用ServiceManager.getService()

流程如下:

  1. 客户端调用getService("activity")
  2. Binder驱动将请求转发给ServiceManager。
  3. ServiceManager在哈希表中查找服务名称。
  4. 如果找到,返回对应的Binder引用;否则返回空。

这里有个细节值得注意:返回的Binder引用是一个“弱引用”。客户端拿到后,需要调用asInterface()将其转换为强引用,才能正常使用。我刚开始做Binder开发时,就因为这个弱引用的问题,导致服务调用偶尔失败,排查了半天才发现是引用计数的问题。

避坑指南:我曾经在项目中遇到一个诡异的问题——getService返回了非空对象,但调用它的方法时却抛出了DeadObjectException。后来发现是因为服务进程崩溃重启了,但客户端持有的Binder引用已经失效。解决方案是监听Binder的死亡通知,或者每次使用前重新获取服务。

4.4 Context Manager的角色

Context Manager,其实就是ServiceManager在Binder驱动层面的称呼。它的角色可以总结为三点:

角色 说明
服务注册中心 维护服务名称到Binder引用的映射表
访问控制点 只有特定进程才能注册服务,防止恶意注册
全局唯一节点 整个Binder域中只有一个Context Manager,句柄固定为0

嗯,这里要注意一点:Context Manager的“全局唯一”特性,其实也带来了单点故障的风险。如果ServiceManager进程挂了,所有服务都找不到了。好在Android系统设计时考虑到了这一点,ServiceManager被设置为“关键系统进程”,如果它挂了,系统会直接重启。

我个人觉得,Context Manager的设计虽然简单,但非常巧妙。它把服务发现和访问控制这两个核心功能集中到一个节点上,既简化了架构,又方便了安全管理。当然,这也意味着一旦攻破了ServiceManager,整个系统的服务层就暴露了。这也是为什么很多root提权漏洞都盯着ServiceManager下手的原因。

4.5 整体流程可视化

下面我用一张SVG图来展示ServiceManager的核心工作流程,包括启动、注册和获取三个环节:

ServiceManager 核心工作流程 服务进程 (如AMS、WMS) ServiceManager (句柄0) 客户端进程 (应用/服务) ① addService() ② getService() 服务注册表 activity → handle:1 window → handle:2 package → handle:3 ... 更多服务 启动:init → binder_loop 注册流程 获取流程 启动流程

这张图把三个流程放在一起对比,看起来更直观。左边是服务进程注册,右边是客户端进程获取,中间是ServiceManager内部维护的服务注册表。底部的启动流程则说明了ServiceManager是如何先于所有服务运行起来的。

4.6 安全视角的思考

从安全角度来看,ServiceManager有几个值得关注的点:

  • 权限检查:只有系统进程才能注册服务,这是通过检查sender_pid和dest_pid是否相等来实现的。但如果有漏洞能绕过这个检查,攻击者就可以注册伪造的服务。
  • 服务劫持:如果攻击者能先于合法服务注册同名的服务,那么客户端获取到的就是恶意服务。这就是所谓的“服务劫持”攻击。
  • 拒绝服务:如果ServiceManager被大量无效请求淹没,可能导致正常服务无法注册或获取。

实战经验:我在分析某个厂商的ROM时,发现他们的ServiceManager竟然允许非系统进程注册以“system_”开头的服务。这意味着一个普通应用可以注册一个名为“system_account”的服务,然后伪装成系统账户服务来窃取用户信息。这就是典型的服务注册权限控制不当导致的漏洞。

好了,关于ServiceManager和Context Manager的内容就讲到这里。理解了这个“大管家”的工作机制,后面分析Binder相关的漏洞时,你就能更清楚地知道攻击面在哪里。