LWN:BPF与安全!
共 6223字,需浏览 13分钟
·
2023-10-31 21:49
关注了就能看到更多这么棒的文章哦~
BPF and security
By Jake Edge
October 4, 2023
LSSEU
ChatGPT translation
https://lwn.net/Articles/946389/
eBPF内核虚拟机即将迎来其加入Linux十周年,它已经发展成为生态系统中多种用途的工具。eBPF的创始人,也是其早期开发的主要负责人Alexei Starovoitov,在2023年Linux安全峰会欧洲的开幕演讲中谈到了BPF与安全的关系。他在演讲中分享了一些有趣的历史,从一个不同的角度来看待这段历史。他说,其中一些内容展示了BPF如何在发展过程中既是安全问题又是安全解决方案。
Universal assembly
BPF有点像vi编辑器,Starovoitov开始说,人们要么喜欢它,要么讨厌它,但两者都只是一系列的命令而已。关于BPF有很多定义,但他想在演讲中使用的是它是"通用汇编语言(universal assembly language)"。它是第一个严格类型化的汇编语言;在BPF中没有"指向内存的指针",所有指针都指向具体的类型。
由于它是通用的,因此它远远不能局限于用户空间告诉内核要做什么的这种使用场景;现在有硬件设备会将BPF发送到内核,以描述如何使用它们。还有用户空间到用户空间的应用程序,内核根本不参与;一个应用程序可能会告诉另一个应用程序,在世界的另一边,要做什么。
他经常被问及BPF和WebAssembly之间的区别。BPF不像WebAssembly或浏览器中的JavaScript那样是一个受沙箱保护的环境;沙箱环境不知道它们将要运行什么代码,因此它们必须限制执行环境。它们创建了一个边界,但由于所有运行时检查,这会降低性能。
相反,BPF是静态验证的,因此 "实际上没有运行时检查";唯一的检查是用于验证器无法静态确定的事情。主要区别在于BPF程序在执行之前其意图是已知的,而沙箱则完全不同,它们必须运行任意代码。他经常在评论中看到(尤其是在LWN上)关于将WebAssembly添加到内核的评论;他的回应是让那些开发者"引入它"。他认为内核足够大,可以容纳WebAssembly或其他沙箱环境以及BPF。
当eBPF首次在2013年提出时,它被称为 "内部BPF"(iBPF);最终发展为 "扩展BPF"(eBPF)。此外,eBPF本身已经多次扩展;在LLVM中,这些较新的指令集是使用值 v1
到 v4
的 ‑mcpu
选项选择的不同CPU模型。 v4
支持是在2023年7月添加的,这是GCC现在也支持的指令集。
Starovoitov说他幻灯片中的下一张(第9张)是他演讲中最重要的一张。每个项目都应该有一个使命宣言,他说。BPF的使命有两个部分,首先是 "创新",其次是 "使其他人创新"。该项目继续满足他对 "创新的渴望",但它还使其他人能够做新的事情。他在BPF上工作最喜欢的部分之一是帮助在邮件列表上发布他们正在尝试做的新事情的人;这是 "作为内核维护者的最佳时刻"。
他认为该项目的这种态度在内核中BPF开发社区的增长中表现出来。他展示了自2019年初以来每月独立开发人员的图表,显示了该时段内总体从大约50人增加到100人以上,而他所在的Meta BPF团队在同一时段内一直保持在10到15名开发人员左右。
Tracing and networking
他说:"BPF起源于tracing(跟踪)这个场景的需求"。eBPF程序可以连接到的第一个挂钩是kprobes和uprobes;接下来是tracepoints,然后是函数入口(fentry)和退出(fexit)。人们经常认为BPF可以在内核内部做任何事情,但实际上它受到相当大的限制。对于tracepoints,BPF程序可以读取内核数据,但无法进行任何修改。网络BPF程序可以读取和修改数据包数据并丢弃数据包,但它们不能修改内核状态。各种类型的BPF程序可以做的事情的限制是基于这些程序的用例。
他举了一些BPF跟踪的例子。Android使用BPF程序来跟踪基于所联系的主机的网络使用情况,他说。因此,如果用户想要在手机上查看他们的Facebook使用情况与YouTube使用情况等,他们通过BPF程序获取了这些信息。PyPerf程序使用BPF来分析
Python程序。此外,BPF程序可以附加到用户空间程序,使用uprobes,以便对程序的每次调用都附加了BPF程序。这可用于查看GCC在处理包含文件与编译代码的时间之间花费了多少时间;并行构建中的每次GCC调用都将被正确地仪器化以收集这些数据。
还有多个网络用例可以使用BPF。他指出,内核的快速数据路径(XDP)特性是一种应对分布式拒绝服务(DDoS)攻击的方式。曾经,Facebook遭受了一次500Gbps的DDoS攻击,通过在网络驱动程序层级上使用XDP放置了一个BPF程序来缓解这种攻击。这种方式吸收攻击提供了比早期DDoS缓解技术提供的10倍的改进,他说。
Security
将BPF程序附加到Linux安全模块(LSM)挂钩的能力(也称为BPF-LSM)是内核的一个最近添加的功能,用于 "防范所关注的安全攻击",Starovoitov说。与其他类型的BPF程序一样,可以附加到LSM挂钩(或系统调用)的BPF程序具有与跟踪、网络或其他程序类型不同的特定功能。这些程序可以读取任意内核数据并拒绝操作,但它们也可以休眠,这对于BPF程序是一种新的特性。这意味着如果它正在访问的用户空间地址已被交换(swap)出去,程序可以引发轻微错误,因此不可能通过引用交换出的内存来回避这些挂钩。
不幸的是,BPF-LSM程序通常不是公开可用的,不像跟踪和网络中的程序那样。他所知道的这些领域的程序中至少有90%是免费提供的;特别是许多大型互联网公司正在共同努力开展DDoS防护等工作,分享他们的代码,互相学习。在跟踪中也是如此,但对于BPF-LSM代码来说,"情况并不那么好"。
至于网络、跟踪甚至安全方面的BPF功能集目前几乎已经成熟;它们已经完成了95%,尽管最后的5%需要更多时间。但仍然在BPF中添加了新功能,包括最近新增的人机接口设备(HID)的BPF功能。这允许BPF程序修改HID设备(如键盘和鼠标)在内核中的呈现方式,以纠正问题(怪癖)或以某种方式更改行为。
Starovoitov说他对可扩展的调度器类感到兴奋,它允许BPF程序执行调度功能以测试新的调度算法。始终存在一些需要更专门的调度器的小众用例,尤其是在云工作负载中,其中虚拟机中的调度程序最终会与超级调度程序(hypervisor scheduler)争夺。到目前为止,至少直到目前为止,使用BPF进行可插拔调度已经被拒绝,尽管在演讲中没有提到这一点。
Unprivileged BPF
30年前创建的最初的伯克利包过滤(BPF)指令集,作为 "经典BPF"(cBPF)在Linux中继续存在,由 tcpdump 和 seccomp() 在继续使用。cBPF的用途是不需要特权的,因此eBPF也效仿了:32种BPF程序类型中的两种可以在没有特权的情况下使用,而且这两种程序只允许读取数据包数据和丢弃数据包。其中一种 BPF_PROG_TYPE_SOCKET_FILTER
完全没有用过,他说,所有其他程序类型总是需要root权限。
他说,这在eBPF存在于内核的头几年内还可以,直到2017年。那一年,Project Zero的Jann Horn编写了一些演示了猜测执行问题的BPF代码,最终成为了所谓的Spectre v1问题。现代CPU都进行猜测执行,但它们的错误预测的副作用仍然存在于缓存中,因此无法隐藏。正如在2017年底(以及之后几年)所看到的,这些副作用可以被转化为安全漏洞。
硬件供应商对这些问题的解决方案是建议通过在代码中添加"load fence"(lfence)指令来阻止任何可能的分支错误预测。Microsoft遵循了这一建议,改变了其编译器以在代码的各个地方发出这些指令;然后重新构建了Windows和其他工具。
硬件供应商要求Linux内核也执行相同的操作,但内核开发人员有其他想法,他说。=lfence= 指令是一个大锤,具有重大性能影响,因此决定内核通过将其引导到安全方向来管理猜测,而不是通过 lfence
关闭它。这需要大量的工作来说服英特尔和Arm这种技术是可行的,但它最终导致了对该问题更好的解决方案. 该补丁中的 array_index_nospec()
宏已经在内核中使用了240次,其中一些位于非常繁忙的路径中,例如查找文件描述符表中的索引。使用 lfence
指令的影响将会很大。
BPF在Horn的攻击中被用到,因此也需要对BPF进行一些更改。BPF不能直接使用这个宏,但它进行了等效的更改,以避免Spectre v1。然而,仅仅几个月后,Horn回来了,用BPF解释器实现了Spectre v2攻击,这引起了关于BPF安全问题的进一步担忧。实际上,攻击并没有加载BPF代码到内核中,而是使用了用户空间中的BPF指令的解释器进行猜测执行。
解决方案是避免在内核可执行代码中具有解释器代码。BPF代码可以被解释或使用即时(JIT)编译器运行,因此添加了 BPF_JIT_ALWAYS_ON
选项来始终启用BPF的编译器并删除解释器。虽然BPF进行了更改以避免此问题(实际上是CPU硬件中的问题),但他认为内核中的任何解释器都可以以这种方式使用;至少还有另外三种解释器,所以内核仍然没有完全安全,Starovoitov说。
这是一个有趣的情况,BPF JIT编译器的认知如何随着时间的推移发生了变化,他说。在2011年,曾发生过JIT spraying攻击,这使一些内核开发人员怀疑JIT编译是否适合放到内核中。当时解决了这个问题,但现在必须启用JIT编译器以避免Spectre v2。BPF开发人员还发现JIT编译器解决了一些性能损失问题(由retpolines 引入,这是另一种Spectre v2防护措施)。
2019年,BPF开发人员决定通过验证器变得更聪明,以避免其他猜测执行问题。Daniel Borkmann对验证器的更改进行了修改,以检测和避免这些问题。为此,验证器模拟了正常和猜测执行,这在行业中是 "独特的,没有其他静态分析工具可以进行这种猜测分析"。
接下来出现了Spectre v4,通过验证器中的少数代码来净化堆栈来进行了缓解。但其他Spectre变种仍然在不断出现,因此最终决定添加配置选项以完全禁用无权限的BPF。可以在没有特权的情况下使用的这两种程序类型是 "极其小众的用例,用户数量少于一只手的手指数量";继续支持该功能对BPF社区的繁荣和增长无益。BPF_UNPRIV_DEFAULT_OFF选项默认设置为 "on",以便发行版不允许无特权的BPF程序,尽管管理员可以修改这个选择。
CAP_BPF
多年来,一直有要求将一些BPF权限从执行几乎所有BPF操作所需的根root特权(实际上是CAP_SYS_ADMIN)中分离出来的呼声。CAP_PERFMON功能是由perf子系统添加的,但它也被BPF采纳;它允许读取内核内存。CAP_BPF功能添加了用于管理各种BPF操作的权限;它可以与CAP_PERFMON组合使用,以允许加载有用的跟踪程序,或者与CAP_NET_ADMIN组合使用,以允许加载有用的网络程序,而无需CAP_SYS_ADMIN。
然而,CAP_BPF存在一个理解问题;不清楚它实际上是用来管理什么。问题的一部分是BPF没有受到命名空间的限制;如果您可以查看内核内存,那么您可以查看所有内核内存,而不仅仅是单个容器中的内存。CAP_BPF的设计目的类似于CAP_SYS_MODULE,这是加载内核模块所需的权限;该功能实际上授予了让内核crash的权限,因为恶意(或有缺陷的)模块可以这样做。
但是,验证器错误可能导致BPF程序崩溃内核,尽管这应该被视为可能性,但却被视为一个安全漏洞,Starovoitov表示。因此,每个验证器错误都会获得一个CVE,这是一个真正的问题。他提到了9月中旬的LWN文章,该文章涉及到 "虚假CVE" 的问题,这也是BPF项目的问题。错误会被修复,但会在早期的内核版本中提出CVE,而尚未进行后期支持;有时这些CVE甚至引用BPF运行的自测代码,以确保错误仍然得以修复。CVE的存在会导致紧急修复旧内核。
一些安全初创公司正在以奇怪的方式使用BPF。他提到了一个未具名的初创公司,抱怨BPF可以做什么,以及存在BPF的系统的危险性;当然,这都是为了推销初创公司的产品。奇怪的地方在于,该产品使用BPF来防护免受其抨击的所有BPF问题。"最终,他们说 'BPF很糟糕,使用BPF来保护免受BPF的威胁'。"
他没有多少时间了,所以他开始迅速地过了他的演讲中剩下的部分。他指出,很少有BPF-LSM程序是开源的,其中一个程序被systemd用于根据允许和拒绝列表防止挂载文件系统类型。他展示了systemd如何使用BPF来执行各种安全策略的几个示例。在某些情况下,它使用BPF-LSM挂钩,但在其他情况下,它使用其他BPF程序类型(如网络和跟踪)来执行其工作。
Starovoitov表示,他认为所有内核模块实际上都应该编写为BPF程序。内核模块的优势在于它们可以编写为任意的C代码,具有对内核符号的完全访问权限,但这也意味着它们可能会因为错误而导致内核崩溃。对于BPF程序,安全性是通过验证器内置的。此外,BPF程序比内核模块更具可移植性。这种可移植性是一种被低估的优势,特别是对于拥有大规模系统的公司来说,其中的一些长尾系统将具有各种不同的内核版本。
他最后简要提到,他认为BPF所使用的C语言版本是内核编程的更好版本。通过验证器内置了可以进行验证的C版本的安全性,对于内核编程来说是更好的选择。他的幻灯片显示了一些可以避免的错误构造,但他无法详细介绍这些细节,尽管其中一些已经在一年前的一个演讲中提到。有人怀疑这个观点在BPF社区之外尚未被广泛接受。
全文完
LWN 文章遵循 CC BY-SA 4.0 许可协议。
长按下面二维码关注,关注 LWN 深度文章以及开源社区的各种新近言论~