近日,谷歌宣布 Android 开源项目(AOSP)现在支持 Rust 编程语言来开发 OS。这一举动将让正火的 Rust 语言热度再次上涨。
内存安全错误
目前,C 和 C++的内存安全错误仍然是不正确性来源中最难解决的问题。
谷歌指出,内存安全错误是稳定性问题的罪魁祸首,它在 Android 严重安全漏洞中长期占据大约 70%的比例。
Android 操作系统广泛使用 Java,以有效保护 Android 平台的大部分内容免受内存错误的影响。但不幸的是,系统中比较低级的层是不能选择 Java 和 Kotlin 的。
OS 中的较低级别需要的是系统编程语言,如 C、C++和 Rust 等。这些语言设计时考虑到了可控和可预测性的目标。它们提供对低级系统资源和硬件的访问。它们的资源需求相对轻量,并具有更可预测的性能特征。
对于 C 和 C++,开发人员负责管理内存的生命周期。但是,这样做很容易出错,尤其是在复杂的多线程代码库中更是如此。
Rust 使用编译时检查(强制执行对象生命周期/所有权)和运行时检查(确保内存访问有效)的组合来提供内存安全保证。提供这种安全性的同时,Rust 的性能表现足以匹敌 C 和 C++。
谷歌表示,“除了正在进行和即将到来的改进内存错误检测的措施外,我们还在进一步努力设法防患于未然。内存安全语言是预防内存错误的成本效益最优的手段。”
沙箱的局限性
谷歌披露,所有 Android 进程均已沙箱化。它们通过遵循三选二规则(Rule of 2)来确定功能是否需要额外的隔离和特权。这种规则很简单:给定三个选项,开发人员只能选择以下三个选项中的两个。
对 Android 来说,这意味着如果代码是用 C/C++编写并解析了不可靠的输入,则应将其包含在一个严格受限和无特权的沙箱中。
遵守三选二规则的好处是可以有效降低安全漏洞的严重性和暴露程度,但是它也存在一些局限:
沙箱的成本很高。它需要的新进程会消费额外的开销并引入延迟,这是由于 IPC 和额外的内存占用导致的。
沙箱无法消除代码中的漏洞。高漏洞密度会降低其有效性,让攻击者可以将多个漏洞链接在一起。
像 Rust 这样的内存安全语言可以通过两种方式来克服这些限制:
降低代码中错误的密度,从而提高当前沙箱的效率。
减少谷歌对沙箱的需求,从而引入更安全、更省资源的新特性。
近 50%的错误出现短于一年
根据分析,大多数内存错误出现在新的或最近修改的代码中,大约有 50%错误出现还不到一年。
因此,谷歌表示,“我们在内存安全语言方面的工作最好专注于新开发的代码,而不是重写成熟的 C/C++代码。并且,引入一种新的编程语言并不能解决我们现有 C/C++代码中的错误。即便我们重新分配 Android 团队中所有软件工程师的工作,要重写几千万行代码也是完全不可行的。“
相比之下,较旧的内存错误很少见,很多人可能会觉得很惊讶。谷歌还发现,较旧的代码并不是亟需改进的。"随着时间的推移,软件错误会被逐渐发现和修复,因此我们可以预期还在维护但未处于活跃开发状态的代码中的错误量会逐渐减少"。
谷歌还指出,对于复杂的 C/C++代码库来说,通常只有少数人能够开发和审查错误修复,而且即使花费大量精力来修复错误,有时修复本身也会是不正确的。
当错误相对少见且危险的错误得到优先关注时,错误检测才是最有效的。为了获得从错误检测中获得的改进所带来的好处,谷歌称“首先防止引入新的错误”。
预防为主
Rust 语言实现了一系列现代化的语言特性,从而提高了代码的正确性:
内存安全性:搭配编译器和运行时检查来增强内存安全性。
数据并发:防止数据争用。它让用户能轻松编写高效、线程安全的代码,也让 Rust 打出了“无畏并发”的口号。
更具表现力的类型系统:帮助防止逻辑编程错误(例如 newtype 包装器、带有内容的 enum 变体)。
引用和变量是默认不可变的:帮助开发人员遵循最小特权的安全原则,仅在实际需要时才将引用或变量标记为可变。C++有常量,而且倾向于偶尔且不一致地使用常量。相比之下,Rust 编译器会为永不突变的可变值提供警告,来帮助避免散乱的可变性注释。
标准库中更好的错误处理:将 Result 中可能失败的调用包装起来,于是编译器会要求用户检查失败,即使是未返回所需值的函数也是如此。这样就避免了 RageAgainstCage 之类的漏洞,这种漏洞是来源于未处理错误的。Rust 简化了通过?运算符传播错误的过程,并优化了 Result 以降低开销,从而鼓励用户以相同的样式编写易错的函数并获得相同的保护。
初始化:要求所有变量在使用前都初始化。未初始化的内存漏洞一直是 Android 上 3-5%的安全漏洞的源头。在 Android11 中,谷歌开始在 C/C++中自动初始化内存以减少这种问题。然而,初始化到零并不总是安全的,尤其是对于返回值这样的事物,因为它可能成为新的错误不当处理来源。而 Rust 要求每个变量在使用前都要初始化为该类型的合法成员,避免了无意初始化为不安全值的问题。类似 Clang for C/C++,Rust 编译器意识到了初始化的要求,并避免了双重初始化的所有潜在性能开销。
更安全的整数处理:默认情况下,Rust 调试版本会启用溢出清理功能。如果程序员确实打算让一个计算溢出,则鼓励他们指定一个 wrap_add;否则,则指定一个 saturating_add。谷歌打算为 Android 的所有版本启用溢出清理功能。此外,所有整数类型转换都是显式转换:在分配给变量或尝试对其他类型进行算术运算时,开发人员不会在函数调用期间意外地转换。
据悉,在过去 18 个月中,谷歌一直在向 Android 开源项目增加对 Rust 的支持。不过,它也坦言,”在 Android 平台上添加新语言是一项艰巨的任务。不仅需要维护很多工具链和依赖项,更新测试基础架构和工具链,还要培训开发人员“。
谷歌称,未来几年计划将 Rust 扩展到 OS 的更多部分。
评论 1 条评论