2、SELinux基础概念:主体、客体、策略、上下文、TE规则语法入门
好,咱们正式开始啃SELinux这块硬骨头。说实话,我刚接触Android安全时,也被SELinux搞得一头雾水。什么主体客体、什么安全上下文,听着就头大。但后来我发现,这东西说白了就是一套「谁可以做什么」的规则系统。
今天这一章,咱们把最核心的几个概念掰开揉碎。你搞懂了这些,后面写策略文件就不会觉得是在写天书了。
2.1 主体(Subject)与客体(Object)
先问个问题:SELinux里谁在干活?谁是被干活的?
主体,就是发起动作的那个。在Android系统里,主体几乎都是进程。比如你打开一个App,这个App的进程就是主体。系统服务比如system_server、mediaserver,也都是主体。
客体,就是被操作的对象。常见的客体有:
- 文件(普通文件、设备文件、socket文件等)
- 目录
- 进程(没错,进程也可以作为客体被其他进程操作)
- 套接字、共享内存等IPC资源
一句话总结:主体是「谁」,客体是「什么」。主体对客体发起操作,SELinux负责判断这个操作是否被允许。
我记得有一次排查一个App无法读取文件的问题,查了半天发现是SELinux策略没写对。当时主体是App进程,客体是那个文件,策略里忘了给读权限,结果就一直被拒绝。嗯,这种坑踩过一次就记住了。
2.2 安全上下文(Security Context)
每个主体和客体,都有一个安全上下文。你可以把它理解成SELinux世界里的「身份证」。这个身份证长什么样?看下面:
u:r:untrusted_app:s0:c15,c256,c513,c768
这串字符由冒号分隔成四个字段:
| 字段 | 含义 | 示例值 |
|---|---|---|
| user | 用户标识 | u(Android统一用u) |
| role | 角色 | r(一般用r) |
| type | 类型(核心!) | untrusted_app |
| level | 安全级别(MLS) | s0:c15,c256,c513,c768 |
这里面最重要的就是type字段。SELinux的TE(Type Enforcement)规则,说白了就是围绕这个type来写的。你想想看,系统里那么多进程和文件,每个都贴一个type标签,然后规则里写「type A可以对type B做哪些操作」——是不是清晰多了?
我的习惯:在写策略之前,先画一张图,把涉及的主体type和客体type列出来。这样后面写规则时思路不会乱。
2.3 策略(Policy)是什么?
策略就是SELinux的「法律条文」。它规定了:
- 哪些type是主体
- 哪些type是客体
- 主体可以对客体做什么操作
- 哪些操作需要额外的权限(比如ioctl、ptrace)
Android的SELinux策略文件放在system/sepolicy/目录下。你打开看看,里面全是.te后缀的文件。这些文件就是咱们要改写的核心。
策略文件编译后会生成一个二进制的sepolicy文件,系统启动时加载到内核里。嗯,这里要注意:策略一旦加载,就不能动态修改(除非你重新编译刷机)。所以写策略时一定要想清楚,别漏了权限。
2.4 TE规则语法入门
好,终于到语法部分了。TE规则其实不复杂,核心就几种句式。我一个个讲。
2.4.1 声明type
在写规则之前,你得先声明有哪些type。语法很简单:
type my_app, domain;
type my_app_data_file, file_type, data_file_type;
第一行声明了一个叫my_app的type,属性是domain(表示它是一个主体)。第二行声明了my_app_data_file,属性是file_type和data_file_type(表示它是一个文件客体)。
注意:属性(attribute)可以理解成type的「标签」。一个type可以有多个属性,方便批量授权。
2.4.2 allow规则
这是最常用的规则,没有之一。语法:
allow 主体type 客体type:客体类别 { 权限列表 };
举个例子:
allow my_app my_app_data_file:file { read write open };
这条规则的意思是:允许my_app这个主体,对my_app_data_file类型的文件,执行读、写、打开操作。
你可能会问:「客体类别」是什么?常见的类别有:
file— 普通文件dir— 目录sock_file— socket文件process— 进程fd— 文件描述符
每个类别能操作的权限不一样。比如对目录,你可以用search、add_name、remove_name等。这些权限列表可以在system/sepolicy/access_vectors文件里查到。
2.4.3 neverallow规则
这个规则是「绝对禁止」的意思。它用来做安全兜底,防止某些危险操作被意外允许。比如:
neverallow { untrusted_app } { system_data_file }:file { write };
这条规则说:任何untrusted_app类型的进程,都不能对system_data_file类型的文件执行写操作。就算你写了allow规则,编译时也会报错。
我曾经踩过的坑:有一次我写了一个allow规则,允许某个服务进程写一个系统文件。编译通过了,但运行时还是被拒绝。查了半天才发现,有一条neverallow规则把这个操作给拦了。所以写规则前,一定要先看看有没有相关的neverallow。
2.4.4 type_transition规则
这个规则用来控制「类型转换」。什么意思呢?比如一个进程执行了一个可执行文件,这个进程的type可能会发生变化。语法:
type_transition 主体type 客体type:客体类别 新type;
举个例子:
type_transition init shell_exec:process init_shell;
这条规则说:当init进程执行一个shell_exec类型的文件时,新产生的进程的type变成init_shell。
嗯,这个规则在Android里用得很多。比如init进程启动各种服务时,就会通过type_transition把服务进程的type切换到对应的domain。
2.4.5 其他常用规则
除了上面这些,还有几个常用的:
- dontaudit:不记录某类拒绝事件(减少日志噪音)
- auditallow:记录某类允许事件(调试用)
- permissive:让某个domain进入宽容模式(只警告不拒绝)
我个人建议,调试阶段可以用permissive来快速定位问题。但上线前一定要关掉,否则SELinux就形同虚设了。
2.5 一个完整的例子
说了这么多,咱们看一个完整的TE文件长什么样。假设我们要给一个叫my_service的服务写策略:
# 声明type
type my_service, domain;
type my_service_exec, exec_type, file_type;
type my_service_data_file, file_type, data_file_type;
# 初始化domain
init_daemon_domain(my_service)
# 允许访问自己的数据文件
allow my_service my_service_data_file:dir { read search };
allow my_service my_service_data_file:file { read write open create unlink };
# 允许读取系统属性
allow my_service system_prop:file { read open };
# 允许与binder通信
binder_use(my_service)
binder_call(my_service, servicemanager)
# 允许创建socket
allow my_service self:unix_dgram_socket { create bind sendto };
你看,实际写起来并不复杂。关键是要搞清楚:你的服务需要访问哪些资源?需要哪些权限?然后一条条写清楚就行。
我的经验:刚开始写策略时,别想着一次写完美。先写一个最小权限集,跑起来看看avc日志,缺什么补什么。这样一步步迭代,比一次性写一大堆要靠谱得多。
2.6 小结
这一章咱们把SELinux的骨架搭起来了:
- 主体是进程,客体是被操作的对象
- 安全上下文就是身份证,核心是type字段
- 策略是规则集合,编译后加载到内核
- TE规则主要用allow、neverallow、type_transition等
下一章,咱们会深入Android特有的SELinux策略结构,看看Google是怎么给整个系统分层授权的。到时候你会发现,理解了基础概念,后面的东西其实都是这些规则的组合和扩展。
好,今天就到这儿。有什么问题,咱们下节课接着聊。