首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >论文精要 | 真实世界中Rust程序的安全实践

论文精要 | 真实世界中Rust程序的安全实践

作者头像
张汉东
发布2020-07-03 14:37:15
9710
发布2020-07-03 14:37:15
举报
文章被收录于专栏:Rust 编程Rust 编程

点击上方蓝字关注我们

理清头脑混沌,觉醒心智天地

本文是对世界顶级学术期刊的论文《理解真实Rust程序中的内存和线程安全实践》中的数据和观点的精要萃取,供学习参考和讨论。 论文地址: 《Understanding Memory and Thread Safety Practicesand Issues in Real-World Rust Programs》 https://blog.mozilla.org/nnethercote/2020/04/24/how-to-speed-up-the-rust-compiler-in-2020/

实际上,该论文团队成员在2019年也发过相似主题的论文:《Fearless Concurrency? Understanding Concurrent Programming Safety inReal-World Rust Software》 地址:https://www.semanticscholar.org/paper/Fearless-Concurrency-Understanding-Concurrent-in-Yu-Song/225250e14d33ac91b319c1c0001af735d31e3d28 只不过2020的论文调研的面更加广泛。

Rust 虽然是安全语言,但是默认写的代码,尤其是用了unsafe或 写并发代码的时候,还会有安全风险。依赖于开发者对所有权、生命周期的理解,以及API设计的功力。

该论文的目的也是为了帮助更好地完善Rust及其社区,包括周边的工具,比如增强IDE的生命周期可视化、专属的bug检测工具等等。

真实Rust程序的调研范围

论文调研范围涉及:五个开源项目、五个广泛使用的库、两个在线安全漏洞数据库、Rust 标准库中的 850 个 Unsafe 代码的用法、分析了170个Bug。

五个开源项目分别是:

  • Servo,Rust实现的浏览器
  • Tock,一个实时嵌入式操作系统(tockOS)
  • Ethereum,以太坊区块链Rust客户端(parity-ethereum)
  • TiKV, Rust 实现的分布式KV数据库,是TiDB的存储底层。
  • Redox,Rust实现的下一代桌面级操作系统。

五个广泛使用的库:rand、crossbeam、threadpool、rayon、lazy_static。

两个在线漏洞数据库: https://cve.mitre.org/ 和 https://rustsec.org/

170 个 Bug ,都是和安全相关的,包括 70 个内存 Bug 和 100 个并发 Bug。

为什么要使用 Unsafe

使用 Unsafe 的场景总结为三类:

  • 42% 是通过 FFi 方式重用现有的 C/Cpp 库。
  • 22% 是为了提高性能。
  • 14% 是为了绕过 Rust 的安全规则在线程间共享全局变量。

使用 Unsafe 进行操作的类型主要是:

  • 66% 用于内存操作。比如裸指针操作和类型转换。
  • 29% 用于调用 unsafe 函数

也发现某些程序员为了代码看上去保持一致性,在不需要 Unsafe 的地方也加了 Unsafe 块(unsafe block),这大概占 Unsafe 使用总数的 5%。

某些程序员用 Unsafe 关键字来发起警告,在使用该函数时注意 UB 风险。这其实也是 Rust 官方团队对于 编写 Unsafe Rust 时的推荐做法,对于使用了 Unsafe 操作,但是没有做边界检查的函数必须使用 unsafe 关键字标识。

这个统计结果表明:

  • 有些场景必须使用 Unsafe 代码,比如 FFi。
  • 有些场景没必要使用 Unsafe,比如提高性能的时候,也不要太盲目,要对比 Safe 代码的性能看是否够用,就可以减少不必要的 Unsafe。
  • 有些场景属于滥用 Unsafe,比如使用 Unsafe 绕过并发安全检查。

重构过程中为什么删除 Unsafe 代码

统计:

  • 提高内存安全性:占 61%
  • 提升代码结构:占 24%
  • 提高线程安全性:10%
  • 为了修复 Bug:3%
  • 删除不必要的使用:2%

如何封装 Unsafe 代码

论文作者们对 Rust 标准库中 250 个 Unsafe 函数进行了统计,得出了三条建议:

  • 如果一个函数的安全性取决于它的使用方式,那么请将其标记为 unsafe 函数。
  • 尽量最小化 unsafe 接口
  • 使用内部可变性的时候需要小心,可能会绕过 Rust 的安全检查

并且发现 Rust 标准库中使用 unsafe 函数的时候,都遵循同样模式:调用 unsafe 函数的时候检查使用条件。这其实就是 Rust 官方也建议的「安全抽象」。只不过论文作者本着从实践中求证的精神,得出的结论和官方的建议也是一致的。

