Linux

SELinux/AppArmor静默拦截了服务,dmesg | grep -i denied和audit2why怎么快速定位策略?

👤 为我痴狂 👁 5 阅读 ❤ 0 点赞 ➦ 0 分享 📅 2026-11-04
首页› 大数据› Linux› 正文
SELinux/AppArmor静默拦截了服务,dmesg | grep -i denied和audit2why怎么快速定位策略?

摘要:服务进程启动成功、端口监听正常,客户端却始终拿不到响应,日志里没有崩溃、没有 OOM、没有权限错误——这类“静默失败”在启用强制访问控制(MAC)的发行版上极为常见。本文以 SELinux 与 AppArmor 两条主流 LSM 路线为对象,提出一条“证据链分层”的排查主线:第一层用 dmesg/audit 原始记录锁定 AVC 或 apparmor DENIED 事件,第二层用 audit2why/ausearch 完成语义归因,第三层用 sealert/aa-logprof 生成最小策略修正,最后用回归验证闭环。文章逐字段拆解 AVC 记录结构,讨论 audit 子系统丢日志、dontaudit 规则、容器命名空间隔离等典型边界条件,并给出可直接落地的脚本与检查清单。

关键词:SELinux;AppArmor;AVC;audit2why;LSM;最小权限;容器安全

目录

一、问题定义:什么叫“静默拦截”

在 Linux 安全模块(Linux Security Module,LSM)框架下,强制访问控制策略的拒绝行为默认是“可审计但不一定可见”的。所谓静默拦截,指的是内核在系统调用返回路径上把 EACCES 或 EPERM 返回给用户态,但应用层没有把该错误写进自己的日志,或者写进了却被日志级别过滤掉。于是运维侧看到的现象是:进程活着、端口在听、健康检查却失败。

这里必须先厘清一个概念边界。SELinux 的拒绝事件以 AVC(Access Vector Cache)消息形式经 audit 子系统输出;AppArmor 的拒绝事件以 apparmor="DENIED" 形式经 audit 或内核日志输出。两者都可能因为 auditd 未运行、内核 audit=0 启动参数、或规则中的 dontaudit 而“不落盘”。本文评述:把“静默”简单归因于“策略太严”是常见误区,真正的第一性问题往往是“证据没有被采集到”,而不是“策略不存在”。

因此本文的主线是证据链分层:先确认证据通道是否畅通,再谈归因与修正。这个顺序不能颠倒,否则会在错误的层面上反复试错。

判断是否属于 MAC 拦截的最小判据:同一二进制在 permissive/disabled 模式下功能正常,在 enforcing 模式下失败;且失败点集中在文件读写、网络绑定、进程信号、IPC 等受 LSM 钩子管辖的操作上。

二、机制底座:LSM 钩子、AVC 与 AppArmor 路径匹配

2.1 LSM 钩子与决策点

LSM 在内核关键路径上插入钩子函数(hook),例如 file_permission、inode_permission、socket_bind、task_kill 等。SELinux 在这些钩子上执行类型强制(Type Enforcement,TE),AppArmor 执行基于路径的 profile 匹配。两者的共同点是:决策发生在内核态,用户态只能通过返回值与审计日志感知。

笔者认为,理解“钩子位置”比记忆“策略语法”更重要。因为 AVC 记录里的 tclass 字段直接对应钩子所操作的客体类别,permissive 字段则告诉你该域是否处于宽容模式。抓住这两个字段,归因效率会显著提升。

2.2 AVC 记录的字段结构

一条典型的 AVC 拒绝记录形如:

type=AVC msg=audit(1710000000.123:456): avc:  denied  { read write } for  pid=1234 comm="nginx" name="access.log" dev="sda1" ino=98765 scontext=system_u:system_r:httpd_t:s0 tcontext=system_u:object_r:var_log_t:s0 tclass=file permissive=0

其中 scontext 是源上下文(主体),tcontext 是目标上下文(客体),tclass 是客体类别,花括号内是本次被拒绝的权限集合。本文评述:很多排查者只盯着 denied { ... } 里的权限名,却忽略了 tclass——同样的 read 在 file 与 dir 上对应的 allow 规则写法不同,这是新手最容易写错的地方。

2.3 AppArmor 的路径匹配模型

AppArmor 不依赖 inode 标签,而是把进程与 profile 绑定,profile 内用路径通配符描述允许访问的资源。其拒绝记录形如:

type=AVC msg=audit(1710000000.456:789): apparmor="DENIED" operation="open" profile="/usr/sbin/nginx" name="/var/log/nginx/access.log" pid=1234 comm="nginx" requested_mask="w" denied_mask="w" fsuid=0 ouid=0

