LWN: splice() 以及 set_fs() 的残留印记!

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

splice() and the ghost of set_fs()

By Jonathan Corbet
May 26, 2022
DeepL assisted translation
https://lwn.net/Articles/896267/

内核开发有一个通常规则,就是不允许导致用户空间出现 regression。那些会破坏以前能正常工作的应用程序的 patch 必须要被 fix 或 revert。不过也有例外,比如 https://git.kernel.org/linus/36e2c7421f02 这个自从合入 5.10 之后就经常有报出 regression 问题的 patch。这里背后的故事正好展示了人们在稳定性、避免出现安全问题、以及代码清理这几个目标发生冲突的时候会导致什么样的结果。

set_fs() 函数在 Linux kernel 很早期已经支持了。它不是在最初的 0.01 版本中,而是在 1991 年末的 0.10 版本之前被添加进来的。通常,那些希望访问用户空间内存的内核代码如果尝试访问内核空间,就会报出 error;比如可以利用这个限制来阻止攻击者通过系统调用访问内核内存。在确有需要的时候可以调用 set_fs(KERNEL_DS) 可以解除限制。set_fs() 的一个常见使用场景就是可以用来从内核中执行文件 I/O。调用 set_fs(USER_DS) 会重新加以限制。

set_fs() 的问题在于,很容易忘记再次调用 set_fs() 来恢复对内核空间的保护,这就会马上出现那种内核开发人员通常会努力避免的“全面被攻破”的场景。多年来人们已经 fix 了许多这种类型的错误,但很明显,真正的解决方案还是完全摆脱 set_fs(),并在确有需要的时候采用更安全的方式访问内核内存。

开发人员(尤其是 Christoph Hellwig)在 2020 年更加关注这个目标了,他在坚定地推动完全消除 set_fs() 的工作。尽管 set_fs() 基础架构的最后部分是在 5.18 中删除的,但他的大部分工作在 5.10 的时候就合入了。然而,在 2020 年的时候出现了一个问题,引发了关于该如何处理 splice() 的讨论。

splice() 系统调用会将打开的文件描述符跟 pipe (管道)连接起来,然后在数据流持续不断的情况下在两者之间移动数据。这些搬移动作完全发生在内核中,可以用来消除大量不必要的系统调用;在某些使用环境中,可以提供显着的性能改进。就其本质而言,splice() 通常必须将数据移入和移出内核空间中的 buffer。为了实现这个目标,它就使用了 set_fs()。

Hellwig 为了在没有 set_fs() 的情况下仍能让 splice() 正常工作,提出了一个新的实现,但 Linus Torvalds 拒绝了它,说他认为这个实现“太复杂和混乱”了。但他也明确表示,他觉得根本不需要保证 splice() 机制可以继续正常使用。他认为在大多数让 splice() 在大多数文件类型上默认可用就会导致许多安全问题。例如,在 2020 年晚些时候,他说:

我宁愿尽可能地限制 splice(以及 kernel_read, 基于同样的理由)。原本允许到处使用它就是一个错误,现在它又回来给我们造成麻烦了。

所以我宁愿让人们注意到这些奇怪的 corner case 并逐个 fix 掉,而不是仅仅说 “什么都可以保证”。

因此,进入 5.10 的 patch 最终破坏了 splice() 对于没有明确支持新的处理方式的类型的文件的支持;人们的想法是随着时间的推移,那些重要的使用场景会被注意到并得以解决。这确实发生了。如果看一下所有那些专门为了禁用 splice() 支持而进行 fix 所提交的 patch 列表,就可以看到针对 AFS 文件系统、9p 文件系统、orangefs 文件系统、/proc/mountinfo、TTY 子系统、kernfs、sendfile()、 nilfs2 文件系统和 JFFS2 文件系统的改动。

最近,Jens Axboe 报告说 splice() 不再适用于 /dev/random 或 /dev/urandom 了。他还提供了一个 patch 来解决这个问题。这些 patch 后来由随机数生成器的维护者 Jason Donenfeld 重新设计,并在 5.19 合并窗口期间应用于 mainline。在此过程中,Donenfeld 观察到这个必须要做的改动导致 /dev/urandom 被读取时的性能下降约 3%。于是他询问这个 fix 是否是必须的?在一番讨论中,Axboe 给了他上了关于 regression 的一课:

如果您有一个使用 slice 来读取 /dev/urandom 的应用程序,那么确实会合理地期望可以继续安全地使用这种机制。如果我们在内核中有一个核心原则,那就是用户应该始终能够对内核进行升级,而不用担心在用户空间 ABI 方面出现违背情况。显然,有时会发生这种情况,但我认为这正是不应该发生的导致用户 ABI 失效的一个典型案例。我们取消了人们所依赖的功能。

确实这是一个会导致出问题的改动,但是这个案例里面,人们知道会导致这种问题的情况下仍然决定做这个修改。Hellwig 在回应 Axboe 的 patch set 时说,“与我最初的担心相比,后来看下来影响实际上并没有那么糟糕”,但仔细阅读上述 fix list 的话可能会有不同结论。

删除 set_fs() 是内核开发过程可以做什么的一个典型案例。一个从一开始就深深嵌入内核的底层结构的基本功能被替换为更安全的替代方案,而不会打乱 kernel 每 9 或 10 周进行一次发布的稳定步伐。然而,这种变化导致的源源不断的 regression 并不是该项目的初衷。可以肯定,这种 regression 后续还会出现。

基于 splice() 系统调用的过去历史,出于对安全问题的恐惧,人们决定了采取这条道路。如果这些担心仍然是有道理的(他们很可能是有道理的;比如 splice() 就是今年早些时候报告的“dirty pipe” 漏洞中的一个要素),那么拒绝让所有现有的 splice() 使用位置在不用 set_fs() 的情况下的正常工作,也许会阻止我们看到一些比当前情况更严重的 regression 问题。必须 fix 文件系统,这个工作是很烦人,如果不得不为未来某个臭名昭著的漏洞再忍受另一次安全问题导致的混乱,那就更加烦人了。

没有办法知道后续事情是否会如此发展。但确实这种类型的插曲使得内核的“no regressions”规则看起来更像仅仅是一个指导方针了。不需要太多这种案例,就会对 kernel project 的声誉带来严重破坏,甚至仍人们无法再相信它。

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

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

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

b79e157e84c6accdf5c5fdeed444d54b.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值