内存安全是必要条件,但还不够

各国政府和标准机构开始推动“内存安全”编程语言,但开发者认为这只是软件安全拼图中的一块。评论者比较了 Rust、Go、Swift、Java、C 和 C++ 在未定义行为、数据竞争、FFI、工具链和包生态方面的差异,指出 Rust 能大幅减少内存破坏,但仍保留其他严重漏洞和不安全的逃逸口。许多人认为内存安全要求是受欢迎的进展,但同时强调还需要更广泛的方案——从更好的语言和类型系统设计,到沙箱、形式化方法,以及更严格的供应链实践。

政府政策与内存安全语言

  • 美国 2024 财年 NDAA 要求 DoD 采用 NSA 关于内存安全语言和工具的指导;NSA 的示例包括 C#、Go、Java、Ruby、Rust、Swift。
  • 尚不清楚欧盟立法是否有类似明确的内存安全措辞;一份欧盟影响评估提到了 Rust 和 Go,但没有提出要求。
  • 有人预计未来的采购规则会对软件实施更严格的审查,可能包括内存安全标准和进口控制。

Rust、“unsafe”,以及与其他语言的比较

  • Rust 的 unsafe 被认为比 Java/Swift 中典型的 FFI/unsafe 更容易控制:类型系统允许你构建带有强生命周期和使用保证的安全抽象。
  • 但如果违反不变量,unsafe 仍会重新引入类似 C 的未定义行为;漏洞可能很隐蔽,并传播得很远。
  • 对 Rust 是否总体上减少了缺陷,还是只是把缺陷从内存错误转移到了逻辑错误/崩溃式失败,存在分歧;有人呼吁进行形式化研究。
  • Swift 被强调为正在向类似 Rust 的所有权/借用机制收敛,并且凭借紧密的 C++ 互操作性,有望成为 “C++ 的 TypeScript”。
  • Go 和托管语言被指出长期以来都提供了内存安全,但它们有不同的权衡(GC、类型系统限制、并发怪癖)。

内存安全与整体正确性

  • 许多人认为内存安全是必要但不充分的:逻辑错误、SQL 注入、路径穿越、加密错误以及反序列化问题在包括 Rust 在内的各种语言中都仍然存在。
  • 有人担心“panic 文化”和过度依赖崩溃而非优雅恢复,尤其是在非安全关键场景中。

未定义行为、C/C++ 与优化

  • 大量子线程讨论 C 的 UB:
    • 一方认为 UB 对高优化至关重要(指针 provenance、别名规则等)。
    • 另一方认为许多 UB 情形可以在性能损失很小的情况下被定义;现代编译器是“对抗性的”,而且过于复杂。
  • 历史背景:C 的设计更偏向于编译器实现的便利和可移植性,而非安全性和完整性;这帮助它传播开来,但也留下了不安全的遗产。
  • -fno-strict-aliasing 和类似标志被讨论为部分缓解手段,但代价是非标准方言以及一定性能损失。

数据竞争与安全

  • 这里区分了:
    • 低级数据竞争(非原子并发内存访问)以及
    • 更高级的竞态条件(TOCTOU、分布式竞态)。
  • 一些人声称,与经典内存破坏相比,进程内数据竞争目前还不是主要被利用的漏洞类别;另一些人则指出内核和基于竞态的利用案例,并认为我们不应因为它罕见就把 UB 视为正常。
  • 有人质疑为什么语言不能把数据竞争定义为产生“写入值之一”;回复提到撕裂、编译器重物化以及优化损失。

沙箱、能力与依赖

  • 人们越来越担心庞大的依赖树和供应链风险;希望有办法在进程内部对库进行沙箱隔离。
  • 提议的机制包括:语言级沙箱(例如 Java SecurityManager、GraalVM isolates)、WebAssembly,以及对象能力模型,其中对文件系统、网络等资源的访问以能力的形式显式传递。

Python 与其他“内存安全”语言的注意事项

  • Python 通常被标记为内存安全,但参与者指出:
    • FFI(ctypes、C 扩展)很容易破坏安全性。
    • 即使纯 Python 也可能通过精心构造的字节码或底层 API 触发解释器内存错误;要修复这一点将需要大量验证机制。
  • 普遍共识是:“内存安全语言”是一个务实标签,意思是“在正常使用中安全”,而不是绝对保证。

未来语言方向与 C++ 继任者

  • 识别出了四种 “C++ 继任者” 策略:什么都不做、在兼容性内加入安全性、打破兼容但仍不安全,以及打破兼容并默认内存安全。
  • 有人质疑这种折中方案:既然都要打破兼容性了,为什么不直接做成默认内存安全?
  • 有一种看法认为,存在一个设计空间:系统语言可以默认内存安全,但在标准库范围、错误处理、语法、并发模型、分配策略、链接和包管理方面做出与 Rust 非常不同的选择。