关注了就能看到更多这么棒的文章哦~
Operations restrictions for io_uring
By Jonathan Corbet
July 15, 2020
From: https://lwn.net/Articles/826053/
Translation benefits from DeepL
io_uring子系统的存在时间才刚刚一年。它是在2019年5月合入到5.1内核的。最初是作为一种更好的从用户空间执行异步I/O的方式而添加的。随着时间的推移,它已经获得了许多特性和支持功能,而不仅仅是做数据搬移而已了。它尚未具备的能力之一,是内核层面的安全机制,除了那些kernel已经为底层系统调用提供的安全机制以外。不过,这种情况可能会有所改变,因为Stefano Garzarella的一组新patch为io_uring添加了一组user-configurable restrictions。
人们可以从它的名字中了解,io_uring是一个基于内核和用户空间的共享机制的ring buffer,允许用户空间向内核来提交各种操作(operation)。此外还有一个ring buffer,里面装的是这些操作的结果。每一个操作都可以看做是一个系统调用的另一种表达方式;每个操作可以读写buffer、打开文件、通过网络发送消息、或者要求进行其他的一些操作。操作可以根据之前一个操作是否成功完成来作为前提条件。简而言之,送入内核的操作流(operation stream)可以看做是一种指定要求内核应该异步执行某些程序的描述性语言。
io_uring执行的操作会在内核内来触发调用那些相应系统调用的代码。例如,一个IORING_OP_READV操作,最后会和readv()系统调用走到同样的地方。这部分代码会使用当初创建ring的进程的credential来执行正常的权限检查。因此,假如没有bug的话,一个进程不能用io_uring来做那些无法直接调用系统调用可以做的事情。不过有个例外,就是seccomp() filters不适用于io_uring。到目前为止,这个工作模型对于io_uring来说工作得很好,不过现在看来,有一个使用场景会需要进行多一点控制。
具体来说,如果一个进程想要创建一个ring,并将其交给另一个不太受信任的进程,会发生什么?例如,如果能够使用io_uring的话,来自virtualized guest内部的I/O操作或许可以大大加速。这种I/O操作目前通常是使用Virtio机制来实现的,涉及到一定量的数据复制和上下文切换,这些开销在使用io_uring时候都可以避免。hypervisor程序可以创建guest系统里所需要的那些文件描述符,这些文件描述符是对应特定的外设或者是某个网络连接的,然后让Guest系统直接对ring操作来进行后续处理。
这个想法有一个问题,guest系统会能够执行io_uring所支持的所有操作。大家还记得,创建之后的ring会保留着创建者的credential,在这种场景里就是hypervisor。把这样一个ring给guest系统的话,将导致其可以访问hypervisor打开的其他文件描述符,或者可以用hypervisor的权限来打开新文件。这会让期待virtualization来作为安全屏障的用户感到非常失望。
根据Garzarella的说法,这个问题的一个解决方案就是允许为每个ring来注册一组限制条件。他为此增加了一个新的opcode(IORING_REGISTER_RESTRICTIONS)。可以支持添加几种类型的restriction:
IORING_RESTRICTION_REGISTER_OP
提供了在这个ring中可以执行的操作的注册操作列表。注册的操作会在ring中添加文件描述符和buffer,并优化它们在后续操作中的使用。换句话说,这些都是对ring本身的设置操作,实际上并不进行什么 I/O操作。
IORING_RESTRICTION_SQE_OP
这个ring中允许的操作(也就是实际的系统调用),也是以列表的形式提供的。这在代码中被称为 "白名单",在补丁进入主线之前,这个术语极有可能会被改掉。任何没有出现在这个列表中的操作都将在这个ring上被禁止。
IORING_RESTRICTION_FIXED_FILES_ONLY
如果应用了这个限制,那么在操作中只能使用之前在ring中注册过的文件描述符。换句话说,它可以用来限制一个ring只能对特定的一组已知文件集合进行操作。
因此,上面的大多数 "限制 "实际上都是permission,它们规定了此ring上允许做的事情。其中,这里的allowlist方法将有助于防止将来(肯定会发生的情况)当新的操作添加到io_uring列表中时出现意外。这些restriction只要应用一次就好,之后只要这个ring还存在,它们就会固定生效。
最后还有一点工作,这是由io_uring维护者Jens Axboe针对之前的patch set提出的,就是新增一个flag (IORING_SETUP_R_DISABLED),可以在第一次创建ring时提供。如果设置了这个flag,那么这个ring在启动时会进入disabled状态。这是注册操作仍然会成功,但任何其他操作都会失败。这使得ring创建者可以执行必要的注册和添加限制,而不必担心其他线程已经开始使用ring进行I/O。等到注册阶段完成后,IORING_REGISTER_ENABLE_RINGS这个注册操作将完成ring的配置并启用所有这些(被允许的)操作。
这种限制机制似乎足以满足上文中描述的场景,即允许对一组特定的文件描述符进行访问限制。很有可能今后有人会在某个时候希望添加更复杂的策略机制,到时候为这些安全策略增加一个BPF hook的做法很可能是无法避免的。不过在近期来说,本提议中的限制机制很可能可以用来加快虚拟机或其他不可信环境中的I/O速度,这会是一个很有用的优化。
全文完
LWN文章遵循CC BY-SA 4.0许可协议。
欢迎分享、转载及基于现有协议再创作~
长按下面二维码关注,关注LWN深度文章以及开源社区的各种新近言论~