eBPF 运行时安全
引言
eBPF 作为当前 linux 系统上最为炙手可热的技术,通常被用于网络流量过滤和分析、系统调用跟踪、性能优化、安全监控,当下比较知名的项目有 Cilium、Falco 等。
Cilium 是一个开源的容器网络和安全性项目,致力于提供高效的容器通信和强大的安全性功能,Cilium 基于 eBPF、XDP、TC 等技术实现了 Layer 3(IP)、Layer 4(TCP/UDP)以及 HTTP 层的负载均衡和网络防护,是一款非常优秀的网络安全工具。Falco 是一个开源的云原生应用安全项目,旨在提供运行时容器安全性监控和威胁检测,Falco 通过监视容器运行时环境的系统调用和其他事件,检测并报告可能的异常行为和安全威胁,使系统管理员和开发人员能够更好地了解和响应与容器运行时环境相关的安全问题。
Cilium 和 Falco 在其各自的领域都是非常优秀的存在,但是它们在运行时安全上面都存在一些缺失,缺少对运行时进程的实时阻断能力。这是否意味着 eBPF 无法实现运行时的阻断?答案是否定的,在 eBPF 的后续不断发展的过程中,增加了对进程和内核函数的阻断能力,这让基于 eBPF 构建一款运行时安全产品成为可能。
Tetragon
2022 年 5 月,Isovalent 发布了基于 eBPF 的安全可观察性和运行时执行项目 Tetragon,Tetragon 可以根据规则在内核中同步进行过滤、阻止和响应,从而可以防止攻击,而不是异步地对其做出反应。
Tetragon 是一款运行时安全执行和可观测性工具。这意味着 Tetragon 直接在内核中使用 eBPF 应用策略和过滤。它在内核中执行过滤、阻断和对事件的响应,而不是将事件发送到用户空间代理。对于可观测性用例,直接在内核中应用过滤器极大地减少了观测开销。通过避免昂贵的上下文切换和唤醒,特别是对于高频事件(如发送、读取或写入操作),eBPF 减少了所需的资源。相反,Tetragon 在 eBPF 中提供了丰富的过滤器(文件、套接字、二进制名称、命名空间/权限等),允许用户在其特定上下文中指定重要和相关的事件,并仅将这些事件传递到用户空间代理。
Tetragon 可以钩入 Linux 内核中的任何函数,并在其参数、返回值以及 Tetragon 收集的有关进程(例如可执行文件名称)、文件和其他属性的关联元数据上进行过滤。通过编写跟踪策略,用户可以解决各种安全性和可观测性用例。关键是,Tetragon 允许在内核深层进行挂钩,用户空间应用程序无法操纵数据结构,从而避免了系统调用跟踪中常见的问题,如错误读取数据、被攻击者恶意更改数据,或由于页面错误和其他用户/内核边界错误而丢失数据。Tetragon 的许多开发人员同时也是内核开发人员。通过充分利用这一知识基础,Tetragon 创建了一组可以解决许多常见可观测性和安全性用例的跟踪策略。
Tetragon 通过 eBPF 技术可以访问 Linux 内核状态。然后,Tetragon 可以将这个内核状态与 Kubernetes 感知或用户策略结合起来,以实时由内核执行的方式创建规则。这使得可以对进程命名空间和权限、进程与套接字的关系、进程文件描述符与文件名等进行注解和强制执行。例如,当应用程序更改其特权时,我们可以创建一个策略,触发警报甚至在进程有机会完成系统调用并可能运行其他系统调用之前终止该进程。
根据上述介绍,Tetragon 具备了在内核中阻断的能力,那么到底 Tetragon 是如何进行阻断的呢?是使用了 eBPF 中的什么功能实现的呢?
分析 Tetragon 的源码发现,Tetragon 在使用了 send_signal()函数下发 FGS_SIGKILL 指令给当前进程,完成阻断动作,这个动作相当于在用户态发送 kill -9 指令给进程。send_signal()函数是 eBPF 的内置函数,在 linux 5.3 版本内核中引入。
除了 send_signal()函数,eBPF 还提供了其它的阻断方式,在 linux 5.7 版本内核中 eBPF 添加了 LSM 的支持,开发者可以在 eBPF 中基于 LSM 实现更细粒度的管。出于兼容性的考虑,Tetragon 没有选择 eBPF LSM。下面通过例子演示一下这两种阻断方式。
send_signal()
send_signal()是 eBPF 的一个功能,它允许用户在内核空间发送信号来干预指定的进程。这个功能是 Linux 5.3 内核提供的一种新的方法,用于实时响应和控制系统行为。通过直接从内核空间发送信号,避免了用户空间的额外开销,从而确保信号能够在事件发生后立即被发送,大大减少了延迟。
send_signal()的主要优势包括:
实时响应:由于减少了延迟,信号可以在事件发生后立即被发送,实现实时响应。
准确性:减少的延迟使得我们可以获得更准确的系统状态快照,对于性能分析和异常检测尤为重要。
灵活性:send_signal()提供了更多的灵活性,开发人员可以根据不同的使用场景和需求来自定义信号的发送逻辑,从而更精确地控制和管理系统行为。
我们使用 kprobe 配合 bpf_send_signal()来阻断内核中 do_sys_openat2 函数,do_sys_openat2 是一个 Linux 内核函数,用于在指定的目录下打开文件或创建文件。例如我们可以配置阻断 curl,以阻止用户使用 curl 对网络进行访问。
当用户使用 curl 的时候,eBPF 程序会发送 bpf_send_signal(9)杀死当前进程。
eBPF LSM
上文提到,Tetragon 使用 send_signal()函数来杀死当前进程,以达到运行时控制的目的。那么有没有一种更细粒度的控制方式,比如只是对进程的某个函数进程控制?答案是有的,eBPF LSM 可以做到这一点。
LSM(Linux Security Module)是 Linux 内核中的一个安全框架,它从 linux2.6 版本内核开始引入。LSM 提供了一系列的安全钩子(hooks),这些钩子允许安全模块在关键系统操作发生时介入。例如,文件系统操作、网络通信、进程创建等都有相应的钩子,允许模块执行安全检查和控制。
eBPF LSM 是 eBPF 技术的扩展,于 linux 5.7 版本内核引入,让 eBPF 程序有了使用 LSM 框架的能力。借助 LSM 框架,eBPF 程序可以阻止进程执行过程中的某个特定函数,而不用将整个程序 kill 掉。
下面是一个 LSM BPF 程序,功能是对特定应用发送网络包的行为进行阻拦,选择 socket_sendmsg 这个 LSM 钩子,当使用 curl 请求网络包的时候,就会进行阻拦:
可以看到这里与 bpf_send_signal(9)有一些不同,使用 LSM BPF 只是阻止了 socket_sendmsg 这个调用,而非直接杀死进程。
总结
借助 eBPF 中的阻断能力,特别是对 LSM 的支持,开发者现在不光可以监控内核中的活动,也可以控制内核中函数的执行。相信在未来我们会看到越来越多基于 eBPF 的运行时安全产品。
评论