当前位置: 首页 > 科技观察

写了10万行代码,发长文吐槽Rust_0

时间:2023-03-18 21:51:27 科技观察

Rust语言因为并发安全而受到众多开发者的喜爱,在多个榜单中被评为最受欢迎的编程语言。然而,现在,有人花了很多时间写了100,000行Rust代码后,写了一篇博客来澄清Rust语言的一系列缺点。以下是博客的主要内容。我钻研Rust以改进Xobs编写的Xous操作系统。Xous是一个用纯Rust编写的微内核消息传递操作系统,专为轻量级(物联网/嵌入式规模)安全第一平台(如Precursor)而编写,用于MMU的硬件强制页面级内存保护。多年来我们为XousOS添加了许多功能,包括网络(TCP/UDP/DNS)、模态和多语言文本的中间件图形抽象、存储(以加密形式)、PDDB、可信启动(trustedboot)和密钥管理库等。我们决定编写自己的操作系统,而不是使用现有的实现,如SeL4、Tock、QNX或Linux,因为我们想真正了解每一行代码在设备中的作用。尤其是Linux,它的源代码库非常庞大而且是动态的,即使是开源的,也不可能把它内核中的每一行代码都搞清楚。因此,Xous只支持我们的平台,尽可能避免内核不必要的复杂性。这种缩小的应用范围也意味着我们还可以利用FPGA中运行的CPU。因此,Xous的目标是一种不寻常的RV32-IMAC配置:具有MMU+AES扩展的配置。FPGA意味着我们有能力在硬件级别修复API错误,从而使核心更精简。这对于处理抽象破坏过程尤其重要,例如从RAM挂起和恢复。我们在创建Xous时考察了多种系统编程语言,Rust名列前茅。当时它刚刚开始支持`no-std`,其特点是强类型、内存安全、良好的工具和新的生态系统。我个人是强类型语言的忠实粉丝,内存安全不仅有利于系统编程,还可以让优化器更好地生成代码,而Rust有助于并发。实际上,我希望Precursor有一个支持标记指针和内存功能的CPU,类似于CHERI。所以我们与CHERI研发团队进行了一些讨论,但显然他们非常专注于C,没有足够的带宽来支持Rust。总的来说,C比Rust更需要CHERI,他们的选择符合资源优先的原则。我们不使用C,但出于安全原因,我希望有一天Rust中会存在硬件强制的胖指针。但是,Rust语言绝不是完美的,甚至给我们的开发带来了很多问题。下面我列出了Rust的缺点。语法杂乱复杂我发现Rust语法密集、沉重且难以阅读,例如:Trying::to_read::<&'aheavy>(syntax,|like|{this.can_be(maddening)}).map(|_|())?;简单来说,上面的代码类似于在对象(实际上是`struct`)上调用一个名为“to_read”的方法。还有一种不遵循Rust语法规则的宏指令也可以运行:#[cfg(all(not(baremetal),any(feature=“hazmat”,feature=“debug_print”)))]的上面的语句对我来说是最令人兴奋的。令人困惑的是使用“=”来表示等价而不是赋值,因为configure指令中的内容不是Rust代码,它就像一个完全独立的元语言。再举一个例子,Rust宏的可读性存在问题——即使是我自己编写的一些Rust宏也“勉强能用”。一门扎实的语言不应该有这些语法问题。Rust确实非常强大。其标准库包含HashMaps、Vecs、Threads等数据结构,内容丰富,易用性强。然而,Rust的“std”库在构建可审计的代码库方面对我们没有任何帮助。Rust并不完整当我们为Xous编写代码时,我们引入了一种名为“constgeneric”的新类型。在此之前,Rust没有处理超过32个元素的数组的原生能力,这是一个令人抓狂的限制。在编写Xous的过程中,Rust的内联汇编、工作区等成熟了,这意味着我们需要重新审视我们编写的代码,将关键的初始启动代码集成到我们构建的系统中。Xous开发的第一年是使用“no-std”完成的,代价是占用大量内存和复杂性。虽然可以编写一个仅具有预先分配的、静态大小的数据结构的操作系统来容纳最坏情况下的元素数量,但我们因此不得不自己发明一些。大约一年前,Xobs将Rust的`std`库移植到Xous,这意味着我们可以访问稳定Rust中的堆,现在Xous捆绑了特定版本的Rust。`std`库从根本上将内存分配和线程创建等“不安全”硬件构造转变为“安全”Rust构造。然而,我必须不断提醒自己,拥有`std`库并不能消除关键代码中存在安全漏洞的风险——它只是将大量关键代码移到了标准库中。Rust有一个固定的更新周期,这意味着我们也必须定期更新Xous以保持与语言的兼容性。但这可能不可持续。最终,我们需要锁定代码库,但我没有明确的退出策略。也许我们可以考虑仍然使用`no-std`来获得一个稳定的`alloc`函数来访问堆。但随后我们还需要使用Vec、HashMap、Thread和Arc/Mutex/Rc/RefCell/Box构造等来使Xous能够高效编码。Rust担心供应链安全。在rustup.rs安装文件中,有如下代码:`curl--proto'=https'--tlsv1.2-sSfhttps://sh.rustup.rs|sh`用户可以下载脚本并在运行前检查它,它似乎比vscode的Windows.MSI安装程序要好得多。然而,这种做法渗透到整个构建生态系统,让我对通过crates.io生态系统进行软件供应链攻击的可能性感到不安。Crates.io也有一个拼写错误,因此很难确定哪些板条箱是好是坏;一些crate的名字正是用户想要的,而放弃了提供所需的功能,而积极维护的crate不得不采用不太直观的名称。当然,这不是Rust独有的问题。还有一个事实是依赖关系是链式的。这意味着当你从crates.io中引入一些东西时,你也会引入该crate的所有依赖项,以及它们所有的build.rs(http://build.rs/)脚本,这些脚本最终将运行在你的机器。因此,仅审计Cargo.toml文件中明确指定的箱子是不够的——您还必须审计所有相关的箱子以防止潜在的供应链攻击。幸运的是,Rust允许您使用Cargo.lock文件将crate固定到特定版本,并且您可以完全指定crate的依赖项。我们尝试通过发布Cargo.lock文件并将我们所有与一阶相关的板条箱指定为次要修订的策略来缓解Xous中的这个问题。然而,我们的大部分调试和测试框架都依赖于一些相当花哨和复杂的crate,它们会引入大量依赖项,即使我尝试为我们的目标硬件运行构建,但在主机上运行的那些依赖于crates和build。rs脚本仍在构建中。针对这个问题,我写了一个叫做“crate-scraper”的小工具,它会为我们Cargo.toml文件中指定的每个源下载源码包,并存储在本地,这样我们就可以获取构建Xous版本的代码快照。它还运行一个快速“分析”程序——搜索名为build.rs的文件并将它们组织到一个文件中,以便我可以更快地grep查找明显的问题。当然,人工审查并不是检测嵌入build.rs(http://build.rs/)文件中巧妙伪装的恶意软件的实用方法,但它至少让我了解了我们攻击面的大小正在处理。令人惊讶的是,我们审查了来自各种第三方的大约5,700行代码,这些代码操纵文件、目录和环境变量,并在我的计算机上运行其他程序。我不确定这个问题是否有更好的解决方案,但是,如果您的目标是构建可信赖的固件,请警惕Rust广泛的软件供应链攻击面。不能复制别人的Rust构建我关于Rust的最后一点是,一台计算机上的构建不能在另一台计算机上复制。我认为这主要是因为Rust将源代码的完整路径作为二进制文件中内置的调试字符串的一部分。这导致了一些令人讨厌的情况,我们的构建在Windows上成功,但在Linux下失败,因为路径名非常不同,这导致一些内存对象在目标内存中移动。公平地说,这些失败是由于Xous中的错误造成的,这些错误已被修复。然而,我们最终还是会收到用户向我们报告我们无法重现的情况,因为他们在构建系统上的路径与我们的不同。最后,我想说,尽管这里列出了所有的抱怨,但如果我们可以重新来过,Rust仍然是我们用来构建Xous的语言的有力竞争者。我用C、Python和Java完成了很多大型项目,所有这些都以Rust避免的“增加技术债务”告终。