12. Android SELinux(一):Android SELinux架构、init.rc与sepolicy集成
好,咱们今天聊聊SELinux在Android里的落地。说实话,我早年刚接触Android安全时,觉得SELinux就是个“黑盒子”——打开强制模式就崩,关掉又怕被骂。后来啃了几个月源码,才慢慢摸清门道。
这一讲,咱们聚焦三个核心问题:SELinux在Android里到底长什么样?init.rc怎么跟它配合?sepolicy文件又是怎么组织起来的?
12.1 Android SELinux整体架构
先看一张我手绘的架构图,帮你建立整体认知。
这张图我画了四层。你可能会问:为什么不是三层?因为策略层虽然运行在内核之上,但它的配置和编译完全独立。我个人习惯把策略层单独拎出来,这样排查问题时思路更清晰。
核心要点:Android SELinux 是“用户态策略 + 内核态强制访问控制”的组合。策略文件在用户态编译成二进制,然后加载到内核。内核里的LSM Hook会在每次系统调用时检查权限。
12.2 init.rc 与 SELinux 的集成
init.rc 是Android启动的“总指挥”。SELinux的初始化,其实就藏在init进程的启动流程里。
我记得第一次看init.rc时,被一堆on early-init、on init搞晕了。后来才明白,SELinux的启动顺序非常讲究:
- early-init阶段:挂载selinuxfs,设置内核安全上下文
- init阶段:加载sepolicy文件,进入 enforcing 模式
- late-init阶段:为关键服务设置安全上下文
来看一段典型的init.rc片段:
on early-init
# 挂载selinux文件系统
mount selinuxfs selinuxfs /sys/fs/selinux
# 设置内核安全上下文
write /sys/fs/selinux/enforce 0
on init
# 加载编译好的sepolicy
selinux_load_policy()
# 进入强制模式
write /sys/fs/selinux/enforce 1
on late-init
# 为系统服务设置安全上下文
setcon u:r:init:s0
# 启动关键服务
class_start core
避坑指南:我曾经在某个项目里,因为init.rc中write /sys/fs/selinux/enforce 1写在了selinux_load_policy()之前,导致系统启动时直接panic。原因是策略还没加载,内核不知道该怎么强制。记住:先加载策略,再开强制。
12.3 sepolicy 文件组织
sepolicy 不是单个文件,而是一个目录树。Android 10之后,它被拆成了多个子目录。我整理了一张表格,方便你对照:
| 目录/文件 | 作用 | 典型内容 |
|---|---|---|
system/sepolicy/ |
系统级策略,所有设备通用 | domain.te, app.te, file_contexts |
vendor/sepolicy/ |
厂商定制策略,硬件相关 | vendor_init.te, hal_xxx.te |
product/sepolicy/ |
产品级策略,特定产品定制 | product_app.te |
device/<oem>/<device>/sepolicy/ |
设备专属策略,最底层 | device.te, file_contexts |
你可能会问:为什么要分这么多目录?说白了,是为了解耦。Google更新系统时,只动system/sepolicy;厂商改硬件驱动时,只动vendor/sepolicy。互不干扰。
12.4 核心策略文件详解
咱们挑几个最重要的文件说说。嗯,这里要注意,不是所有.te文件都平等——有些是“骨架”,有些是“血肉”。
12.4.1 domain.te —— 所有进程的“祖宗”
domain.te 定义了所有进程域共有的权限。比如:
# 所有进程都可以读取自己的安全上下文
allow domain domain:process getattr;
# 所有进程都可以向logd写日志
allow domain logd:unix_dgram_socket sendto;
# 禁止所有进程直接访问内核对象
dontaudit domain kernel:system *;
我个人习惯把domain.te比作“宪法”——它规定了底线。任何新加的进程域,都必须继承domain属性。
12.4.2 app.te —— 应用沙箱的边界
应用的安全策略全在app.te里。Android用untrusted_app和platform_app来区分普通应用和系统应用:
# 普通应用:只能访问自己的数据目录
allow untrusted_app self:file read write;
allow untrusted_app app_data_file:dir create_dir_perms;
# 系统应用:可以访问更多系统资源
allow platform_app system_app_data_file:dir search;
allow platform_app radio:unix_stream_socket connectto;
警告:千万不要随意给untrusted_app添加额外的allow规则。我在一个OEM项目里见过,厂商为了“方便调试”,给所有应用加了allow untrusted_app system_file:file read,结果被Google CTS测试直接判fail。SELinux的粒度越细,安全性越高。
12.4.3 file_contexts —— 文件的安全标签
文件的安全上下文不是自动继承的,而是通过file_contexts文件静态指定。比如:
/system/bin/init u:object_r:init_exec:s0
/system/bin/servicemanager u:object_r:servicemanager_exec:s0
/data/app/ u:object_r:apk_data_file:s0
/data/data/ u:object_r:app_data_file:s0
这里有个细节:_exec后缀表示“可执行文件的安全上下文”。当init执行/system/bin/servicemanager时,内核会根据servicemanager_exec的标签,自动将进程域切换为servicemanager。这就是SELinux的“域转换”机制。
12.5 策略编译与加载流程
最后,咱们走一遍策略从源码到内核的完整流程。我画了个流程图:
这个流程里,最容易出问题的是步骤2到步骤3的转换。我记得有一次,我在.te文件里写了个allow规则,但忘了声明对应的type,结果编译出来的cil文件直接报错。排查了半天才发现是type声明缺失。
调试技巧:如果你在开发板上调试,可以用adb shell执行dmesg | grep avc查看SELinux的拒绝日志。格式通常是:avc: denied { read } for pid=1234 comm="xxx" scontext=u:r:app:s0 tcontext=u:object_r:system_file:s0 tclass=file。这个日志会直接告诉你:谁想访问什么,被拒绝了。
12.6 小结
这一讲,咱们把Android SELinux的骨架搭起来了。从四层架构,到init.rc的集成,再到sepolicy的文件组织,最后走了一遍编译加载流程。说白了,SELinux在Android里就是一套“规则引擎”——规则写在.te文件里,编译成二进制,内核照着执行。
下一讲,咱们会深入具体的.te文件语法,教你如何编写自己的SELinux策略。到时候,我会分享几个我踩过的“血泪坑”。
公众号:蓝海资料掘金营,微信deep3321