flawopen.com/Incidents/rust-unsafe-smallvec-overflow-january-2021
●
servo/rust-smallvec · CVE-2021-25900 · January 2021 · CVSS 9.8 · 严重
安全研究 · FlawOpen
CVE-2021-25900:Rust smallvec 堆缓冲区溢出
SmallVec::insert_many 信任 size_hint 的下限;迭代器若产生更多元素,就会写出堆缓冲区边界。已在 0.6.14 和 1.6.1 修复。
💡 通俗易懂的原理解析 (ELI5)
一座仓库以安全著称,因为机器人会在放东西前检查每个货架。有几项工作要关掉机器人,由人手工完成,并保证会小心。其中一人问司机:“你大概带了多少箱?”,照这个数搭了货架就开始码放。可那个估计只是猜测,不是保证;到货比说好的多时,箱子就堆出了货架边缘。
核心概念与专有名词
SmallVec- 一种 Rust 集合,少量元素直接内联存放,增长时转用堆上的缓冲区。它的高速来自手动管理这块缓冲区的 unsafe 代码。
size_hint- Iterator 的一个方法,报告剩余元素数量的估计下界和上界。这只是提示:安全代码可以返回任何值,哪怕是错的。
unsafe Rust- unsafe 块中的代码可以做编译器无法检查的事,比如通过裸指针写入;作者必须对任何可能的安全调用方保证内存安全。
可靠性(soundness)- 如果任何安全代码,无论多么反常,都无法通过某个安全 API 引发未定义行为,这个 API 就是可靠的。insert_many 并不可靠,因为一个合法的 size_hint 就可能破坏内存。
框架映射
事件演进时间线
2021-01-08
Yechan Bae 与佐治亚理工学院 SSLab 的 Rust 小组报告了该缺陷;RustSec 发布 RUSTSEC-2021-0003,smallvec 0.6.14 和 1.6.1 包含修复。
2021-01
CVE-2021-25900 被分配;cargo audit 开始在依赖树中标出受影响的版本。
攻击执行流程分解
步骤 1
迭代器少报了长度
调用 insert_many 时传入的迭代器,其 size_hint 下界小于它实际产出的元素数量。安全代码这样做是允许的。
步骤 2
按提示预留空间
insert_many 只按下界预留空间,并把已有元素移动相应的位置。
步骤 3
所有元素照样写入
在 unsafe 块中,循环通过裸指针写入每个元素,多出来的元素就落在了分配区域的末尾之外。
步骤 4
堆被破坏
溢出覆盖了相邻的堆内存。凡是把受不可信输入影响的迭代器传给 insert_many 的程序,都会受到影响。
源代码对比:漏洞与安全实现
存在漏洞的实现
// 根据一个*提示值*分配空间……
let (lower, _) = iter.size_hint();
self.reserve(lower);
unsafe {
let ptr = self.as_mut_ptr().add(index);
for (i, item) in iter.enumerate() {
// ……但实际写入
// 所有到达的元素。
ptr::write(ptr.add(i), item);
}
}加固后的安全修复
// 对每个元素都重新检查容量。
// 绝不能让安全代码中的值
// 决定 unsafe 代码使用的边界。
for (i, item) in iter.enumerate() {
self.reserve(1);
unsafe {
let ptr = self.as_mut_ptr()
.add(index + i);
ptr::write(ptr, item);
}
}
// 实际修复还大幅缩小了
// unsafe 代码的范围。工程与系统安全加固清单
- ✓绝不要根据安全 trait 方法(如 size_hint、trait 对象上的 len 或比较器)来确定 unsafe 代码所用缓冲区的大小;每次写入前都要重新检查容量。
- ✓为每个 unsafe 块写一条 // SAFETY: 注释,说明它依赖的不变量;如果该不变量依赖不可信的安全代码,就不要通过评审。
- ✓在 CI 中用 Miri 运行使用恶意实现的测试,例如 size_hint 撒谎的迭代器、会 panic 的 Drop,以及不满足全序的比较器。
- ✓让 unsafe 块尽可能小;smallvec 的修复也减少了 insert_many 中的 unsafe 代码,使其可以被验证。
- ✓在 CI 中运行 cargo audit(或 cargo deny),让带有 RustSec 公告的依赖(例如 0.6.14 或 1.6.1 之前的 smallvec)导致构建失败。