Rust生态安全漏洞总结系列 | Part 3
作者: 张汉东
本系列主要是分析RustSecurity 安全数据库库中记录的Rust生态社区中发现的安全问题,从中总结一些教训,学习Rust安全编程的经验。
本期分析了下面十一个安全问题:
-
RUSTSEC-2021-0110: Vulnerability in wasmtime
-
RUSTSEC-2021-0098: Vulnerability in openssl-src
-
RUSTSEC-2021-0082: Unsoundness in vec-const
-
RUSTSEC-2021-0093: Vulnerability in crossbeam-deque
-
RUSTSEC-2021-0077: Vulnerability in better-macro
-
RUSTSEC-2021-0106: Vulnerability in bat
-
RUSTSEC-2021-0073: Vulnerability in prost-types
-
RUSTSEC-2021-0078: Vulnerability in hyper
-
RUSTSEC-2021-0072: Vulnerability in tokio
-
RUSTSEC-2021-0070: Vulnerability in nalgebra
-
CVE-2021-31162: Vulnerability in std
看是否能给我们一些启示。
RUSTSEC-2021-0110: Vulnerability in wasmtime
在 Wasmtime 中发现多个代码缺陷。包括 UAF(use-after-free)、越界读写等。
漏洞描述:
- 漏洞类型:Vulnerability
- 漏洞分类:memory-corruption/ memory-exposure
- CVE 编号: CVE-2021-39216、CVE-2021-39219、CVE-2021-39218
- 详细:https://rustsec.org/advisories/RUSTSEC-2021-0110.html
- 补丁:
>=0.30.0 - 关键字:
use-after-free/out-of-bounds read /out-of-bounds write / Wasm/garbage collection
漏洞分析
背景:
externref是 WebAssembly 引用类型(Reference Types)中引入的概念,用于表示 Host 引用。
Use after free passing externrefs to Wasm in Wasmtime
当从 Host 传递给 Guest externrefs 时会引发 UAF 。满足下列条件之一可触发此 Bug :
- 同时明确地从Host传递多个
externrefs给wasm实例 - 通过将多个
externrefs作为参数从 Host 代码传递给wasm函数 - 从Host定义的多值返回函数中返回多个
externrefs给wasm
如果 Wasmtime 的 VMExternRefActivationsTable在传入第一个externref后容量被填满,那么传入第二个externref可能会触发垃圾回收。然而,在把控制权传给Wasm之前,第一个externref是没有根(root)的,因此,如果没有其他东西持有对它的引用或以其他方式保持它的live,就会被GC回收。然后,当控制权在垃圾收集后被传递给Wasm时,Wasm可以使用第一个externref,但这时它已经被释放了。
Out-of-bounds read/write and invalid free with externrefs and GC safepoints in Wasmtime
在Wasmtime运行使用externrefs的Wasm时,存在一个无效释放和越界读写的错误。
要触发这个错误,Wasmtime需要运行使用externrefs的Wasm,Host 创建非空的externrefs,Wasmtime执行一个垃圾收集(GC),并且堆栈上必须有一个Wasm帧,它处于GC Safepoint(安全点就是指代码运行到这个地方,它的状态是确定的, GC就可以安全的进行一些操作),在这个安全点上没有 Live 的引用,这种情况下 Wasmtime 会错误地使用 GC Stack map 而非 安全点。这就会导致释放一些不应该释放的内存,以及潜在的越界读写。
Wrong type for Linker-define functions when used across two Engines
Engine,是在 wasmtime 中被用于跨线程管理wasm模块的全局上下文。
Linker,是用于支持模块链接的结构。
在 Linker::func_* 安全函数中发现了一个问题。wasmtime 不支持函数的跨 engine 使用,这可能导致函数指针的类型混乱,导致能够安全地调用一个类型错误的函数。这种情况应该 panic!
RUSTSEC-2021-0098: Vulnerability in openssl-src
openssl-src 是用于构建 OpenSSL 给 openssl-sys 库使用的。OpenSSL 最近又发现了很多新的安全缺陷,也记录到这里了。
具体这个漏洞是指 处理ASN.1字符串时的读取缓冲区超限问题。
漏洞描述:
-
漏洞类型:Vulnerability
-
漏洞分类:denial-of-service / crypto-failure
-
CVE 编号:CVE-2021-3712
-
详细:https://www.openssl.org/news/secadv/20210824.txt
-
补丁:
>=111.16
漏洞分析
ASN.1字符串在OpenSSL内部被表示为一个ASN1_STRING结构,它包含一个容纳字符串数据的缓冲区和一个容纳缓冲区长度的字段。这与普通的C语言字符串不同,后者表示为一个字符串数据的缓冲区,以NUL(0)字节结束。
虽然不是严格的要求,但使用OpenSSL自己的 "d2i "函数(和其他类似的解析函数)解析的ASN.1字符串,以及任何用ASN1_STRING_set()函数设置值的字符串,都会在ASN1_STRING结构中以NUL结束字节数。
然而,应用程序有可能直接构建有效的ASN1_STRING结构,通过直接设置ASN1_STRING数组中的 "data "和 "length "字段,不以NUL方式终止字节数组。这也可以通过使用ASN1_STRING_set0()函数来实现。
许多打印ASN.1数据的OpenSSL函数被认为ASN1_STRING字节数组将以NUL结尾,尽管这对直接构建的字符串来说是不保证的。如果应用程序要求打印一个ASN.1结构,而该ASN.1结构包含由应用程序直接构建的ASN1_STRING,而没有以NUL结束 "data "字段,那么就会发生读取缓冲区超限。
如果一个恶意行为者可以使一个应用程序直接构建一个ASN1_STRING,然后通过受影响的OpenSSL函数之一处理它,那么这个问题可能会被击中。这可能导致崩溃(造成拒绝服务攻击,DOS)。它还可能导致私人内存内容(如私人密钥或敏感明文)的泄露。
其他 OpenSSL 问题
OpenSSL 缺陷列表: https://rustsec.org/packages/openssl-src.html
RUSTSEC-2021-0082: Unsoundness in vec-const
vec-const试图从一个指向常量切片的指针构造一个Vec。
漏洞描述:
-
漏洞类型:Unsound
-
漏洞分类:memory-corruption
-
CVE 编号:CVE-2021-3711
-
详细:https://github.com/Eolu/vec-const/issues/1#issuecomment-898908241
-
补丁:暂无,不建议使用该 crate
-
关键字: memory-safety
漏洞分析
这个crate 违反了Rust的规则,使用起来会有危害。你不应该使用这个crate。这个crate不应该存在。它创建了不健全的抽象,允许不安全的代码伪装成安全代码。
这个crate声称要构造一个长度和容量都不为零的const Vec,但这是做不到的,因为这样的Vec需要一个来自分配器(allocator)的指针。参见:https://github.com/rust-lang/const-eval/issues/20。
RUSTSEC-2021-0093: Vulnerability in crossbeam-deque
crossbeam-deque中发生了数据竞争。
漏洞描述:
-
漏洞类型:Vulnerability
-
漏洞分类:memory-corruption
-
CVE 编号: GHSA-pqqp-xmhj-wgcw、 CVE-2021-32810
-
详细:https://github.com/Eolu/vec-const/issues/1#issuecomment-898908241
-
补丁:
>=0.7.4, <0.8.0/>=0.8.1
漏洞分析
在受影响的版本中,队列的一个或多个任务会被弹出两次,如果在堆上分配,会导致 dobule free 和 内存泄漏。如果不是堆上分配,则会引起逻辑错误。
修复PR :https://github.com/crossbeam-rs/crossbeam/pull/726
问题是因为任务窃取相关条件判断错误导致的,是逻辑 Bug。
RUSTSEC-2021-0077: Vulnerability in better-macro
better-macro 是一个假的 crate,它在 "证明一个观点",即proc-macros可以运行任意的代码。这是一个特别新颖或有趣的观察。
它目前打开的 https://github.com/raycar5/better-macro/blob/master/doc/hi.md,似乎没有任何恶意的内容,但不能保证会一直如此。
这个 crate 没有任何有用的功能,不应该被使用。
#![allow(unused)] fn main() { #[proc_macro] pub fn println(input: TokenStream) -> TokenStream { if let Ok(_) = Command::new("xdg-open").arg(URL).output() { } else if let Ok(_) = Command::new("open").arg(URL).output() { } else if let Ok(_) = Command::new("explorer.exe").arg(URL).output() { } let input: proc_macro2::TokenStream = input.into(); let out = quote! {::std::println!(#input)}; out.into() } }
RUSTSEC-2021-0106: Vulnerability in bat
bat 中存在不受控制的搜索路径元素,可能会导致非预期代码执行。
0.18.2之前的windows系统中的bat会从当前工作目录中执行名为less.exe的程序。
漏洞描述:
-
漏洞类型:Vulnerability
-
漏洞分类:code-execution
-
CVE 编号:CVE-2021-36753 、 GHSA-p24j-h477-76q3
-
详细:https://nvd.nist.gov/vuln/detail/CVE-2021-36753
-
补丁:
>=0.18.2 -
平台: Windows
漏洞分析
修复 PR: https://github.com/sharkdp/bat/pull/1724
对传入的 Path 进行了合法验证。使用的库是 grep_cli。
RUSTSEC-2021-0073: Vulnerability in prost-types
从prost_types::Timestamp到SystemTime的转换可能导致溢出和恐慌。
漏洞描述:
-
漏洞类型:Vulnerability
-
漏洞分类:denial-of-service
-
CVE 编号:CVE-2021-36753 、 GHSA-p24j-h477-76q3
-
详细:https://github.com/tokio-rs/prost/issues/438
-
补丁:
>=0.8.0
漏洞分析
在prost-types 0.7.0中,从Timestamp到SystemTime的转换使用UNIX_EPOCH上的+和-运算符。如果输入的Timestamp是不被信任的,这可能会溢出和恐慌,造成拒绝服务的漏洞。因为 SystimeTime 内部实现的 + 和 - 使用 checked_add/checked_sub会发生 panic。
#![allow(unused)] fn main() { use prost_types::Timestamp; use std::time::SystemTime; SystemTime::from(Timestamp { seconds: i64::MAX, nanos: 0, }); // panics on i686-unknown-linux-gnu (but not x86_64) with default compiler settings SystemTime::from(Timestamp { seconds: i64::MAX, nanos: i32::MAX, }); // panics on x86_64-unknown-linux-gnu with default compiler settings }
另外,转换涉及到调用Timestamp::normalize,它使用了+和-运算符。这可能会引起恐慌或环绕(wrap around,取决于编译器设置),如果应用程序被编译为溢出时恐慌,也会产生拒绝服务的漏洞。
解决问题的思路是:
Timestamp::normalize可能应该使用 saturating_{add,sub} 方法,如果时间戳的nanos字段超出了范围,这可能会默默地改变时间戳,最多3秒,但这样的时间戳可以说是无效的,所以这可能是好的。
SystemTime 没有Saturating_{add,sub}方法,也没有MIN和MAX常数,应该再次使用 SystemTime::checked_{add,sub} 进行转换。
修复 PR: https://github.com/tokio-rs/prost/pull/439
RUSTSEC-2021-0078: Vulnerability in hyper
对Content-Length进行宽松的 header 解析,可能会使请求被偷渡(走私,smuggling)。
背景: 请求偷渡
不合法的请求被夹杂在合法请求中被得到处理。需要通过
content-length和Transfer-Encoding两个header 来构造攻击。
漏洞描述:
-
漏洞类型:Vulnerability
-
漏洞分类:http、parsing
-
CVE 编号:CVE-2021-32715
-
详细:https://github.com/hyperium/hyper/security/advisories/GHSA-f3pg-qwvg-p99c
-
补丁:
>=0.14.10
漏洞分析
hyper的HTTP/1服务器代码存在一个缺陷,即错误地解析和接受带有前缀加号的Content-Length头的请求,而这一请求本应作为非法请求被拒绝。这与上游HTTP代理不解析这种Content-Length头而转发的情况相结合,可能导致 "请求偷渡("request smuggling) "或 "去同步攻击(desync attacks)"。
修复代码:https://github.com/hyperium/hyper/commit/06335158ca48724db9bf074398067d2db08613e7
需要判断 content-lenght 是不是可以正常转换为有效数位。
RUSTSEC-2021-0072: Vulnerability in tokio
当用JoinHandle::abort中止一个任务时,对于 LocalSet上生成的任务不正确, 容易导致竞态条件。
漏洞描述:
-
漏洞类型:Vulnerability
-
漏洞分类:memory-corruption
-
CVE 编号:CVE-2021-32715
-
详细:https://github.com/tokio-rs/tokio/issues/3929
-
补丁:
>=1.5.1, <1.6.0>=1.6.3, <1.7.0>=1.7.2, <1.8.0>=1.8.1
漏洞分析
当用JoinHandle::abort中止一个任务时,如果该任务当前没有被执行,那么在调用abort的线程中,Future会被 Drop。这对于在LocalSet上生成的任务是不正确的。
这很容易导致竞态条件,因为许多项目在它们的Tokio任务中使用Rc或RefCell以获得更好的性能。
修复 PR: https://github.com/tokio-rs/tokio/pull/3934
RUSTSEC-2021-0070: Vulnerability in nalgebra
nalgebra 库中 VecStorage 的Deserialize实现没有保持元素数量必须等于nrows * ncols的不变性。对特制的输入进行反序列化时,可能会允许超出向量分配的内存访问。
漏洞描述:
- 漏洞类型:Vulnerability
- 漏洞分类:memory-corruption/ memory-exposure
- CVE 编号:CVE-2021-32715
- 详细:https://github.com/dimforge/nalgebra/issues/883
- 补丁:
>=0.27.1
漏洞分析
这个缺陷是在v0.11.0(086e6e)中引入的,因为为MatrixVec增加了一个自动派生(derive)的Deserialize实现。MatrixVec后来在v0.16.13(0f66403)中被改名为VecStorage,并继续使用自动派生的Deserialize实现。
修复 PR : https://github.com/dimforge/nalgebra/pull/889
在反序列化的过程中,对 nrows.value() * ncols.value() == data.len() 进行校验。
CVE-2021-31162: Vulnerability in std
在 Rust 1.52.0之前的Rust标准库中,如果释放元素时出现panic ,在Vec::from_iter函数中会出现 double free。
漏洞描述:
- 漏洞类型:Vulnerability
- 漏洞分类:memory-corruption
- CVE 编号:CVE-2021-31162
- 详细:https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-31162
- 补丁:
>=1.52.0
漏洞分析
漏洞复现代码:
use std::iter::FromIterator; #[derive(Debug)] enum MyEnum { DroppedTwice(Box<i32>), PanicOnDrop, } impl Drop for MyEnum { fn drop(&mut self) { match self { MyEnum::DroppedTwice(_) => println!("Dropping!"), MyEnum::PanicOnDrop => { if !std::thread::panicking() { panic!(); } } } } } fn main() { let v = vec![MyEnum::DroppedTwice(Box::new(123)), MyEnum::PanicOnDrop]; Vec::from_iter(v.into_iter().take(0)); } // Output : free(): double free detected in tcache 2
因为枚举MyEnum在 析构的时候panic,导致资源泄漏,而引发了双重 free 的问题。
修复 PR: https://github.com/rust-lang/rust/pull/84603
在 Vec::from_iter 中执行 forget_allocation_drop_remaining,即,忘记已经被drop的src的元素分配的内存,即便 drop 发生了 panic,也不会泄漏资源。