LWN:阻止userfaultfd()处理kernel-fault!

本文探讨了userfaultfd()系统调用的安全性问题,介绍了如何通过新增的UFFD_USER_MODE_ONLY标志和sysctl开关来限制其在内核空间中的应用,防止潜在的攻击。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

Blocking userfaultfd() kernel-fault handling

By Jonathan Corbet
May 8, 2020

原文来自:https://lwn.net/Articles/819834/

主译:DeepL

userfaultfd()系统调用是一个有点奇怪功能。它允许用户空间来负责处理page fault,这中任务通常是内核应该做的事情。因此,那些会攻击Linux内核安全的人来说,也就会想要利用它来做些事情了。最近Daniel Colascione的一组小patch set,做出了一个重要的改变,至少可以帮助阻止一类使用userfaultfd()的攻击。

调用userfaultfd()会返回一个可以用于控制memory management的fd。通过进行一组ioctl()调用,用户空间进程可以接管那些发生在它指定地址区间的page fault处理。此后,该范围内的page fault将产生一个可以从fd中读取的event事件;进程可以读取该event,并采取必要的行动来解决page fault。然后,它应该将一个描述此page fault解决方法的response写到同一个fd中,之后导致fault的代码将恢复执行。

这个机制通常用于多线程进程中,由一个线程承担故障处理任务。userfaultfd()有很多使用场景。最初的一个例子是处理进程从一台机器到另一台机器的live migration(运行时迁移)。进程可以被移动到新系统上再重新启动,但是会留下大部分内存在原位,后面当它需要的时候,这些页面可以利用userfaultfd() event来按需通过网络进行调取。这种方案就能够减少进程被迁移时的停机时间。

由于内核会等待user space handler处理程序来解决page fault,因此发生这种page fault的话会导致此进程的执行被延迟不可知的时长。肯定会出现这种情况。例如,如果一个进程依赖网络上其他服务器的内存内容的话,出现相关的page fault就会立即停止执行,不确定多久能够恢复执行。不过,userfaultfd()有一个特点:解决故障所需的时间是由进程直接决定的。

通常情况下,这个特点不会导致什么问题,毕竟进程如果处理太慢的话它只是在减缓自己的速度。但有时候,内核中也会产生page fault。其实几乎所有的系统调用都会导致内核访问用户空间内存,可能是来自一次I/O操作,也可能是因为调用了copy_from_user(),或者其他许多许多可能。每当内核访问user space内存时,它必须要有相关page可能不存在的觉悟。换句话说,kernel必须触发并处理好page fault。

攻击者可能就会利用这种行为,使内核代码的正常执行在特定时间段内如攻击者的意愿而卡住。特别是,攻击者可以使用userfaultfd()来控制一个特定的内存范围,然后他们确保这个范围内的page都不在RAM中。当攻击者进行系统调用,试图访问该范围内的内存时,他们会得到一个userfaultfd()事件,告诉他们内核已经停下来并正在等待该page。

攻击者暂停kernel之后就完全可以利用某种race condition等来攻击系统。比如说,假设攻击者发现了一个潜在的time-of-check-to-time-of-use的代码bug,那么在那个关键时间点改变内存中的某个值,就会导致内核执行一些错误操作了。利用这样的漏洞本来需要在内核检查一个值和使用一个值之间这么短的时间窗口内来做坏事,因为窗口很窄,其实很难用。但是如果内核可以在这个时候被阻塞住,那么攻击者就会拥有近乎无限长的时间,来从容利用这种漏洞了。

我们完全可以剥夺user space处理内核空间中发生的page fault的权利。不过,这样做的话肯定会破坏现有的代码功能,所以补这个漏洞的话还需要做一些其他的工作。Colascione 的patch分两步解决了这个问题,首先是为 userfaultfd() 添加了一个新的flag ,即UFFD_USER_MODE_ONLY,它用来指定所生成的fd只能用于处理在user space中发生的fault。因此,任何创建时带了这个flag的fd都不能被攻击者利用来做上面介绍的攻击了。

人们可以尝试礼貌地要求攻击者在他们的userfaultfd()调用中使用UFFD_USER_MODE_ONLY(开玩笑!),但我们面对的是那些不会在意这些礼貌请求的人。所以,这个patch set增加了一个新的sysctl开关,名称很简明扼要:vm/unprivileged_userfaultfd_user_mode_only。如果设置为1的话,假如application不设置UFFD_USER_MODE_ONLY flag,非privileged用户的userfaultfd()调用就会失败。这时,尚在试图获得root访问权限的攻击者将无法再利用kernel-space fault handling。不过默认值必须为0,以保持与旧内核的兼容性。

到目前为止,这套patch set的唯一回应来自于Peter Xu,他指出可以利用现有的vm/unprileged_userfaultfd开关来扩展一下实现这个功能。这个开关可以设置为0来完全禁止 userfaultfd() 对非特权进程进行访问,而它的默认值(1)则允许这样的访问。Xu建议将其设置为2就可以允许非特权进程使用,但只允许user space fault。这种方法可以避免增加一个新的开关。

除此以外,大家对这个patch set似乎没有什么争议。这是一个很小的patch,对现有用户来说没有任何破坏,所以应该不会被mainline拒绝的。

全文完

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

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值