4、ServiceManager与上下文管理:ServiceManager的启动、服务注册与获取流程、Context Manager的角色
好,我们进入第四章。这一章聊的是ServiceManager,也就是Android系统里的“大管家”。
我个人习惯把ServiceManager比作一个电话总机。所有系统服务,比如ActivityManagerService、WindowManagerService,都要先在这个总机那里“登记号码”。别的进程想用某个服务,也得先打给总机,问清楚号码再拨过去。
说白了,ServiceManager就是Binder通信的“黄页”。没有它,服务之间根本找不到彼此。
4.1 ServiceManager的启动流程
ServiceManager是在系统启动的非常早期就开始运行的。我记得在分析Android启动时序时,它几乎是第一个启动的Java层服务。
具体流程是这样的:
- init进程启动servicemanager:在init.rc脚本中,servicemanager被定义为一个核心服务,它会在zygote启动之前就运行起来。
- 进入binder_loop:servicemanager启动后,会调用binder_loop()函数,进入一个无限循环,等待其他进程通过Binder发送请求。
- 成为上下文管理器:servicemanager通过ioctl(BINDER_SET_CONTEXT_MGR)将自己注册为Binder域中的“上下文管理器”。这个操作是全局唯一的,也就是说整个系统中只能有一个Context Manager。
关键点:ServiceManager本身也是一个Binder服务,但它的句柄是固定的——永远是0。所有进程都知道,只要向句柄0发送请求,就能找到ServiceManager。
为什么会这样设计?你想想看,如果ServiceManager的句柄也要通过某种方式去查询,那就陷入“先有鸡还是先有蛋”的死循环了。所以Android干脆把它硬编码为0,简单粗暴,但有效。
4.2 服务注册流程
当一个系统服务(比如AMS)启动后,它需要把自己的Binder对象注册到ServiceManager中。这样其他进程才能找到它。
注册流程大致如下:
- 服务进程调用
ServiceManager.addService()。 - 这个调用会封装成一个Binder事务,发送给句柄0(也就是ServiceManager)。
- ServiceManager收到请求后,将服务名称和Binder引用存入内部的一个哈希表。
- 返回成功状态给调用者。
我们来看一段核心代码,这是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()。
流程如下:
- 客户端调用
getService("activity")。 - Binder驱动将请求转发给ServiceManager。
- ServiceManager在哈希表中查找服务名称。
- 如果找到,返回对应的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内部维护的服务注册表。底部的启动流程则说明了ServiceManager是如何先于所有服务运行起来的。
4.6 安全视角的思考
从安全角度来看,ServiceManager有几个值得关注的点:
- 权限检查:只有系统进程才能注册服务,这是通过检查sender_pid和dest_pid是否相等来实现的。但如果有漏洞能绕过这个检查,攻击者就可以注册伪造的服务。
- 服务劫持:如果攻击者能先于合法服务注册同名的服务,那么客户端获取到的就是恶意服务。这就是所谓的“服务劫持”攻击。
- 拒绝服务:如果ServiceManager被大量无效请求淹没,可能导致正常服务无法注册或获取。
实战经验:我在分析某个厂商的ROM时,发现他们的ServiceManager竟然允许非系统进程注册以“system_”开头的服务。这意味着一个普通应用可以注册一个名为“system_account”的服务,然后伪装成系统账户服务来窃取用户信息。这就是典型的服务注册权限控制不当导致的漏洞。
好了,关于ServiceManager和Context Manager的内容就讲到这里。理解了这个“大管家”的工作机制,后面分析Binder相关的漏洞时,你就能更清楚地知道攻击面在哪里。