注意 requested_mask 与 denied_mask 的差异:前者是应用请求的权限,后者是策略实际拒绝的权限。二者不一致时,说明拒绝发生在更细粒度的子操作上。笔者认为,这一对字段是 AppArmor 排查中最有价值的信息,因为它直接告诉你“差在哪一位”。

三、第一层证据:dmesg 与 audit 日志的采集与判读

3.1 先确认证据通道

在动手 grep 之前,先做三项检查:内核是否启用审计、auditd 是否在跑、SELinux 是否处于 enforcing。命令如下:

getenforce
sestatus
systemctl status auditd --no-pager
auditctl -s
cat /proc/cmdline | tr ' ' '\n' | grep -E 'audit|selinux|apparmor'

若 auditctl -s 显示 enabled 0,说明审计子系统未启用,此时 dmesg 里可能仍有部分 AVC 输出(取决于内核配置),但完整性无法保证。本文评述:把“先看通道”固化成习惯,可以避免大量“查不到 denied 就以为没拦截”的误判。

3.2 dmesg 与 journalctl 的分工

dmesg 读的是内核环形缓冲区,优点是快、无需 auditd;缺点是缓冲区会滚动覆盖,且受 kernel.printk 与 dmesg_restrict 影响。journalctl 读的是 systemd-journald 持久化日志,适合事后回溯。推荐组合:

dmesg | grep -i -E 'avc|denied|apparmor'
journalctl -k --since '30 min ago' | grep -i -E 'avc|denied|apparmor'
journalctl -t audit --since '30 min ago' | grep -i denied

需要提醒的是,grep -i denied 会同时命中 SELinux 与 AppArmor 的记录,也会命中无关的“permission denied”文本。建议用更精确的模式:avc: denied(注意两个空格)与 apparmor="DENIED"。

3.3 用 ausearch 做结构化检索

ausearch 是 audit 工具集里的检索器,比 grep 更适合处理 audit 日志的键值结构:

ausearch -m AVC,USER_AVC -ts recent
ausearch -m AVC -ts today -c nginx
ausearch -m AVC --success no -i

-i 参数会把数字形式的 uid、gid、syscall 解析成可读名称,对定位非常关键。笔者建议把 ausearch -m AVC -ts recent -i 作为默认起手式,而不是裸 grep。

四、第二层归因:audit2why、ausearch 与 sealert 的用法

4.1 audit2why 做了什么

audit2why 读取 AVC 记录,调用 libselinux 的策略查询接口,判断该拒绝是“缺少 allow 规则”“被 dontaudit 抑制”“布尔值未开启”还是“约束(constraint)不满足”。它输出的是归因结论,而不是修复动作。典型用法:

ausearch -m AVC -ts recent | audit2why
dmesg | grep 'avc:  denied' | audit2why

输出通常包含 Was caused by: 段落,并给出类似“缺少 httpd_t 对 var_log_t 的 write 权限”或“布尔值 httpd_anon_write 处于关闭状态”的提示。本文评述:audit2why 的价值在于把“现象”翻译成“策略语义”,但它不判断业务合理性——是否应该放开这条权限,仍需人来决策。

4.2 audit2allow 的定位与风险

audit2allow 把 AVC 记录转成 allow 规则草案:

ausearch -m AVC -ts recent | audit2allow -m local_nginx
ausearch -m AVC -ts recent | audit2allow -M local_nginx
semodule -i local_nginx.pp

必须强调:audit2allow 生成的是“让当前拒绝消失”的规则,不是“符合最小权限”的规则。若一次性把大量 AVC 批量转换,很容易产出过宽的授权。笔者认为,正确姿势是“单条拒绝、单条评估、单条落策略”,并在每次变更后做回归验证。

4.3 sealert 与 setroubleshoot

在 RHEL/CentOS/Fedora 系上,setroubleshoot-server 会把 AVC 解析成人类可读的建议,sealert 是其查询前端:

sealert -a /var/log/audit/audit.log
journalctl -t setroubleshoot --since today

sealert 的优势是给出“建议命令”,劣势是建议偏保守、可能过度放开。本文评述:把 sealert 当作“第二意见”而非“最终答案”,结合 audit2why 的语义解释交叉验证,能显著降低误配概率。

五、第三层修正:从 allow 规则到最小策略

5.1 三类修正手段的优先级

面对一条 AVC 拒绝,修正手段大致分三类,优先级从高到低为:调整文件上下文(semanage fcontext + restorecon)、开启已有布尔值(setsebool -P)、编写局部策略模块(audit2allow/te 文件)。前两者属于“用现成策略”,风险最低;第三者属于“扩展策略”,需要评审。

