使能SELinux
《SELinux介绍》一文中,阐述了一些SELinux的基本逻辑,包括TE,RBAC和MLS。那么在一个发型版上怎么将SELinux跑起来呢?我们能对策略文件做点什么呢?这个策略文件能不能动态修改呢?本篇主要目的是回答这几个问题。我不打算做成step by step的教程,具体的步骤可以google之。但会对主要的资源做一些索引,并通过我的思路将它们串联起来。
Fedora上的SELinux
Fedora/RHEL是默认支持SELinux的,且默认就是enforcing状态。如果需要修改,可以
- 临时关闭(不用重启机器):
setenforce 0 ##设置SELinux 成为permissive模式
- 修改配置文件需要重启机器:
修改/etc/selinux/config文件, 将SELINUX=enforcing改为SELINUX=disabled
Fedora policy文件的位置在/etc/selinux/targeted/policy/policy.31。31表示版本号。具体的文件格式信息,需要查看Linux源码policydb_read
函数,位置在security/selinux/ss/policydb.c
值得注意的是,Ubuntu使用的是AppArmore,而不是SELinux
可以认为:SELinux和AppArmore都是基于LSM(Linux Secure Module)的两个平行的模块。如果希望在Ubuntu上使能SELinux,需要disable AppArmore。如果直接安装并使能SELinux而不关闭AppArmore,系统会挂死。因为2个安全系统拥有不同的文件系统读写配置,形成死锁。网上有很多相关的帖子,可以尝试着做一做实验。我已经以身试法上面说的挂死的情况了。请莫效仿。
Labelling
SELinux的核心思想就是一个标签体系(security context,简称SContext)。那么这个标签是怎么打上去的?这个打标签的过程称为labelling。那么labelling是怎么和内核启动相结合以及如何与终端用户(或者管理员用户)交互的呢?
如果暂时放下SELinux,我们想一想应该怎么做?我认为如果是我,可能会:
- 先给所有的process打上类似
user_u:role_r:type_t:s0-s1:c0,c1-c255
这样的标签 - 然后给所有的file(此处的file,应该是Linux中万物皆文件的file)也打上类似的标签,不过role就是固定的object_r了
那么看看SELinux到底是不是这样做的呢?
给进程打标签
First of all, know that SELinux supports inheritance of contexts. Furthermore more, inheritance of contexts is the default behavior: if there is no policy in SELinux that specifies otherwise, then anything created will inherit the context of its parent.[1]
我们知道Linux的所有进程都是kernel启动时的1号进程通过fork系统调用叫起来的。1号进程可能是openrc或者systemd之类的进程。所以所有的进程一上来都会继承init进程的所有标签,然后再根据具体的policy设定进行type transition。
正好,SELinux就支持这种Domain/Type Transition的语法(当然不是正好,是SELinux设计者们就是这么设计的)。
注:通过《SELinux介绍》一文,我们可以知道Domain其实就是进程的Type
举个Domain Transition的例子
type_transition init_t apache_exec_t : process apache_t
翻译成人类语言就是:
当init_t Domain中的进程执行type为apache_exec_t类型的可执行文件(fork并execv)时(文件的class为process),所属Domain(对process而言,肯定是指Domain)需要切换到apache_t域。
要做到这个type transition,那显然要涉及到以下3个权限:
1.首先,你得让init_t域中的进程能够执行type为apache_exec_t的文件
allow init_t apache_exec_t : file execute;
2.然后,你还得告诉SELiux,允许init_t做DT切换以进入apache_t域
allow init_t apache_t : process transition;
3.最后,你还得告诉SELinux,切换入口(对应为entrypoint权限)为执行apache_exec_t类型的文件
allow apache_t apache_exec_t : file entrypoint;
所以一条type transition的规则就要搭配另外至少3条规则(事实上,具体的发行版可能会更多)来运行。于是SELinux就发明了宏,类似函数来方便定义进程的标签转换。参考安卓的实现[1]:
# [external/sepolicy/te_macros]
# 定义domain_trans宏。$1,$2等等代表宏的第一个,第二个....参数
define(`domain_trans', `
allow $1 $2:file { getattr open read execute };
allow $1 $3:process transition;
allow $3 $2:file { entrypoint read execute };
...
')
#定义domain_auto_trans宏,这个宏才是我们在te中直接使用的
define(`domain_auto_trans', `
domain_trans($1,$2,$3)
type_transition $1 $2:process $3;
')
对应到之前的例子,如果apache_exec_t类型的文件被init进程叫起来以后,要切换到apache_t域就要用下面这条宏调用。
domain_auto_trans(init_t, apache_exec_t, apache_t)
以一个真实的例子结束这一段:
# 在external/sepolicy/init_shell.te中就有上述宏的用法:
./init_shell.te:4:domain_auto_trans(init, shell_exec, init_shell)
给文件打标签
进程的标签是从init进程继承,并按照策略文件进行响应的domain transition来的。那么文件的标签是从哪来的呢?例如u:object_r:cache_file:s0
。
事实上初始的文件系统也是有个初始值的,这个初始值通常放在一个叫file_contexts的文件里。例如Android的在external/sepolicy/file_contexts
。再摘去一段看看,这个file_contexts到底是啥:
/dev(/.*)? u:object_r:device:s0
/dev/akm8973.* u:object_r:akm_device:s0
/dev/accelerometer u:object_r:accelerometer_device:s0
/dev/alarm u:object_r:alarm_device:s0
/dev/android_adb.* u:object_r:adb_device:s0
/dev/ashmem u:object_r:ashmem_device:s0
/dev/audio.* u:object_r:audio_device:s0
可见file_contexts描述的就是一些文件和文件夹的安全标签(SContext)。
那么新建一个目录或者一个文件,他的标签又怎么来呢?
当创建一个新文件时(目录也是一种文件),实际上它的标签会继承自其父目录,之后按照规则文件规定的做type transition。和进程的打标签过程是不是很类似?
具体的语法可以参考[1]
特殊的标签sid
可以参考[5]。[2]中阐述了sid以及sid_context的语法。但是对为什么有这两个东西,并且它们有什么用上,没有说清楚。[5]做了比较好的解释。
总结一下就是,Linux kernel在init进程加载policy之前,也需要一些基本的标签,而这些标签也就是所谓的sid(secure ID)。每个sid对应一个sid_context。这些都是固化在内核里的。在init加载以后,所有sid对应的标签都会被转换到正常的设置。
策略/标签文件从哪来?
先看实例,Android的策略文件的位置在
Android系统策略文件(.te)位置在system/sepolicy
,device/<manufacturer>/<device-name>/sepolicy
#############################
# Default HALs
#
/(vendor|system/vendor)/bin/hw/android\.hardware\.audio@2\.0-service u:object_r:hal_audio_default_exec:s0
/(vendor|system/vendor)/bin/hw/android\.hardware\.automotive\.audiocontrol@1\.0-service u:object_r:hal_audiocontrol_default_exec:s0
/(vendor|system/vendor)/bin/hw/android\.hardware\.automotive\.evs@1\.0-service u:object_r:hal_evs_default_exec:s0
/(vendor|system/vendor)/bin/hw/android\.hardware\.automotive\.vehicle@2\.0-service u:object_r:hal_vehicle_default_exec:s0
/(vendor|system/vendor)/bin/hw/android\.hardware\.bluetooth@1\.0-service u:object_r:hal_bluetooth_default_exec:s0
既然和写代码一样,那就也有开发环境了,参考[2]的7.6一节。在此不做深究。
安卓上编译策略文件的流程如下图:
策略更新
策略加载的流程通常都是由1号进程完成的。它负责读取配置文件,加载编译后的策略文件,并传入内核的LSM(Linux Secure Module)来完成SELinux的初始化。那么如果我们要修改策略(增删改),那势必要重新编译,重新部署。这一部分可以参考[3]。虽然里面的软件版本比较旧,但核心的思路不变。我理解的大致流程如下(我还没有试验过,可以找一个虚拟机试试看):
- 编译策略文件,生成二进制的策略文件policy.bin
- 将policy.bin放入/etc/目录
- 使用setfiles重新标记文件系统
- 使用semanage创建管理员账号
- 重启生效
以上的操作都要由root账号来完成。可见root还是很无敌啊。
用户态工具链
引用[4]中的文字:
The policycoreutils package installs the following utilities:
- fixfiles: Fixes the security context on file systems
- load_policy: Loads a new SELinux policy into the kernel
- restorecon: Resets the security context on one or more files
- setfiles: Initializes the security context on one or more files
- secon: Displays the SELinux context from a file, program, or user input
- semodule_package: Creates an SELinux policy module package
- restorecond: Is a daemon that watches for file creation and sets the default file context
- semodule: Manages SELinux policy modules
- sestatus: Displays SELinux status
- setsebool: Sets SELinux Boolean value
我所用过的:
- checkpolicy: 编译策略文件(.te)
- setfiles: 给文件系统打标签
- semanage: 管理SELinux下的用户系统