GCC 的 ASAN 能否提供与 Rust 相同的内存安全性?

Can GCC's ASAN provide the same memory safety as Rust?

Rust 被称为内存安全语言,但 GCC 中有一个名为 AddressSanitizer (ASAN) 的安全功能:

./configure CFLAGS="-fsanitize=address -g" CXXFLAGS="-fsanitize=address -g" LDFLAGS="-fsanitize=address"
make
make check

ASAN 能否提供与 Rust 相同的内存安全性,或者 Rust 是否有更多技巧?甚至可以比较两者吗?

免责声明:我不是程序员。

没听说过这个选项,不过好像修改了输出程序。换句话说,它在程序运行时进行检查。

另一方面,Rust 检查程序何时创建(或用程序员的话说编译),因此首先不存在这些内存安全错误。

链接的文章提到它只涵盖一种情况,在return之后使用。

不,这两个特征没有可比性。

地址清理不是安全功能,也不提供 memory-safety:它是一种调试工具。程序员已经有了工具来检测他们编写的代码是否存在内存问题,例如 use-after-free 或内存泄漏。 Valgrind 可能就是 best-known 的例子。此 gcc 特性提供(部分)相同的功能:唯一的新功能是它与编译器集成在一起,因此更易于使用。

您不会在生产中启用此功能:它仅用于调试。你用这个标志编译你的测试,它们会自动检测由测试触发的内存错误。如果您的测试不足以触发问题,那么问题仍然存在,并且它仍然会在生产中导致相同的安全漏洞。

Rust 的所有权模型通过使包含此类缺陷的程序无效来防止这些缺陷:编译器不会编译它们。您不必担心您的测试不会触发问题,因为如果代码编译通过,就不会出现问题。

这两个功能针对不同的问题集。地址清理的一项功能是检测内存泄漏(分配内存但稍后忽略释放)。 Rust 使得编写内存泄漏比在 C 或 C++ 中更难,但它仍然是可能的(如果你有循环引用)。 Rust 的所有权模型可防止顺序和 multi-threaded 情况下的数据竞争(见下文)。地址清理的目的不是检测这两种情况。

顺序代码中数据争用的一个示例是,如果您遍历对象集合,同时添加或删除元素。在 C++ 中,更改大多数集合将 使 任何迭代器无效,但程序员需要意识到发生了这种情况:它未被检测到(尽管某些集合在调试版本中有额外检查)。在 Rust 中,不可能在集合上存在迭代器时改变集合,因为所有权模型阻止了这种情况。

多线程代码中数据竞争的一个例子是有两个线程共享一个对象,访问受互斥锁保护。在 C++ 中,程序员可能会在更改对象时忘记锁定互斥量。在 Rust 中,互斥量本身 拥有 它所保护的对象,因此不可能不安全地访问它。 (不过,还有许多其他类型的并发错误,所以不要得意忘形!)

消毒剂

GCC 和 Clang 都有套件消毒剂;到目前为止,它们都是在Clang中开发,然后移植到GCC,所以Clang拥有最先进的版本:

  • Address Sanitizer (ASan):检测 out-of-bounds 访问、use-after-free、use-after-scope、double-free/invalid-free 并添加对内存泄漏的支持(预期内存开销3x),
  • Memory Sanitizer (MemSan):检测未初始化的读取(预期 slow-down 3x),
  • Thread Sanitizer (TSan):检测data-races(预期slow-down 5x-15x,内存开销5x-10x),
  • Undefined Behavior Sanitizer (UBSan):各种局部未定义行为,例如未对齐指针、integral/floating 点溢出等...(最小 slow-down,代码大小略有增加)。

Type Sanitizer 方面的工作也在进行中。


消毒剂 vs Rust

不幸的是,使用消毒剂将 C++ 提升到 Rust 的安全级别是不可能的;即使结合所有现有的消毒剂仍然会留下空白,众所周知它们是不完整的。

您可以在 CppCon 2017, the slides can be found on github 查看 John Regher 关于未定义行为的演示文稿,我们从中获取当前的报道:

这并没有说明 消毒剂彼此不相容的事实 。也就是说,即使您愿意接受 slow-down(15x-45x?)和内存开销(15x-30x?)的组合,您仍然无法使 C++ 程序像 Rust 程序一样安全.


加固与调试

消毒剂如此 CPU/memory 饥饿的原因是因为它们是调试工具;他们试图为开发人员提供尽可能精确的诊断,以便对调试最有用。

对于生产中的 运行 代码,您正在寻找的是 强化 。强化是关于以尽可能低的开销消除未定义的行为。例如,Clang 支持多种加固二进制文件的方法:

这些工具可以结合使用,对性能的影响很小 (< 1%)。不幸的是,它们覆盖的范围比消毒剂少得多,而且最值得注意的是,它们不会试图覆盖经常成为攻击目标的 use-after-free/use-after-scope 或 data-races。


结论

我看不出有任何方法可以将 C++ 提升到 Rust 所结合的安全级别,如果没有:

  • 非常严格的语言限制:参见MISRA/JSF指南,
  • 非常严重的性能损失:消毒剂、禁用优化、...
  • 对标准库和编码实践的彻底检修,其中 Core Guidelinesstart

另一方面,值得注意的是 Rust 本身使用 unsafe 代码;并且它的 unsafe 代码也需要审查(参见 Rust Belt project)并将受益于上述所有 sanitizers/hardening 检测过程。