LWN:算数溢出的内核防护!

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

Arithmetic overflow mitigation in the kernel

By Daroc Alden
July 1, 2024
Gemini-1.5-flash translation
https://lwn.net/Articles/979747/

2024 年 5 月 7 日,Kees Cook 在 linux-kernel 邮件列表中发送了一个 提案,要求内核开发人员开始着手解决无意(unintentional)算术溢出问题。算术溢出一直是许多 bug 的根源。这不是 Cook 第一次提出类似的要求;他在 2024 年 1 月 提交了一套相关的补丁。一些核心开发人员出于不同的原因对该计划表示反对。在收到他们的反馈后,Cook 修改了他的方法,以一系列更小的步骤来解决这个问题。

Cook 提及了他在 2024 年北美 Linux 安全峰会上 发表演讲时的 幻灯片,称内核“平均每年大约有 6 个重大的整数溢出漏洞”。在他的电子邮件中,他明确表示自己并不谈论未定义行为:“我们已经要求我们的编译器为所有算术运算使用一个定义明确的溢出解析策略;溢出导致回绕(感谢 ‘-fno-strict-overflow’)。”

相反,Cook 担心的是无意算术溢出,即使它是定义明确的,也会导致计算出不正确的值。当它牵涉到边界检查时会尤其麻烦,但这并不是唯一一个错误值可能造成安全隐患的地方。不幸的是,识别无意算术溢出发生的位置很困难,因为内核中有很多地方使用了 故意 的算术溢出。因此,任何解决方案都将需要一定的人工干预,以确定在任何给定情况下溢出是否预期。

Cook 提出了两种可能的解决方案:添加一个新的清理工具 (sanitizer),或者操作符重载 (operator overloading)。内核已经使用了几种清理工具来捕获诸如无效内存访问和未定义行为等问题。Cook 的第一个提议是为预期回绕的值添加 typedef,然后随着时间的推移开始对内核进行切换。清理工具最初会发出警告,直到内核被转换,届时它可以被更改为发出错误。这是 Cook 首选的方法。另一种可能的解决方案将使用 C 中 最近提出的(一旦编译器实现它)用于操作符重载的功能,该功能将允许内核自定义不同类型的处理方式。

回应

David Laight 反对了该提议,理由是性能问题,而 Cook 在他的原始电子邮件中并没有提到这个问题,因为他认为这是一个次要的考虑因素。Laight 担心所需的额外检查会使代码膨胀,并使分支预测变得更糟。Cook 回应了一些性能测量结果,表明当清理工具被配置为发出警告时,为有符号整数溢出启用清理工具会导致运行时开销增加 1%,而当它被配置为导致内核崩溃时,开销为 0.57%。

Linus Torvalds 也不支持该提议,他说:“无符号算术被定义为回绕,而且它这样做是有原因的”。他继续说道:

我认为你需要限制你对回绕的抱怨,并且认真考虑如何限制它们。如果你将“回绕是错误的”作为一种普遍的规则,我会无视你,我会告诉人们无视你,并拒绝任何由于这种愚蠢的规则而产生的愚蠢的补丁。

在 回复中,Cook 表示他同意 Torvalds 的大多数观点——一些算术溢出是预期的,清理工具需要足够智能,以避免对诸如现有溢出检查等无害情况发出警告。问题是,Cook 认为无法从源代码中判断代码开发人员是否打算发生算术溢出。“我需要的是在源代码级别捕获_意图_。不幸的是,编译器和人都不是无所不知的”。Cook 还解释了为什么他认为基于类型的解决方案是最好的:

是的,我同意,单独注释计算看起来会使代码可读性降低很多。这与开发人员的普遍反馈相一致。其他语言中使用的解决方案(取得了巨大成功)是将回绕意图与类型绑定在一起。我们也可以这样做。

Torvalds 反驳道,在某些情况下,意图从上下文就很清楚,任何工具都需要足够智能,以便从值的用法中确定这一点:“如果它用作大小或数组索引,它可能有问题。但如果它用作掩码,然后使用*掩码*版本作为索引,它显然没有问题”。一个允许溢出然后使用掩码获取正确索引的例子是 Kent Overstreet 最近发布的 通用环形缓冲区。Torvalds 还反对添加另一种类型,理由是不会增加认知负担:“我们已经对许多内核开发人员寄予厚望。我们不应该因为你的宠物项目而增加他们的负担”。

Cook 认为将他的工作描述为“宠物项目”是不公平的,因为有大量的学术研究针对这类缺陷。他也同意,要求清理工具不产生毫无意义的警告是可以理解的,“但如果这个标准是无所不知,那将是行不通的”。为了使讨论更具体,他引用了一个具体的问题,这个问题从被 首次识别到 正确修复之间经历了 8 年时间,声称清理工具会立即发现它。在这种情况下,perf 事件的计算大小可能被记录错误,因为存储大小的结构只使用了 16 位来存储它。Cook 质疑如果不采用类似他的提议的方法,如何防止这类问题。

