5、SELinux基础概念:TE模型、安全上下文与策略规则
好,咱们今天来聊聊SELinux最核心的几个概念。说实话,我刚接触SELinux那会儿,也被这些术语搞得头大。什么TE模型、安全上下文、allow规则……感觉像在看天书。但后来我发现,只要抓住几个关键点,这东西其实没那么玄乎。
咱们先搭个框架,看看今天要讲的内容之间的关系:
5.1 TE模型——SELinux的骨架
TE,全称是Type Enforcement,中文叫「类型强制」。说白了,它就是SELinux的核心思想:所有东西都被贴上类型标签,然后根据标签决定能不能访问。
我打个比方你就明白了。想象一下你公司的大楼:
- 每个人(进程)都挂着一个工牌,上面写着「研发部」、「HR」、「保安」——这就是主体(Subject)的类型
- 每个房间(文件、设备)门口也有牌子,写着「研发办公室」、「档案室」、「机房」——这就是客体(Object)的类型
- 保安能不能进研发办公室?看规则怎么说——这就是策略规则
在TE模型里,主体通常是进程,客体是文件、目录、套接字、设备等。每个主体和客体都有一个安全上下文,里面包含了它们的类型信息。
核心思想:TE模型不关心你是谁(root还是普通用户),只关心你是什么类型(type)。一个root进程如果类型不对,照样访问不了文件。
我记得有一次帮客户排查问题,他们死活想不通:为什么root用户启动的服务,连自己的配置文件都读不了?我一看,进程的type是untrusted_app,文件的type是system_file,中间没有allow规则——那当然不行。root在SELinux面前,有时候真不好使。
5.2 安全上下文——对象的身份证
安全上下文(Security Context),你可以把它理解成每个进程和文件上的「身份证」。在Android系统里,你用ls -Z就能看到文件的身份证,用ps -Z能看到进程的身份证。
一个完整的安全上下文长这样:
u:r:init:s0
u:object_r:system_file:s0
u:r:untrusted_app:s0:c512,c768
它由四个字段组成,用冒号隔开:
| 字段 | 含义 | 示例 | 说明 |
|---|---|---|---|
| user | 用户 | u | Android里基本都是 u,表示SELinux用户 |
| role | 角色 | r / object_r | r 用于进程,object_r 用于文件 |
| type | 类型 | init, system_file | 最关键的字段,决定访问权限 |
| range | 安全级别 | s0, s0:c512,c768 | MLS多级安全,Android常用s0 |
嗯,这里要注意:type字段是TE模型的核心。策略规则里写的allow init system_file:file read,意思就是「允许类型为init的进程,读取类型为system_file的文件」。
小技巧:我习惯用 ls -Z /system/bin/ 和 ps -Z 来快速查看系统里各种对象的类型。遇到权限问题,第一步就是看类型对不对得上。
5.3 策略规则语法——allow、neverallow、auditallow
有了TE模型和安全上下文,接下来就是制定规则了。SELinux的策略规则,说白了就是回答一个问题:谁(主体类型)可以对什么(客体类型)做什么(操作)?
5.3.1 allow——允许访问
这是最常用的规则。语法如下:
allow 主体类型 客体类型:客体类别 操作集合;
举个例子:
# 允许 init 进程读取 system_file 类型的文件
allow init system_file:file read;
# 允许 init 进程对 device 类型的目录进行搜索和读取
allow init device:dir { search read };
# 允许 untrusted_app 访问网络
allow untrusted_app self:netlink_socket create_socket_perms;
这里有几个关键点:
- 客体类别(class):比如 file(文件)、dir(目录)、socket(套接字)、binder(Android的Binder通信)等
- 操作集合(permissions):比如 read、write、execute、create、open 等
- 多个操作可以用花括号括起来,用空格隔开
我在项目中遇到过最典型的场景:给第三方应用加权限。厂商说「我的app需要读某个系统文件」,结果一查,app的type是untrusted_app,目标文件是system_data_file,中间没有allow规则。加一条规则就解决了:
allow untrusted_app system_data_file:file read;
但要注意——别随便加allow规则。加得越多,安全风险越大。
5.3.2 neverallow——绝对禁止
neverallow 是SELinux里的「红线」。它表示:任何策略都不能允许这个操作。如果编译时发现有人写了allow规则违反了neverallow,编译直接报错。
# 绝对不允许普通应用访问内核文件
neverallow untrusted_app kernel_file:file read;
# 绝对不允许任何域(domain)对安全策略文件进行写操作
neverallow domain security_file:file write;
为什么要用neverallow?说白了,就是防止有人「手滑」或者「故意」开了后门。比如系统核心的安全策略文件,谁都不能改。你写个neverallow,编译阶段就能拦住。
避坑指南:我曾经见过一个团队,为了临时解决问题,在neverallow规则上打了补丁。结果编译是过了,但安全审计直接被否决。记住:neverallow是底线,不要试图绕过它。如果确实需要,先跟安全团队确认,再修改neverallow本身。
5.3.3 auditallow——记录审计日志
auditallow 的作用是:允许某个操作,并且记录到审计日志里。它和allow的区别在于:allow只允许,不记录;auditallow既允许又记录。
# 允许 init 执行某个文件,并记录日志
auditallow init system_file:file execute;
你可能会问:为什么要用auditallow?
- 调试阶段:想知道某个操作是否真的发生了,可以用auditallow来跟踪
- 安全审计:某些敏感操作(比如提权、访问用户数据),需要留下日志记录
- 合规要求:某些行业标准要求记录所有特权操作
我个人习惯在调试新规则时,先用auditallow代替allow。这样既能验证功能,又能看到日志输出。确认没问题后,再改成allow。
5.4 三个规则的关系
咱们用一张表总结一下:
| 规则 | 作用 | 是否记录日志 | 使用场景 |
|---|---|---|---|
| allow | 允许访问 | 否 | 正常授权 |
| neverallow | 绝对禁止 | 编译时检查 | 安全红线 |
| auditallow | 允许并记录 | 是 | 调试/审计 |
嗯,这里再补充一点:neverallow是编译时检查,不是运行时。如果你在运行时违反了neverallow,系统不会报错——因为根本编译不过。所以写策略的时候,一定要跑一遍完整的编译检查。
5.5 实战小例子
咱们来看一个完整的例子。假设我有一个自定义服务,类型叫my_service,它需要读取/data/my_config.conf这个文件,文件的类型是my_config_file。
第一步,定义类型(在my_service.te文件里):
type my_service, domain;
type my_config_file, file_type, data_file_type;
第二步,写allow规则:
# 允许 my_service 读取 my_config_file 类型的文件
allow my_service my_config_file:file read;
# 允许 my_service 搜索 /data 目录(因为文件在 /data 下)
allow my_service data_file:dir search;
第三步,加上审计(调试阶段):
# 记录读取操作,方便排查问题
auditallow my_service my_config_file:file read;
第四步,检查有没有违反neverallow:
# 确保 my_service 不是 untrusted_app 的子类型
# 否则会违反 neverallow untrusted_app data_file:file read
你看,一套流程下来,思路很清晰。我在实际项目中就是这么干的,从来没出过问题。
总结一下:TE模型是骨架,安全上下文是标签,策略规则是法律。三者缺一不可。写规则的时候,记住「最小权限原则」——只给必要的权限,多一条都不要给。