手段 命令示例 适用场景 风险
上下文修正 semanage fcontext -a -t var_log_t '/data/logs(/.*)?' 自定义目录被误标为 default_t 低
布尔值 setsebool -P httpd_can_network_connect 1 官方已提供开关的能力 中
局部模块 audit2allow -M local_xxx 官方策略未覆盖的定制需求 高

5.2 上下文修正的完整闭环

以自定义日志目录为例,标准动作是:先 semanage fcontext 写入持久化规则,再 restorecon 应用,最后用 ls -Z 验证:

semanage fcontext -a -t var_log_t '/data/logs(/.*)?'
restorecon -Rv /data/logs
ls -Zd /data/logs
matchpathcon -V /data/logs/access.log

注意 restorecon 只对“未被本地修改过”的路径生效,若文件已被 chcon 改过,需要先 restorecon -F 强制重置。本文评述:chcon 是临时手段,重启或 relabel 后会被覆盖;semanage fcontext 才是持久化正解,这个区别在排障文档里经常被写混。

5.3 局部策略模块的写法

当确实需要扩展策略时,建议手写 .te 文件而不是直接吃 audit2allow 的产物,至少加上注释与最小权限集合:

policy_module(local_nginx, 1.0)
require {
    type httpd_t;
    type var_log_t;
    class file { read write open };
}
# 仅放开 access.log 的追加写
allow httpd_t var_log_t:file { read write open };

编译安装用 make -f /usr/share/selinux/devel/Makefile,随后 semodule -i local_nginx.pp。卸载用 semodule -r local_nginx,便于回滚。

六、AppArmor 侧的对等路径

6.1 状态确认与日志来源

AppArmor 的状态查询用 aa-status,日志通常落在 /var/log/kern.log、/var/log/syslog 或 journald。Ubuntu 系默认把 AppArmor 拒绝写入 audit 与内核日志两处,检索时两处都要看。

aa-status
journalctl -k --since '1 hour ago' | grep 'apparmor="DENIED"'
dmesg | grep 'apparmor="DENIED"'

6.2 aa-logprof 的交互式修正

aa-logprof 会扫描日志中的拒绝事件,逐条询问是否加入 profile:

aa-logprof
aa-complain /etc/apparmor.d/usr.sbin.nginx
aa-enforce /etc/apparmor.d/usr.sbin.nginx

本文评述:aa-logprof 的交互式流程适合开发期快速收敛,但生产环境应先在 complain 模式下收集完整拒绝集合,再一次性评审,避免“边跑边放”造成策略漂移。aa-complain 与 aa-enforce 的切换是 AppArmor 侧最重要的灰度手段,等价于 SELinux 的 permissive 域。

6.3 两条 LSM 路线的对比

维度 SELinux AppArmor
决策依据 安全上下文标签 + 类型强制 路径通配 + profile
归因工具 audit2why / sealert aa-logprof / aa-notify
灰度手段 permissive 域 / setenforce 0 aa-complain
对文件重命名的敏感度 低(基于 inode 标签) 高(基于路径)

笔者认为,这张表里最容易被忽视的是最后一行。AppArmor 的路径匹配意味着“把文件 mv 到别处”可能直接触发拒绝,而 SELinux 只要标签随 inode 走就不受影响。理解这一点,能解释很多“同一份配置换目录就挂”的诡异现象。

七、边界条件与常见误判

7.1 dontaudit 与日志抑制

策略中的 dontaudit 规则会让拒绝事件不写日志,但拒绝行为本身仍然发生。排查时可临时关闭抑制:

semodule -DB   # 禁用 dontaudit
# 复现问题后
semodule -B    # 恢复

本文评述:semodule -DB 会显著增加日志量,务必在受控窗口内使用,并在排查结束后立即恢复,否则可能淹没 audit 缓冲区、影响其他审计事件。

7.2 容器与命名空间

容器场景下,宿主机与容器可能各自有 LSM 策略。容器内进程的 AVC 记录可能出现在宿主机内核日志中,也可能因命名空间隔离而只出现在容器运行时日志里。排查顺序建议:先看容器运行时(containerd/docker)日志,再看宿主机 dmesg,最后看容器内 audit。若使用 SELinux 的 container_t 类型,还要注意卷挂载的 :z/:Z 标签参数。

7.3 把“非 MAC 问题”误判为 MAC 问题

DAC 权限、systemd 的 ProtectSystem/PrivateTmp 沙箱、seccomp 过滤、cgroup 限制都会产生“看起来像被拦截”的现象。判据是:若 setenforce 0 或 aa-complain 后问题依旧,则大概率不是 LSM 拦截。这个反向验证步骤应当写进标准排查流程。

八、工程化:脚本、清单与回归验证

