4、init.rc中的权限声明:解析init语言中的权限相关配置指令

好,我们继续往下走。上一章聊了SELinux策略的加载,这一章咱们聚焦在init.rc文件本身。你想想看,init进程是Android系统的第一个用户空间进程,它启动的所有服务、设置的所有属性,都跟权限息息相关。那么,这些权限到底是怎么声明的?

说白了,init.rc就是用一种叫「init语言」的脚本,告诉init进程:你要启动哪些服务、设置哪些属性、在什么时机做什么事。而权限控制,就藏在这些指令里。

4.1 init语言的基本结构

先简单回顾一下。init.rc文件由一系列语句组成,主要包括:

  • 动作(Action):在特定事件触发时执行一系列命令
  • 服务(Service):定义需要启动的守护进程
  • 选项(Option):修饰服务的行为
  • 命令(Command):具体的操作指令

权限相关的配置,主要集中在服务定义和属性设置上。我个人习惯把init.rc里的权限控制分为三类:

  1. 进程权限:服务以什么用户/组运行
  2. 能力(Capabilities):赋予进程哪些特权
  3. 属性权限:系统属性的读写控制

核心观点: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

这里的关键指令是 usergroup。它们决定了服务进程以哪个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 实战建议

好了,讲了这么多,我总结几条实战经验:

我的三条黄金法则

  1. 能不用root就不用root:每个服务都配一个专属的Linux用户,比如 user audioserveruser cameraserver
  2. 能力列表要精确:不要图省事给 ALL,把服务真正需要的能力列出来。我曾经见过一个服务给了 SYS_TIME 能力,结果被用来修改系统时间,导致证书验证全部失效。
  3. 属性权限要收紧:特别是 persist. 开头的属性,一旦被恶意篡改,重启后还会生效,危害极大。

最后说一句,init.rc的权限声明只是第一步。真正运行时,还有SELinux在背后盯着。下一章我们就来聊聊SELinux策略和init.rc是怎么配合的。

嗯,今天就到这里。你回去可以翻翻自己设备上的 /init.rc,看看那些系统服务都配了哪些权限,说不定能发现一些有趣的东西。