LSM)认知梳理,覆盖 LSM 的核心机制、发展脉络、6.x 内核阶段能力演进以及实战落地思路SELinux、AppArmor 与 eBPF-LSM 的关系和使用边界我不再将这个世界与我所期待的、塑造的圆满世界比较,而是接受这个世界,爱它,属于它。---《悉达多》
持续分享技术干货,感兴趣小伙伴可以关注下 ^_^
在 Linux 安全生态中,Linux Security Modules(LSM)是贯穿内核与用户态、连接传统主机安全与云原生安全的核心框架。它打破了早期 Linux 单一安全模型的局限,通过模块化设计让不同安全策略按需集成,成为企业级安全防护的底层支柱。
在当前 AI agent 的场景里看,LSM 的价值会更直观。一个真正可用的 agent 往往不只是“会聊天”,而是具备读取文件、调用命令、访问网络、操作容器乃至连接集群控制面的能力。风险点也恰恰在这里:一旦 agent 进程被提示词绕过、插件被滥用、依赖链存在漏洞,问题很快就会从“模型输出不准”升级成“主机权限外溢”。
这时候,单靠应用层的参数校验、提示词约束或者业务代码里的 if/else 是不够的,因为这些限制都运行在同一个信任域里;而 LSM 能把约束下沉到内核对象访问层面,变成真正的“最后一道硬边界”。换句话说,哪怕 agent 已经拿到了某个 Linux 用户身份,SELinux、AppArmor、eBPF-LSM 仍然可以继续裁决:它到底能读哪些目录、能执行哪些二进制、能连接哪些 socket、能不能碰 ssh key、/var/run/docker.sock、kubeconfig 这类高敏感资产。
从工程落地视角看,LSM 很适合给 AI agent 做“分层最小权限”设计:
agent 划分独立安全域,例如检索型 agent 只读知识库目录,运维型 agent 仅允许执行白名单命令agent 主进程与高风险工具执行器拆开,通过不同 SELinux type 或 AppArmor profile 做隔离,避免“一次越权,全盘通吃”agent 对主机敏感文件、Unix Socket、挂载点和网络端口的访问,把能力收敛到任务所需的最小集合agent 在什么上下文下触发了哪次拒绝”,方便后续做策略收敛与攻击溯源如果再往云原生方向延伸,这个思路会更重要。很多企业开始让 AI agent 直接参与 CI/CD、集群巡检、日志分析、自动化修复,这意味着它背后连接的是更高价值的基础设施。此时 LSM 不只是传统主机加固组件,更像是 agent runtime 的内核级权限闸门:它可以和 namespace、cgroup、capabilities、seccomp 一起组成分层防护,确保 agent 即便“能思考、会调用工具”,也依然只能在预先定义好的安全边界内行动。
因此,理解 LSM 的意义,已经不只是理解 SELinux/AppArmor 的配置语法,更是在理解一个越来越现实的问题:当 AI agent 开始具备执行能力时,我们如何用内核级机制把“可用的自动化”约束成“可控的自动化”。
很多时候我们会把 LSM 和 Linux 原生文件权限(rwx)自主访问控制(DAC)混为一谈。两者都能做访问控制,但设计目标和控制能力并不一样。
rwx 位 | ||
可以简单记忆为:DAC 解决“谁可以访问文件”,LSM 解决“这个进程在当前策略下究竟能做什么”。
如果把 Linux 系统比作一栋办公楼:
DAC(传统权限)像门上的机械钥匙:你有钥匙就能开门,没有就进不去。LSM 像楼里的安保系统:即使你有钥匙,也要看你是谁、什么时候来、要去哪个区域、当前规则是否允许。也就是说,DAC 更像“静态门禁”,LSM 更像“动态风控”。
再举个常见例子:
root,按传统权限它几乎什么文件都能读。SELinux/AppArmor,策略可以明确规定:这个进程只允许读 A 目录,访问 /etc/shadow 仍然会被拦截。所以从结果上看,LSM 不是替代 rwx,而是在 rwx 之上再加一道更细的“安全裁决”。这也是为什么生产环境里常说:权限最小化 不只看 Linux 用户权限,还要看 LSM 策略是否收口。
但是往往我们在做实验,或者一些生产环境的应急排障场景,会先选择关闭 LSM。这个决策通常不是“LSM 不好”,而是“当前场景下先保业务可用”。
常见原因:
SELinux/AppArmor 策略,启动就被拒绝但代价也很明显:系统少了一层强制访问控制,越权访问和横向移动风险会变高。
更推荐的做法是“临时放松,不长期裸奔”:
SELinux 场景先用 permissive 收集日志,再切回 enforcingAppArmor 场景先用 complain 观察行为,再切到 enforce可以临时关闭用来定位问题,但不建议把“关闭 LSM”当成长期方案。
LSM 并非单一安全模块,而是 Linux 内核提供的一套通用安全扩展框架。核心目标是在最小化内核改动的前提下,支持多种强制访问控制(MAC)模型与安全策略的灵活集成。
早期 Linux 主要依赖自主访问控制(DAC,例如文件权限 rwx),安全性在很大程度上依赖进程行为自律,难以抵御恶意进程越权访问。LSM 通过在关键访问路径(文件读写、网络连接、进程创建、IPC 通信等)插入钩子,提供了统一的安全决策入口。
通过这些钩子,不同安全模块可按策略对访问行为做“允许/拒绝”判定,弥补 DAC 对强制隔离能力不足的问题。
用一句话讲 LSM = 在内核关键对象访问路径上埋入统一安全钩子,再基于安全上下文做最终裁决。
它不是简单地“在系统调用入口拦一下”,而是把检查点嵌进 VFS、进程管理、网络栈、IPC、挂载、凭据变更等真实执行路径里。所以 LSM 的价值不只是能拒绝访问,更在于它能在对象已经解析、身份已经明确、内核语义已经收敛的阶段做决策,这比很多用户态包装式防护更接近真实执行面。
LSM 的基础是内核在大量关键路径上预留的 hook。这些钩子并不直接实现安全策略,而是提供统一“插槽”,让 SELinux、AppArmor、Smack、Yama、BPF LSM 等模块把自己的检查逻辑挂进去。
常见的钩子大致分几类:
inode_permission、file_open、path_unlink、sb_mountbprm_check_security、task_fix_setuid、task_killsocket_create、socket_connect、socket_bind、socket_listencred_prepare、cred_transfer、inode_init_securityBPF LSM 可以在对应 lsm/* hook 点上动态挂程序从实现视角看,LSM 并不是“每个模块各改各的内核代码”,而是:
security_*() 包装函数LSM hook 链这个模型很像网络栈里的“统一分发入口 + 不同协议处理器”,只不过这里分发的不是数据包,而是安全决策。
只有钩子还不够,内核还需要知道“谁在访问谁”。这就是安全上下文(security context)的作用。
在 LSM 体系里,主体和客体都可以带有额外的安全属性,常见对象包括:
task_struct / cred:进程身份、安全域、能力集等inode / file / super_block:文件、目录、挂载点的安全属性socket:网络对象的访问控制属性msg_msg、ipc、key:消息队列、共享内存、密钥对象等内核不会把这些对象都改成某个安全模块私有结构,而是预留 security blob 指针或相关扩展区,供各个 LSM 附加自己的元数据。
这也是为什么不同 LSM 的“上下文表达”不一样:
SELinux 更偏向标签化,典型是 user:role:type:levelAppArmor 更偏向 profile 和路径匹配Smack 也是标签模型,但语义更轻量BPF LSM 往往不自带长期标签体系,而是读取内核对象状态、cgroup、comm、uid、inode 等字段进行动态判定所以从本质上说,LSM 自己不定义唯一的安全模型,它定义的是安全决策框架;至于“上下文长什么样、策略怎么写、拒绝条件是什么”,由具体模块决定。
这个过程非常值得展开,因为它最能体现 LSM 不是“粗暴拦系统调用”,而是沿着内核真实路径做检查。
以应用执行下面这个动作举例:
cat /etc/shadow从概念路径上看,大致会经过下面几个阶段:
open/openat/openat2VFS 路径解析,找到目标 dentry/inodeVFS 在关键节点调用如 inode_permission、file_open 这类安全检查security_inode_permission() / security_file_open() 分发给各个已注册 LSM-EACCES 或 -EPERM也就是说,真正决定“能不能读”的,不只是传统 rwx 位,还包括访问路径上叠加的 LSM 判断。
一个更接近内核思维的认知是:
DAC 先回答:从 Unix 用户权限看,这个动作是否基础可行LSM 再回答:即使基础可行,在当前安全域下是否仍然允许这也是为什么很多场景下,进程明明是 root,但依然会被 SELinux/AppArmor 拒绝。
除了文件访问,exec 和 socket 往往是主机安全里最敏感的两条链路。
一个程序从 fork 到 execve,LSM 不只是检查“能不能执行这个文件”,还会参与:
setuid/setgid 后的安全域变化典型相关 hook 包括:
bprm_set_credsbprm_check_securitybprm_committing_credsbprm_committed_creds这也是为什么 SELinux 能基于 domain transition 做“执行某个二进制后进入新域”,而不是简单沿用原来权限。
网络路径则更多体现在:
bind 某个端口connect 到某个地址典型 hook 包括:
socket_createsocket_bindsocket_connectsocket_sendmsgsocket_recvmsg这在云原生和 AI agent 场景很有价值,因为它不只是限制“能不能联网”,还能继续约束“能连什么、从哪个上下文连、是不是能碰控制面端口或敏感 Unix Socket”。
现代内核经常不是只跑一个 LSM,而是多模块协同,例如:
capabilityyamaapparmor 或 selinuxbpf可以先看系统当前启用顺序:
# 查看当前启用的 LSM 模块cat /sys/kernel/security/lsm输出可能类似:
capability,landlock,lockdown,yama,integrity,apparmor,bpf这里有两个关键认知:
capability 本身也是 LSM 体系的一部分,负责 Linux capability 相关裁决因此多 LSM 协同的本质不是“谁最后说了算”,而是“多个安全模块共同收紧边界”。这和很多中间件责任链不同,它更偏向“并联审核,任一票否决”。
很多人做安全控制时,习惯在用户态包一层,比如:
这些方法不是没用,但它们最大的问题是:检查点离真实资源访问点太远。
而 LSM 的优势在于它工作的时机更晚、更贴近对象:
所以对于高风险场景,常见做法不是“只做用户态控制”,而是:
业务白名单 + 容器隔离 + seccomp + capabilities + LSM
这里的 LSM 负责的是最后的内核对象裁决。
如果系统开启了 tracing/debugfs,可以先看看当前支持哪些 LSM tracepoint:
# 查看 LSM 相关 tracepointsudo cat /sys/kernel/debug/tracing/available_events | grep '^lsm:'如果能看到类似 lsm_inode_permission、lsm_socket_connect 之类事件,说明内核已经把对应 hook 暴露给 tracing 体系。
接下来可以做一个最直接的观察实验:
# 开一个终端观察 inode 权限检查sudo bpftrace -e 'tracepoint:lsm:inode_permission { printf("comm=%s pid=%d mask=%d inode=%llu\n", comm, pid, args->mask, args->inode); }'另开一个终端执行:
cat /etc/hosts >/dev/nullcat /etc/passwd >/dev/null如果你的内核和 bpftrace 环境支持该 tracepoint,就能看到文件访问时 LSM 检查在持续触发。这个实验的重点不是“看见 deny”,而是建立认知:哪怕只是一次普通读文件,LSM 检查也真实地位于内核访问路径中。
执行链路也可以做一个轻量观察。先看系统中和 exec 相关的 LSM 事件:
sudo cat /sys/kernel/debug/tracing/available_events | grep -E '^lsm:.*bprm|^lsm:.*task'如果有对应事件,可以直接追踪:
sudo bpftrace -e 'tracepoint:lsm:bprm_check_security { printf("exec-check comm=%s pid=%d\n", comm, pid); }'然后执行:
/bin/true/usr/bin/id如果环境支持,你会看到每次 execve 前都经过了对应安全检查点。理解这个实验的价值很重要:LSM 不只是保护“文件读写”,它还深度参与“进程要不要获得新的执行映像和凭据”。
如果系统支持 BPF LSM,可以进一步理解“LSM 不只是静态模块,还能程序化扩展”。
一个最小化思路是:在 file_open 或 socket_connect hook 上挂一个 eBPF 程序,当命中某个进程名或目标对象时返回负错误码,直接拒绝访问。
例如下面这个示意逻辑:
SEC("lsm/file_open")intBPF_PROG(block_bad_reader, struct file *file, int mask, int ret){char comm[16];if (ret != 0) {return ret; } bpf_get_current_comm(comm, sizeof(comm));if (__builtin_memcmp(comm, "cat", 3) == 0) {return -EACCES; }return0;}它表达的语义非常直接:
ret0:允许继续当然,生产里不能真按 comm=cat 这么粗糙地做,但这个 Demo 很适合建立 eBPF-LSM 的第一层认知:安全逻辑已经从“静态配置”扩展成“可编程裁决”。
可以用下面命令确认环境能力:
# 查看当前内核支持的 LSM/BPF 相关能力bpftool feature probe | grep -i lsm# 查看已加载的 eBPF LSM 程序bpftool prog show --type lsm大部分访问会先经过 Unix 传统权限,再经过 LSM。不是有了 SELinux/AppArmor 就可以不关心文件属主和 rwx。
SELinux 只是 LSM 之上的一个具体实现;AppArmor、Smack、Landlock、BPF LSM 都是同一框架里的不同策略模型。
AppArmor 更偏路径语义,SELinux 更偏 inode/label 语义。两者都能做强控制,但底层依赖对象模型不同,这决定了它们在重命名、挂载、硬链接等场景下的策略表达方式和维护体验也不同。
它适合做运行时补强、细粒度审计、快速试验和特定场景防护;但在大规模长期治理里,成熟的 SELinux/AppArmor 仍然更适合承接稳定基线策略。
下面是常见的检查命令,可以先建立一个“系统当前 LSM 状态”的基础认知:
# 查看当前启用的 LSM 模块(顺序也很关键)cat /sys/kernel/security/lsm# 查看当前内核支持的 LSM 能力(部分发行版输出格式略有差异)bpftool feature probe | grep -i lsm# 查看已加载的 eBPF LSM 程序bpftool prog show --type lsm# 查看 LSM 相关 tracepoint(需要开启 tracing/debugfs)sudo cat /sys/kernel/debug/tracing/available_events | grep '^lsm:'# SELinux:查看文件安全上下文ls -Z /etc/passwd# SELinux:查看进程安全上下文ps -eZ | head# AppArmor:查看 profile 执行状态aa-status与用户态系统调用拦截相比,LSM 钩子在内核对象访问流程中做决策,更贴近内核真实执行路径,能有效减少 TOCTOU 类绕过风险。这也是它能长期成为 Linux 主机安全、容器安全、AI agent runtime 权限约束底座的根本原因。
理解 LSM 生态时,一个很容易踩的坑是把它简单理解成“SELinux 和 AppArmor 二选一”。实际上,Linux 现在的安全模块已经形成了一个分层体系:
SELinux、AppArmorYama、LockdownLandlock、BPF LSM所以这一节更推荐按“能力定位”来理解各个模块,而不是只按历史知名度排序。
SELinux 是最典型、也最成熟的 LSM 实现之一。它的核心思想不是“按文件路径做判断”,而是给主体和客体打上安全标签,再按策略决定“某类主体能否对某类客体执行某种操作”。
其常见决策模型可以概括成:
主体 domain/type + 客体 type/class + 操作 perm -> allow/deny
例如:
httpd_t 是否允许读取 httpd_sys_content_tcontainer_t 是否允许连接某类端口init_t 执行某个二进制后是否发生 domain transitionSELinux 的核心特点:
它的典型优势是:
它的典型成本也要说清楚:
常见场景:
RHEL/CentOS/Fedora 体系主机加固Kubernetes/OpenShift 容器隔离快速观察命令:
# 查看 SELinux 当前模式getenforce# 查看进程安全上下文ps -eZ | head# 查看文件安全标签ls -Z /etc/passwd如果用一句话总结:SELinux 更像一套“内核级对象标签治理系统”,而不只是“更严格的权限管理”。
和 SELinux 相比,AppArmor 的认知门槛通常更低。它更强调“给某个程序绑定 profile,然后按路径规则限制它能访问什么、执行什么、联网做什么”。
典型策略关注点包括:
AppArmor 的核心特点:
它的典型优势:
它的边界也很明确:
常见场景:
Ubuntu/Debian/SUSE 体系默认或常见主机防护快速观察命令:
# 查看 profile 状态aa-status# 查看某个程序是否有 profileapparmor_status | grep nginx如果用一句话概括:AppArmor 更像“面向程序路径和运行画像的安全外壳”。
Smack(Simplified Mandatory Access Control Kernel)也是标签型 MAC,但它的目标不是像 SELinux 一样把策略做得非常庞大,而是提供一个更简单直接的标签访问模型。
其基本思路是:
它的特点是:
相比 SELinux,Smack 的优势在于轻量、规则更直接;但在通用企业 Linux 生态里,它的普及度和工具链成熟度通常不如 SELinux/AppArmor。
可以把它理解成:如果 SELinux 像一套大型安全治理系统,Smack 更像一个收敛后的简化标签控制器。
TOMOYO 也是比较有代表性的 LSM,但它的思路和 AppArmor 有点像,同样关注路径和程序行为,只是它更强调从运行行为中学习、再逐步固化策略。
它比较适合用于:
这个思路很适合历史系统改造,因为很多老应用很难一开始就人工写出完整安全策略。
不过从今天的主流企业落地看,TOMOYO 的声量明显低于 SELinux/AppArmor,更多是特定场景或研究型认知里会接触到。
Yama 的定位和前面几个不一样,它不是全量 MAC 框架替代者,而是一个“补强型 LSM”。
它最常被提到的能力是限制 ptrace 行为,例如:
很多发行版会通过:
cat /proc/sys/kernel/yama/ptrace_scope来控制其行为级别。
它的价值在于:哪怕系统没有启用特别复杂的 MAC 策略,也可以先把高风险调试类能力收紧,降低本地横向利用面。
所以 Yama 更像是“针对攻击链关键动作的专项闸门”。
Landlock 是近几年非常值得关注的模块,因为它代表了另一个方向:不是只让系统管理员写内核级安全策略,而是让普通应用也能主动给自己加沙箱。
它的关键价值在于:
这和传统 SELinux/AppArmor 的运维视角不完全一样。Landlock 更像给开发者提供一个“用户态自助式最小权限”接口。
典型适用场景:
它的边界是:
SELinux 那么全能如果用一句话概括:Landlock 是把 LSM 能力部分下放给应用开发者的尝试。
很多人在看 /sys/kernel/security/lsm 时会发现除了熟悉的 selinux/apparmor/bpf,还会看到 lockdown、integrity 这类模块。
这两个模块更偏“平台完整性”和“内核自保护”:
Lockdown:限制即使是 root 也不能随意做某些会破坏内核信任边界的操作,比如直接访问内核内存、篡改某些底层接口Integrity:为完整性度量、文件签名、可信启动链等能力提供基础支撑,常和 IMA/EVM 这类机制相关它们和 SELinux/AppArmor 的区别在于:
在安全启动、受监管环境、机密计算、固件到内核的可信链路治理里,这类模块很重要。
eBPF-LSM 是现代 Linux 安全体系里非常关键的变化点。它并不试图完全替代 SELinux/AppArmor,而是让开发者可以在 LSM hook 上动态加载 eBPF 程序,把安全检查逻辑写成代码。
它带来的变化主要有三点:
典型应用方式:
它的优势很明显:
但它也有明确边界:
更合理的工程思路通常是:
SELinux/AppArmor 负责长期稳定基线eBPF-LSM 负责运行时补强、灰度试验、快速响应和深度审计快速观察命令:
# 查看内核是否支持 LSM 相关 BPF 能力bpftool feature probe | grep -i lsm# 查看已加载的 BPF LSM 程序bpftool prog show --type lsm严格说,capability 也属于 LSM 生态的一部分,而且很多系统里它几乎一定会启用。它负责的是 Linux capability 相关的权限裁决,例如:
CAP_SYS_ADMINCAP_NET_ADMINCAP_SYS_PTRACE它的价值在于把传统“root 全有、非 root 全无”的粗粒度权限拆成可组合能力位。
不过 capability 解决的是“超权能力拆分”问题,不等价于完整的 MAC 体系。它通常是所有安全方案里的基础层,而不是最终层。
如果从工程视角给一个简单判断:
SELinuxAppArmorLandlockeBPF-LSMYama、Lockdown、Integrity真正成熟的生产环境,往往不是只靠某一个模块,而是多 LSM 与其他机制一起协同:
capabilities + seccomp + namespace/cgroup + SELinux/AppArmor + eBPF-LSM
这才是现代 Linux 主机和云原生安全生态更真实的样子。
LSM 的演进可以粗略分为三个阶段:
如果回头看这段历史,LSM 出现并不是因为社区一开始就想清楚了“要做一个完美安全框架”,而是因为 Linux 安全扩展长期处于“每家都改自己 patch”的割裂状态。
早期多个安全项目都在尝试把自己的访问控制模型塞进内核:
问题在于,这些能力大多以“内核补丁集”的方式存在,彼此之间很难共存,也很难进入主线长期维护。
根据 Linux Kernel 官方文档中的历史说明,2001 年 3 月,NSA 在 2.5 Linux Kernel Summit 上介绍了 SELinux;随后社区讨论逐渐收敛到一个更现实的方向:不要把某一个安全模型直接主线化,而是先把“安全钩子框架”主线化。 官方文档同时指出,LSM 项目随后启动,并在 2003 年 12 月 合入主线内核。
这个决策非常关键,它本质上做了两件事:
从内核设计角度看,这是一种非常典型的主线化妥协:
也正是因为这个架构选择,Linux 才没有把“安全”固化成单一答案,而是给后续 SELinux、Smack、TOMOYO、AppArmor、Yama 乃至后来的 Landlock、BPF LSM 留出了演进空间。
如果想从运行系统角度感受这套设计现在的落点,可以先看两个基本事实:
# 当前系统启用了哪些 LSMcat /sys/kernel/security/lsm# 当前内核默认的 LSM 顺序(部分系统可直接读取)grep '^CONFIG_LSM=' /boot/config-$(uname -r) 2>/dev/null这两个文件本身就体现了 LSM 的核心思想:内核负责框架与顺序,具体安全语义由各模块定义。
基础建设期最重要的成果,不是“某个安全模块更强了”,而是 Linux 内核第一次对安全扩展给出了统一工程接口:
这使得 LSM 从一开始就是“框架先行”,而不是“策略先行”。
框架进入主线之后,第二个问题马上出现了:有了 hook,不代表就能稳定落地。真正困难的是下面这些工程问题:
这一阶段的关键词其实不是“发明新模型”,而是“把模型做成可运营系统”。
从官方文档可以看到,LSM 后来逐渐形成了一套更清晰的对象承载方式:
task_struct、credsuper_blockinode、filekern_ipc_perm、msg_msg这一步的价值很大,因为它意味着 LSM 不再只是一堆零散 hook,而是逐渐形成“对象生命周期 + 安全属性生命周期”协同的能力。
与此同时,社区也长期面对一个现实限制:早期和相当长一段时间里,LSM 更偏向“一个 major 模块 + 若干 minor 模块”的模式。
Linux Kernel 文档里对这一点有比较明确的表述:
capability 总会存在minor 模块,例如 Yamamajor 模块通常只有一个,比如 SELinux 或 AppArmor这背后的原因并不神秘,主要是三个层面的复杂度:
所以这个阶段的 LSM,本质上完成的是“从理论可扩展到可工程部署”的过渡。很多今天看起来理所当然的能力,其实都来自这一时期的打磨:
/proc/.../attr 这类进程安全属性接口逐渐稳定/sys/kernel/security/lsm 这种运行时可观察入口越来越清晰SELinux/AppArmor 建立更成熟的策略包、审计工具和排障流程从工程角度说,这个阶段最大的贡献是:LSM 不再只是内核安全研究者能用的东西,而开始变成发行版、运维团队、平台团队可以真正接住的能力。
可以用下面命令感受一下这些“工程化接口”:
# 查看运行中的 LSM 顺序cat /sys/kernel/security/lsm# 查看当前进程可见的 attr 接口ls /proc/self/attr# SELinux 体系下查看审计告警sudo ausearch -m AVC -ts recent 2>/dev/null | head很多团队第一次接触 LSM,都会把问题理解成“功能够不够强”。但进入生产后真正的挑战往往是:
这也是为什么 SELinux 和 AppArmor 会在不同发行版生态里走出不同路线。它们不只是“技术实现不同”,更是对“安全强度 vs 运维复杂度”给出了不同平衡点。
真正推动 LSM 再次“进化”的,不只是内核技术本身,而是工作负载形态变了。
传统主机时代,很多进程是长期稳定运行的:
这使得静态策略虽然麻烦,但还能维护。
到了容器和云原生时代,情况明显变了:
于是 LSM 进入了一个新的阶段:不再只是“给系统服务写静态 MAC 策略”,而是要和现代运行时协同,支持更动态的约束方式。
这一阶段最关键的两个变化是:
今天的系统里,更常见的不是“只启一个大模块”,而是多种机制共同工作:
capability 提供基础能力位裁决Yama、Lockdown、Integrity 提供特定攻击面补强SELinux 或 AppArmor 提供主基线 MACLandlock 提供应用自收权BPF LSM 提供运行时动态补强与审计虽然不同模块在“major/minor”上的角色划分仍然存在,但从治理视角看,现代 Linux 已经明显从“单安全模块中心化”转向“多层安全机制协作化”。
BPF LSM 的出现非常关键。根据内核官方 LSM BPF Programs 文档,eBPF 程序可以直接附着到 LSM hook 上,实现系统级 MAC 与审计策略。
这意味着安全逻辑第一次真正具备了下面这些特性:
另外,Landlock 也代表了另一条现代化路线。内核官方文档指出,Landlock 首次在 Linux 5.13 引入,它允许非特权进程主动收缩自己的环境权限。这说明 LSM 的角色已经不再局限于“管理员给系统上锁”,而开始支持“应用自己给自己上锁”。
这两条线叠加起来,LSM 的定位就发生了变化:
到了今天,很多企业已经不满足于“保护主机上的传统服务”,而是要保护:
CI/CD 任务AI agent这时 LSM 的意义明显扩大了。它不只是一个传统 Linux 安全名词,而是在回答一个现代问题:
当工作负载越来越短命、越来越自动化、越来越具备执行能力时,谁来提供最后的内核级权限边界?
答案通常不是某一个单独机制,而是一组机制叠加,其中 LSM 负责最贴近内核对象的那一层:
namespace/cgroup + seccomp + capabilities + LSM + eBPF observability
对于 AI agent 尤其如此。agent 一旦能读文件、起进程、连网络、调容器接口,它就不再只是一个聊天程序,而是一个高自动化执行体。此时:
SELinux/AppArmor 负责稳定基线BPF LSM 负责快速补强和运行时策略实验Landlock 适合部分应用自我收权场景这也解释了为什么今天重新学习 LSM,不应该只停留在“SELinux 配置很麻烦”这一层,而要把它放进现代运行时安全体系里看。
在 6.x 内核阶段,LSM 相关能力的重点可归纳为三类:
多 LSM 协同已是常见部署方式。典型组合是:
面向 K8s 场景,策略下发、命名空间隔离、工作负载标签化治理需求越来越强,LSM 正在和容器运行时、网络策略组件、审计平台形成更紧密协同。
很多同学会把三者理解成“互相替代”。更准确的说法是:SELinux、AppArmor、eBPF-LSM 都是基于 LSM 框架工作的不同实现形态,侧重点不同,常见是叠加而不是二选一。
可以理解为:
SELinux/AppArmor 偏“稳定基线”,适合长期、可审计、可复用的安全边界eBPF-LSM 偏“动态补位”,适合快速响应新行为、新风险和新业务模式落地原则建议:
SELinux(RHEL/Fedora 生态)或 AppArmor(Ubuntu/SUSE 生态)eBPF-LSMeBPF-LSM 裸跑生产,缺少成熟基线时治理成本会明显上升组合 A:SELinux + eBPF-LSM
适用:金融、政企、强审计场景
思路:SELinux 兜底关键访问边界,eBPF-LSM 补充运行时异常行为检测
组合 B:AppArmor + eBPF-LSM
适用:云原生平台、迭代快的业务团队
思路:AppArmor 快速建立基础隔离,eBPF-LSM 处理短周期风险规则
组合 C:仅 SELinux 或仅 AppArmor
适用:安全边界较稳定、变更频率低的传统业务
风险:对突发新型行为的响应速度通常弱于“基线 + 动态”双层方案
误区:启用 eBPF-LSM 后可以完全替代 SELinux/AppArmor。更合理的工程结论是:eBPF-LSM 更像“高机动策略层”,而 SELinux/AppArmor 是“稳定底盘”。两者分层协作,通常比单栈方案更稳、更快、更易审计。
以 AppArmor 为例,为 /usr/bin/cat 配置最小权限,限制其访问敏感文件:
# /etc/apparmor.d/usr.bin.cat#include <tunables/global>/usr/bin/cat {#include <abstractions/base> deny /etc/shadow rw, /usr/* r,}# 重载并启用sudo apparmor_parser -r /etc/apparmor.d/usr.bin.catsudo aa-enforce /usr/bin/cat验证策略:
aa-status | grep /usr/bin/catcat /etc/shadowsudo grep DENIED /var/log/audit/audit.log | tail如果是 SELinux 场景,通常通过标签与策略模块控制访问边界:
getenforcels -Z /etc/shadowsudo ausearch -m AVC -ts recent在容器环境中,常见做法是“容器隔离 + LSM 策略 + 审计联动”:
在 K8s 中可先做基础核查:
# 查看 Pod 安全上下文kubectl get pod <pod-name> -o jsonpath='{.spec.securityContext}'# AppArmor 注解示例kubectl annotate pod <pod-name> \container.apparmor.security.beta.kubernetes.io/<container-name>=localhost/<profile-name># 进入容器后检查 LSM 状态(视镜像能力而定)aa-statusgetenforce下面示例是“思路代码”,用于说明 eBPF-LSM 工作方式:在 file_permission 钩子中识别访问行为并返回拒绝。实际生产实现需按内核版本和 helper 能力调整。
// lsm_nginx.c (示意)SEC("lsm/file_permission")intBPF_PROG(check_file_permission, struct file *file, int mask){// 1. 获取当前进程信息// 2. 解析目标路径// 3. 按策略判定是否允许// 4. return 0 允许 / return -EPERM 拒绝return0;}常用验证命令:
# 查看 LSM 类型的 eBPF 程序bpftool prog show --type lsm# 观察运行日志(需开启 tracing)sudo cat /sys/kernel/debug/tracing/trace_pipe很多线上问题最终并不是“程序真的坏了”,而是“程序被 LSM 拦了”。如果排障时没有先确认这一层,就很容易把时间浪费在错误方向上。
一个比较稳妥的思路是:
无论是 SELinux、AppArmor 还是 eBPF-LSM,第一步都应该先确认宿主机当前到底启用了什么。
# 查看当前激活的 LSM 顺序cat /sys/kernel/security/lsm# 查看内核启动参数里是否显式指定了 lsm 顺序cat /proc/cmdline# 查看 securityfs 是否已挂载mount | grep securityfs# 查看当前系统内核版本uname -r如果这里连 selinux、apparmor、bpf 都不在激活列表里,那后面的很多排查动作就没有意义了。
SELinux 场景下,最常见的问题并不是“服务起不来”,而是“服务起得来但某个动作被悄悄拒绝”。所以排查顺序通常是:模式 -> 上下文 -> AVC 日志。
# 查看 SELinux 当前模式getenforce# 查看 SELinux 全量状态sestatus# 查看目标进程的安全上下文ps -eZ | grep nginx# 查看目标文件或目录的安全上下文ls -lZ /var/www/html# 查询最近的 AVC 拒绝sudo ausearch -m AVC -ts recent# 将审计日志转换成人类可读建议sudo ausearch -m AVC -ts recent | audit2why如果想临时确认“问题是否就是 SELinux 引起”,更稳妥的方式不是直接禁用,而是先切到宽容模式观察:
# 临时切到 permissive,只审计不阻断sudo setenforce 0getenforce# 复现问题后查看 AVCsudo ausearch -m AVC -ts recent# 验证结束后切回 enforcingsudo setenforce 1getenforceAppArmor 的排查一般更直观,因为它以 profile 和路径规则为中心。工程里最常用的判断方式就是:当前 profile 是否生效、是否处于 complain 模式、内核日志里有没有 DENIED。
# 查看 AppArmor 总体状态sudo aa-status# 查看某个进程当前使用的 profileps auxZ | grep nginx# 查看已加载 profile 文件sudo find /etc/apparmor.d -maxdepth 1 -type f | sort | head# 查询最近的 AppArmor 拒绝日志sudo journalctl -k | grep -i apparmor | tail -n 20如果怀疑是 AppArmor 拦截,可以先把单个 profile 切到 complain 模式,只记录不拦截:
# 将指定 profile 切到 complain 模式sudo aa-complain /etc/apparmor.d/usr.bin.cat# 验证状态sudo aa-status | grep /usr/bin/cat# 复现业务动作后查看日志sudo journalctl -k | grep -i apparmor | tail -n 20# 确认规则无误后切回 enforcesudo aa-enforce /etc/apparmor.d/usr.bin.cateBPF-LSM 排障和 SELinux / AppArmor 最大的不同在于:问题不只是“策略对不对”,还包括“程序有没有成功加载、有没有成功附着、有没有被 verifier 拒绝”。
# 查看系统是否支持 BPF LSMbpftool feature probe | grep -i lsm# 查看当前已加载的 LSM 类型 BPF 程序sudo bpftool prog show --type lsm# 查看某个程序的详细信息sudo bpftool prog show --type lsm -j# 观察 BPF/LSM 相关内核日志sudo journalctl -k | grep -Ei 'bpf|lsm' | tail -n 50# 如启用了 tracing,可观察运行时输出sudo cat /sys/kernel/debug/tracing/trace_pipe如果 bpftool prog show --type lsm 看不到任何程序,而你又确信已经部署过 eBPF-LSM,那么优先检查加载流程和内核能力,不要先怀疑业务代码。
LSM 最容易出事故的阶段,不是长期运行,而是第一次上策略。所以更推荐的方式不是“一次性全量 enforce”,而是先做最小闭环验证:先能看到、再能解释、最后才真正阻断。
SELinux 最经典的灰度方式就是 permissive -> 分析 AVC -> 调整上下文/策略 -> enforcing。
# 1. 临时宽容模式sudo setenforce 0# 2. 触发一次目标业务访问curl http://127.0.0.1/# 3. 查看最近拒绝并分析原因sudo ausearch -m AVC -ts recent | audit2why# 4. 如需修复文件上下文,可先查看默认映射sudo semanage fcontext -l | grep httpd | head# 5. 恢复目录默认标签并重试sudo restorecon -Rv /var/www/html# 6. 验证后切回强制模式sudo setenforce 1这类流程的价值在于:你不是“拍脑袋关掉 SELinux”,而是先用审计模式拿证据,再决定怎么修。
AppArmor 更适合做“小步快跑”的 profile 收敛,因为它支持先观察行为,再回收权限。
# 1. 将 profile 切到 complainsudo aa-complain /etc/apparmor.d/usr.bin.cat# 2. 执行一次目标命令cat /etc/passwd >/dev/null# 3. 查看拒绝/学习日志sudo journalctl -k | grep -i apparmor | tail -n 20# 4. 重新加载 profilesudo apparmor_parser -r /etc/apparmor.d/usr.bin.cat# 5. 切回 enforcesudo aa-enforce /etc/apparmor.d/usr.bin.cat# 6. 再次验证sudo aa-status | grep /usr/bin/cat如果团队还不熟悉 AppArmor,建议先从单一二进制、单一目录的最小 profile 开始,不要一上来就做全业务覆盖。
eBPF-LSM 的优势是快,但风险也在于快。 更稳妥的做法是先把程序写成“只记录、不阻断”,确认命中条件正确后,再把 return 0 改成 return -EPERM 一类拒绝动作。
# 查看当前 LSM BPF 程序sudo bpftool prog show --type lsm# 查看 BPF map,确认命中计数或事件缓冲区是否在增长sudo bpftool map show# 如果你的程序通过 ringbuf/perf event 上报事件,可在用户态读取sudo journalctl -kf# 观察 trace_pipe 中的调试输出sudo cat /sys/kernel/debug/tracing/trace_pipe在生产里,eBPF-LSM 更适合作为“快速响应层”而不是“第一次就全量阻断层”。先确认规则命中质量,再决定是否真正拒绝,是比较稳的做法。
容器里看到“Permission denied”时,很多人第一反应是镜像权限问题,但真实原因可能是节点上的 AppArmor / SELinux / seccomp / 运行时策略共同作用。
# 节点侧:查看 LSM 状态cat /sys/kernel/security/lsm# 节点侧:检查容器运行时crictl info | head# Pod 侧:查看安全上下文kubectl get pod <pod-name> -o yaml | grep -A20 securityContext# Pod 侧:查看注解中的 AppArmor 信息kubectl get pod <pod-name> -o yaml | grep -i apparmor# 节点侧:查看最近内核拒绝日志sudo journalctl -k | grep -Ei 'apparmor|avc|seccomp' | tail -n 50在 K8s 里,LSM 排障一定要把“节点视角”和“工作负载视角”放在一起看,只盯着容器内部往往不够。
LSM 是 Linux 安全能力的底座,不是某一个具体产品。它的核心价值在于:为不同安全模型提供统一内核接入点,并在传统主机与云原生场景中持续演进。
在工程落地上,建议采用“基线策略 + 动态策略 + 可观测审计”三层体系:用 SELinux/AppArmor 稳定兜底,用 eBPF-LSM 快速响应新风险,用审计链路验证策略效果与性能影响。
© 文中涉及参考链接内容版权归原作者所有,如有侵权请告知 :)
https://docs.kernel.org/security/lsm.html
https://docs.kernel.org/bpf/prog_lsm.html
https://www.kernel.org/doc/html/latest/admin-guide/LSM/index.html
© 2018-至今 liruilonger@gmail.com, 保持署名-非商用-相同方式共享(CC BY-NC-SA 4.0)