Rust 接棒 C 语言:盘点那些用 Rust 重写的知名 C 项目

2024-01-02 22:02 👁️ 331 阅读
https://mp.weixin.qq.com/s/9TFQ0UcVbTAsdMSFwwCHqw

本文继续来探索还有哪些知名的C项目是用Rust重写了。

sudo-rs

sudo-rs[1] 是Prossimo项目的一部分,由ISRG主导开发,并得到NLNet基金会的资助进行独立安全审核。开发团队是由Ferrous Systems和Tweede Golf团队组成的团队。

sudo工具为类Unix系统(例如Linux和FreeBSD)的特权用户提供了以root身份运行命令的方式。它存在一定的风险,因为低权限的恶意用户或软件可能会找到滥用它的方法,例如利用代码中的漏洞来提升他们的访问权限到root或超级用户级别。理想情况下,sudo和su应该尽可能安全和无漏洞,因为它们作为控制系统的完全控制的入口。

"

" sudo命令算是一个典型的安全关键工具,它既普遍存在又不被重视。对于这类工具的安全改进将对整个行业产生巨大影响。"  —— Chainguard (一家网络安全公司)的首席执行官兼联合创始人丹·洛伦克(Dan Lorenc)

根据ISRG的Prossimo项目执行主任Josh Aas的说法,原始sudo中三分之一的安全漏洞源于内存管理问题。

在2023年8月,`sudo-rs` 首次发布了release版本[2]。在用Rust重写sudo-rs时还带来了附加收益:sudo-rs开发了一个测试套件,帮助发现了原始sudo C实现中的错误。因为sudo已经是一套非常成熟的软件了,用Rust对其重写,需要覆盖一份完整的功能测试套件。

在2023年9月4日至9月15日,ROS(Radically Open Security[3])对sudo-rs进行了水晶盒渗透测试,目的是验证在没有适当身份验证的情况下无法执行特权操作。此次审计是在sudo-rs代码库的b5eb2c6分支版本上于进行的,点此查看完整审计报告[4]

ROS团队发现了一个中等严重性问题和两个低严重性问题:

  • CLN-001:相对路径遍历漏洞(中等)
  • CLN-003:Cargo配置不会剥离(strip)符号(symbol)(低)
  • CLN-004:对 chown 调用的默认权限设置不正确(低)

除了这些发现之外,ROS还对sudo-rs代码库的不同组件进行了模糊测试,但没有发现任何问题。

pendulum

Pendulum[5] 同样是Prossimo项目的一部分,由ISRG主导开发,2023年7月,主权科技基金( Sovereign Tech Fund[6])投资了Pendulum,确保了2023年的开发和维护,并在2024年进行维护和采用工作。Pendulum项目包含Statime(PTP)和ntpd-rs(NTP)两个项目,旨在构建现代化、开源的网络时间协议和精确时间协议的实现,以达成下面两个目标:

  • 提供可靠的时间同步
  • 易于扩展以适应未来的时间标准改进

"

在数百万设备和服务器上运行的NTP和PTP是互联网和其他关键基础设施的重要组成部分:金融和广播领域,电网和电信领域以及安全协议中

"

网络时间协议(NTP)是一种用于在网络中与计算机时钟时间源进行同步的互联网协议。它属于TCP/IP套件的最古老的部分之一。术语NTP既适用于协议,也适用于在计算机上运行的客户端-服务器程序。David Mills是特拉华(Delaware)大学的教授,于1981年开发了NTP。它被设计成高度容错和可扩展的,同时支持时间同步。

NTP

NTP时间同步过程涉及以下三个步骤:

  • NTP客户端与NTP服务器发起时间请求交换。
  • 客户端随后能够计算链路延迟和本地偏移,并调整本地时钟以与服务器计算机上的时钟相匹配。
  • 通常情况下,需要进行大约五到十分钟的六次交换来最初设置时钟。

一旦同步,客户端大约每10分钟更新一次时钟,通常只需要一次消息交换,除了客户端与服务器的同步。此事务通过用户数据报协议(UDP)在端口123上进行。NTP还支持对对等计算机时钟进行广播同步。世界各地有成千上万个NTP服务器。它们可以访问高精度的原子钟和全球定位系统钟。需要专门的接收器与NTP服务器直接通信以获取时间服务。

