4、init.rc中的权限声明:解析init语言中的权限相关配置指令
好,我们继续往下走。上一章聊了SELinux策略的加载,这一章咱们聚焦在init.rc文件本身。你想想看,init进程是Android系统的第一个用户空间进程,它启动的所有服务、设置的所有属性,都跟权限息息相关。那么,这些权限到底是怎么声明的?
说白了,init.rc就是用一种叫「init语言」的脚本,告诉init进程:你要启动哪些服务、设置哪些属性、在什么时机做什么事。而权限控制,就藏在这些指令里。
4.1 init语言的基本结构
先简单回顾一下。init.rc文件由一系列语句组成,主要包括:
- 动作(Action):在特定事件触发时执行一系列命令
- 服务(Service):定义需要启动的守护进程
- 选项(Option):修饰服务的行为
- 命令(Command):具体的操作指令
权限相关的配置,主要集中在服务定义和属性设置上。我个人习惯把init.rc里的权限控制分为三类:
- 进程权限:服务以什么用户/组运行
- 能力(Capabilities):赋予进程哪些特权
- 属性权限:系统属性的读写控制
核心观点:init.rc中的权限声明,是Android系统启动阶段的第一道防线。如果这里配错了,后面SELinux再严格也救不了你。
4.2 服务定义中的用户和组
先看一个典型的服务定义:
service servicemanager /system/bin/servicemanager
class core
user system
group system
critical
onrestart restart healthd
onrestart restart zygote
onrestart restart audioserver
onrestart restart media
onrestart restart surfaceflinger
onrestart restart inputflinger
onrestart restart drm
onrestart restart cameraserver
onrestart restart keystore
onrestart restart gatekeeperd
这里的关键指令是 user 和 group。它们决定了服务进程以哪个Linux用户和组的身份运行。
我在项目中遇到过一个问题:有个第三方服务被配成了 user root,结果它崩溃后导致整个系统重启。为什么?因为root权限太大了,它访问了不该访问的内核接口。后来我们改成 user system,问题就解决了。
我的建议:尽量使用最小权限原则。服务能用 system 就别用 root,能用 radio 就别用 system。你想想看,每个服务只拿它需要的权限,系统安全性会高很多。
4.3 能力(Capabilities)的精细控制
Linux传统上把权限分成两类:普通用户权限和root权限。但这样太粗糙了。所以引入了Capabilities机制,把root权限拆分成一个个独立的能力。
在init.rc中,用 capabilities 选项来声明:
service netd /system/bin/netd
class main
user root
group root
capabilities NET_ADMIN NET_RAW SYS_MODULE
这里netd虽然以root运行,但只获得了三个能力:
NET_ADMIN:网络管理操作NET_RAW:原始套接字SYS_MODULE:加载内核模块
嗯,这里要注意:capabilities 选项会覆盖默认的能力集。如果你没写这个选项,服务默认拥有所有能力(如果以root运行)或者没有能力(如果以普通用户运行)。
我曾经踩过的坑:有一次我写了一个服务,需要访问 /proc 下的某个文件。我给了它 DAC_OVERRIDE 能力,但忘了给 DAC_READ_SEARCH。结果服务启动后一直报权限错误,排查了半天才发现是能力没给全。所以,能力列表一定要仔细核对。
4.4 属性权限控制
Android系统属性(System Properties)是全局的键值对存储,任何进程都可以读写。但这样太危险了。所以init.rc里有一套属性权限控制机制。
看这个例子:
on property:sys.boot_completed=1
start bootanim
on property:persist.sys.usb.config=mtp
write /sys/class/android_usb/android0/enable 0
write /sys/class/android_usb/android0/functions mtp
write /sys/class/android_usb/android0/enable 1
这里 on property: 是一个触发器,当某个属性被设置为特定值时,执行后面的命令。但问题来了:谁可以设置这些属性?
实际上,属性权限是在 property_contexts 文件中定义的,但init.rc中可以通过 setprop 命令来设置属性。而 setprop 命令本身受SELinux策略控制。
我建议你记住一个原则:
| 属性前缀 | 含义 | 谁可以设置 |
|---|---|---|
persist. |
持久化属性,重启后保留 | 通常只有system_server |
sys. |
系统运行时属性 | system_server和部分系统服务 |
ro. |
只读属性,一旦设置不可更改 | init进程在早期设置 |
ctl. |
控制属性,用于启动/停止服务 | 只有init进程可以处理 |
4.5 其他权限相关指令
除了上面这些,init.rc里还有一些零散的权限控制指令:
seclabel:显式指定服务的SELinux上下文。如果不指定,init会根据服务路径自动推导。disabled:服务默认不启动,需要其他服务通过ctl.start来触发。这其实也是一种权限控制——不是谁都能启动这个服务的。oneshot:服务退出后不重启。有些安全敏感的服务只执行一次,比如installer。
service installd /system/bin/installd
class main
user installd
group installd
seclabel u:r:installd:s0
oneshot
你看,这里用了 seclabel 明确指定了SELinux上下文,确保installd运行在正确的安全域中。
4.6 实战建议
好了,讲了这么多,我总结几条实战经验:
我的三条黄金法则:
- 能不用root就不用root:每个服务都配一个专属的Linux用户,比如
user audioserver、user cameraserver。 - 能力列表要精确:不要图省事给
ALL,把服务真正需要的能力列出来。我曾经见过一个服务给了SYS_TIME能力,结果被用来修改系统时间,导致证书验证全部失效。 - 属性权限要收紧:特别是
persist.开头的属性,一旦被恶意篡改,重启后还会生效,危害极大。
最后说一句,init.rc的权限声明只是第一步。真正运行时,还有SELinux在背后盯着。下一章我们就来聊聊SELinux策略和init.rc是怎么配合的。
嗯,今天就到这里。你回去可以翻翻自己设备上的 /init.rc,看看那些系统服务都配了哪些权限,说不定能发现一些有趣的东西。