10.2.2 域间转换
同 Tomoyo 一样, AppArmor 的强制访问控制机制是基于文件路径的。在 AppArmor 中的域主要是由进程所执行的文件的路径决定的。 Tomoyo 会不厌其烦地将进程以及进程的祖先所执行过的文件的路径都记录在进程的域中。 AppArmor 不同,它只会将最后一次执行的文件的路径作为域。
AppArmor 将域设计成一种树状结构,域下可以有子域。这种设计的目的是让进程的域转换受到额外的限制。假设系统中有 A、 B、 C 三个域。系统管理员定制策略允许这三个域互相转换。域 A 下又有 a、 b、 c 三个子域。系统管理员可以定制策略允许域 A 转换到子域 a、 b、 c,但无法定制出策略让域 B 和域 C 直接转换到域 A 的子域 a、 b、 c。
和其他安全模块一样,在 AppArmor 中域间转换的途径有两条,一是执行文件,二是进程运行时自我改变。同样,域间转换会反映在策略上,需要策略允许。
1.文件执行
AppArmor 中的域与进程所执行的文件相关,所以在进程执行文件时就有可能引起域的变化。结果是下面几种情况之一:
(1)根据文件名找到域,转换到文件所对应的域。这种情况又分为两种情况:
1)在当前域中存在相关的子域,转换到子域。
2)不存在子域,或不允许转换到子域,在当前域外存在一个以被执行的文件的文件名命名的域,转换到那个外部域。
(2)留在当前域。
(3)转换到“不受控制”(unconfined)的域。
10.1 节说过 AppArmor 的设计主旨是针对单个应用的安全,而不致力于整个系统的安全。一般情况下, AppArmor 的策略只能覆盖一部分进程。系统中其他进程都工作在不受控制的状态,这些进程的域就是“unconfined”。不仅如此, AppArmor 的策略还允许进程将域转换为“unconfined”,从受控制的状态转换到不受控制的状态。
2.自我改变
AppArmor 的域的第二种转换方式是通过伪文件接口/proc/[pid]/attr/current。这种转换不能为所欲为,需要受到几个条件的限制:
(1)只能改进程自己的域,不能改别的进程的域。
( 2)如果进程不是处在“unconfined”状态,并且进程的 no_new_privs㊀ 被置位,则不能修改。
(3)如果进程不是处在“unconfined”状态,并且进程的 no_new_privs 未被置位,则需要策略允许改动。
“自我改变”又有两种方式,第一种方式是向/proc/self/attr/current 中写入“ changeprofile NewDomain”。如果策略允许,那么执行后进程的域就是“NewDomain”。“changeprofile”是“单程车票”,第二种方式“changehat”是“往返车票”。第二种方式是向/proc/self/attr/current 中写入“changehat token^hatdomain”,如“changehat 1234^hat”,如果策略允许,那么执行后进程的域就是“hat”。之后进程再向/proc/self/attr/current 中写入“changehat 1234”,进程的域就恢复为原先的域了。上面例子中的“1234”称为令牌(token),它是进程和内核之间的“小秘密”,专门用来让进程返回原先的域。
“changeprofile”可以用来做域间转换,包括转换到外部域和转换到子域。“changehat”只能用于父域转换到子域。
3. 命名空间
AppArmor 也提供了命名空间(namespace)的概念,不同命名空间中策略配置相互隔离,不同的命名空间中同名的域可以有不同的策略。在实现中命名空间是一个被“:”包裹的字符串:
:namespace://domainname
其中“//”是可选项。
切换命名空间只能通过“ changeprofile”方式,也就是向/proc/self/attr/current 中写入“:namespace://domainname”。
10.3 策略语言
AppArmor 的策略语言并不复杂,但是文档太少。要想完整地描述 AppArmor 的策略语言,需要阅读 AppArmor 用户态工具的源代码。下面举个例子,简单描述一下:
@{LIBVIRT}=” libvirt ”
/usr/sbin/libvirtd { #include <abstractions/authentication> capability kill, capability net_admin, /bin/* PUx,
㊀ 关于 no_new_privs,可以参考内核文档 prctl/no_new_privs.txt。
/sbin/* PUx, audit deny /sbin/apparmor_parser rwxl, change_profile -> @{LIBVIRT}-[0-9a-f]*-[0-9a-f]*, rlimit data <= 100M, }
AppArmor 的策略语言允许使用 include 语句包含一个已有的策略文件。 客体类别 capability和 rlimit 比较简单, capability 列出允许的能力, rlimit 列出限定的资源值。
客体类别文件略复杂。
其基本操作许可为: r(读)、 w(写)、 a(添加)、 l(链接)、 k(锁)、 m(在 mmap 的内存中执行)、 x(执行)。 x 又可以有若干前缀,比如“PUx”。下面对前缀做个分析,见表 10-1。
这些前缀可以组合使用,例如“pix”,意思是先找有没有与被执行文件匹配的域,如果没有就维持当前域不变。当然也不是所有前缀都可以组合的,例如“p”和“P”, “p”和“c”就不能组合。
不知读者是否注意到,一条策略语句的头部可以有修饰符,例如下面列出的语句:
audit deny /sbin/apparmor_parser rwxl,
audit 表示记录日志, deny 表示拒绝访问。 deny 的引入很重要,有了它, AppArmor 可以基于黑名单进行访问控制。白名单是指策略只列出允许的操作,没有列出的都不被允许。黑名单则相反。下面看个例子:
/bin/my-shell { file, capability, deny /home/zhi/my-test r, }
这个策略允许处于/bin/my-shell 域的进程做任何操作,除了读文件/home/zhi/my-test。下面再看一个“changehat”的例子:
#include <tunables/global> /usr/lib/apache2/mpm-prefork/apache2 { #include <abstractions/base> #include <abstractions/nameservice> capability kill, / rw, /** mrwlkix, ^DEFAULT_URL { #include <abstractions/base> #include <abstractions/nameservice> / rw, /** mrwlkix, } }hat 子域的定义需要前缀“^”。
10.4 模式
AppArmor 规定了四种工作模式: enforce、 complain、 unconfined、 kill。像其他安全模块一样, enforce 就是严格按照策略办事,不符合策略的就拒绝访问请求; complain 是允许访问请求,但会把违反策略的访问请求记录到日志中; unconfined 就是和没有 AppArmor 一样, AppArmor 不对访问请求做控制。不同的是 kill,这种模式下不仅拒绝访问请求,还会将违反策略的进程杀死。
每个域都有自己的工作模式。不同的域可以工作在不同的工作模式之中。规定工作模式的方法是在域名后添加“flags”参数。举个例子:
/usr/bin/firefox flags=(complain){ …
}
10.5 伪文件系统
10.5.1 proc 文件系统
/proc/[pid]/attr 目录及其下文件是 SELinux 引入内核的,其他安全模块也可以用它来实现功能。 AppArmor 用到了/proc/[pid]/attr 目录下的部分文件: current、 prev、 exec。
(1) current
此文件可读可写。读时返回进程所在的域。写时又分两种情况:
1) changehat写入一行:
changehat <token>[^<hat name>]
举个例子:
changehat 1234^hat1
意思是转到“hat1”子域,同时记录“1234”作为与之对应的令牌。这个令牌是在从子域返回时用到的,例如:
changehat 1234
就会从子域“hat1”返回刚才的父域。
2) changeprofile语法是:
changeprofile [:<namespace>:]<profile>
(2) prev
显示前一个域的名字。“前一个域”特指在使用“changhat”机制转换域时,内核中保留的父域(前一个域)。
(3) exec
这个文件的操作方法和“current”文件相同。“exec”文件对应进程中专门在 execve 系统调用中使用的域名。
10.5.2 sys 文件系统
在/sys/modules/apparmor/parameters 目录下有很多文件,通过它们可以动态调整或读取AppArmor 的运行状态。
(1) audit
查看或设置审计(日志)模式。文件的内容为一个字符串: normal、 quiet_denied、 quiet、noquiet、 all。从代码注释中可以看出这 5 种模式的含义:
security/apparmor/include/audit.h #define AUDIT_MAX_INDEX 5 enum audit_mode { AUDIT_NORMAL, /* follow normal auditing of accesses */ AUDIT_QUIET_DENIED, /* quiet all denied access messages */ AUDIT_QUIET, /* quiet all messages */ AUDIT_NOQUIET, /* do not quiet audit messages */ AUDIT_ALL /* audit all accesses */ }(2) audit_header
查看或设置审计日志消息中是否出现消息头部数据。文件内容为一个字符: “y”“Y”与“1”表示出现, “n”“N”与“0”表示不出现。
(3) debug
查看或设置 AppArmor 的调试模式。文件内容为一个字符: “y”“Y”与“1”表示处于调试模式, “n”“N”与“0”表示不处于调试模式。
(4) enabled
这是一个只读文件,用于查看 AppArmor 的状态。其内容为一个字符, “Y”表示 AppArmor处于使能状态,其他表示 AppArmor 不处于使能状态。
(5) lock_policy
查看或设置策略文件锁的状态。文件内容为一个字符: “y”“Y”与“1”表示不能修改策略, “n”“N”与“0”表示可以修改策略。
(6) logsyscall
和前面的文件类似,这个文件的内容是一个表示布尔值的字符。但是这个文件所对应的布尔值似乎在内核代码中没有用到。
(7) mode
查看或设置 AppArmor 的整体模式。文件的内容是一个字符串,值为下列之一: “enforce” “complain”“kill”与“unconfined”。
(8) paranoid_load
决定加载策略文件时是否进行严格检查。文件内容为一个字符: “y”“Y”与“1”表示严格检查, “n”“N”与“0”表示不做严格检查。
(9) path_max
查看或设置路径的最大长度,文件内容是一个表示长度的整数。
10.5.3 securityfs 文件系统
同 Tomoyo 一样, AppArmor 也使用了 securityfs,相关的目录通常在/sys/kernel/security/ apparmor。此目录下包含 4 个文件和两个子目录。下面描述它们的用法。
(1) .load
这个文件用于加载策略。 AppArmor 的策略和 SELinux 类似,也是二进制格式的。用户需要预先用一个工具(apparmor_parser)将用户友好的文本格式的策略文件编译为内核更易处理的二进制格式策略文件,然后再通过.load 文件载入内核。
(2) .replace
这个文件用于加载策略,和.load 的区别是, .load 是添加, .replace 是替换。
(3) .remove
这个文件用于删除策略。删除以域为单位。
(4) profiles
这是一个只读文件,虽然文件允许位标记可写,但是内核根本没有实现此文件的写函数!通过它可以读出所有策略。
(5) policy
这是一个目录,其下又有两个子目录: 1) namespaces
其下的文件或目录与名字空间有关。2) profiles
其下的文件或目录与域有关。
(6) features
这是一个目录,其下有子目录和文件,全部为只读接口,通过它们可以得到当前系统支持的 AppArmor 特性㊀ 。举个例子:
$ cat /sys/kernel/security/apparmor/features/file/mask create read write exec append mmap_exec link lock
当前系统对文件相关的操作许可控制包括: create、 read、 write、 exec、 append、 mmap_exec、link、 lock。
10.6 总结
AppArmor 的最大特点是“不安全”,或者换一个积极正面的词汇——简单易用。 AppArmor的这个特点体现在两个方面:
(1) AppArmor 设计的宗旨是安全加固某个应用或某几个应用。
(2) AppArmor 提供方法让用户可以用黑名单的方式定制策略。
AppArmor 这么做有一定道理。在现实中,一个系统迫切需要安全加固的往往只是一个或几个应用。例如一个 Web 服务器,只有 Web 服务进程和外界交流, Web 服务安全了, 90%的安全问题就解决了。
抛开安全性, AppArmor 的缺陷是文档。到 2016 年 2 月, AppArmor 的文档还不能算是完善,一些特性语焉不详,一些文档前后矛盾。一个标榜易用性的安全模块却没有把文档做好,实在是有些不应该。
10.7 参考资料
读者可参考 http://wiki.apparmor.net/index.php/Main_Page。
习题
AppArmor 设计的宗旨是安全加固某个或某几个应用。思考一下,如何利用 AppArmor 来加固整个系统?是否需要修改代码?如果不修改代码,又该如何制定策略?