计算机网络上所有设备的准确时间对许多原因都很重要;即使是一秒钟的差异也可能引发问题。所以NTP的安全健壮和性能非常重要。

ntpd-rs[7] 是一个完全使用Rust编写的开源实现的网络时间协议,重点是暴露最小的攻击面。应该已经支持NTPv5协议。

2023年5月,`ntpd-rs` 经过ROS的安全审计[8]。2023年5月,`ntpd-rs` 经过ROS的安全审计[9]

审计发现了该项目的首个CVE[10] : ntpd-rs在接收到的NTP数据包中不验证NTS cookie的长度,攻击者可以通过发送一个特制的NTP数据包来使服务器崩溃,该数据包中包含的cookie长度小于服务器预期的长度。当服务器未配置处理NTS数据包时,也会导致服务器崩溃。这个CVE不算是内存安全问题,但它会有DDos风险。

此外,NTP的NTS(网络时间安全)扩展使用TLS在NTP服务器和客户端之间建立可信链接。这意味着一些敏感的安全密钥被保存在内存中,可能会被攻击者提取出来。虽然这种攻击很难实施,但审计建议进一步增加其难度。

当密钥被丢弃时, zeroize crate确保存储密钥的内存被设置为零。然而,这并不能完全保证密钥不再存在于内存中,因为Rust允许移动内存。密钥字节将保留在原始位置。所以要确保敏感数据不被不必要地复制或移动,以及及时清理所有可能的副本,是提高安全性的关键考虑因素。

PTP

NTP(Network Time Protocol)是用于不同计算机之间同步时钟的网络协议。 它的设计目标是使所有的互连的机器之间的时钟与UTC时间只相差若干毫秒。

PTP是英文 "Precision Timing Protocol" 的缩写,即"精确时间协议"。PTP的设计目标是使机器之间的时钟偏差在亚微秒级(sub-microsecond)范围。

PTP管理的时钟遵循主从层次结构。从属时钟将同步到其主时钟。层次结构由每个时钟上运行的最佳主时钟 (BMC) 算法更新。只有一个端口的时钟可以是主时钟也可以是从属时钟。此类时钟称为普通时钟 (OC)。具有多个端口的时钟可以是一个端口上的主时钟以及另一个端口上的从属时钟。此类时钟称为边界时钟(BC)。顶层主时钟称为_超级主时钟_。超级主时钟可与全球定位系统 (GPS) 同步。这样,不同的网络便能够以高准确度实现同步。

PTP实现主要分为硬件和软件两种方式。硬件支持是PTP的主要优势,通常在网络性能和安全性要求高的场合下,采用硬件实现方式,其他采用软件方式。各种网络交换机和网络接口控制器 (NIC) 都支持PTP。虽然可以在网络内部使用启用非PTP的硬件,但为所有PTP时钟之间的网络组件启用PTP硬件可实现最大的准确性。

通过PTP,我们可以实现很多高精度的实时应用,如音视频传输、金融交易、无人驾驶车辆等。同时,PTP还广泛应用于工业自动化领域中,通过对工业控制系统进行精确的时间同步,能够实现高效、高质量的自动化生产。此外,PTP还可以帮助网络管理员检测和处理网络延迟,通过确定延迟或时钟偏移,对网络进行实时监测和通信优化。

Statime[11] 是Rust实现的精确时间协议(PTP)的开源实现。高精度定时是关键的网络基础设施的一部分。通过Statime为现有实现提供了一个内存安全的替代方案。PTP用Rust实现的优势在于,软件和硬件支持模块都是Rust实现,均可利用Rust优势。

Binder

2023年11月,Google Android团队宣布,已经将Android的Binder代码重新用Rust实现,并已提交到Linux内核中。

Binder负责Android上的进程间通信(IPC)和其他任务,用内存安全的Rust代码替换它应该是系统安全性的重要提升。

在Google工程师发到Linux内核邮件列表的 RFC[12] 中写道:"我们通常不赞同重写,但是... ..."。为什么用Rust重写?

