2. SELinux基础概念
Android 从 4.3 开始引入 SELinux,到 Android 5.0 进入 Enforcing 模式,再到 Android 8.0 之后推行 Treble 与 sepolicy 分区化,SELinux 已经成为 Android 安全模型中不可绕过的一层。要读懂 AOSP 中的 *.te、file_contexts、property_contexts 等策略文件,必须先建立一组基础概念:强制访问控制、类型强制、域、角色、用户、安全上下文以及策略语言的基本结构。本节把这些概念串成一条可理解、可落地的知识链。
一、从 DAC 到 MAC:为什么需要强制访问控制
传统 Linux 使用自主访问控制(DAC),即通过文件属主、属组和 rwx 权限位决定谁能访问什么。DAC 的核心问题是“主体可以自主决定授权”,例如 root 用户几乎可以访问所有资源,一个被攻陷的 root 进程可以任意读写系统文件。SELinux 引入强制访问控制(MAC),由内核统一依据预先编译好的策略进行判定,主体和客体都不能绕过策略自行放行。
在 Android 中,MAC 的价值非常直接:即使某个 native 进程以 root 身份运行,只要它的安全上下文没有对应权限,内核仍会拒绝其访问。这样就把“权限提升”造成的破坏面压缩到策略允许的范围内。
二、类型强制(TE):SELinux 的核心机制
SELinux 最常用的模型是类型强制(Type Enforcement,TE)。在 TE 模型中,每个主体(进程)和客体(文件、socket、属性等)都被赋予一个“类型”。进程的类型通常称为“域”(domain),文件的类型则直接称为 type。策略规则描述的是“某个域能否对某个类型执行某种操作”,例如:
allow untrusted_app app_data_file:file { read write open };
这条规则表示:域 untrusted_app 可以对类型为 app_data_file 的文件执行 read、write、open 操作。规则的基本形式是:
allow 源类型 目标类型:客体类别 权限集合;
TE 规则是白名单式的:没有显式允许的操作默认被拒绝。因此调试 SELinux 时,最常见的动作就是根据 avc denied 日志补全 allow 规则,而不是去“关闭 SELinux”。
三、域、角色与用户:主体侧的三层标识
在 SELinux 中,一个进程的安全上下文由 user、role、type、level 四部分组成。对 Android 而言,最常关注的是 type(域),但 user 和 role 仍然存在并参与判定。
- 用户(user):SELinux 用户,如
u、system_u,用于区分不同登录身份集合,与 Linux UID 不是一一对应。 - 角色(role):连接用户与域的中间层,如
r、object_r。进程通常使用角色r,文件使用object_r。 - 域(domain):进程的类型,如
init、zygote、system_server、untrusted_app,是 TE 规则中的源类型。
角色与域的绑定通过 role_transitions 或 role ... types ... 声明完成。Android 策略中大量使用 domain_auto_trans 宏,让父进程在 fork/exec 后自动切换到子进程应有的域。
四、安全上下文:SELinux 的“身份证”
安全上下文(security context)是 SELinux 判定时实际读取的标签,格式为:
user:role:type[:level]
例如:
u:r:init:s0
u:object_r:system_file:s0
u:r:untrusted_app:s0:c512,c768
进程上下文可通过 ps -Z 或 cat /proc/<pid>/attr/current 查看;文件上下文可通过 ls -Z 查看。Android 中文件标签由 file_contexts 在编译时生成,并在启动时由 restorecon 应用。
五、策略语言基础:从 .te 到二进制策略
Android SELinux 策略使用 C 风格的策略语言编写,主要文件类型包括:
| 文件/目录 | 作用 |
|---|---|
*.te | 类型强制规则,定义域、类型、allow、neverallow 等 |
file_contexts | 文件路径到安全上下文的映射 |
property_contexts | Android 属性到安全上下文的映射 |
service_contexts | Binder 服务名到安全上下文的映射 |
seapp_contexts | 应用进程与数据目录的上下文分配规则 |
mac_permissions.xml | 应用签名与 seinfo 的映射 |
策略编译流程大致为:checkpolicy/checksepolicy 检查语法与 neverallow,sepolicy-analyze 做一致性分析,最终生成 sepolicy 二进制文件并打包进 system.img 或 vendor.img。Android 10 之后还支持策略分区化,system 与 vendor 策略分别编译、分别升级。
六、案例:一次典型的 avc denied 分析
假设日志中出现:
avc: denied { read } for pid=1234 comm="system_server"
name="vendor_file" dev="dm-0" ino=5678
scontext=u:r:system_server:s0
tcontext=u:object_r:vendor_file:s0
tclass=file permissive=0
解读顺序是:源上下文 system_server,目标上下文 vendor_file,客体类别 file,被拒绝权限 read。对应修复规则为:
allow system_server vendor_file:file read;
但要注意:不能只看一条日志就加规则,还要确认该访问是否合理、是否已有更合适的域、是否应通过属性或服务转发。盲目 allow 会破坏最小权限原则。
七、常见误区与复盘
- 误区一:把 SELinux 当成 DAC 的替代品。 实际上两者叠加,DAC 不通过时 SELinux 根本不会被调用。
- 误区二:用 permissive 模式“解决”问题。 permissive 只记录不拦截,上线后仍会失败,且掩盖真实权限缺口。
- 误区三:只改 file_contexts 不改 TE 规则。 标签变了但规则没变,访问依然被拒。
- 误区四:忽略 neverallow。 某些规则在编译期就会被 neverallow 拦截,必须从设计上调整域划分。
八、本节概念关系总览
下图把本节涉及的核心对象放在同一张关系图中:用户、角色、域、类型、安全上下文、策略文件与内核判定之间的关系,便于在后续章节中随时回看。