本文大量参考了 Gentoo Linux 文档中与 SELinux 相关的 wiki
SELinux 安全子系统是对基于 UNIX 权限位的 Linux 常规访问控制的补充,不同于常规访问控制,SELinux 提供的访问控制更加安全但同时也更加难以维护。SELinux 灵活且复杂,但它的工作原理其实很简单,我们将逐步深入,介绍 SELinux 支持的各种功能。
访问控制与SELinux上下文
为了说明 SELinux 到底是如何进行访问控制的,让我们来假设这样一个使用场景:现在有一名已登录的用户,向 shell 进程发送指令,要求 shell 进程读取一个文件 File。那么,SELinux 现在需要判断 shell 进程是否有权限读取文件 File。
SELinux 进行判断的依据非常简单。首先,无论是进程还是文件,SELinux 都会维护它们的”标签“,比如说 shell 进程的”标签“是 user_t,而文件 File 的”标签“是 lib_t,然后根据它们两个的标签, SELinux 将在规则集合中寻找相应的条目,如果找到的条目为 allow,即允许访问,那么 SELinux 将放行此次访问,否则拒绝。
这就是 SELinux 的工作原理:基于”标签“来查找匹配的规则。
给”标签“打上双引号是因为这只是方便读者理解原理而使用的名字,它的正式称呼是 SELinux 上下文。另外,我们所述的 user_t 并不是完整的 SELinux 上下文。完整的 SELinux 上下文由以下3个部分组成(有时是4个部分):
- 第一部分是 SELinux 用户
- 其次是 SELinux 角色
- 接下来是 SELinux 类型
- 最后是可选部分,表示敏感度级别
比如说,一个文件的 SELinux 上下文可能会是这样的:system_u:object_r:lib_t:s0
。system_u
表示 SELinux 用户,object_r
表示 SELinux 角色,lib_t
是 SELinux 类型,最后的s0
则表示敏感度级别。
或许你已经注意到,SELinux 用户、角色、类别都有一个后缀,这只是一个命名惯例。在 SELinux 世界中,基本上可以认为,带有_u
后缀的名字是在指代 SELinux 用户,带有_r
后缀的名字是在指代 SELinux 角色,带有_t
后缀的名字在指代 SELinux 类型。我们接下来讨论 SELinux 也会不时运用这些命名惯例,所以当你看到_t
后缀的名字但没说明它是什么的时候,请不要惊讶。
每个进程和文件都有各自的上下文,在启用了 SELinux 的系统上,如果想要查看文件或进程的 SELinux 上下文,请在调用相关命令时添加-Z
参数,比如ls -Z
可以显示文件的上下文,ps -Z
可以显示进程的上下文。
SELinux 使用每个字段来决定对访问的控制,我们将逐步介绍 SELinux 上下文中这些字段的具体作用。但实际上大多数规则都是围绕着 SELinux 类型来制定的,从这个角度出发,我们决定先介绍 SELinux 上下文中最重要的信息: SELinux 类型。
类型强制规则
类型强制(Type Enforcement,简称TE)基于 SELinux 上下文中的 SELinux 类型。TE 模型是 SELinux 发挥作用的基石,构成了绝大多数 SELinux 策略。
TE 模型规则的底层逻辑可以归结为三个单词组成的句子:“Subject-Access-Object”,即“主体-操作-客体”。
在 SELinux 中,主体(Subject)指的是进程。操作(Access)指代的是一系列动作,比如 read、write、ioctl 等系统调用。客体(Object)指的是操作适用的系统资源,包括文件、进程、socket 等,客体是被动的,不会主动执行任何操作——当它主动执行任何操作时它就成为了主体。
进程既可以成为主体,也可以作为客体,比如,一个进程向另一个进程发送终止信号时,进程就既是主体也是客体。
规则底层逻辑的这三个要素体现在了具体的规则中。一个典型的 TE 规则的形式如下所示:
kind source target:class permissions;
- kind——有几种选项,常用的是 allow、neverallow 和 dontaudit。allow 表示允许;neverallow 表示不允许;dontaudit 表示出现对应违规项后静默,不输出任何拒绝日志
- source——规则的主体的类型。“谁在请求访问”
- target——客体的类型。“请求访问什么”
- class——类别。正在访问的客体(file、 socket、process 等)的类别
- permissions——正在执行的操作(或一组操作)(例如,read、write)
一个实际的示例如下:
allow user_t bin_t:file { read write };
上面的规则的含义是:允许类型为 user_t 的进程,读取或写入,类型为 bin_t 而且类别(class)为 file 的客体。
需要注意的是,由于主体和