【译文】白宫的软件内存安全呼吁是一种误导

内存安全

在 “神圣的编程语言之战 “中,系统编程的通用语言(也称为 C 语言)经常被抨击为不安全、易出错,以及未定义的行为类型多于 C 标准所定义的行为类型。许多编程语言都被说成是 “C 语言杀手”,但 C 语言至今仍有生命力。美国白宫国家网络总监办公室(ONCD)发布了一份报告,其中抨击 C 和 C++ 在内存管理方面 “不安全”

报告全文(PDF 格式)的技术细节非常简单,仅引用了微软和谷歌的博客文章作为 “专家来源”。关于内存安全问题是导致CVEs的主要原因的说法没有事实根据,或者至少忽略了CVEs的严重性,因为CISA统计的是活跃的漏洞利用。除了呼吁 “内存安全 “之外,报告还呼吁进行更多的测试和验证,同时踢开了早在20世纪70年代就通过Steelman要求和1975年高阶语言工作组(HOLWG)打开的大门。

ONCD 报告的真正影响和事实依据是什么?

CVE 质量而非数量

在ONCD报告–以及由NSA、CISA和其他机构撰写的篇幅更长但非常类似的题为《内存安全路线图案例》的报告–中反复提出的主张中,最令人头疼的可能是内存安全问题是首要问题。这些说法似乎总是与微软和谷歌的报告联系在一起,而不是与被积极利用的CVE列表联系在一起,所有这些CVE在2023年的报告中都占据了显著位置,例如2022年的12大热门漏洞列表,其中有每个人都喜欢的漏洞,如Log4j (CVE-2021-44228),其特点是输入验证马虎,或者微软Exchange服务器中的三个CVE,击中了常见弱点枚举(CWE)的三重打击。

就像 2022 年的榜首(Fortinet SSL VPN)一样,这包括 CWE-22:对受限目录路径名的不当限制。Exchange Server 也因 CWE-918(服务器端请求伪造,SSRF)和 CWE-287(验证不当)而受到关注。其中,内存安全问题可能是 CWE-287(如 CVE-2021-35395)的一个因素,尽管非常零散。特别是远程利用(与 “网络安全 “相关)的模式绝大多数与输入验证和处理有关,主要涉及检查遗漏和逻辑错误。

当最严重的 CVE 事件都是由于程序员没有对路径遍历进行基本检查和忘记对用户凭证进行全面检查时,把重点放在内存安全上就有点可疑了。同样令人担忧的是,我们完全没有提到军事、医疗和航空领域最喜欢的语言:Ada。

钢铁人Steelman

如前所述,Steelman 要求是由当时最重要的计算机科学专家提出的,要求高级语言必须满足这些要求才能用于美国国防部最苛刻的任务。1997 年,大卫-惠勒(David A. Wheeler)对 Ada 95、C89、C++ 和 Java 进行了比较,确定了这些语言对 Steelman 要求的遵守情况,其中 Ada 显然得分最高(93%),Java 为 72%,C++ 为 68%,而 C 语言则排在后面,仅为 53%。当然,值得注意的是,从那时起,所有这些语言都对各自的标准进行了多次更新,但它仍然提供了一个有用的快照。

目前,C 和 C++ 中缺乏内置并发支持的问题已经通过 C++11 的标准内存模型得到了部分解决,但在不修改语言基础并使其与现有代码库不兼容的情况下,很难完全解决。只有脚本语言等基础性较差的语言才能做到这一点,即便如此,也很可能会在很多年内扰乱大部分用户的使用习惯。

Ada 不仅在并发处理方面表现出色,在类型系统(包括参数和返回值)方面也是如此。从其他语言移植过来的 Ada 程序员新手通常会感到不适应的是,他们首先必须用约束条件来设置类型,这看起来既费时又没有必要。但正如《Steelman》一书中所描述的,这些限制以及尽可能消除模糊性的代码有助于避免编程错误。