作为回应,Torvalds 提出了一种替代方法:与其一开始就解决所有算术溢出,不如先专注于一些容易发现问题并且很可能很重要的子集。例如,他建议专注于那些由于 C 的整数提升规则而并非技术上的未定义行为,但涉及将有符号数量提升为无符号数,然后以溢出的方式使用的情况。其他可能的领域包括将某个算术表达式的结果明确用作分配大小的地方,或涉及直接指针算术的地方。Torvalds 还反驳了清理工具可以防止 Cook 引用问题的断言,指出这是一个“隐式转换删除了有效位”的情况,而不是普通的算术溢出。

内核已经针对这些问题进行了各种有针对性的检查,Cook 回应道:

没错,我们一直在做所有这些事情。不过,它们都具有共同的根本原因:意外的回绕。但是我明白你的意思:不要追根溯源,找到与之密切相关的信号强烈的因素。

Cook 同意在解决算术溢出之前,先关注隐式整数截断作为缓解措施的下一个目标,因为 Torvalds 更愿意考虑这一点。

编译器问题

Torvalds 消息中的一项细节促使 Peter Zijlstra 发表了进一步的评论。Torvalds 声称有符号整数溢出是未定义的行为,它已经被内核使用的 未定义行为清理工具 (UBSAN) 识别。“我们使用 -fno-strict-overflow 编译,这意味着 -fwrapv,这通过强制执行二进制补码来消除有符号溢出的 UB”,Zijlstra 说。Justin Stitt 声称这并不完全正确,因为 LLVM 引入了一种让 -fwrapv 和 UBSAN 协同工作的方法。Zijlstra 称这种变化是“我们不需要的药物”,他说,由于 -fwrapv 使行为定义明确,因此不应被 UBSAN 捕获。Fangrui Song 澄清道,在最近的 拉取请求之后,LLVM 将不再将 -fwrapv 解释为意味着 =-fsanitize=signed-integer-overflow=。

谈论什么是或不是未定义行为,这不是重点,Cook 说。“这_不是_关于未定义行为。这里的目的是找到一种方法,使 C 作者的意图明确无误。”Zijlstra 仍然认为,该行为不应在 UBSAN 中被检查。Stitt 声称,“我们应该将 UBSAN 视为‘意外行为’清理工具”,而不是未定义行为清理工具。UBSAN 代码中存在许多清理工具“在_技术上_不处理未定义行为”,他说。他强调了注释预期算术溢出发生位置的重要性,因为没有其他好的方法来识别这些位置。

Ted Ts'o 对内核开发人员应该进行另一套类似的重大更改的建议感到不满:

但问题是,当你将大量的繁重工作强加给内核开发人员时,而他们并没有为此做好准备,而他们不一定有时间自己完成所有工作,而他们的企业主也不愿为他们提供处理无资金授权的人员编制,而这些人正在寻找一个光明美好的未来——不要感到惊讶,如果内核开发人员强烈反对。

Ts'o 继续说,对于任何类似的提议的更改,衡量标准不是发现了多少安全漏洞,而是安全特性产生了多少噪音。Cook 回应道,他的提议的意图并不是要求所有核心内核开发人员花时间来做这件事。他重申,更改将是渐进的,并且虽然他很感谢帮助,但他没有要求其他人参与清理工作。他在内核中做了十年类似的工作,从未将发现的 bug 数量作为衡量标准——相反,他想“消除整类 bug”,这样就不会有那么多的 bug 需要发现。“我想我_确实_关心 bug 数量,但我只希望它们为_零_。”

Cook 确实说,“我听到了你(还有 Linus 和其他人)关于最大限度地减少对其他开发人员的负担的意见”。他说,这就是他将该提议提交到邮件列表中的原因,以便他可以获得关于如何解决算术溢出问题的反馈。Ts'o 道歉,澄清说他并非有意批评 Cook 的工作,而是反驳 Stitt 的断言,即每个预期算术溢出发生的地方都应该进行注释。他重申了他的立场,虽然发现安全问题很重要,但清理工具需要谨慎,不要产生太多误报,以至于他们的警告被忽略。“因此,请将此视为一项恳求,要求在任何时候都尽可能地 真正 认真地降低清理工具的误报率。”

在收集了其他内核开发人员的反馈后,Cook 不再打算继续为所有算术溢出提供清理工具——至少目前还没有。相反,他 打算采取三个“小步骤”:完成正在进行的有符号整数溢出重构工作,开始解决有符号整数截断的情况,然后查看特定类型(从 Torvalds 提议的 size_t 开始)的无符号整数溢出。鉴于过去类似的更改需要多长时间,这似乎很可能会占用 Cook 的一段时间。

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

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

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

1ec7ac17392e1e7eaf8d7013a833ead4.jpeg

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值