关注了就能看到更多这么棒的文章哦~
Taming the BPF superpowers
By Jonathan Corbet
September 29, 2021
LPC
DeepL assisted translation
https://lwn.net/Articles/870269/
人们希望能对 BPF program 进行签名认证(sign),这个工作一直在努力进入 mainline。它希望通过限制哪些 BPF program 可以被加载到内核中,来提高系统安全性。正如 John Fastabend 在 2021 年 Linux Plumbers Conference 上的 "Watching the super powers" session 中所描述的,这个新功能有可能会导致他的工具完全无法再使用。但是,他并不只是提出这个抱怨而已,而是决定要研究并提供解决方案。他最终提出了一个大致的审计机制(auditing mechanism),希望在控制哪些 program 可以运行的时候带来更大的灵活性。
内核多年来一直支持对 loadable module 进行签名,所以对 BPF 程序提供同样的机制当然是有意义的。但是,尽管内核 module 和 BPF program 看起来很相似(他们都是从用户空间加载到内核的代码),但它们之间有一些明显区别。内核 module 的安全性完全取决于开发者是否足够勤劳。因为它们是通过一直保持的渠道来建立和发布的,跟特定内核版本相关联,并且可以保持多年可用,它们给用户空间提供的 API 是非常稳定的。相反,BPF 程序则依赖于 loader (加载器)中的安全,由 loader 来强制保证。它们通常是动态构建和优化的,会在系统运行时来打补丁(are patched),避免同内核版本绑定起来,而且它们各自的寿命各有不同,很多时候,它们是临时创建出来的,很快就会被废弃掉。这些差异表明,如果使用同样的签名机制,对这两种类型的程序来说可能不会有达到相同的效果。
Fastabend 介绍了 BPF 签名方案,感兴趣的读者可以在 LWN 之前 4 月份的文章中找到更完整的描述。简而言之:BPF program 的加载是一个复杂的、多步骤的过程,涉及到许多系统调用。签名需要能覆盖整个过程。为了实现这一点,就加载了另一个 BPF 程序来处理这个过程。这个机制的大部分已经实现,但还有一些细节正在继续解决。
他说,这种机制当然是有好处的。如果 Alice 和 Bob 手中拥有签名过的 BPF 程序,他们可以照常使用这些程序。如果 Eve 带着一个用来窃听内核的未签名的 BPF program 程序,那么在加载的时候这个 program 就会被拒绝,Eve 肯定会受挫。但这里也有一个代价:如果 Alice 是在运行时动态(on the fly)生成的程序,那么这些程序不会被签名,也就不再能被加载。用于给程序签名的密钥不应该存在于系统中,所以签名无法在现场进行,这样以来 Alice 的正常工作流程就无法进行了。尽管 Alice 是一个合法的用户,但她也会感到同样受挫。
这不仅仅是一个假设性的案例。现在很多工具都是采用这样的方式来工作的。最著名的例子可能是 bpftrace,但它并不是唯一例子。P4 system 定义了一个特定领域的语言,用于管理网络数据路径(networking data paths)。Fastabend 在 Cilium 上进行的一些工作就是针对 BPF program 的运行时(run-time)优化。PcapRecorder 则是一个基于 XDP 来实现的用来替代古老的 tcpdump 的工具。除此之外还有很多。这些工具都不能在这些要求 BPF 程序必须被签名的环境中正常工作。
他说,许多安全方面的目标都可以通过使用 Linux 现在支持的 fs-verity 机制来实现。通过 fs-verity,只读文件可以被签名,内核将在每次访问时检查签名。如果文件被破坏了,无论是什么原因,签名都将不再吻合,这样对该文件的访问就将被阻止。因此,这里可以做的一件事是使用 fs-verity 来对把 BPF program 加载到内核的程序进行签名。系统将自动可以确保这个程序不会被篡改,而且也可以限制有哪些密钥可以用来创建有效签名。
但可以比这更进一步,Fastabend 说。使用某种 policy engine(可能是来自另一个 BPF program 或 Linux security module),内核就可以检查用来签署某个 program 的密钥,并把相应的一些权限跟它绑定起来。最起码可以有一个单独的 "可以加载 BPF program" 的特权,这实际上就相当于将 CAP_BPF capability 跟这个 program 绑定起来。这个方案甚至可以做一些更细粒度的检查,比如控制是否能访问 BPF maps 等等。他说,有了这种机制之后,就不再需要对 BPF object 本身进行签名检查了。
在之前说的情况中,Alice 的使用 BPF 的进程在运行时被攻击破坏了,那么对 BPF 程序进行签名并不能解决这里的问题。如果用户空间代码被攻击了,它可以继续对系统做一些破坏,比如改变 BPF maps 中的某些值、改变 program 的 attach 位置等等。换句话说,对一个 BPF program 进行签名几乎无法保证该程序可以在有恶意的环境中正确运行。但是如果我们使用了一个更灵活的 policy 机制的话,情况其实会更好,因为它可以在 BPF program 试图做一些不被允许的事情的时候阻止它。也许可以允许未签名的程序被加载,但它们不会有能力来对用户空间进行写入、或者不能访问到内核内存等等。同样也可以拒绝它访问那些 pinned maps。
这个机制目前还没有被实现出来,但他对如何做到这一点有一些想法。LLVM 编译器可以给 object 附加属性(attributes),可以修改这个功能来记录程序所调用的所有 helper functions、或者所有 BPF maps 相关操作等等。然后,BPF verifier 将确认该程序会被限制来只能进行这些操作,而监督机制(supervisor mechanism)可以根据 attributes 来决定允许或拒绝这个程序。剩下的工作就是弄清楚这一切将如何实际实现。
Fastabend 最后重申了他的目标,即确保动态生成的 BPF program 也能继续正常使用。程序签名似乎是个错误的解决方案,它只在签名的程序不会被改变的场景下有效果。而如果有了一套适当的 policy rules,就可以确保安全地让系统运行一些没有被签名的 BPF program。在随后的简短讨论中,Alexei Starovoitov(现在签名机制的作者)很热情地指出还有许多其他类型的权限可以被添加进来,比如限制一个程序中所允许的最大指令数。因此,人们看起来对这个想法很感兴趣,但要想真正证明这一点,那还是跟往常一样,需要给出代码来证明。
本次会议的视频可以在 YouTube 上找到。
全文完
LWN 文章遵循 CC BY-SA 4.0 许可协议。
欢迎分享、转载及基于现有协议再创作~
长按下面二维码关注,关注 LWN 深度文章以及开源社区的各种新近言论~