Binder在过去的15年中不断发展,以满足Android不断变化的需求。在这段时间里,它的责任、期望和复杂性都大大增加。虽然团队期望Binder能够与Android一起不断发展,但目前有一些因素限制了开发和维护它的能力。简要来说,这些因素包括:

  • 复杂性:Binder处于Android的各个交叉点上,并承担了许多超出IPC之外的责任。它对于不同的人来说有着不同的用途,由于其众多的功能及其相互作用,其复杂性相当高。仅仅在6kLOC中,它必须将事务传递到正确的线程。它必须正确解析和转换事务的内容,其中可能包含多个不同类型的对象(例如指针、文件描述符),这些对象可以相互交互。它控制用户空间中线程池的大小,并确保事务以避免线程池耗尽线程的方式分配给线程。它必须正确地在多个进程之间转发共享对象的引用计数更改。它必须处理众多的错误场景,并结合/嵌套了13个不同的锁、7个引用计数器和原子变量。最后,它必须以尽可能快速和高效的方式完成所有这些工作。即使是轻微的性能回退也会导致用户体验明显下降。
  • 改进的事项:随着代码库的有机增长,可能会出现千行函数、容易出错的错误处理和混乱的结构。经过十多年的开发,这个代码库需要进行全面改进。
  • 安全关键:Binder是Android沙盒策略的关键部分。即使是Android最低权限的沙盒(例如Chrome渲染器或SW编解码器),也可以直接访问Binder。与其他任何组件相比,Binder提供强大的安全性并且本身对安全漏洞具有鲁棒性非常重要。
  • 使用Rust,因为它直接解决了Google Android团队在过去几年中在Binder中遇到的一些挑战。它可以防止引用计数、锁、边界检查等方面的错误,并且在错误处理方面也做了很多工作来降低复杂性。此外,还能够使用更具表达力的类型系统来编码各种结构体和指针的所有权语义,这将管理对象生命周期的复杂性从程序员手中解放出来,减少了使用后释放和类似问题的风险。

Rust在类型系统中使用许多不同的指针类型来编码所有权语义,这可能是它在Binder中帮助的最重要的方面之一。Binder驱动程序有许多具有复杂所有权语义的不同对象;一些指针拥有引用计数,一些指针具有独占所有权,而一些指针只是引用对象,并以其他方式保持其活动状态。使用Rust,可以为每种指针使用不同的指针类型,这使得编译器能够强制执行正确实现所有权语义。

另一个有用的功能是Rust的错误处理。Rust允许使用诸如析构函数之类的功能来简化错误处理,并且如果错误没有得到正确处理,编译将失败。这意味着尽管Rust要求你编写比C更多的代码行,例如写下在C中隐含的不变量,但Rust驱动程序仍然比C绑定器稍微小一些:Rust为5.5kLOC,C为5.8kLOC。(这些数字不包括空行、注释、binderfs以及Rust驱动程序中尚未实现的任何C调试工具。这些数字包括rust/kernel/中的抽象,这些抽象不太可能被除Binder以外的其他驱动程序使用)。

尽管这次重写完全重新思考了代码的结构和强制执行的假设方式,但我们并没有从根本上改变驱动程序执行任务的方式。对现有设计进行了很多仔细的思考。重写的目的是改善代码的健康性、结构性、可读性、健壮性、安全性、可维护性和可扩展性。我们还增加了更多的内联文档,并改善了代码中对假设的强制执行方式。此外,所有Unsafe的代码都用一个"SAFETY"注释进行了标注,解释了其正确性。

PubNub

PubNub[13] 致力于打造一种先进的边缘网络消息系统,用于构建任何实时功能的组合,包括聊天、实时观众参与、多用户协作、设备控制、数据流传输和地理位置/调度等。PubNub的规模巨大,每个月设备连接数超过8亿,每月API调度超过3万亿次,5个9的服务级别( 99.999%正常运行时间服务)。

PubNub之前是用C写的,他们花费很多时间和精力做到了服务的稳定和高性能。但为什么要转向Rust呢?在最新的一期访谈[14]中,PubNub的CTO畅谈了这个问题。

大约五年前PubNub内部就开始探索Rust,通过逐步的探索他们发现,Rust在内存安全性和接近C语言速度的性能方面非常吸引人,尤其在PubNub如此巨量规模下更加吸引人,基本上这也是为什么PubNub引入Rust的主要原因。

