← 返回信息流
AI 资讯Hacker News·3 小时前

Rust与C/C++内存安全漏洞CVE存在显著差异

原标题:Memory safety CVEs differ between Rust and C/C++

速览

一项新研究揭示了Rust与C/C++在内存安全漏洞(CVE)分布上的显著差异。尽管Rust旨在通过所有权机制消除内存安全问题,但实际数据表明其漏洞类型与C/C++截然不同。这一发现对于评估不同编程语言在系统安全方面的实际表现具有重要意义。

AI 深度解读

Rust 与 C/C++ 内存安全 CVE 的本质差异:深度解读

背景

CVE(Common Vulnerabilities and Exposures,通用漏洞披露)是一个用于分类和报告软件安全漏洞的数据库。软件中存在的漏洞种类繁多,有些仅仅是由程序逻辑错误引起的(例如近期在 Cargo 中报告的一个 CVE),但最棘手、危害最大的漏洞往往源于内存不安全(memory unsafety),这类漏洞极易被利用进行攻击。

在技术社区中,经常有人将 Rust 与 C/C++ 软件中的 CVE 数量进行直接对比,并据此得出“Rust 并非真正内存安全”或“既然 Rust 中仍存在 CVE,就不值得采用”的结论。这种现象在教授 Rust 给习惯 C/C++ 的程序员时也屡见不鲜。

然而,这种对比往往忽略了一个关键事实:Rust 和 C/C++ 在处理潜在内存安全漏洞的方式上存在根本性的差异。这种差异并不直观,尤其是对于不熟悉 Rust 机制的人来说。本文旨在澄清这一误解,并解释为何在 Rust 中报告的 CVE 往往比 C/C++ 中的更为“严格”。

核心内容

1. 前提澄清:Rust 并非绝对免疫

首先必须明确,Rust 程序并非完全不可能出现内存不安全 bug 或未定义行为(Undefined Behavior, UB)。在绝大多数情况下,这需要开发者显式使用 unsafe 关键字。声称“Rust 程序绝对不会发生 UB”的观点是错误的。此外,Rust 中同样可能出现与内存无关的一般性漏洞(例如忘记检查管理员权限),这在任何语言中都可能发生。

2. 案例研究:libcurl 的 curl_getenv

为了具体说明差异,文章以广泛使用的 C 语言网络库 libcurl 为例。libcurl 由 Daniel Stenberg 等维护者精心维护了 30 年,以健壮性著称。

测试一个简单的 C 程序:

#include <curl/curl.h>
int main(void) {
    curl_getenv(NULL);
}

这段代码编译无警告,但执行时会发生段错误(Segmentation fault),导致内存安全漏洞。这看似是一个严重的库缺陷,但维护者绝不会将其视为 curl 库的漏洞。

为什么这不算是库的漏洞?

  • 契约难以精确描述:C 语言类型系统有限,库作者无法在 API 层面精确指定所有前置条件(preconditions)。curl_getenv 的文档并未明确禁止传入 NULL,而是假设用户会“正确使用”库。
  • 滥用成本过高:如果将所有可能导致 UB 的“错误用法”都报告为 CVE,C/C++ 库将淹没在数百万个 CVE 中。因此,在 C/C++ 生态中,CVE 通常针对的是库本身的特定缺陷,而非 API 被误用的可能性。如果用户以错误方式调用 API 导致崩溃,责任在于用户代码,而非库。

3. Rust 的严格性:Soundness(健全性)

在 Rust 中,情况截然不同。假设 Rust 的网络库 hyper 有一个类似的函数 hyper::foo,如果用户编写如下代码:

fn main() {
    hyper::foo(None);
}

如果这段代码(不包含任何 unsafe 块)在运行时发生段错误或内存错误,这绝对会被视为 hyper 库的一个 CVE。

核心逻辑差异:

  • Safe Rust 的契约:在 Rust 中,如果在不使用 unsafe 的情况下,通过任何 conceivable(可想象)的方式使用库 API 导致了内存错误,这被定义为库的健全性漏洞(Soundness Hole)
  • 责任归属:这意味着,只要 API 标记为 safe,用户就不可能错误地使用它(就内存安全而言)。如果发生了内存错误,那就是库作者的错,而不是用户的错。
  • 预防性报告:Rust 社区会在发现 API 存在被误用导致内存风险的可能性时就报告 CVE,即使尚未在野外(wild)发现实际利用案例。相比之下,C/C++ 只有在实际发生利用或极难避免的缺陷时才会报告 CVE。

4. 结论:判断“正确用法”的标准

  • 在 C/C++ 中:判断函数是否被“正确”使用(就内存安全而言)往往很困难,依赖文档和开发者经验。如果出错,通常归咎于用户误用。
  • 在 Rust 中:答案非常简单——如果调用的函数没有标记为 unsafe,那么答案永远是YES(即用法正确)。在 Safe Rust 中,不可能以导致内存不安全的方式调用这些函数。

关键要点

  • 比较基准不同:直接对比 Rust 和 C/C++ 的 CVE 数量具有误导性,因为两者对“什么是库的漏洞”定义不同。
  • C/C++ 的宽容度:在 C/C++ 中,API 被误用(如传入非法参数)导致的崩溃通常被视为用户代码错误,而非库本身的 CVE,因为无法在语言层面强制约束所有输入。
  • Rust 的严格性:Rust 的 safe API 具有强契约保证。任何在 Safe Rust 中触发的内存错误都被视为库的严重缺陷(Soundness Bug),必须修复并报告 CVE。
  • unsafe 是关键分界线:Rust 将内存安全的责任明确划分。unsafe 块内的行为由开发者负责,而 safe 代码内的内存安全由编译器保证。
  • CVE 的“严格”程度:Rust 报告的 CVE 往往更“严格”,因为它们涵盖了潜在的风险路径,而不仅仅是已知的利用案例。

意义与影响

这一差异深刻影响了软件安全生态:

  1. 安全保证的层级不同:Rust 通过语言特性将内存安全从“最佳实践”提升为“编译期保证”。开发者无需担心因疏忽导致的内存错误,只要遵循 Safe Rust 规则。
  2. 漏洞报告的透明度:Rust 社区对健全性漏洞的零容忍态度,使得 Rust 生态中的 CVE 往往反映了更深层的设计缺陷,而非简单的 API 误用。这提高了 Rust 库的整体可信度。
  3. 对迁移决策的影响:对于从 C/C++ 迁移到 Rust 的团队,理解这一差异至关重要。Rust 中报告的 CVE 并不意味着 Rust 不安全,反而证明了其类型系统在捕获潜在内存风险方面的有效性。
  4. 维护者责任的转移:在 C/C++ 中,维护者只需修复明显的逻辑错误;在 Rust 中,维护者必须确保所有 safe API 在所有合法输入下都不会引发内存错误,这要求更高的设计严谨性。

总之,Rust 和 C/C++ 在 CVE 处理上的差异并非源于漏洞数量的多少,而是源于对“内存安全责任”归属的不同哲学。Rust 通过语言设计将责任牢牢锁定在库作者身上,从而为用户提供了更强的安全保障。

查看原文 →kobzol.github.io