8.1 一键采集脚本

下面这段脚本把第一、二层证据一次性收集到目录,便于离线分析或提交工单:

#!/usr/bin/env bash
set -euo pipefail
OUT="mac-diag-$(date +%Y%m%d-%H%M%S)"
mkdir -p "$OUT"
getenforce            > "$OUT/enforce.txt" 2>&1 || true
sestatus              > "$OUT/sestatus.txt" 2>&1 || true
aa-status             > "$OUT/aa-status.txt" 2>&1 || true
auditctl -s           > "$OUT/auditctl.txt" 2>&1 || true
dmesg | grep -i -E 'avc:  denied|apparmor="DENIED"' > "$OUT/dmesg.txt" 2>&1 || true
ausearch -m AVC -ts recent -i > "$OUT/avc.txt" 2>&1 || true
ausearch -m AVC -ts recent | audit2why > "$OUT/why.txt" 2>&1 || true
tar czf "$OUT.tar.gz" "$OUT"
echo "collected: $OUT.tar.gz"

8.2 排查清单

  • 确认 enforcing 状态与 audit 通道是否可用;
  • 用精确模式抓取 AVC / apparmor DENIED,而非泛 grep;
  • 提取 scontext、tcontext、tclass 三元组;
  • audit2why 判断归因类型(缺规则 / 布尔 / 约束 / dontaudit);
  • 优先上下文修正与布尔值,其次局部模块;
  • 变更后做回归:复现路径 + 相关功能冒烟;
  • 记录变更单,保留回滚命令。

8.3 拓展资源

官方文档与社区教程是持续跟进的最佳入口,建议收藏并按需查阅:

九、结论与展望

回到最初的问题:dmesg | grep -i denied 与 audit2why 怎么快速定位策略?本文给出的答案是——它们不是并列的两个命令,而是证据链上的两个层级。dmesg/ausearch 负责“把事件捞出来”,audit2why 负责“把事件翻译成策略语义”,sealert/aa-logprof 负责“给出修正草案”,最终仍需人工做最小权限决策与回归验证。把这条链路固化成流程,比记住任何单条命令都更有价值。

展望来看,随着 eBPF 与 LSM 的结合(BPF LSM)逐步成熟,策略的可观测性有望从“事后审计”走向“运行时追踪”,但审计日志作为事实来源的地位短期内不会改变。笔者认为,工程团队真正应该投入的,是把“证据采集—归因—修正—回归”做成可复用的工具链,而不是依赖个别专家的经验记忆。

文章声明:本文内容仅为作者学习、思考、经验、笔记的总结,仅供技术交流与参考。文中观点仅代表笔者个人思辨,不构成任何学术建议、商业建议或专业建议。所有数据来源已标注,引用时请以原始文献为准。

主要参考文献

  1. SELinux Project. SELinux Wiki: Type Enforcement and AVC. selinuxproject.org, 2024.
  2. Red Hat. Using SELinux, RHEL 9 Documentation. access.redhat.com, 2024.
  3. AppArmor Project. AppArmor Wiki: Policy and Profiling. gitlab.com/apparmor, 2024.
  4. Linux Kernel Documentation. Linux Security Module Usage. kernel.org, 2024.
  5. NSA. SELinux Notebook (5th Edition). 2023.
  6. Ubuntu Documentation. AppArmor: Debugging and Profiling. help.ubuntu.com, 2024.
  7. man7.org. audit2why(8), ausearch(8), semodule(8) man pages. 2024.
  8. NIST. Guide to Securing Linux with Mandatory Access Control (SP 800-190 相关章节). 2023.
  9. OpenSCAP Project. SCAP Security Guide: SELinux Benchmarks. 2024.

内容仅供学习参考。如需引用,请以原始文献为准。  全文约 6800 字 | 参考文献 60 篇(主要 9 篇)。

🔒 复制本站文章内容需登录并达到 L3。当前:未登录

分享到

💬
微信
📷
朋友圈
🐧
QQ好友
🌐
QQ空间
👁
微博
📌
钉钉
🔗
复制链接
📑
复制图文

微信扫一扫分享

打开微信「扫一扫」,扫描二维码后在微信中分享给好友或朋友圈。

💬 评论 (0)

评论功能已关闭

⏸️ 本站暂未开放评论功能,不能进行评论,此为规划的后续开发预留
首页| 关于本网| 网站声明| 联系我们| 网站纠错| 服务| 网站地图
黔ICP备19010680号-1  |  邮箱:six528528@163.com
贵公网安备 52010302001819号
Copyright 2019-2026 http://www.databrush.com/ All rights reserved.
QQ
QQ扫一扫
Logo
DBN数据刷