Skip to content

【Zig 日报】内存安全的绝对主义者 #348

Description

@jiacai2050

看到这篇文章标题时,我敢打赌,有些人脑海中闪过的第一个念头肯定是:“Rust 开发者!”这种联想并非毫无根据。Rust 开发者往往对内存安全充满热情。我确信,其中一部分原因可能源于经典的语言之争心态:我的语言比你的好,原因就在这里。不过我希望,大多数自称关心内存安全的 Rust 开发者,是真正关心如何让软件变得更安全,而不仅仅是为了批评与 Rust 竞争的语言。

出人意料的是,这篇文章并不是关于 Rust 开发者的。

直到最近,大多数关于内存安全的讨论都有一个相对简单的基础,至少在考虑 C、C++、Zig 和 Rust 等非垃圾回收(GC)、系统编程语言时是这样。Rust 旨在禁止编译可能引入内存安全问题的程序(代价有时是拒绝编译原本安全的程序),并提供了一个“unsafe”形式的逃生舱,允许(除其他外)解引用原始指针。C 和其他语言则将内存安全留给程序员去保证。语言提供的帮助程度各有不同,例如 C++ 中的 RAII 和智能指针,或者 Zig 中的 defer,但在很大程度上,没有任何机制能阻止你违反内存访问规则。

如今情况有所不同,出现了一种让 C、C++ 甚至未来可能让 Zig 代码实现内存安全的新方法:Fil-C。用 Fil-C 编译的 C 和 C++ 代码在遇到无效内存访问(如越界访问或释放后重用)时会发生 panic。它通过结合垃圾回收机制(GC)和 InvisiCaps(一种跟踪指针可访问内存的方法)来实现这一点。Zig 的作者最近宣布了一种受 Fil-C 启发的新 Zig 编译模式。Fil-C 是一个非常有趣的项目,我由衷希望它能成功,并且至少有一些流行的 C 和 C++ 项目会提供 Fil-C 编译的版本。

在一个理想的世界里,关注内存安全的 Rust 程序员以及 C/C++/Zig 程序员都会为有更多方法来尽量减少内存安全漏洞而感到高兴,然而,我们并非生活在理想的世界中,我无法摆脱这样一种感觉:最近对 Rust 的许多批评都是不真诚的。如果你在推特(Twitter)上阅读 Fil-C 作者的观点,你会发现大量声称 Rust 是不安全语言的言论,因为在使用 unsafe 时可以绕过 Rust 的某些保证。Zig 的作者安德鲁·凯利(Andrew Kelley)似乎也持类似立场,这在他提出的 fil 编译模式 Issue 标题中可见一斑:“引入一种真正内存安全(与 Rust 不同)的、受 Fil-C 启发的编译模式”。意思就是:Rust 是不安全的,只有 Fil-C 或 Zig 的“fil”编译模式才能处理与内存安全相关的漏洞。

在关于 Rust 和 Fil-C 的讨论中,我经常看到类似这样的言论:“如果 Rust 宗派真的关心内存安全,他们就会推广 Fil-C 并抛弃 Rust,因为 Fil-C 更安全,否则他们关心的只是他们新颖闪亮的语言,而不是内存安全。”这其中也包括 Fil-C 的作者本人。我不太确定安德鲁·凯利的想法,但我前面提到的那个 Issue 标题给人的感觉非常接近这种倾向。在我看来,这类论点无视了现实,带有许多人指责 Rust 开发者时所表现出的狂热和邪教般的行为。

如果 Fil-C 是一个完全零权衡的直接替代品,我或许会部分赞同这种情绪,但它是有权衡的:它与非 Fil-C 编译的程序存在 ABI(应用二进制接口)不兼容问题,在某些情况下可能会慢好几倍,并且它引入了垃圾回收机制(GC)。对于某些程序来说,这些都不是致命问题。你日常使用的许多程序比现在慢好几倍,你甚至都不会注意到。它们中的许多也没有动态链接任何东西,因此 ABI 兼容性也无关紧要。但并非每个软件程序都是简单的实用工具。有许多流行的项目,其中 GC 和 ABI 不兼容是个大问题,他们绝不可能开始使用 Fil-C 这类技术,至少目前的形式不可能。至关重要的是,那些无法使用 Fil-C 的软件程序,往往非常适合用 Rust 编写。

