作者 | David Cassel
译者 | 平川
策划 | Tina
本文最初发布于 THENEWSTACK。
在分析了从 2014 年 7 月 15 日到 2023 年 12 月 14 日近 10 年的 CVE 后,谷歌研究人员计算得出,在 C++ 中,至少 40% 的安全漏洞与空间内存漏洞(如越界写入内存)有关。
不过,谷歌正在努力解决这一问题,同时为这个充满不安全遗留代码的世界树立榜样。
谷歌研究人员 表示,他们能够将空间安全 “加装 ”到 C++ 代码库中,而且对性能的影响非常非常小。网上有一篇 关于他们研究成果的博文,引发了更为广泛的讨论,其中不乏一些有趣的跟帖,有人甚至大声质疑,这种低影响是否意味着 C++ 代码可以默认包含空间内存安全特性。
该文还提出了一种可能性,即一些已经存在很长时间的假设可能会随着时间的推移而受到挑战——当面临的问题比较严重时,或许可以尝试引入新技术。
改进安全设计
这一切都源于一篇博文,该文描述了谷歌如何通过在 C++ 代码中添加边界检查来保护其服务器端生产系统,从而提高 Gmail、YouTube、谷歌地图甚至谷歌搜索引擎的安全性。谷歌是内存安全语言和其他安全编码实践的早期采用者,但全面过渡往往需要数年时间。也就是说,他们还需要额外做一些工作:“尽可能地在现有 C++ 代码库中改用安全设计原则"。
该博文由资深软件工程师 Alex Rebert 和 Kinuko Yasuda(与谷歌的 Max Shavrick 合作,后者也是安全基础团队的成员)撰写。该文首先指出,C++ 标准库的 LLVM 实现中包括几种用于发现未定义行为的加固模式,从而可以对 C++ 代码进行边界检查。
该文解释说:”[我们] 现在已将其作为服务器端生产系统的默认设置,同时密切监控上线情况。“
结果如何?
该团队发现了 1000 多个 Bug。(据谷歌估计,平均每年发现的 Bug 可增加到 1000 至 2000 个)。
谷歌 “整个生产环境的基线分段故障率降低了 30%”。在博文中,他们将此归功于更好的代码可靠性和质量:“除了崩溃之外,检查还发现了一些错误。这些错误可能会表现为不可预测的行为或损坏数据"。
最终,它还帮助谷歌 “发现并修复了多个在代码中潜伏了十多年的 Bug”。经过加固的 C++ 检查 “将许多难以诊断的内存损坏转化为即时且易于调试的错误”。
它甚至还瓦解了内部的一次红队演习,“证明了它在挫败漏洞利用方面的有效性”。
“我错了”
其中有一项结果最引人关注。谷歌的博文称,加固libc++
“对我们的服务平均造成了 0.30% 的性能影响”。
C++ 中的边界检查:有人问,0.3% 的开销是否属实?这不仅仅是一个基准测试结果,我们是通过 Google-Wide profiling 得到的这个结果,我们通过它获得了来自 DC 的实时洞察。这也让我们大吃一惊,因为那比我们预想的要少得多。 https://t.co/zBUvoYzGi1 https://t.co/7NAWBuxdtP — Kinuko Yasuda (@kinu) 2024 年 11 月 16 日
谷歌杰出工程师 Chandler Carruth 回应了他们的博文。他也是 新兴编程语言 Carbon 的创始人和联合牵头人。Carruth 撰写了一篇博文,其中第一节的标题就是 "边界检查的开销:我错了"。
“其他人关于成本的一些历史报告,再加上我自己的一些简单实验,让我坚定地认为,边界检查的成本不可能低到可以默认启用的程度。然而,到目前为止,它们看起来确实非常低。“
遗憾的是,这种普遍的看法使得高质量的动态安全检查没有出现在 libc++ 及其他 C++ 库中,最初也没有出现在 LLVM 中。
C 的加固之路
但 Carruth 看到,Microsoft Visual C++ 引入了基于编译器的检查,而 “在这里,苹果公司所有推动 在 C++ 中实现安全缓冲区 的人都做了一项了不起的工作,让 LLVM 生态系统(包括 Clang 和 libc++)最终在这一领域拥有了一个可靠的工具”。Carruth 认为,另一个因素是内存安全语言开发人员为 LLVM 做出了更多的贡献(因为更多的非 C/C++ 语言开始使用 LLVM),从而带来了 “一系列稳定而系统化的改进”。
回首过去,Carruth 认为,虽然 LLVM 经过了多年的改进,但"我们并没有真正注意到什么时候已经达到了一个临界点。在那一刻,所有改进一起从根本上降低了此类检查的实际成本,使其在默认情况下就普遍适用。“
“这种水平的可用性改变了安全游戏的规则,因为我们不用再在性能或安全性之间做出痛苦的权衡取舍,我们可以两者兼得。“
Carruth 用粗体字写道:"我认为,通过努力,借助编译器对内存安全检查的持续支持,我们确实有机会默认实现空间内存安全,即使是在 C 和 C++ 中,即使是在性能最受限制的环境中。”
这就提出了一个问题:现在是否应该考虑下参考计数等其他安全检查——即使我们认为这些检查会带来令人望而却步的性能影响。
“我认为,有足够的证明表明,对于比较小的系统(手机、笔记本电脑、其他用电池的东西),缓存流量和潜在同步开销的成本即使再大也微不足道。我认为,Swift 已经提供了强有力的证据,只要在优化基础设施上投入一些资金并认真实施,参考计数就能够在这些处理器上实现极高的效率。”
当然,谷歌的博文也在 LinkedIn 上引发了不少讨论:
是否需要优化?
Carruth 还回顾了为了“让 LLVM 在优化加固方面做得更好”所作出的努力。他认为,性能敏感型工作负载得益于配置文件引导优化(或 PGO),以及“人们系统性地构建优化基础设施...... 将代码的热门路径与安全检查的开销隔离开来,并释放了 LLVM 围绕它们所开发的所有其他优化技术,最大限度地降低了它们的成本”。
至于谷歌,他们的博文确实承认了这一点以及其他提高性能的技巧,但也补充说:"即使没有这些先进技术,边界检查的开销仍然微乎其微。(谷歌的博文将这种低影响归因于加固后的libc++
的高效设计,以及编译器在优化过程中消除冗余检查的方式)。
不过,当发现 LLVM 在执行一项不必要的检查(并且对性能有很大影响)时,他们编写了一个修复程序,并将其贡献给了 LLVM 项目,“以便与广大的 C++ 社区分享这项改进带来的好处”。
各方反应
谷歌的博文在网络上引发了一些积极的反响。在 Hacker News 上,WebAssembly 联合创始人 Ben Titzer 对此作出了回应,他还记得 20 年前关于 C++ 中边界检查必要性的争论。”程序中存在边界检查能捕捉到的 Bug。将其作为一种内置特性,编译器就会对其进行专门针对边界检查的优化,这样可以消除许多 Bug,并大大降低动态成本。“
“仅仅在库中打开安全检查并不一定能实现所有的编译器优化,但这只是一个开始。安全检查应该真正地内置于语言中"。
另一个回应来自 Walter Bright,他创建了另一种编译器 Zortech C++。Bright 也是系统编程语言 D 的创建者。他 描述 了 20 年前 D 引入数组边界检查后发生的事情:“这是一个巨大的胜利“。在另一条评论中,Bright回忆 了如何使边界检查成为 D 语言的默认选项。
“事实证明,这是正确的选择。”
Rust 基金会杰出顾问 Shane Miller发表 在 LinkedIn 上的回应可能是最积极的,“很高兴你们没有止步于通过这一举措获得安全方面的胜利。“
“你们在识别 Bug 和提高可靠性方面所掌握的数据是一个很好的参考"。
https://thenewstack.io/google-retrofits-spatial-memory-safety-onto-c/
声明:本文为 InfoQ 翻译,未经许可禁止转载。
热门跟贴