17611538698
webmaster@21cto.com

Google 将空间内存安全功能“改造”到 C++ 上

编程语言 0 64 2024-12-13 08:57:59

图片

在分析了将近 10 年的 CVE(从 2014 年 7 月 15 日到 2023 年 12 月 14 日)后,Google 研发人员计算出 C++ 中至少 40% 的安全漏洞与内存空间的漏洞有关(常见如写入内存位置越界)。

图片

Google 正在尝试对此采取行动——同时也为充满不安全的遗留代码的世界树立了一个优秀榜样。

Google 研究人员通过实践表明,他们已经能够将空间安全性“改造”到他们的 C++ 代码库中,而且对应用性能的影响非常小。

一篇关于他们研究结果的博客文章引发了更广泛的社区讨论,网上还出现了一些有趣的后续评论,有些人甚至大声质疑这种低影响是否意味着 C++ 代码可以默认包含空间内存安全性。

这也提出了长期存在的假设,可能随着时间的推移而受到挑战的可能性——并且当面临严重问题时,新技术总有可能提供帮助。

安全设计改造


一切始于一篇官方博客,该篇文章描述了 Google 如何通过在其 C++ 代码中添加边界检查来保护其服务器端生产系统,从而提高 Gmail、YouTube、Google 地图甚至 Google 搜索引擎的安全性。


Google 已经是内存安全语言和其他安全编码实践的早期采用者,但全面过渡仍需要数年时间,这意味着他们还需要采取额外措施:“尽可能将安全设计原则改进到我们现有的 C++ 代码库中。”


文章是由高级软件工程师Alex RebertKinuko Yasuda撰写的(他们与 Google 的Max Shavrick合作,后者也是安全基础团队的成员)。文章指出,LLVM 对 C++ 标准库的实现包括几种强化模式,用于发现未定义的行为,从而可以对 C++ 代码进行边界检查


“我们现在已将其作为我们服务器端生产系统的默认设置,”博客文章解释道,同时密切关注其推出情况。


结果数据如下:


  • 该团队发现了 1,000 多个漏洞。(Google 估计,平均每年可以发现 1,000 到 2,000 个漏洞。)

  • Google 还经历了“整个生产过程中基线分段错误率降低 30%”。他们的文章将此归因于更好的代码可靠性和质量:“除了崩溃之外,检查还捕获了原本会表现为不可预测行为或数据损坏的错误。”

  • 它最终还帮助 Google“识别并修复了十多年来潜伏在我们代码中的多个错误。”强化的 C++ 检查“将许多难以诊断的内存损坏转变为即时且易于调试的错误。”

  • 它甚至扰乱了一次内部红队演习,“证明了其在阻止攻击方面的有效性”。

图片

“我错了”


有一个具体结果引起了最多的关注。谷歌的博客报告称,强化libc++“导致我们所有服务的性能平均下降了 0.30%”。(他们甚至强调写道:“是的,只有三分之一个百分点……”)


图片

此帖子得到了谷歌杰出工程师Chandler Carruth的回应,他也是新Carbon 编程语言的创始人和联合负责人。Carruth 写了一篇自己的博客文章,其中第一部分的标题是“边界检查的开销:是我错了。”

“其他人对成本的大量历史报告,加上我自己进行的一些相当随意的实验,让我坚信边界检查不可能便宜到可以默认启用。然而,到目前为止,它们看起来非常实用。”

不幸的是,这种普遍的观念使得高质量的动态安全检查无法进入libc++(和其他 C++ 库),并且最初也无法进入 LLVM。

强化 C ++之路


但是 Carruth 看到基于编译器的检查已进入 Microsoft Visual C++,而“Apple 中推动C++ 中安全缓冲区的 LLVM RFC 的所有人在这里做出了出色的工作,让 LLVM 生态系统(包括 Clang 和 libc++)最终在这个领域拥有了可靠的工具。” 


Carruth 认为另一个因素是内存安全语言开发人员对 LLVM 的贡献更多(因为越来越多的非 C/C++ 语言开始使用它),带来了“一系列稳定而系统的改进”。


回顾过去,Carruth 认为 LLVM 经过了“许多年”的改进,“我认为我们并没有真正注意到临界点何时到来。在那个时候,这些改进结合起来,从根本上降低了这类检查的实际成本,并使其默认且普遍可用。


“这种级别的可用性改变了安全游戏规则,因为我们不再需要在性能或安全性之间做出痛苦的权衡,我们可以同时获得两者。”


Carruth 写道,通过努力,并且编译器继续支持内存安全检查,“我认为我们有机会默认实现空间内存安全,即使在 C 和 C++ 中,即使在性能最受限制的环境中也是如此。”


这就引发了一个问题:现在是否至少也应该考虑其他安全检查(例如引用计数)——即使我们认为这些检查也会对性能产生严重影响。


“我认为有一些令人信服的证据表明,对于较小的系统(手机、笔记本电脑、连接电池的设备),缓存流量和潜在同步开销的成本最多是微不足道的。我认为 Swift 已经提供了强有力的证据,证明只要在优化基础设施上进行一些投资并谨慎实施,引用计数在这些处理器上可以非常高效。”


Google的帖子在 LinkedIn 上引发了不少讨论:


图片

图片

有没有发生优化?


Carruth 还回顾了“让 LLVM 在优化强化方面做得更好”的努力。


他在这里建议,性能敏感的工作负载受益于配置文件引导优化 (PGO),以及“人们系统地构建优化基础设施……将代码的热路径与安全检查的开销隔离开来,并解锁 LLVM 围绕它们开发的所有其他优化技术,以最大限度地降低成本。”


说到谷歌,他们确实承认了这一点和其他提高性能的技巧——然后补充说“即使没有这些先进的技术,边界检查的开销仍然很小。”(谷歌的博客文章反而将低影响归因于强化libc++设计高效的事实——以及编译器可以在优化过程中消除冗余检查的方式。)


然而,当他们发现 LLVM 执行了不必要的检查(这严重影响了性能)时,他们编写了一个修复程序并将其贡献给 LLVM 项目“以与更广泛的 C++ 社区分享好处”。


用户反应


好消息是,Google 的帖子在网络上引起了一些用户积极的反响。


在 Hacker News 上,它引起了 WebAssembly 联合创始人Ben Titzer回应,他还记得 20 年前曾争论过 C++ 中边界检查的必要性。“程序中存在边界检查可以捕获的错误。使其成为一种内置语言,可使其暴露于专门针对边界检查的编译器优化,从而消除许多错误并大大降低动态成本。


“仅在库中启用它们并不一定能暴露所有编译器优化,但这是一个开始。安全检查应该真正内置到语言中。”


另一个回复来自Walter Bright,他创建了替代的 Zortech C++ 编译器。


Bright 也是系统编程语言 D 的创建者,他描述了20 年前 D 语言添加数组边界检查后发生的事情:“这是一个巨大的胜利。”在另一条评论中,Bright回忆起将边界检查设为 D 语言中的默认选项。“为了关闭数组边界,你必须打开一个开关,而且这只发生在标记为 @system 的代码上。


“事实证明,这是正确的举措。”


但也许最积极的回应来自Shane Miller (Rust 基金会的杰出顾问)在 LinkedIn 上发布的呼吁。“很高兴你们没有止步于通过这一举措来获取安全胜利。


“你们掌握的关于识别错误和提高可靠性的数据是极好的参考。”

作者:大雄

评论