1、1. 简介SELinux 策略语言主要描述 policy.conf 的相关语法,其相关部分如下图所示:2. 类型强制概念SELinux 策略大部分内容都是由多条类型强制规则构成的,这些规则控制被允许的使用权,大多数默认转换标志,审核,以及固定部分的检查。 SELinux 策略大部分都是一套声明和规则一起定义的类型强制(TE:Type Enforcement)策略,一个定义良好、严格的 TE 策略可能包括上千个 TE 规则,TE 规则数量的巨大并不令人惊奇,因为它们表达了所有由内核暴露出的允许对资源的访问权,这就意味着每个进程对每个资源的访问尝试都必须至少要有一条允许的 TE 访问规则,如果我们
2、仔细思考一下现代 Linux 操作系统中进程和资源的数量,就明白为什么在策略中有那么多的 TE 规则了。当我们添加由 TE 规则控制的审核配置和标志时,对于具有严格限制的SELinux 策略,常常会见到它包含有上千条规则,在“ 创建和编写 SELinux 安全策略”中,我们将会讨论如何创建和管理这些大量的规则,本文旨在理解 TE 规则是如何工作的。TE 规则的绝对数量对理解 SELinux 策略是一个大的挑战,但是规则本身并不复杂,它们的分类相对较少,所有的规则基本上都属于两类范畴: 访问向量(AV)规则 类型规则我们使用 AV 规则允许或审核两个类型之间的访问权,我们在某些情况下使用类型规则
3、控制默认的标记决定。SELinux 的一个重要概念是 TE 规则是将权限与程序的访问结合在一起,而不是结合用户。所有SELinux 策略语言特性都是处理主体(正常的运行中的进程)对客体(文件、目录和套接字等)的访问权的,主要集中于程序访问控制决策,这也是 SELinux 的主要益处,它允许 SELinux 策略编写者基于程序的功能和安全属性,加上用户要完成任务需要的所有访问权做出访问决策,可以将程序限制到功能合适,权限最小化的程度,因此,即使它出了故障或被攻击破坏,但整个系统的安全并不会受到威胁。SELinux 是不会管用户的 ,可以给同一个程序指定多个域类型(因此有不同的特权集),这样就允许
4、引入角色的概念,尽管如此,访问控制的标准仍然是基于程序的域类型而不是用户的特权。焦点是程序的访问权,而不是用户的访问权。3. 类型、属性和别名正如你从术语类型强制猜测的那样,类型是构成 TE 规则的最小单位,SELinux 主要就是使用类型来确定什么访问是被允许的;属性和别名是为减轻管理和使用类型的策略特性,我们使用属性利用单个标识符来引用一组类型。通常,策略语言允许我们在 TE 规则中类型的适当位置使用属性,而别名允许我们为类型定义另一个名字,别名标识符和类型标识符做同等地位对待。 3.1 类型声明在使用类型前,必须使用 type 语句明确地声明一个类型标识符,SELinux 没有预定义类型
5、,我们必须自行声明,例如:假设我们想声明一个类型(httpd_t),并打算将其作为 Web 服务器的域类型,而另一个类型(http_user_content_t)准备应用于用户数据文件,即 Web 服务器显示内容的文件,我们使用type 语句进行声明,如下: cpp view plaincopy1. type httpd_t; 2. type http_user_content_t; 声明了类型后就可以在安全上下文、TE 规则和其它策略语句中使用它们了。类型声明的语法: type 类型名称 alias 别名集 ,属性集;1) 别名集:如果指定的不止一个别名标识符,要在一对大括号中用空格将各个别
6、名区别开来,如:alias aliasa_t aliasb_t。2) 属性集:是一个或多个预先声明的属性标识符,如果同时指定多个属性标识符,属性之间使用逗号进行分隔,如:type bin_t, file_type, exec_type;3) 类型声明在整个策略中,以及基础载入模块和非基础载入模块中都是有效的。但在有条件的语句中无效。3.2 类型和属性可能你已经想到,一个大型的,复杂的策略可能包括上万个代表系统上不同资源的类型,例如:Fedora Core 4(FC 4)的 targeted 策略相对较小,但也声明了超过 800 个类型。由于默认的规则是拒绝所有的访问,所以任何一个访问都需要明确
7、地被允许,这就导致了类型注定会很冗长,这时策略语言的属性特性就派上用场了。属性可以理解为:1) 类型的性质或属性,或二者兼得2) 一组类型在任何一种情况下,原理都是相同的。假设我们想让一个备份程序可以访问所有的文件,首先我们创建一个备份应用程序的域类型(backup_t),并允许它访问与任何文件关联的类型:cpp view plaincopy1. type backup_t; 2. allow backup_t httpd_user_content_t : file read; 3. allow backup_t shadow_t : file read; 为了完成这个例子,我们编写了一个用于
8、其他所有文件类型的规则,依赖声明的类型数量,我们需要大量的 allow 规则授予备份程序足够的访问权(每个类型都要一个)。另外,每次向策略中增加一个文件类型时,同时要为 backup_t 增加一条 allow 规则,这是一个单调且错误频出的过程,属性使得这种“ 组访问“更容易指定,通过将所有关联的文件类型定义一个属性,然后授予该属性的访问权(而不是每个类型了),于是,我们可以使用一条规则授予 backup_t 必需的访问权。使用 attribute 语句进行属性声明,如:cpp view plaincopy1. attribute file_type; 这个语句声明一个叫做 file_type
9、 的属性,类型和属性共享相同的命名空间,因此,类型和属性的名字不能雷同,假设我们将所有适当的类型都与属性 file_type 进行关联,然后就可以使用一条规则来指定backup_t 读取这些文件,如: cpp view plaincopy1. allow backup_t file_type :file read; 在我们使用了一条规则替代了上千条 allow 规则,而授予的访问权却是一样的,当这个策略编译好后,这条规则会自动扩展成上千条规则,分别控制不同文件类型的访问,更重要的是,当我们给文件定义了一个新类型时,我们需要做的仅仅是将这个新类型与 file_type 属性进行关联,域类型 ba
10、ckup_t 将会自动获得读取访问权。属性声明语法: attribute 属性名称 ;1) 属性和类型,别名都在同一个命名空间,因此不能与其他类型或别名重名。2) 属性声明在整个策略,基础载入模块和非基础载入模块中都有效,但在有条件的语句中无效。3.3 关联类型和属性3.3.1 在 type 中指定属性 迄今为止,我们已经讨论了如何定义类型和属性,下面将要讲述的是如何将它们关联起来,最常见的关联方式是在用 type 语句声明类型时就指定其属性,例如:我们可以将声明 http_user_content_t 类型的语句修改为:cpp view plaincopy1. type http_user_
11、content_t,file_type; 这个语句描述了声明类型 http_user_content_t 时,同时关联了 file_type 属性,它会自动向具有file_type 属性的类型组 中添加 http_user_content_t 类型,但从概念上将,它实质上已经改变了http_user_content_t 类型的性质,因为它现在已经具有基于属性的访问许可了,而不只局限于类型本身了。一个类型可以有多个属性,例如:我们可以再为所有 Web 服务器要用的文件创建一个属性httpdcontent,拥有 httpdcontent 属性的类型可能是拥有 file_type 属性的类型的一个子
12、集,下面的代码扩展了我们前面的例子:cpp view plaincopy1. type httpd_user_content_t, file_type, httpdcontent; 2. type shadow_t, file_type; 3. 4. allow backup_t file_type : file read; 5. allow httpd_t httpdcontent : file read; 类型具有的属性数量没有限制,就和类型一样,我们可以合理定义相应的属性。3.3.2 使用 typeattribute 指定属性除了使用 type 语句关联类型和属性外,还可以使用 type
13、attribute 语句,这个语句允许我们在声明类型时,单独关联属性,在策略中可能也就是一个单独的文件了,例如:将前面举的示例语句:cpp view plaincopy1. type httpd_user_content_t, file_type, httpdcontent; 分成两条语句进行表述:cpp view plaincopy1. type httpd_user_content_t; 2. typeattribute httpd_user_content_t file_type, httpdcontent; typeattribute 允许我们在一个地方定义类型,在另一个地方关联属性,
14、增强了语言的灵活性,在设计策略源文件时,可以考虑进行模块化设计了。typeattribute 语句语法: typeattribute 类型名 属性名;1) 一个或多个事先声明的属性标识符,如果指出多个属性标识符,属性标识符之间使用逗号分隔,如typeattribute bin_t file_type, exec_type;2) typeattribute 语句在单个策略,基础载入模块和非基础载入模块中都是有效的,只有在条件语句中无效。3.4 别名( 为确保兼容性而存在 )别名是引用类型时的一个备选的名字,能够使用类型名的地方就可以使用别名,包括 TE 规则,安全上下文和标记语句,别名通常用于策
15、略改变时保证一致性,例如:一个旧策略可能引用了类型netscape_t,更新后的策略可能将类型名改为 mozilla_t 了,但同时提供了一个别名 netscape_t 以保证与旧模块能够正确兼容。3.4.1 在 type 中声明别名cpp view plaincopy1. type mozilla_t alias netscape_t, domain; 注意别名声明是放在属性的前面的。3.4.2 使用 typealias 申明别名cpp view plaincopy1. # 这两条语句等同于 2. type mozilla_t, domain; 3. typealias mozilla_t
16、alias netscape_t; 4. 5. #下面这一条语句 6. type mozilla_t alias netscape_t, domain; typealias 语句语法 typealias 类型名称 alias 别名名称1) 类型名称:要添加别名的类型的名称,类型必须使用 type 语句单独声明,而且这里只能指定一个类型名称。2) 别名名称:如果同时指定多个别名,别名之间用空格分开,并使用大括号将所有别名括起来,如aliasa_t aliasb_t。3) typealias 语句在单个策略,基础载入模块和非基础载入模块中都有效,只有在条件语句中无效。4. 访问向量规则AV 规则就
17、是按照对客体类别的访问许可指定其具体含义的规则,SELinux 策略语言目前支持四类 AV规则:在代码中,客体类别的访问许可集是由一些叫做访问向量的掩码表现的,因此就有了术语访问向量。 allow:表示允许主体对客体执行允许的操作 dontaudit:表示不记录违反规则的决策信息,且违反规则不影响运行( 允许操作且不记录) auditallow:表示允许操作并记录访问决策信息(允许操作且记录) neverallow: 表示不允许主体对客体执行指定的操作本小节剩余部分将详细讨论这些规则的语法和语义,以及一些示例。4.1 通用 AV 规则语法虽然这些规则的用途不一样,但它们的基本语法是一样的,每个
18、规则都要包含下面五个元素: 规则名称: allow,dontaudit ,auditallow 和 neverallow 源类型:授予访问的类型,通常是进程的域类型 目标类型:客体的类型,它被授权可以访问的类型 客体类别:客体的类别 许可:表示主体对客体访问时允许的操作类型(也叫做访问向量)。一个简单的 AV 规则有一个源类型,目标类型,客体类别和许可,在我们前面的 allow 规则中可以看到许多 AV 规则,如:cpp view plaincopy1. allow user_t bin_t : file execute; 这个 allow 规则的源类型为 user_t,目标类型为 bin_t
19、,客体类别 file,许可 execute,这个规则可以解读为“允许 user_t 执行类型为 bin_t 的文件“4.1.1 AV 规则的密钥(哈希 Key)在内核中,所有的 AV 规则都是通过一组【源类型+目标类型+ 类别】进行唯一性标识,这个三重组叫做一个密钥,当做哈希表使用,缓存在策略数据结构中,规则是靠这个密钥存储和检索的,当一个进程产生了一个访问请求时,SELinux LSM 模块被要求允许基于这个密钥进行访问。那么,如果不止一个规则使用同一个密钥(即相同的源类型,目标类型和许可)时会发生什么状况呢?如下面的规则:cpp view plaincopy1. allow user_t
20、bin_t : file execute; 2. allow user_t bin_t : file read; 类型为 user_t 的进程对类型为 bin_t 的文件是可读还是可执行?答案是两者皆可。所有有相同密钥的规则通过 checkpolicy 进行组合,编译后的策略将只有一条规则,但它同时具有 read 和 execute 许可,它们都会被安全服务器接受。所有的 AV 都按照这种方式进行累加。4.1.2 使用 AV 规则中的属性虽然到目前为止我们看到的 AV 规则都很简单,但语法支持多种方法列出类型、客体类别和许可,使我们可以灵活地利用,并使规则语句更简单。在前面的简单样式的规则示例
21、中,直接引用了源类型(user_t)和目标类型(bin_t),这样在源类型或目标类型中要引用多个类型也是很方便的,其中一个方法就是使用属性,在 AV 规则中能使用类型的地方都可以使用属性(类型组)。例如,假设我们定义了一个属性(exec_type ),我们打算将其与所有的普通用户程序(通过域类型user_t 标记)都可以执行的文件类型关联,那么我们可以将上面的例子改为引用属性 exec_type,而不用再明确地指定类型 bin_t 了,如:cpp view plaincopy1. allow user_t exec_type : file execute; 与属性关联的每个类型都有一个独立的密
22、钥。我们也可以在 AV 的源类型位置处使用属性,或者干脆在源类型和目标类型处都使用属性,例如:假设我们创建了一个属性(domain),并将所有的域类型(包括 user_t)都与其关联,我们想要所有的域类型都可以执行属性为 file_type 的文件类型,使用一条规则就实现这个目标:cpp view plaincopy1. allow domain exec_type : file execute; 为了更好地解释规则扩展的原理,假设我们的策略关联了类型为 user_t 和 staff_t 的属性 domain,以及文件类型为 bin_t,local_bin_t 和 sbin_t 的属性 exe
23、c_type,那么上面那一条规则的效果就等同于下面这些规则:cpp view plaincopy1. allow user_t bin_t : file execute; 2. allow user_t local_bin_t : file execute; 3. allow user_t sbin_t : file execute; 4. 5. allow staff_t bin_t : file execute; 6. allow staff_t local_bin_t : file execute; 7. allow staff_t sbin_t : file execute; 4.1.
24、3 AV 规则中的多类型和属性在 AV 规则中的源和目标区域,我们都没有限制类型和属性的数量 ,相反,可以在源和目标字段处列出多个类型和属性,如果有多个类型或属性时,它们之间使用空格进行分隔,并使用大括号将它们括起来,如:cpp view plaincopy1. allow user_t bin_t sbin_t : file execute; 在这个规则中,目标是 bin_t 和 sbin_t,在源和目标区域有多个类型或属性时,展开方法同单个属性一样,在前面的例子中,内核包括两个密钥,每个目标类型都有一个。我们还可以在源或目标区域混合类型和属性,也可以在这两个位置都使用混合的形式,如:cpp
25、 view plaincopy1. allow user_t domain bin_t file_type sbin_t : file execute ; 如果我们明确地列出了类型以及类型具有的属性,这是可以的,即我们实际上列出了两次类型,内核会自动处理这个冗余,只会处理一个实例规则。4.1.4 特殊类型 self策略语言保留了一个关键字 self,它用于 AV 规则中的目标区域,可以当做一个类型使用,如下面这两条规则是相等的:cpp view plaincopy1. # 这两条规则是相等的 2. allow user_t user_t : process signal; 3. allow u
26、ser_t self : process signal; 关键字 self 说明目标类型使用的源类型自身,即目标类型等于源类型,前面的例子中,第二条规则只是用关键字创建了一条规则,表明源类型和目标类型都是 user_t。cpp view plaincopy1. # 这两条规则 2. allow user_t user_t : process signal; 3. allow staff_t staff_t : process signal; 4. 5. #等于下面这一条规则 6. allow user_t staff_t self : process signal; 注意:你可能只会在 AV
27、规则的目标区域使用特殊类型 self,特别要注意的是不能在 AV 规则的源区域使用 self 类型,另外,也不能声明一个类型或属性标识符叫做 self。cpp view plaincopy1. allow domain domain : process signal; # 每个进程都能向它自己和其它进程发送 signal 2. allow domain self : process signal; # 每个进程都能向它自己发送 signal 4.1.5 “非“ 特殊操作符AV 规则中最后一个类型语法是类型否定,它可以从一个类型列表中将某个类型移除,也可以用于用一个属性中移除某个类型,通过在要移
28、除的类型名称前面放一个非操作符(-)实现,例如:我们想让所有的域类型都可以访问所有属性为 exec_type 的文件,除了 sbin_t 类型外,那么编写规则时就可以这样:cpp view plaincopy1. allow domain exec_type -sbin_t : file execute; 2. #等同于 3. allow domain -sbin_t exec_type : file execute; 这个规则在展开时就好像属性 exec_type 没有包括类型 sbin_t 一样。4.1.6 在 AV 规则中指定客体类别和许可AV 规则也可以包括客体类别和许可列表,语法和类
29、型一致,使用空格进行分隔,并用大括号括起来,如:cpp view plaincopy1. allow user_t bin_t : file dir read getattr ; 2. 3. #这条规则将会产生两个密钥,每个客体类别一个,这条规则等同于下面这两条规则: 4. allow user_t bin_t : file read getattr ; 5. allow user_t bin_t : dir read getattr ; 注意客体类别被展开了,但每条规则都有相同的许可列表,这意味着列表中的所有许可对所有客体类别都是有效的。cpp view plaincopy1. # 无效的规
30、则,因为 search 对于客体类别 file 是无效的 2. allow user_t bin_t : file dir read getattr search ; 3. 4. #当许可对两个客体类别不是都有效时,需要两条规则 5. allow user_t bin_t : file read getattr ; 6. allow user_t bin_t : dir read getattr search ; 4.1.7 AV 规则中的特殊许可操作符对于列在 AV 规则中的许可,我们可以使用两个特殊的操作符,第一个是通配符( *),通配符包括了客体类别的所有许可:cpp view plai
31、ncopy1. allow user_t bin_t : file dir *; 这条规则扩展后将包括 file 和 dir 的所有许可。通配符语法与列出所有的许可有点不同,使用通配符时,许可包括每个客体类别的许可,此时不会考虑其中一个许可是否对另一个客体类别是否有效,这样就可以在规则中使用多个客体类别,即使这些客体类别有不同的许可,因此,上面这条规则就安全地处理了许可,不会像前面那条规则那样,这里只对 dir客体类别有效的规则不会影响到 file 客体类别。第二个特殊操作符是求补算操作符(),即除了列出的许可外,其它的许可都包括 ,如:cpp view plaincopy1. allow u
32、ser_t bin_t : file write setattr ioctl ; 编译时,这条规则允许所有的许可,除了 write,setattr 和 ioctl 外,与通配符类似,求补算操作符也扩大了客体类别的许可列表。4.1.8 通用访问向量规则语法完整的 AV 规则通用语法如下 : 规则名称 类型集 类型集:类别集 许可集;1) 规则名称:访问向量规则的名称,有效的规则名称是 allow,auditallow ,auditdeny ,dontaudit 和neverallow。2) 类型集: 一个或多个类型和(或)属性,规则中源和目标类型有其独立的类型集,多个类型集或属性使用空格进行分隔
33、,并使用大括号将它们括起来,如bin_t sbin_t。可以使用(- )来排除类型,如exec_type -sbin_t。在目标类型区域可以使用关键字 self,但在源类型区域不能使用。neverallow 规则也支持通配符来代表所有的类型,求补算操作符()也表示所有的类型,除了明确列出的之外。3) 类别集: 一个或多个客体类别,多个客体类别必须使用大括号括起来,如file lnk_file。4) 许可集: 一个或多个许可,所有许可对类别集列出的所有客体类别都要有效,多个许可必须用大括号括起来,如read create。通配符(*)指出所有客体类别的所有许可 ,求补算操作符()用于指出所有的许
34、可,除了明确列出的之外。所有 AV 规则在单个策略,基础载入模块和非基础载入模块中都有效,所有 AV 规则除了auditdeny,neverallow 规则外,其它的在条件语句中也有效。4.2 允许(allow) 规则到目前为止,你已经看到了许多的 allow 规则,allow 规则是策略中最常见的规则,它实现了 SELinux策略的主要目的(即允许访问)。正如前面讨论的,我们使用 allow 规则指出了所有运行时授予的许可,它们是 SELinux 策略中允许许可的唯一方法,记住,默认情况下,不允许任何访问,我们指定了两个类型列表(源和目标类型),根据列出的客体类别的许可指定访问权,如:cpp
35、 view plaincopy1. allow user_t bin_t : file read execute ; 这个规则允许任何安全上下文中类型具有 user_t 的进程对任何安全上下文中具有类型为 bin_t 的普通文件所有 read 和 execute 访问权。allow 规则共享了通用 AV 规则的的所有语法,并且也没有增加任何额外的语法了。4.3 审核(audit)规则SELinux 有大量的工具记录日志信息,或审核、访问尝试被策略允许或拒绝的信息。审核消息通常叫做“AVC 消息“,它提供了详细了关于访问尝试的信息,包括是允许还是拒绝,源和目标的安全上下文,以及其它一些访问尝试涉
36、及到资源信息。AVC 消息与其它内核消息类似,都是存储在/var/log 目录下的日志文件中,它是策略开发、系统管理和系统监视不可缺少的工具。在此,我们检查是哪一个访问尝试产生了审核消息。默认情况下,SELinux 不会记录任何允许的访问检查,只会记录被拒绝的访问检查。这并没什么奇怪的,在大多数系统上,每秒会允许成千上万的访问,只有很少的一部分会被拒绝,允许的访问通常是在预料之中的,通常不需要审核,被拒绝的访问通常是(但不总是)非预期的访问,对它们进行审核便于管理员发现策略的 bug 和可能的入侵尝试。策略语言允许我们取消这些默认的预料之中的拒绝审核消息,改为记录允许的访问尝试审核消息。SEL
37、inux 提供两个 AV 规则允许我们控制审核哪一种访问尝试:dontaudit 和 auditallow。使用这两条规则我们就可以改变默认的审核类型了,最常用的是 dontaudit 规则,它指出哪一个访问尝试被拒绝时不审核,这样就覆盖了 SELinux 默认的审核所有拒绝的访问尝试的行为。cpp view plaincopy1. dontaudit httpd_t etc_t : dir search; 记住,审核(audit)规则让我们覆盖了默认的审核设置, allow 规则指出了什么访问是允许的,auditallow 规则不允许访问,它只审核允许的许可。注意:在许可模式和强制模式下审核
38、是不一样的。运行在强制模式下时,每次允许或拒绝时都会进行审核,应该在策略中对审核频率进行限制(可以使用 auditctl 实现)。在许可模式下时,只会记录第一次访问尝试,直到下一次策略载入,或固定为强制模式,在开发时通常使用的就是许可模式,这种模式可以减少日志文件的大小。4.4 neverallow 规则最后一个 AV 规则是 neverallow 规则,我们使用这个规则来指定永远不会被 allow 规则执行的访问,你可能会疑惑,为什么会有这个规则?因为默认情况下所有的访问都是被拒绝的,设计这个规则的主要目的是为了帮助编写策略时,可以明确地指出不想要的访问许可,因此可以预防意外发生,回想一下,
39、在一个 SELinux 策略中可能包含成千上万条规则,可能不小心加入了我们本不想授予的访问权,此时,neverallow 规则就可以帮助预防这种情况发生,如: cpp view plaincopy1. neverallow user_t shadow_t : file write; 这条 neverallow 规则可以有效地阻止我们在策略中添加一条允许 user_t 对类型为 shadow_t 的文件进行写操作的规则,如果添加了这样的规则在编译时就会报错,这条规则不会移除访问权,它只是会产生编译错误。我们在编写策略时,neverallow 规则往往放在 allow 规则前面,首先声明哪些访问是
40、明确地被拒绝的,然后再声明哪些访问是可以接受的,这样就可以预防我们人为出错了。neverallow 规则支持一些特殊的其它 AV 规则不支持的语法,在 neverallow 规则中的源和目标类型列表中可以使用通配符(*)和求补算操作符(),如:cpp view plaincopy1. neverallow * domain : dir read getattr ; 这条规则指出没有哪条 allow 可以授予任何类型对具有 domain 属性的类型的目录有任何访问权,除了read 和 getattr 访问权外(即读访问权),这条规则的中通配符意味着所有的类型,在真实的策略中,类似这样的规则很常见
41、,它们用来阻止对/proc/目录适当的访问。我们从前面这个例子中看出,在源类型列表中需要使用通配符,因为我们想要指出任何类型或所有类型,包括那些还没有创建的类型,使用通配符可以预防我们未来犯错。另一个常见的 neverallow 规则是:cpp view plaincopy1. neverallow domain domain : process transition; 这条 neverallow 规则增强了 domain 属性,它指出了进程不能转换到无 domain 属性的类型,这就使得要为一个类型无 doamin 属性的进程创建一个有效的策略是不可能的。5. 类型规则类型规则在创建客体或在
42、运行过程中重新标记时指定其默认类型,它仅提供一个新的默认类型标记。在策略语言中定义了两个类型规则: type_transition:在域转换过程中标记行为发生时以及创建客体时,指定其默认的类型。 type_change:使用 SELinux 的应用程序执行标记时指定其默认类型。我们叫这些规则为“类型规则“,因为它们与 AV 规则类似,除了规则的末尾是一个类型名而不是许可集外。5.1 通用类型规则语法与 AV 规则一样,每条类型规则有不同的用途和语义,但它们的语法都是通用的,每条类型规则都具有下列五项元素: 规则名称:type_transition 或 type_change 源类型:创建或拥有
43、进程的类型 目标类型:包含新的或重新标记的客体的客体类型 客体类别:新创建的或重新标记的客体的类别 默认类型:新创建的或重新标记的客体的单个默认类型类型规则语法 规则名称 类型集 类型集:类别集 单个默认类型;1) 规则名称:类型规则的名称,有效的规则名称有 type_transition,type_change 和type_member。2) 类型集:一个或多个类型或属性。在规则中源和目标类型有其独立的类型集,多个类型和属性使用空格进行分隔,并用大括号将它们括起来,如bin_t sbin_t,可以在类型名前放一个(- )符合将其排除,如exec_type sbin_t。3) 类别集:一个或多
44、个客体类别,多个客体类别必须使用大括号括起来,并用空格分开,如file lnk_file。4) 默认类型:为新创建的或重新标记的客体类别指定的单个默认类型,这里不能使用属性和多个类型。5) 所有类型规则在单个策略,基础载入模块,非基础载入模块和条件语句中都有效。类型规则语法大部分都和 AV 规则类似,但也有一些不同的地方,首先就是类型规则中没有许可,不像 AV 规则那样,类型规则不指定访问权或审核,因此就需要许可了;第二个不同点是客体类别没有关联目标类型,相反,客体类别指的是将要被默认类型标记的客体。最简单的类型规则包括一个源默认类型,一个目标默认类型和一个客体类别,如:cpp view pl
45、aincopy1. type_transition user_t passwd_exec_t : process passwd_t; 它指出了当一个类型为 user_t 的进程执行一个类型为 passwd_exec_t 的文件时,进程类型将会尝试转换,除非有其它请求,默认是换到 passwd_t,当声明的客体类别是进程(process)时,隐含着目标类型要与 file 客体类别关联,声明的客体类别(process)与源和默认类型关联,这个隐藏着的关联很容易被忽略,即使你成为一个策略编写专家也容易犯这个错。5.2 类型转换规则 type_transition我们使用 type_transitio
46、n 规则指定默认类型,目前有两种格式的 type_transiton 规则:1) 支持默认域转换事件2) 支持客体转换,它允许我们指定默认的客体标记这两种形式的 type_transition 规则帮助增强了 SELinux 透明转换到 Linux 用户的安全性,默认情况下,在 SELinux 中,新创建的客体继承包括它们的客体的类型(如目录),进程会继承父进程的类型,type_transition 规则允许我们覆盖这些默认类型,这非常有用,例如:为了确保密码程序在/tmp/目录下创建一个文件时要给一个不同与普通用户的类型。type_transition 规则没有 allow 访问权,它仅提供
47、一个新的默认类型标记,要成功进行类型转换,也必须要一套相关联的 allow 规则,以允许进程类型可以创建客体和标记客体。此外,默认的标记指定在type_transition 规则中了,只有创建进程没有明确地覆盖默认标记行为它才有效。5.2.1 默认域转换(进程类型转换 process)让我们一起来详细地看一下这条规则中的域转换格式,执行一个文件时,域转换改变了进程的类型,如下面这条规则:cpp view plaincopy1. type_transition init_t apache_exec_t : process apache_t; 这条规则指出类型为 init_t 的进程执行一个类型为
48、 apache_exec_t 的文件时,进程类型将会转换到apache_t。客体类别 process 只表示这是一个域转换规则的格 式。下图显示了一个域转换,实际上,域转换只是改变了进程现有的类型,而不是新创建了一个进程,这是因为在 Linux 转换创建一个新的进程首先是要调用 fork()系统调用复制一份现有的进程,如果进程类型在fork 上被改变了,它就会允许域在新的域中执行任意的代码了,通过 execve()系统调用执行一个新的程序时,发生域转换时就更安全些。正如前面谈到的,只有当策略允许了有关的访问权时才会发生类型转换,域转换要成功,策略必须允许下面三个访问权: execute:源类型
49、( init_t)对目标类型( apache_exec_t)文件有 execute 许可 transition:源域(init_t)对默认类型(apache_t)必须要有 transition 许可 entrypoint: 新的(默认)类型(apache_t )对目标类型(apache_exec_t)文件必须要有entryponit 许可同时,上面的域转换规则要想成功,还必须要有下面的 allow 规则:cpp view plaincopy1. # 这条域转换规则. 2. type_transition init_t apache_exec_t : process apache_t; 3. 4. # 至少需要下面三条 allow 规则才能成功 5. allow init_t apache_exec_t : file execute; 6.