但 Rust 是不安全的,不是吗?它毕竟有 unsafe!如果你想那么严格,换句话说,如果你是一个内存安全绝对主义者,对你来说这或许是真的。我(也希望大多数人)比这更务实。关于 Rust 在实际中到底有多安全,并没有太多数据,但据我所知,Rust 软件中并没有出现太多可资利用的内存安全漏洞,而且有一些大型项目拥有相关数据,比如 Android 中有超过 500 万行代码(5M+ LOC):

在 Android 平台中,大约有 500 万行 Rust 代码,发现并修复了 1 个潜在的内存安全漏洞(在发布前修复),我们估计 Rust 的漏洞密度为每 100 万行(MLOC)0.2 个漏洞。

我们关于 C 和 C++ 的历史数据显示,其密度接近每 MLOC 1000 个内存安全漏洞。我们的 Rust 代码目前的漏洞密度低了几个数量级:减少了 1000 倍以上。

我确信这些数字对其他项目来说会有所不同,但我认为现在已经可以确定的是,在实践中,Rust 最大限度地降低了引入内存安全问题的风险。

如果你可以做出选择:一种技术能防止 100% 的程序中 99.9% 的问题,另一种技术能防止 90% 的程序中 100% 的问题,你会选择哪一个?我不知道真实的数字是多少,但你懂我的意思。值得庆幸的是,与某些人的说法相反,我们不必二选一。我希望那些能够接受权衡的、用 C/C++/Zig 编写的项目能够提供 Fil-C 编译的二进制文件,而无法接受的软件则将使用那些完全或主要消除引入内存安全漏洞风险的语言编写。

我也认为,即便你可以使用像 Go 或 Fil-C 这样带有 GC 的语言,使用 Rust 也完全没有问题。内存安全绝对主义者会告诉你这不可接受,尽管其中一些人似乎只有在涉及 Rust 时才奉行内存安全绝对主义,而对 C、C++ 或 Zig 却并非如此,真让人匪夷所思。事实是,那些也可以用支持 GC 的语言编写的程序,通常根本不需要 unsafe;而需要 unsafe 的程序,往往本来就无法使用 GC。以我的经验,那些不以狂热态度解决问题的人,往往会权衡利弊。对许多人来说,偶尔遇到严重内存安全问题的极低风险,被其他语言保证(比如防止数据竞争)和其他语言特性所抵消。更何况,还记得每 MLOC 1000 个内存安全漏洞吗?在 Fil-C 的作用下,它们变成了程序崩溃(crashes)。这仍然比引入安全漏洞要好,但要修复的崩溃数量也相当可观。如果我们处于害怕相对罕见问题的领域,或许值得指出的是,过去曾有一些安全漏洞正是因为攻击者能够让程序崩溃而得以实现的。

这篇文章的最后我想说,如果有人对内存安全的关切程度如此之高,以至于连 Rust 每 100万行代码 0.2 个漏洞都无法接受,我真心希望他们能够同样严厉(甚至更严厉)地批评那些编译“随性盲目(YOLO)”的 C/C++ 以及未采用 fil 模式的 Zig 的开发者。毕竟,如果你对内存安全的关心如此强烈,以至于连 Rust 对你来说都不够安全,那你肯定更不会允许人们使用安全性甚至更低的替代品,对吧?

https://itsallaboutthebit.com/memory-safety-absolutists/

加入我们

Zig 中文社区是一个开放的组织,我们致力于推广 Zig 在中文群体中的使用,有多种方式可以参与进来:

  1. 供稿,分享自己使用 Zig 的心得
  2. 改进 ZigCC 组织下的开源项目
  3. 加入微信群Telegram 群组

Metadata

Metadata

Assignees

No one assigned

    Labels

    日报daily report

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions