LWN: /dev/userfaultfd

Linux News搬运工

共 2686字,需浏览 6分钟

 ·

2022-06-30 18:49

关注了就能看到更多这么棒的文章哦~

/dev/userfaultfd

By Jonathan Corbet
June 13, 2022
DeepL assisted translation
https://lwn.net/Articles/897260/

userfaultfd() 系统调用允许一个线程在用户空间帮另一个线程来处理 page fault。有许多有趣的实用例子,比如虚拟机进行的实时迁移(live migration)。不过,也有一些不那么美好的用法,大部分都是被希望控制这个机器的攻击者来利用的。多年来,人们一直试图让 userfaultfd() 不容易被利用来作为一种攻击工具,但 Axel Rasmussen 的这个 patch set 采取了另一种方式,完全规避了系统调用。

对 userfaultfd() 的调用会返回一个 attach 到当前进程的特殊文件描述符。这个描述符的功能之一就是可以被用来(与 ioctl()一起)注册一些内存区域(regions of memory)。当前进程中的任何线程在这些注册进来的 memory region 中遇到 page fault 时,它就会被阻塞住,然后有一个 event 会被发送到 userfaultfd()文件描述符去。管理线程(managing thread)在收到该 event 时,对于如何处理 fault 有几种选择,其中包括将数据复制到一个新的 page、创建一个全零的 page、或者映射到地址空间中位于其他地方的 page。在 fault 完成处理之后,发生 fault 的线程就会继续执行。

线程在用户空间中运行时通常总会遇到 page fault 的,例如,它可能引用了一个指向了不存在的 page 的指针。但有些时候,page fault 也会在内核中发生。举一个简单的例子,假设有一个 read() 调用;如果提供给 read()的 buffer 此时未驻留在 RAM 中的话,那么内核试图访问时就会产生一个 page fault。此时会像上面说的一样被 block 住,但这次是被 block 在内核中,而不是在用户空间。

在处理操作用户空间的内存时,经常会出现在内核中阻塞在 page fault 的现象,此时一切都能正常工作。除了一点:如果攻击者能够在内核中的一个已知位置强迫触发一个 page fault(这通常不难做到)他们可以使用 userfaultfd() 来永远阻止内核线程的继续执行。这反过来又可以用来扩大一个原本很难碰到甚至完全不可能碰到的 race window,给攻击者机会在内核等待的时候来干坏事了。

这种对 userfaultfd()的滥用已经不仅仅存在于理论上了。多年来,各种利用 userfaultfd() 的漏洞(如这个例子https://duasynt.com/blog/linux-kernel-heap-spray )已经暴露了出来。人们已经认为这个问题非常严重了,所以在 2020 年增加了一些限制。如果 vm/unprivileged_userfaultfd 这个 sysctl 开关被设置为 0(许多发行版上的情况都是这样),那么如果希望 userfaultfd() 调用成功,就一定需要满足如下两个条件之一:要么调用进程具有 CAP_SYS_PTRACE 这个 capability,要么就需要在调用系统调用的时候提供 UFFD_USER_MODE_ONLY flag。在后者情况下,在内核中运行时遇到的 page fault 将不会通过 userfaultfd() 机制来处理,哪怕它们发生在注册进来的内存区域里。

这个改动在 2020 年底被合并到 5.11 中。它使得攻击者无法使用 userfaultfd(),但它也使合法的进程(但是也许没有特权)无法使用全部功能了。正如 Rasmussen 在系列补丁中的一个 patch(https://lwn.net/ml/linux-kernel/20220601210951.3916598-3-axelrasmussen@google.com/ )所指出的,这个问题可以通过给有关进程提供 CAP_SYS_PTRACE 能力来解决,但这可以实现一些与 userfaultfd()无关的操作。具体来说,它可以允许进程从系统中的任何其他进程中读取数据或注入代码,这可能不是一个好事情。相反,能够为一个进程启用完整的 userfaultfd() 功能,而不授予它更广泛的、本来并不不需要的特权,这才是比较好的做法。

Rasmussen 的解决方案是创建一个新的特殊文件,名为 /dev/userfaultfd,它可以访问这个功能,而不需要调用 userfaultfd()。人们可能会认为,打开这个文件会得到一个文件描述符,就跟来自 userfaultfd() 的描述符一样,但其实并不是这么简单的。这里唯一可以对 /dev/userfaultfd 文件描述符所做的事情就是用 USERFAULTFD_IOC_NEW 命令来调用 ioctl();这将创建一个 userfaultfd() 式的文件描述符。

以这种方式创建的文件描述符与 userfaultfd()的文件描述符基本完全一样,但除了一处例外:无论调用进程的权限级别或全局的 sysctl 开关的设置是怎样的,都允许 kernel fault 处理。换句话说,这个效果是规避了 2020 年的 patch,使所有进程都可以使用完整的 userfaultfd() 功能了。不过这个进程必须首先能够打开/dev/userfaultfd,然后才能获得这个功能。通过设置这个文件的访问权限,管理员就可以控制谁能够打开它并以这种方式来使用 userfaultfd()。

换句话说,/dev/userfaultfd 允许管理员将处理 kernel fault 的能力赋予特定进程,而不需要授予任何其他权限了。这组 patch 已经是第三次修订了,到目前为止收到的 review 意见已经得到解决。如果没有什么意外的话,这个围绕 userfaultfd()的安全策略的新改动似乎有可能在不久的将来的某个合并窗口内进入内核了。

全文完
LWN 文章遵循 CC BY-SA 4.0 许可协议。

欢迎分享、转载及基于现有协议再创作~

长按下面二维码关注,关注 LWN 深度文章以及开源社区的各种新近言论~



浏览 16
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报