我们已经知道进程拥有安全上下文(后面简称为上下文),可以使用ps –eZ 命令查看。
root #ps -eZ | grep auditd
system_u:system_r:kernel_t:s0 448 ? 00:00:04 kauditd
system_u:system_r:auditd_t:s0 6731 ? 00:00:22 auditd
但是进程怎么进入特定上下文呢?
答案是转换。
上下文继承
首先需要知道的是SELinux支持上下文的继承。甚至上下文的继承是默认行为:如果SELinux中不存在策略规则,新建的东西都会继承它父母的上下文。
l 某个进程以foo_t上下文运行,它产生了其他的子进程。新的子进程也会以foo_t上下文运行(只要SELinux没有指定规则)
l 文件或目录在某个上下文为bar_t的目录里被创建,它们也将会得到相同的山下文bar_t(只要SELinux没有指定规则)。继承总是这样的,即使SELinux管理工具(但是没有SELinux策略规则)在某处声明;即使存在一个上下文定义(context definition)规定/var/log/audit的上下文应该为auditd_log_t,当你在/var/log目录下新建audit目录时,它也将会继承它父目录的安全上下文var_log_t。只有后面运行restorecon,它的上下文才会改变。
因为我们说到继承,大概可能root的上下文对于所有实体都存在影响。当然,存在策略所定义的和SELinux支持的内容,但是假设针对进程的root上下文(域)是kernel_t(因为这个上下文是内核运行使用的,它产生了init进程,init同样产生多种其他的进程)和针对文件系统的root上下文是root_t是安全的。
user $ls -ldZ /
dr-xr-xr-x.root root system_u:object_r:root_t:s0 /
转换
当然,如果SELinux不支持到其他上下文的转变,它基本上没什么实用性。以下内容讨论下进程上下文转换:进程一旦被创建,它将拥有和父进程不一样的上下文。
当进程fork时,SELinux支持上下文转换(实际上是通过调用execve()实现的,这里说的fork不是fork()系统调用)。forking看似奇怪的编程术语,当你执行程序的时候,意味着对着一个文件(二进制或者脚本)说“执行它”,于是指定的文件被装载并“成为”一个进程。
SELinux策略作者可以做的是定义上下文转换,像下面这样:
When an init_t process executes a file with context initrc_exec_t,
then the resulting process should run in the initrc_t context.
或者,以别扭的ASCII-art定义,如下:
基本上SELinux策略作者想实现的是,当init进程执行init脚本(在/etc/rc.d/init.d或者/etc/init.d中)的时候,这些脚本将会在initrc_t域运行。[init_t] --(execute initrc_exec_t) ---> [initrc_t]
在之前的列子中,你注意到目标文件(将要被执行的)的上下文有被使用到。SELinux内部不使用路径,它使用上下文。于是策略作者不使用/etc/init.d/sysstat,而是使用initrc_exec_t上下文。
被使用的语法又是命名约定,但是这不是强制的。SELinux不会解析命名,上下文可以随意命名。命名约定可以有趣,因为它允许人们可以明白上下文的命名的目的。比如initrc_exec_t,我们可以从中知道“这是可执行文件的上下文,当被执行的时候,很可能就是initrc_t上下文的进程在执行”。
实际例子:SSH的启动
让我们看看实际的例子:SSH守护进程的启动。
基本上,当SSH守护进程通过服务启动的时候发生了什么:
1. linux内核执行/sbin/init,启动了init进程
2. init进程执行/etc/init.d/sshd,启动init服务脚本执行一段时间
3. sshd init服务脚本执行/usr/sbin/sshd,启动sshd守护进程
对于SELinux,已加载的公共策略可以被标注为以下:
我们这里初略定义的是一个域到另一个域的进程转换,它通过文件的执行。[kernel_t] --(execute init_exec_t)--> [init_t] [init_t] --(execute initrc_exec_t)--> [initrc_t] [initrc_t] --(execute sshd_exec_t)--> [sshd_t]
转换什么时候被允许
当匹配如下三个规则时,这样的转换才能出现。
1. 原始域必须拥有文件的执行权限。
2. 对于目标域,文件上下文本身必须被标示为进入点(entry point)。
3. 原始域必须被允许到目标域的转换。
只有所有三个规则匹配,只有此时这样的域转换才能发生。详细内容如下:
文件的执行权限
正如我们知道的,SELinux拥有很多细粒度的权限集合。文件的执行权限就是其中之一。如果一个域没有这个文件的执行权限,它就没办法执行这个文件,很简单。
这也是大多拒绝或权限问题:进程试图执行没有被允许执行的文件。正如过去遇到的问题,这也可能是错误的文件上下文引起的。在Gentoo系统(一种linux发行版),执行文件失败的守护进程,很可能是他们没有执行上下文为bin_t的文件,而是试图执行lib_t上下文的文件(一般是/usr/lib目录下文件的上下文),
下面的例子是init脚本(initrc_t)拥有SSHd二进制文件(sshd_exec_t)执行权限:
root #sesearch -s initrc_t -t sshd_exec_t-c file -p execute -Ad
Found 1semantic av rules:
allow initrc_t sshd_exec_t : file {read getattr execute open } ;
域的进入点
执行一个拥有特殊标签的文件,光靠这个特殊标签不足以触发域转换。记住SELinux不会解析上下文(这里上下文注重于type字段)本身,SELinux完全不懂sshd_exec_t对于SSH守护进程该怎么做,更别说sshd_t。于是我们需要告诉sshd_exec_t和sshd_t之间的联系——声明sshd_exec_t文件是sshd_t域的进入点(entrypoint)。
root #sesearch -s sshd_t -t sshd_exec_t -cfile -p entrypoint -Ad
Found 1semantic av rules:
allow sshd_t sshd_exec_t : file { ioctlread getattr lock execute execute_no_trans entrypoint open } ;
举个反例,检查是否 ssh_exec_t(SSH 客户端使用,不同于 sshd_exec_t)是SSH守护进程(sshd_t)的进入点。如下,并无输出:
root #sesearch -s sshd_t-t ssh_exec_t -c file -p entrypoint -Ad
我们使用sesearch查找下 ssh_exec_t是哪些域的进入点:
root #sesearch -t ssh_exec_t -c file -pentrypoint -Ad
Found 5semantic av rules:
allow xm_ssh_tssh_exec_t : file { ioctl read getattr lock execute entrypoint open} ;
allow ssh_t ssh_exec_t : file { ioctlread getattr lock execute execute_no_trans entrypoint open } ;
allow sge_job_ssh_t ssh_exec_t : file {ioctl read getattr lock execute entrypoint open } ;
allow condor_startd_ssh_t ssh_exec_t :file { ioctl read getattr lock execute entrypoint open } ;
allownx_server_ssh_t ssh_exec_t : file { ioctl read getattr lock executeentrypoint open } ;
以上输出了很多,如果相同的上下文可以被多个目标域使用,那么怎么知道哪个域可以被使用。所以进入第三个需求:进程转换。
进程转换
第三个需求是SELinux策略中必须告知,从什么源域到目标域,这样域转换才能被允许。以下是initrc_t到sshd_t:
root #sesearch -s initrc_t -t sshd_t -cprocess -p transition -Ad
Found 1semantic av rules:
allow initrc_t sshd_t : process {transition siginh } ;
这里的权限允许我们 拥有很多域的进入点,而保留一个明确的转换。想想先前SSH客户端的例子: ssh_exec_t是多个域的进入点,包括 xm_ssh_t和 nx_server_ssh_t,然而,通过执行SSH客户端转换到 xm_ssh_t域的原始域和通过执行SSH客户端转换到 nx_server_ssh_t域的原始域是明显不同的。
root #sesearch -t xm_ssh_t -c process -ptransition -Ad
Found 2semantic av rules:
allow xm_t xm_ssh_t : process {transition getattr } ;
allow xm_ssh_t xm_ssh_t : process {fork transition sigchld sigkill sigstop signull signal getsched setschedgetsession getpgid setpgid getcap setcap share getattr noatsecure siginhrlimitinh dyntransition execmem setkeycreate setsockcreate } ;
root #sesearch -t nx_server_ssh_t -cprocess -p transition -Ad
Found 2semantic av rules:
allow nx_server_t nx_server_ssh_t :process { transition getattr } ;
allow nx_server_ssh_t nx_server_ssh_t :process { fork transition sigchld sigkill sigstop signull signal getschedsetsched getsession getpgid setpgid getcap setcap share getattr noatsecuresiginh rlimitinh dyntransition execmem setkeycreate setsockcreate } ;
用不正规的ASCII流图,可将上面的例子表示为下面这样:
所以说,虽然ssh_exec_t是多个目标域的进入点,但是真正的转换是唯一的。[xm_t] --(execute ssh_exec_t)--> [xm_ssh_t] [nx_server_t] --(execute ssh_exec_t) --> [nx_server_t]
我们需要记住
l SELinux默认继承上下文,要么通过进程(fork)要么父文件/目录
l 进程的上下文可以转换,但必须满足以下条件:
1. 对于源域,目标文件的上下文有执行权限
2. 目标文件上下文被标示为目标域的进入点
3. 源域被允许转换到目标域
链接:
https://wiki.gentoo.org/wiki/SELinux/Tutorials/How_does_a_process_get_into_a_certain_context