实际上,好的编程语言会通过设置限制条件来了解你的意图,并提供使用基于契约的编程方法来限制函数和过程的手段,从而让编译器尽可能多地了解上下文。同时,代码应尽可能用简单的英语编写,不使用隐晦的符号和容易打错的符号,例如,将比较变成赋值。这也是 Ada 不区分大小写的原因:既然上下文的意思很清楚,为什么 coolVarcoolvar 会有不同呢?

C++ 中的内存安全很简单

阅读旧的国防部报告相当有趣,例如美国空军 1991 年的一份名为《Ada 和 C++》的报告(PDF):商业案例分析》。这份报告是在 C++ 被标准化之前写成的,但作为国防部 “降低构建成本 “活动的一部分,C++ 声称将 Ada 的许多功能添加到了 C 语言中,这一点得到了包括美国联邦航空局在内的多个政府部门的调查。当时的结论是,迄今为止,Ada 仍然是最佳选择,有明显迹象表明,其强大的代码可重用性和自文档化代码有助于降低维护成本。

即便如此,由于 Ada 在上述领域之外并不受欢迎,导致 Ada 程序员匮乏,最终 C++ 被批准用于 F-35 计划等国防部项目,尽管对可接受的代码进行了严格限制,使其与 Ada 更加一致。由此可见,问题也许不在于 C++,而在于如何使用它。

毕竟,只要不使用与 C 兼容的语法,C++ 本身在内存管理或大量未定义行为方面不会有什么大问题。此外,C++ 的标准模板库(STL)中的 std::string和容器取代了噩梦般的、容易出错的 C 风格字符串和数组,突然间,你不得不努力去编写与内存相关的代码,因为简单的缓冲区超限不再发生。

当然,很多人在编程过程中会受到底层优化的诱惑,最终编写出使用原始内存指针的lock-free ring bufferzero-copy RPC RPC 库。在这种情况下,你首先要对底层硬件是如何工作的有一个扎实的了解,并真正熟悉 Valgrind 等工具,尤其是 Valgrind 的价值很难被高估,因为只要花点精力,你就能分析代码的内存安全性、多线程问题(线程、互斥等)以及内存使用情况。

魔力之触

也许在这个即时满足的 LLM 代码生成工具和 “从 Stack Overflow 剪切/粘贴 “的时代,我们已经忘记了编程最重要的一点。也就是说,编程是一门工程学科,需要规划、记录、测试,而且在编写更复杂的程序时,你会感觉学得越多,知道的越少,犯的错误也越多。作为一个正在逐步将个人 C++ 项目移植到 Ada 的人,我在这一过程中发现,虽然我很喜欢 C++,但 Ada 的一些东西真的让我很兴奋。

当然,要习惯于处理默认的不可变字符串类型确实有点麻烦,而且很容易为了获得立竿见影的效果而使用预定义的整数和浮点类型包,更不用说还要花上几个小时盯着 Ada 编译器的输出结果,因为它会告诉你代码中存在的所有问题。然而,一旦你克服了最初的障碍,程序运行起来就不会出现像 C++ 代码那样的故障或怪异现象,那种感觉几乎是神奇的。

这也许就是ONCD报告让人感觉如此错误的原因,因为它既没有包含过去的教训,也没有包含那些编写代码以维持社会运转的人的来之不易的经验。你几乎可以听到许多资深软件工程师的哭泣声,他们不知道自己在政府机构眼中是否只是一块被剁碎的猪肝,而这些机构却因为无数行 Ada、COBOL、C 和 C++ 代码而得以运转。更别提那些绝望的安全研究人员了,因为基本的输入验证又一次被忽略了,只剩下一些大公司推崇的流行语。

本文文字及图片出自 THE WHITE HOUSE MEMORY SAFETY APPEAL IS A SECURITY RED HERRING

余下全文(1/3)
分享这篇文章:

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注