标准库中稳定的 unsafe API 的安全使用条件大都满足下面两类:

  • 69% 的内部 unsafe 代码都需要有效的内存空间或有效的 UTF-8 字符
  • 15% 要求合法的生命周期和所有权条件

标准库中其实也有 5 例使用 unsafe 不恰当的地方,虽然未引起 Bug,但还有潜在的安全问题。

内存安全问题

论文团队调研了 70 个 Rust 内存安全问题及其详细的修复过程,从两个维度对 Bug 进行了分析:错误的传播性和影响力。

内存 Bug 分类及产生原因:

  • 缓冲区溢出(Buffer Overflow)。在统计的 21 个内存 Bug 中,有 17 个遵循相同的模式: 在 Safe 代码中计算缓冲区大小或索引时发生了错误,然后在 Unsafe 代码中发生了越界访问。
  • Null 。
    • 解引用空指针。
    • 未初始化。
    • 访问未初始化内存。比如,在 Unsafe 代码里创建了未初始化内存区,而在 Safe 代码里去读取。
  • 无效释放(Invalid Free)。这属于 Rust 特有,发生在 Unsafe 代码中。
  • 释放后使用( UAF, use after free)。比如,在 Safe 代码中被释放内存,而在 Unsafe 代码中还使用其指针。
  • 二度释放(Double Free)。这也属于 Rust 特有,是由 Unsafe 代码中的错误传播到 Safe 代码中发生的。比如,t2 =ptr::read::<T>(&t1),该代码中,t1 的内容被绑定给了 t2,但是 t1 的所有权却没有被移出。

上述 Bug 经统计,一般存在三种修复策略

  • 可以通过设置检查条件(前置检查 + 后置检查)来跳过危险代码。(安全抽象)
  • 调整生命周期。可通过这种策略修复的 Bug,多半是因为对生命周期认识不足引起的。
  • 修正 Unsafe 的操作对象。比如,调用 Vec::from_raw_parts() 时将长度和容量更改为正确的顺序。

总体而言,这些内存 Bug 的主要原因还是因为开发者对 Rust 的生命周期认识不足。

线程安全问题

线程安全问题论文团队在 分析了 100 个并发安全问题之后,将其分为两大类:

  • 阻塞类 Bug。比如死锁。
  • 非阻塞类 Bug。这里指数据竞争。

引起阻塞类 Bug 的原因,又大体分为三种:

  • 无法获取到锁。
  • 二度锁定。
  • 获取锁的顺序有关系。

本质原因,是还是因为开发者对生命周期理解不到位导致的。因为 Rust 是利用生命周期来隐式解锁( unlock)。

(下图是阻塞类 Bug 统计信息。)

阻塞类 Bug 的修复策略主要有四种方法:

  • 改变 lock 相关方法的位置,从而调整其生命周期,以改变隐式解锁的时机。
  • 调整线程同步机制。
  • 修改为非阻塞代码(避免用锁)。
  • 显式 drop 替代隐式解锁(这种方式不太 Rust)。

引起非阻塞类 Bug 的原因

  • 使用 Unsafe 进行线程间共享,跳过了安全检查。
  • Safe 代码内共享,但是违反了程序语义(应该属于逻辑 Bug)。

使用 Unsafe 代码进行线程共享,还有几种方式:

  • 直接传递裸指针
  • 使用 Unsafe 代码访问系统级调用和硬件资源。比如,多个线程共享系统调用getmntent()的返回值,并且该返回值指向描述文件系统的结构体。

而在 Safe 代码里,虽然每个值都满足安全规范,但是它们组合在一起,却违反了程序语义,从而引发了数据竞争。

(下图是非阻塞类 Bug 统计信息。)

非阻塞类 Bug 的修复策略主要有两种方法:

  • 强制对共享内存进行原子访问
  • 强制对不同线程的共享内存访问排序

如何尽量避免非阻塞类 Bug

  • 在实现了 Sync 的结构体中,如有内部可变性的函数,必须检查其内部是否正确互斥。
  • 开发者应该仔细设计接口(推敲可变与不可变借用),以避免非阻塞性 Bug。API 的设计能力深深影响 Rust 编译器检查 Bug 的能力。

小结

通过这类调研,我们可以对真实世界中存在的 Rust 程序的安全性有一个比较全面的认识,这些结论对社区开发者来说,是非常有借鉴意义的。并且对于开发 Rust 周边的工具指明了方向,比如 IDE中添加可视化生命周期功能、专属 Rust 的 Bug 检查工具等等。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-07-01,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 觉学社 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
检测工具
域名服务检测工具(Detection Tools)提供了全面的智能化域名诊断,包括Whois、DNS生效等特性检测,同时提供SSL证书相关特性检测,保障您的域名和网站健康。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档