在之前使用C的过程中,PubNub团队经常遇到"段错误"。出现这种情况通常意味着可能会有数据损坏或丢失,这是个大问题。C语言性能强劲,节省硬件成本,但是C却没有节省工程成本。对于PubNub这样大规模的系统,工程成本远远大于硬件成本。另外,PubNub也是利用的所有招聘方式想找到C高手,因为即使是十年前,找到C语言专家就已经是一个挑战了。即便是找到了一个C专家,他也可能不想再写C了。在PubNub中,必须编写超级稳定的C代码,然而,作为一个C开发者,遇到段错误或其他类似的东西,这是一个必经之路,这是一定会发生的事情。是的,问题不是会不会发生,而是什么时候发生。

PubNub也尝试过使用Go语言来重写PubSub(发布/订阅)总线的一部分,但性能远远比不上C。即使在低负荷下,延迟也立即慢了10倍。然后还有GC暂停,所以延迟会周期性地突然增加。所以后面换为了Rust 。

现在,Rust是PubNub最受欢迎的语言,到目前为止,PubNub所有的新服务通常都选择用Rust编写,未来所有的服务都将是Rust,这是因为PubNub的规模,他们已经从中看到了出色的结果。

ockam

Ockam[15]  是一套开源编程库和命令行工具,用于在大规模环境中协调端到端加密、相互认证、密钥管理、凭证管理和授权策略执行。

"

Ockam与InfluxData的首席技术官Paul Dix一起讨论了InfluxDB和Ockam为什么用Rust重写的视频[16] ,对,InfluxDB也用Rust重写了,只不过它是从Go转向Rust。

在Ockam的早期阶段使用C语言开发,然后在几个月后决定放弃那数万行C代码并改用Rust重写[17]。下面是Ockam用Rust重写C的故事。

2019年,Ockam开始用C语言构建,并希望Ockam能够在各种设备上运行,从受限的边缘设备到强大的云服务器。他们还希望Ockam能够在任何类型的应用程序中使用,不论该应用程序使用的是哪种语言构建。

基于这个目标,在当初C语言成为该团队构建这个系统的首选。因为C可以编译为99%的计算机,并且几乎可以在任何地方运行(一旦你弄清楚如何处理所有特定目标的工具链)。而且,所有其他流行的语言都可以通过某种本地函数接口调用C库。

在Ockam的核心是一组分层的加密和基于消息的协议,如Ockam安全通道和Ockam路由。这些是异步的、多步骤的、有状态的通信协议,他们希望将这些协议的所有细节从应用程序开发人员中抽象出来。他们想象中的用户体验是一个单行函数调用,用于创建端到端的身份验证和加密的安全通道。

然而,与加密相关的代码也往往存在很多隐患,一点小错误就会导致系统不安全。因此,简洁对Ockam来说不仅仅是一种审美理念,还是确保能够赋予每个人构建安全系统的关键要求。Ockam希望将这些安全隐患隐藏起来,并以易于正确使用且难以损害应用程序的方式提供接口给开发者。

这就是C语言严重不足的地方。Ockam团队在C语言中尝试暴露安全简单的接口并不成功。在每次迭代中,他们发现应用程序开发人员需要了解太多关于协议状态和状态转换的细节。

与此同时,他们也用Elixir语言创建了一个Ockam安全通道覆盖Ockam路由的原型。Elixir程序运行在BEAM上,即Erlang虚拟机。BEAM提供了Erlang进程。Erlang进程是轻量级的有状态并发执行者。由于执行者可以在并发运行的同时保持内部状态,因此可以轻松运行一组有状态的协议堆栈 :Ockam传输 + Ockam路由 + Ockam安全通道。这样就做到了能够隐藏所有有状态的层,并创建一个简单的一行函数,任何人都可以调用它来创建一个端到端加密的安全通道,可以通过多跳、多协议的路由。应用程序开发人员将调用此简单函数,多个并发的执行者将运行底层的有状态协议。当通道建立或出现错误时,函数将返回。这正是Ockam团队想要的接口。

但是Elixir不像C语言。它在小型/受限制的计算机上(嵌入式)运行效果不好,也不适合用特定语言的习惯进行包装。

