(一)、简介
在SELinux中,所有的访问都要被明确的同意。SELinux默认的是没有访问,不管Linux的用户ID和组ID是什么。是的,这就意味着在SELinux中没有默认的超级用户,不像在标准Linux中的root用户。被同意的访问的方式是由主体的类型(也就是域)和客体的类型使用一个allow规则指定的。一个allow规则有四个元素:
1:source type(s),通常是尝试访问的进程的域
2:target type(s),要被进程访问的客体的类型
3:object class(es),指定的允许访问的客体类别
4:permission(s),主体访问客体类别的访问方式
举个例子,下面的规则:
allow user_t bin_t : file {read execute getattr};
这个例子展示了TE allow规则的基本语法。这个规则有两个类型标识符:源类型(或者是主体,或者是域),user_t;目标类型(或者是客体),bin_t。标识符file是一个被定义在策略中的对象类型的名称(在这个例子中,代表着一个二进制文件)。在花括号中的permissions是一个文件对象类别有效的permission的子集合。这条规则的翻译是:
类型标识符是user_t的一个进程能够一个类型标识符为bin_t的文件对象进行读,执行或者是获取属性。
正如我们下面将要讨论到的,在SELinux中的权限(permissions)要比标准的linux更加细化,在标准linux中只有三个权限,rwx(可读,可写,可执行)。在这个例子中,read和execute和传统的是一样的。getattr不是很明显。实际上,getattr权限是能够允许调用者去查看(不是改变)一个文件的属性,例如日期,时间以及自主控制访问模式。在标准Linux系统中,一个调用者仅仅需要该文件目录的搜查的权限就能够查看该文件的这些信息,即使他没有对文件读访问的权限。
假设,user_t是一个普通的,没有特权的用户进程(例如一个登陆shell进程)的域类型,bin_t是一个用户必须要有特有的安全特权才能运行的可执行程序(例如,/bin/bash)的标识符,该规则可能就允许用户来执行shell程序,例如bash shell。
注意:
在类型标识符名称中,_t是没有特殊意义的。这个仅仅是被用在大多数SELinux策略中的一个命名习惯;只要符合策略语言的语法,策略定义者就可以定义任何符合规范的名称。
通过这个章节,我们经常会使用一些符号来描述允许的访问:圆代表进程,盒子代表客体,箭头代表允许访问。例如,下面这个图描述了被上一个allow规则允许的访问。
(二)、类型强制机制举例
SELinux的allow规则,例如上面提到的那个例子,在SELinux中用来同意访问。面临的问题就是,在决定成千上万的访问,只能允许必要的访问的时候,还能使系统正常的工作,使其尽可能的安全。
为了更加深入的探索TE机制,我们来举一个密码管理程序(也就是passwd)的例子。在linux中,读取或者是修改shadow密码文件(/etc/shadow)是值得信赖的,在shadow文件中,被加密的密码保存在里面。密码程序实现了他自己内部的安全策略,允许普通的用户只能修改他们自己的密码,然而允许超级用户修改任何密码。为了完成这项可以信赖的工作,密码程序需要有移动和重新创建shadow文件的能力。在标准Linux中,他有这个特权,因为密码程序可执行文件有setuid位集合,所以,当他被其他人执行的时候,也会以超级用户运行的(他能够访问任何文件)。
然而,很多程序能够以超级用户的身份运行(事实上,所有的程序都有可能以超级用户的身份运行)。也就是说,任何以超级用户的身份运行的程序都有可能修改shadow密码文件。TE机制让我们做的就是确保只有密码程序(或者是相似的可以信任的程序)才能够访问shadow文件,不管运行该程序的用户是谁。
下图展示了使用TE机制的SELinux中,密码程序是如何运作的。
在这个例子中,我们定义了两种类型。passwd_t是被密码程序所使用的一个域类型。shadow_t标识符是shadow密码文件的标识符。如果我们检查在磁盘上这样的一个文件,我们将会看到下面的信息:
# ls -Z /etc/shadow
-r—- root root system_u:object_r:shadow_t shadow
同样,在这种策略下,检查一个运行在密码程序中的进程,将会显示这样的信息:
#ps -Z
joe:user_r:passwd_t 16532 pts/0 00:00:00 passwd
就现在来说,你可以先忽略安全上下文中的用户和角色元素,只关注标识符。
检查在表figure 2-2中的allow规则,这个规则的目的是给进程标识符(passwd_t)能够访问shadow文件标识符的权限,该权限能够允许进程移动或者是创建一个shadow密码文件。所以,再重新审视一下figure 2-2,杯描述的运行密码程序的进程能够成功的管理shadow密码文件,因为他有有效的超级用户的用户ID(root)(在标准linux中的访问控制),并且因为TE的allow规则允许他对shadow密码文件标识符有充足的访问权限(在SELinux中的访问控制)。这两者是必要条件,但不是充分条件。
2.2.2 域转换的问题
如果我们需要做的事情就是允许一个进程来访问客体,例如文件,那么编写一个TE策略将会是简单的。但是我们必须需要弄清楚,如何确保一个正确的程序运行在一个有正确域类型的进程上面。例如,有一个程序以某种方式使用passwd_t域类型运行在进程中,但是这个程序是不可信的,我们不想他去访问我们的shadow密码文件。这可能是灾难性的。这个问题会带给我们域类型转换的问题。
为了阐述清楚,请查看figure 2-3,在该图中,我们详细阐述了以前的密码程序的例子。在这个典型的系统中,一个用户(叫做joe)通过登录进程登录系统,一个shell进程杯创建(例如,运行bash)。在标准linux安全中,真实有效的用户IDS(也就是joe)是一样的。在我们例子SELinux策略中,进程类型是user_t,user_t就是适用于普通的,不可信的用户进程的域类型。当joe的shell运行其他程序的时候,在joe用户被创建的新进程的域类型也会保持user_t,除非采取了一些其他的行为。那么,joe如何修改密码呢?
我们不希望joe的不可信的域类型user_t有直接读或者是写shadow密码文件的能力,因为这将可能会使任何程序(包括joe的shell程序)能够查看或者是修改重要文件的内容。正如在之前讨论的,我们希望只有密码程序,并且该密码程序仅仅以域类型passwd_t运行的时候才有这样的访问权限。那么问题来了,如何能够提供一套安全可靠的,并且不唐突的机制来使域类型为user_t的joe的shell程序过渡到运行着域类型为passwd_t的密码程序的进程。
2.2.3 回顾一下标准Linux安全中的setUID程序
在讨论如何处理域转换的问题之前,我们先来回顾一下在标准linux中相似的问题是怎样被处理的,也就是如何提供给joe一种安全修改他的密码的方式。在Linux中处理这种问题的方式就是通过给passwd设置用户标识符(setUid)到root程序。如果你在典型的linux系统中,列出密码程序的文件,你将会看到下面的形式
注意关于这个列表的两个事情。首先就是在x点处s的所有者权限。这就是所谓的setuid位,这就意味着对于任何执行这个文件的进程来说,他的有效的UID(也就是,用于访问控制抉择的用户ID)将会被改变成文件的所有者。在这个例子中,root是文件的拥有者,因此,可运行的密码程序将会一直以有效的root的用户ID运行。下面的图展示了这个流程。
当joe运行密码程序的时候,joe的shell程序将会使用fork()系统调用来创造一个与自己相近的副本。这个副本进程依然保持着同样的用户ID(joe),并且依然运行shell程序。然后,在被创建之后,新的进程将会执行execve()系统调用来运行密码程序。同时在标准linux中要求进程要对程序有可执行的权限(x),在这个例子中是正确的,因为任何人对该密码文件都有可执行权限。在成功执行完execve()调用后会发生两个关键的事情。第一个就是运行的新进程中的shell程序将会被密码程序替换。其二,由于setuid为被设置成了文件拥有者,那么有效的用户ID从进程原来的ID改变成文件拥有者的ID(在这个例子中就是root)。因为root能够访问任何文件,现在密码程序就能够访问shadow密码文件并且能够处理来自joe的修改密码的请求了。
setuid位的使用在构建在类unix操作系统中,并且是一个简单并且强大的特征。然而,他也阐释了标准linux安全方面主要的缺陷。密码程序需要以root用户的方式去访问shadow文件。然后,当以root用户运行的时候,密码程序同样也能够访问任何系统资源。这同样也违反了安全工程学的核心原则-最少权限。因此,我们必须信任密码程序不会在系统上采取其他可能的行动。对于真正的安全应用来说,密码程序需要大量的代码审核以确保他不会滥用他的权限。进一步来说,当始料未及的错误发生在密码程序当中的时候,他可能就造成能够安全漏洞,该漏洞远远超过访问shadow密码文件所造成的损失。虽然密码程序是比较简单和高度可信的,那么考虑一下其他程序(包括登录程序)如果以root用户运行的话,会怎么样呢?
我们真正喜欢的方式就是,确保密码程序和其他任何一定要有权限的程序有最少的权限。简单的说,我们希望密码程序仅仅能够访问shadow文件和其他一些与密码相关的文件以及必要的最小系统资源。同时我希望确保除了密码程序其他程序都不能访问shadow密码文件。以这样的方式,当为用户账户管理评估安全问题的时候,我们仅仅需要密码程序(或者是相似的程序)在管理用户账户方面所扮演的角色而不必考虑我们自己的其他程序、
下面讲一下TE安全机制的域转换
2.2.4 域转换
正如刚刚显示在figure 2-2的图中,allow规则能够确保passwd进程域类型(passwd_t)能够访问shadow密码文件。然而,我们依然存在刚刚描述的域转换问题。提供安全的域过渡 和setuid程序的概念是相似的,但是是在TE机制下进行的。为了阐述,我们采用setuid的例子并添加上TE。
现在我们的例子更加复杂。我们详细的了解一下这个图。首先注意到的是我们已经添加了之前展示的三个类型,也就是joe的shell域(user_t),密码程序的域标识(passwd_t),以及shadow密码文件标识(shadow_t)。除此之外,我们也为passwd可执行文件添加了文件标识(passwd_exec_t)。例如,列举在磁盘上的密码程序的安全上下文,将会得到下面的结果。
现在哦我们就有足够的信息来创建TE策略规则来允许密码程序以passwd_t为域标识运行。我们来看看上图中的规则。第一条规则:
这条规则是允许joe的shell(user_t)初始化一个在passwd可执行文件(passwd_exec_t)的execve()系统调用。SELinux的execute文件权限在本质上是和标准linux文件的x访问权限是一样的。(shell在尝试运行之前会统计文件,所以也需要getattr权限)回顾一下关于shell程序是如何工作的描述。首先,他先fork一个自己的拷贝,包括完全相同的安全属性。这个拷贝依然保持着joe的shell原始的域标识(user_t)。因此,execute权限一定要指向原始域(也就是说,shell的域标识)。这就是为是么user_t是这条规则的源标识。(Source type)
下面我们看一下第二条allow规则:
这条规则提供了访问passwd_t域的入口点(entrypoint)。entrypoint是在SELinux中相当有价值的权限。该权限所做的就是定义哪一个可执行文件(因此,哪一个程序)可能会进入一个域。对于一个域过渡来说,新的或者是”to-be-entered”(将要被进入的)域(在这个例子中,是passwd_t)一定要有访问可执行文件的entrypoint,用于过渡到新的域标识。在这个例子中,假设仅仅passwd可执行文件被标志为passwd_exec_t,仅仅标识passwd_t有passwd_exec_t的entrypoint权限,这样就仅仅password程序能够运行在passwd_t域标识中。这是一个非常强大的安全控制。
警告:
entrypoint权限的概念是非常重要的。如果你不能完全理解前述的例子,请在继续向下阅读之前重新读一遍。
现在我们看一下最后一个规则:
这是第一条我们所见到的不提供对文件客体访问的allow规则。在这个实例中,客体类是process,也就是代表进程的对象类。回顾一下所有的系统资源都被封装在客体类中。这个概念也适用于进程。在最后一条规则中,权限是transition访问。这个权限进程的安全上下文的标识发生改变。原始的标识(user_t)一定要有transition权限才能被允许转换成新的标识(passwd_t)。
这三个规则一块提供了域转换发生的必要条件。为了能使一个域转换成功,所有的这三个条件必须得满足。因此,当下列的三个条件都满足的时候,一个域转换才被允许。
1:进程新的域标识有对可执行文件标识的entrypoint权限
2:进程当前(或者是过去的)域标识有对入口点文件标识的execute权限
3:进程当前的域标识有对新的域标识的transition的权限
当在TE策略中这三个所有的权限都被同意了,一个域转换才会发生。进一步讲,对在可执行文件entrypoint权限的使用,我们就有能力去严格控制哪一个程序能够运行在给定的域标识上。execvte()系统调用时改变域标识的唯一的方式,让策略定义者来控制个人程序的访问权限,而不管调用程序的用户是谁。
现在问题就是joe如何表明自己想要进行域转换。上述的规则仅仅是允许域转换,他们不会命令他。有一些方式能使程序设计人员或者是用户能够明确的要求域转换(如果被允许的话)。joe所想要做的就是运行密码文件,他希望系统能够确保他能够执行。我们需要一种方式来使系统能够默认初始化一个域转换。
2.2.5 默认的域转换 : type_transition 语句
为了支持域转换默认发生(正如我们密码程序例子中一样),我们需要介绍一个新的规则,type transtion规则(type_transtion)。这个规为SELinux策略提供了一种方式,如果一个明显的转换没有被要求的话,需要指定一个应该被尝试的默认的转换。我们来为allow规则添加下面一个tyoe transition规则:
这条规则的语法与allow规则是不同的。他依然有源和目标标识(user_t 和 passed_t),并且也存在客体类(process)。然而,没有权限,我们有一个第三方标识,默认标识(passwd_t).
type_transition规则用于与默认标识该表相关的多个不同的目标。就现在来说,我们关心的是有process作为他的客体类的type_transitin。这样的规则导致一个要尝试的默认的域转换。type_transition显示出,默认情况下,在一个exece()系统调用中,如果一个调用的进程的域标识为user_t,并且一个可执行文件的标识是passwd_exec_t(上面figure的例子),一个域转换到一个新的域(passwd_t)将会被尝试。
type_transition规则允许策略定义者在没有明确的用户输入的时候,默认初始化一个域转换。这使得TE机制不会让用户感到很强制。在我们例子中,joe不想知道任何关于访问控制和标识的事情;他想要去修改密码。系统和策略定义者可以使用type_transition规则来为用户执行这些转换。
注意:
一定要记住,一个type_transition规则会导致一个默认的域转换尝试,但是他不会允许他。为了能够保证域转换的成功完成,你依然需要提供域转换所必须的三个标识,无论他是默认初始化的还是作为一个用户明确的要求发生的。