SELinux策略下回归用例适配教程 - 第4节

类型强制(TE)核心规则与 allow 规则详解

好,咱们今天正式切入 SELinux 策略的心脏——类型强制(Type Enforcement,简称 TE)。说白了,SELinux 所有安全决策的根基就是 TE 规则。而我个人做回归测试这么多年,踩过的坑、修复的漏洞,十个里有八个跟 TE 规则写错或没写全有关。所以这一章你务必吃透。

TE 是什么?一句话讲清楚

类型强制,就是给系统中的每个 主体(进程)和 客体(文件、端口、套接字等)都打上一个标签——这个标签叫“类型”(Type)。

  • 主体类型:比如 httpd_t、init_t、user_t。
  • 客体类型:比如 httpd_log_t、etc_t、tmp_t。

然后 SELinux 根据预设的规则,决定“某个主体类型能否针对某个客体类型,执行某种操作”。这个操作不是简单的 read/write,而是一组细粒度的访问向量(Access Vector)。

核心结论:没有明确的 allow 规则,默认就是拒绝。这就是“默认拒绝”的安全模型。

allow 规则的语法结构

allow 规则是 TE 中使用最频繁、也是最容易出错的地方。先看一个典型例子:

allow httpd_t httpd_log_t:file { read write };

这条规则的意思是:类型为 httpd_t 的进程,可以对类型为 httpd_log_t 的文件(file 类),执行 read 和 write 操作。

语法拆解:

  • 主体类型(一个或多个,用逗号分隔)
  • 客体类型(一个或多个)
  • 对象类(冒号后,比如 file、dir、socket、process 等)
  • 访问向量(花括号内,多个权限用空格分隔)

我在项目里见过不少新手把顺序搞反,把主体和客体写颠倒。结果策略加载后没报错,但进程死活没权限。我曾经花了一整个下午追查一个 SSH 登录失败的问题,最后发现是 allow 规则里把 sshd_t 和 login_t 的客体写反了——方向错,权限全废。

对象类与访问向量的对应关系

不同的对象类,能使用的访问向量是不同的。比如文件类和目录类就不一样。下面这张表格列出了几个常见对象类及其常用访问向量:

对象类 常用访问向量(举例)
file read write create unlink open getattr
dir read write add_name search remove_name
socket bind connect listen accept send recv
process fork signal transition dyntransition
tcp_socket name_connect name_bind recv_msg send_msg

小提示:访问向量定义在 access_vectors 文件中。你可以用 seinfo -cfile -x 来查看某种对象类支持的全部访问向量。我在做回归用例时,经常先用这个命令确认哪些向量可用,再写 allow 规则,避免写了不存在的权限导致编译失败。

allow 规则的多种写法

单主体对单客体

最直接的形式,上面已经见过。适合针对特定类型精确授权。

批量授权:使用通配符和属性

现实策略中不可能给每个类型都写一条规则。SELinux 提供了两种批量手段:

  • 属性(attribute):把多个类型打上同一个标签,然后对属性写 allow 规则。例如所有域类型都继承 domain 属性,那么 allow domain self:process signal 就允许所有域对自己发信号。
  • 通配符:使用 * 表示“所有”,但要注意它只对当前层次生效。我建议一般用属性,通配符容易无意中扩大权限。

条件 allow 规则

通过布尔值(boolean)动态控制规则是否生效。写法:

allow httpd_t httpd_cvs_spool_t:file { read };
if (httpd_enable_cgi) {
    allow httpd_t httpd_cvs_spool_t:file write;
}

这在回归测试中特别重要——同一个功能可能在布尔值开启和关闭下表现完全不同。你想想看,如果你的用例只测了开启状态,关闭状态下的权限缺失就测不到了。我曾经因为没覆盖布尔值关闭分支,导致正式环境升级后策略生效,网站直接 403。

使用宏简化规则

大型项目中多用宏(tunable_policy、gen_tunable 等)来封装重复逻辑。但作为回归测试人员,你更要关注展开后的实际规则。我个人习惯在调试时用 sesearch --allow 来反查宏展开后的具体 allow,避免被宏绕晕。

核心规则树:TE 决策流程

理解了 allow 规则后,我们看看 SELinux 到底是怎么做“允许 or 拒绝”决策的。这段流程我建议你刻在脑子里,因为回归用例设计就是围绕它展开的。

TE 决策流程:allow 规则匹配逻辑 主体类型 (如 httpd_t) 发起访问请求 检查客体类型 + 对象类 (如 httpd_log_t : file) 查找匹配的 allow 规则 (遍历策略库) 检查访问向量是否包含 (如 read, write) 拒绝 (AVC 审计) 允许 (正常执行) 匹配规则不存在 → 默认拒绝 匹配规则中缺少向量 → 拒绝

回归测试中必须关注的 allow 规则陷阱

陷阱一:遗漏客体类型转换

allow 规则不止管“进程访问对象”,还管主体类型转换(type transition)。比如 init 启动一个服务,需要 init_t 到 httpd_t 的转换规则。如果漏了 allow init_t httpd_t:process transition,服务根本起不来。

避坑指南:我曾经在测试一个容器化部署场景时,主机策略允许进程读写容器文件,但容器内进程始终无法创建新进程。查了半天,发现缺少 domain 转换的 allow。所以回归用例里一定要包含“新创建进程/文件是否获得预期类型”的检测点。

陷阱二:同一个对象类下不同访问向量的依赖

有些操作需要多个访问向量组合。比如要删除一个文件(unlink),通常还需要父目录的 remove_name 和 write 权限。如果你只写了 allow A B:file unlink,忘了写目录的权限,操作会失败。这就是很多“权限不足”的隐蔽原因。

陷阱三:属性 vs 直接类型写法的差异

用属性写的 allow 规则,在 sesearch 里显示的是属性名,而不是展开后的类型。你如果在回归脚本里直接用 sesearch -A -t httpd_t 可能看不到通过属性授权的规则。我建议测 always 策略时,优先用 sesearch -A -b 或 apol 做全量规则遍历。

总结:回归用例设计对照表

基于以上内容,我整理了一个回归测试需要验证的 check list:

检查项 典型命令/方法 说明
主体类型是否正确 ps -eZ 确认进程运行在预期的域中
客体类型是否正确 ls -Z 确认文件、端口等标签正确
allow 规则是否存在 sesearch --allow -s httpd_t -t httpd_log_t -c file -p read 精确检查某个权限
布尔值分支覆盖 分别设置布尔值 0/1 后重复测试 确认两种条件下规则都生效
域名转换规则 sesearch -T -s init_t 检查 domain transition 规则
审计日志验证 ausearch -m AVC 拒绝时应有 audit 记录

嗯,这一章的内容就到这里。allow 规则看着简单,实际要覆盖所有分支场景并不容易。回归测试中我见过太多因为漏写或错写一条 allow 规则导致的服务故障——所以吃透这一章,你的 SELinux 策略适配能力就扎实了一半。