Rust 程序可靠性保证相关技术探索与实践
QCon 2022·上海站
软件正在重新定义世界
Software Is Redefining The World
https://qcon.infoq.cn/2022/shanghai/schedule
姜剑峰 蚂蚁集团 安全计算 2022.11.25 上海
自我介绍
- 本硕毕业于复旦大学
- 在蚂蚁集团安全计算部门从事于机密计算系统软件的开发
- 2.5 年 Rust 开发经验,关于 Rust 模糊测试的工作曾获得 ACM 杰出论文奖
为什么用 Rust 开发系统软件
Rust for Linux
- Rust for Linux 被正式合入了 Linux 6.11
- Linux 内核第一次引入对新语言的支持
- Linux:有利于减少内核中内存安全相关的漏洞
- Rust:更大的舞台上检验语言本身的能力
系统软件的特点
- 什么是系统软件1?操作系统,数据库,容器,虚拟机.......
- 系统软件是硬件与用户应用程序之间的桥梁
- 对性能敏感
- 与底层硬件频繁的交互
- 开发和调试一般更加困难
- 内部逻辑复杂
Application <-> System software <-> Hardware
Rust语言的特点
- 安全:内存安全和线程安全
- 效率:No GC, 理论上接近 C/C++ 的执行效率和内存使用效率
- 对底层资源的掌控能力:裸指针,数据布局,内联汇编,SIMD
- 现代化的语言设计:错误处理,空值,函数式编程,泛型与trait,统一的包管理工具
- 友好的社区氛围
Rust 开发系统软件是未来
- Rust 语言的特点适合进行系统软件的开发 系统软件有使用惯性
- 传统领域 PK:相比较传统软件展现出足够的优势(性能,安全, 维护性等)
- 进军新兴领域:机密计算,Web3.0......
Rust for 蚂蚁集团安全计算部门
- 应用于机密计算的系统软件的开发,构建基于 TEE 的可信技术栈
- 机密计算的核心是信任:机密计算硬件+系统软件构成 TCB
- Occlum (ASPLOS21): 兼容 Posix 接口的机密计算操作系统 Hyperenclave (ATC22): 基于虚拟化技术的通用 TEE 平台 Sworndisk (Ongoing): 高效安全的虚拟硬盘
Rust 其他的应用领域
- 命令行实用程序: minigrep, nushell
- 数据处理: DataFusion
- 服务端框架:actix-web, rocket
- Web框架:yew, iced
Rust 程序可靠性问题
Rust 提供的安全是什么?
- 内存安全:访问不安全的内存区域: Use after free, double free, use before initialization, buffer overflow
- 线程安全:对数据可能存在的竞争访问
- 其他检查:整数溢出;字符串合法性检查
Unsafe 的魔法 - 安全都是有代价的,保证安全会限制灵活性,引入额外的开销
- Unsafe 从功能上来说可以做一些比较危险的操作
- Unsafe 是一种安全约定,约定了由谁来保证安全性质可以满足。Unsafe 分为两种,对外暴露的和不对外暴露的
- Unsafe trait 或者是 Unsafe fn: impl 这个 trait 或者调用这个方法
是 unsafe 的,需要调用者来保证安全 - Unsafe block 或者 Unsafe impl: 实现者保证内部的代码是安全的 ( You can trust me that I am right )
Rust 真的安全吗?从 CVE 的角度
- 学术界研究了 Rust 现有的所有内存安全相关的 CVE
- Rust 中仍然存在一些传统的内存安全问题,诸如UAF (82), DF(13), UNINIT(12) 等等
- 绝大多数 Rust 的安全性问题都是库 API 的 soundness 问题。存在安全隐患, 但很少产生实际的后果。
- Rust 总体上是兑现了安全承诺的
- 即使很多写法是 unsafe 的,最终也没有产生安全问题
- 权责分明:到底是 API 实现错误还是 API 误用?到底是谁来负责修复
Rust 真的可靠吗?从系统软件的角度
“If the Rust compiler ends up doing hidden allocations, and they then cause panics, then one of the main points of Rustification is entirely broken.” —— Linus Torvalds
“There is clearly a need to identify dynamic checks that can never fail and dynamic
checks that could potentially fail. ” —— Alastair Reid
Rust 程序的可靠性问题定义
在任何合法使用 API 的情况下
- 内存安全和线程安全不应该被 unsafe 代码所破坏
- 所有动态检查都不应该被违背(可以被安全的移除),除非 panic 是一种允许的行为
现有的机制是否足够呢?
- Unsafe 代码没有破坏内存安全性
- no memory leakage
- panic free
现有的研究工作
- 模糊测试(afl.rs, libfuzzer):分支覆盖率;用例程序的构造
- 符号执行(klee, angr):路径爆炸;求解困难
- 静态分析(MirChecker, Rudra, SafeDrop):分析特定问题;假阳性
- 形式化验证(RustBelt):无法方便的验证第三方库
- 其他工具(Miri等)........
基于程序合成的 Rust 模糊测试
模糊测试简介
- 初始化种子集合
- 选择一个种子并且进行变异
- 使用变异后的种子执行程序
- 如果这个种子比较有趣的话
- Yes 把这个种子加入种子集合
- No
模糊测试系统软件的局限性
系统软件一般都是 Stateful 的
- 要达到某个特定的状态需要一个比较长的调用序列
- 同一个序列执行多次会有不同的结果
- 某一特定状态是否可被复现是不可预测的
系统软件内部可能有一些随机的操作
很多都是 soundness 问题,并不会真的触发异常行为
总结与展望
个人学习 Rust 的一些体会
- Rust 并没有想象的那么难学,但前期学习曲线比较陡峭
- Rust 可以减少开发者的心智负担,很多情况下程序跑通约等于程序正确 Rust 的语法噪音是比较大的,这也是可以进一步优化的点
总结与展望
- 保证可靠性主要是库开发者的工作,库开发者要保证 API 的 soundness。Rust 中的不安全很多时候都是违反了 soundness 的要求,并不意味着真的出现了实际的安全问题。
- 由于 unsafe 的传播性,供应链安全对 Rust 很重要
- Rust 编译器的检查可以防止程序中大部分的安全问题
- 对于可靠性的保证仍然需要各种程序分析以及软件测试的手段