第2章 SELinux安全模型:MAC原理、与DAC的区别、安全上下文
我记得刚接手一个嵌入式项目时,同事扔给我一份SELinux策略文件,说“照着配就行”。我瞄了一眼,发现所有进程都是用同一个域运行的——说白了就跟没开一样。从那天起,我意识到不理解MAC和DAC的区别,写出来的策略就是摆设。
2.1 强制访问控制(MAC)到底是什么?
MAC的全称是Mandatory Access Control,强制访问控制。它和传统Linux的DAC(自主访问控制)最本质的区别,就三个字:不由你说了算。
传统DAC下,文件所有者可以随意改权限:chmod 777,想把文件给别人读就给谁读。但SELinux的MAC是在DAC之上再加一层强制策略,连root用户都得遵守。说白了,你不是文件的主人,策略才是主人。
我在项目中遇到过一桩经典案例:某服务需要写日志文件,/var/log/app.log,它的父目录给了755权限。按理说DAC层面没问题,但日志就是写不进去。查了半天才发现,SELinux策略规定该服务域只能往var_log_t类型的文件里写,而那个日志文件居然是admin_home_t类型——这当然是撞枪口上了。
write权限是独立控制的,你甚至可能遇到“DAC允许写,MAC禁止写”的场景。这时候audit2why是你的救命稻草。
2.2 DAC与MAC的核心差异
你想想看,DAC就像你自己家门的钥匙,你可以配一把给朋友。但MAC是整个小区物业的电子门禁,即使你有钥匙(DAC允许),物业说你不能进那个楼,你就进不去。系统里同时存在两套门禁系统,且MAC优先。
| 特性 | DAC(自主访问控制) | MAC(强制访问控制) |
|---|---|---|
| 控制者 | 文件/资源的所有者 | 系统安全策略(root也要遵守) |
| 权限粒度 | 读、写、执行(粗粒度) | 具体到每个系统调用,如open、read、ioctl |
| 绕过方式 | chmod 777、root不受限 | 除非禁用或加载宽松策略,否则无法绕过 |
| 典型错误 | 权限过度授予 | 类型标注错误或权限缺失(AVC审计) |
| 与进程关系 | 进程继承创建者的UID/GID | 进程运行在特定安全上下文中,策略控制其域对客体的访问 |
嗯,这张表我经常在培训时用。你记住一条原则:DAC是“你能做什么”,MAC是“你可以做什么”。两者同时满足才能成功,任何一层拒绝都会失败。
setenforce 0临时关闭SELinux后,MAC被绕过,但DAC仍然生效。恢复策略模式后必须重新标记系统文件上下文,否则重启时会满屏AVC拒绝。
2.3 安全上下文——SELinux的身份标识
安全上下文(Security Context)是什么?就是SELinux给每个主体(进程)和客体(文件、端口、设备)打上的标签。这个标签由四部分组成:user:role:type:level。我个人习惯把它看成“系统身份证号码”,系统根据它来决定能不能放行。
示例:进程的安全上下文
system_u:system_r:httpd_t:s0
↓ ↓ ↓
user role type level
示例:文件的安全上下文
system_u:object_r:httpd_sys_content_t:s0
↓
object_r 表示客体角色
这里的关键是type字段。对于进程,type叫“域”(domain),对于文件,type叫“类型”(type)。规则就是:域对类型有哪些权限。
比如httpd_t域被允许read和getattr httpd_sys_content_t类型的文件,但不允许写httpd_log_t类型的文件——除非策略里明确给了write。
我最常用的一条调试命令:
# 查看当前进程的安全上下文
ps -eZ | grep httpd
# 示例输出:
system_u:system_r:httpd_t:s0 12345 ? 00:00:00 httpd
# 查看文件的安全上下文
ls -Z /var/www/html/index.html
# 示例输出:
system_u:object_r:httpd_sys_content_t:s0 /var/www/html/index.html
看到不同?进程的role是system_r,文件的role是object_r。这是SELinux强制区分的,搞混了策略引擎就不认了。
2.4 策略引擎如何匹配安全上下文?
当进程试图访问文件,SELinux会查策略规则库。规则写法大致是:
allow httpd_t httpd_sys_content_t:file { read getattr open };
这行规则说:允许httpd_t域对类型为httpd_sys_content_t的文件执行读取操作。如果类型不匹配或权限不在白名单里,SELinux会生成AVC拒绝审计日志,并返回EACCES给进程。
我见过最诡异的一次:某脚本在/tmp里创建临时文件,然后试图读取自己刚写的东西——居然被拒绝了。原来/tmp目录是tmp_t类型,脚本进程的域是initrc_t,策略里没有定义initrc_t对tmp_t的写权限,哪怕脚本是root跑也没用。
避坑指南:不能想当然以为“同一个进程写完后自己读”就肯定OK。MAC下每一笔访问都要过策略引擎,进程的域、客体的类型、中间临时文件的上下文,缺一不可。
2.5 安全上下文结构总览
上面这张图展示了我刚才说的流程。主体和客体各自带着安全上下文标签进入策略引擎,引擎根据规则决定是否放行。注意:DAC的检查在MAC之前,但DAC通过了不一定MAC就能过,两条线都绿灯才行。
2.6 回归测试中安全上下文的核心作用
回归测试用例适配,说白了就是确保你的策略变更不会导致正确的安全上下文被拒绝。我举个例子:你新增了一个服务进程,需要写日志到/var/log/myapp。你给进程域打了myapp_t,给日志目录和文件打了myapp_log_t。但回归测试时要验证:
- 进程能否读取自己的配置文件(
myapp_conf_t类型) - 进程不能读取
/etc/shadow(shadow_t类型) - 日志目录的父目录是否也允许递归访问
- 重启服务后上下文不会自动重置(比如
restorecon后是否正确)
嗯,这里有一个几乎所有新人都会犯的错:他们只检查DAC权限,却忘了检查ls -Z输出的类型。我曾经见过一个生产事故,就是升级后selinux-policy-targeted包更新导致某个文件类型被重新标注,服务起不来,最后发现是restorecon把本地自定义类型给覆盖了。从那以后,我要求所有回归用例必须包含一条ls -Z前后比对步骤。
一个实用的回归测试检查清单(安全上下文部分):
- 确认所有受影响的进程域(type)在策略中正确定义
- 确认文件/端口/设备等客体的类型标注与策略一致
- 确认是否有
dontaudit规则掩盖了潜在的AVC告警 - 使用
seinfo -t列出所有关联类型,看是否有冲突 - 对关键路径执行
audit2allow生成允许规则并回归验证
2.7 再聊一个避坑经验
关于MLS(多层安全)中的级别字段(s0, s0:c0.c1023等),大部分场景下不用管,直接用s0。但如果你在政府或军工项目里遇到了,注意级别检验是严格的分层结构,低级别进程无法读高级别数据。我修过一个bug,就是开发环境级别配错了,s0-s0:c0.c255与s0:c0.c255含义不同,前者表示从s0到低敏感度范围,后者表示只有s0加类别集——这个区别让策略引擎拒绝了一堆合法操作。回归测试时一定要检查level字段的完整性。
好了,总结一下本章核心:
- MAC:强制的、不可绕过的访问控制,优先级高于DAC。
- DAC vs MAC:前者由拥有者决定,后者由系统策略决定,两者都要通过。
- 安全上下文:
user:role:type:level,type是核心,绑定进程域与客体类型。
这些概念是后续回归用例适配的所有基础。你如果连安全上下文是什么意思都说不清楚,那策略文件写出来只能凭运气——而运气通常不太好。