所以,上述问题促使该团队开始尝试探索Rust语言。于是,Rust的一些特性很快吸引了他们:

  • 与C-ABI调用约定的兼容性。Rust库可以导出与C的调用约定兼容的接口。这意味着任何能够静态或动态链接并调用C库中函数的语言或运行时环境也可以以完全相同的方式链接并调用Rust库中的函数。由于大多数语言支持C中的本地函数,它们也已经支持Rust中的本地函数。这使得从我们需要在核心库周围具有特定于语言的包装器的角度来看,Rust与C是等价的。
  • 跨平台支持。Rust使用LLVM进行编译,这意味着它可以针对非常多的计算机进行目标编译。这个集合可能没有C语言使用GCC和各种专有GCC分支所能覆盖的那么大,但仍然是一个非常大的子集,并且正在进行工作,使Rust能够与GCC一起编译。随着对新的LLVM目标的不断支持和Rust在GCC上的潜在支持,从我们能够在任何地方运行的需求来看,这似乎是一个不错的选择。
  • 强类型和强大的类型系统。Rust的内存安全特性消除了使用后释放、双重释放、溢出、越界访问(非编译时)、数据竞争和许多其他常见错误的可能性,这些错误已知会导致大型C或C++ 代码库中60-70% 的高严重性漏洞。Rust在编译时提供了这种安全性,而不需要使用垃圾回收器在运行时安全地管理内存,从而避免了性能开销。这使得Rust在编写需要高性能、在受限环境中运行和高度安全的代码方面具有重要优势。
  • async/await异步编程和可插拔的异步运行时。最后一个让该团队相信Rust非常适合Ockam的特性是 async/await 。Ockam已经确定需要轻量级的Actor来创建Ockam协议栈的简单和安全接口。基于Rust生态的tokio和async-std ,可以轻松构建Ockam的Actor实现。另一个显著的重要方面是,在Rust中, async/await 与其他语言(如Javascript)中的 async/await 有一个重要的区别,就是它的异步运行时(tokio/async-std)是可插拔的。当在嵌入式环境下,也可以选择更轻量的异步运行时。这就意味着,无论它在哪里运行(大型计算机或小型计算机),都可以向用户呈现完全相同的接口。Ockam所有基于Ockam Workers的协议接口也可以呈现完全相同的简单接口,无论它们在哪里运行。

基于以上的优点,Ockam决定用Rust重写整个项目。

 

参考资料

[1] sudo-rs: https://github.com/memorysafety/sudo-rs

[2] 在2023年8月,sudo-rs首次发布了release版本: https://www.memorysafety.org/blog/sudo-first-stable-release/

[3] Radically Open Security: https://www.radicallyopensecurity.com/

[4] 点此查看完整审计报告: https://github.com/memorysafety/sudo-rs/blob/audit-report/docs/audit/audit-report-sudo-rs.pdf

[5] Pendulum: https://tweedegolf.nl/en/pendulum

[6] Sovereign Tech Fund: https://www.sovereigntechfund.de/

[7] ntpd-rs: https://github.com/pendulum-project/ntpd-rs

[8] ntpd-rs经过ROS的安全审计: https://tweedegolf.nl/en/blog/94/report-ntp-security-audit

[9] ntpd-rs经过ROS的安全审计: https://tweedegolf.nl/en/blog/94/report-ntp-security-audit

[10] 首个CVE: https://github.com/pendulum-project/ntpd-rs/security/advisories/GHSA-qwhm-h7v3-mrjx

[11] Statime: https://github.com/pendulum-project/statime

[12] RFC: https://lore.kernel.org/lkml/20231101-rust-binder-v1-0-08ba9197f637@google.com/

[13] PubNub: https://www.pubnub.com/

[14] 最新的一期访谈: https://corrode.dev/podcast/s01e02-pubnub/

[15] Ockam: https://github.com/build-trust/ockam

[16] Ockam与InfluxData的首席技术官Paul Dix一起讨论了InfluxDB和Ockam为什么用Rust重写的视频: https://www.influxdata.com/resources/meet-the-founders-an-open-discussion-about-rewriting-using-rust/

[17] 然后在几个月后决定放弃那数万行C代码并改用Rust重写: https://www.ockam.io/blog/rewriting_in_rust