Rust下的二进制漏洞 CVE-2024-27284 分析
Rust被誉为 【能够规避内存漏洞】的语言,在这几年收到很多开发者喜爱。所以在这个语言下能出现的UAF漏洞,自然也有价值研究一下。本文就一个常见开源库中发现的UAF漏洞进行分析。
漏洞背景
漏洞本身来自一个叫做Casandra-rs的开源库。
Cassandra 是一个开源的分布式数据库管理系统,由 Apache 软件基金会开发和维护。它被设计为具有高度可扩展性和容错性的分布式存储系统,用于处理大规模数据集的高吞吐量和低延迟的应用程序。Cassandra 使用一种称为 CQL(Cassandra Query Language)的查询语言,它类似于 SQL,但具有一些特定于 Cassandra 的扩展和功能。CQL 提供了灵活的数据模型和查询选项,可以满足各种应用程序的需求。 —— 来自Apache
当前库是一个Rust写的库,理论上Rust是很少能出问题的,但是在现实场景中,由于对底层逻辑的操作需求,Rust也不得不引入unsafe
关键字对一些底层的内容进行操作。然而一旦引入了unsafe,Rust在编译期间进行的检查就会失效,在这个过程中就会导致漏洞的出现。
Patch分析
根据漏洞公告,可以看到漏洞描述如下
Code that attempts to use an item (e.g., a row) returned by an iterator after the iterator has advanced to the next item will be accessing freed memory and experience undefined behaviour. Code that uses the item and then advances the iterator is unaffected. This problem has always existed.
This is a use-after-free bug, so it's rated high severity. If your code uses a pre-3.0.0 version of cassandra-rs, and uses an item returned by a cassandra-rs iterator after calling next() on that iterator, then it is vulnerable. However, such code will almost always fail immediately - so we believe it is unlikely that any code using this pattern would have reached production. For peace of mind, we recommend you upgrade anyway.
根据描述,我们可以直到这个漏洞的几个特征:
- 漏洞类型为UAF
- 漏洞和迭代器
iter
有关 - 漏洞的触发和
next()
有关系
同时可以找到程序的patch在这个位置。其中有一段内容比较关键:
## Lending iterator API (version 3.0)
Version 3.0 fixes a soundness issue with the previous API. The iterators in the
underlying Cassandra driver invalidate the current item when `next()` is called,
and this was not reflected in the Rust binding prior to version 3.
To deal with this, the various iterators (`ResultIterator`, `RowIterator`,
`MapIterator`, `SetIterator`, `FieldIterator`, `UserTypeIterator`,
`KeyspaceIterator`, `FunctionIterator`, `AggregateIterator`, `TableIterator`,
`ColumnIterator`) no longer implement `std::iter::Iterator`. Instead, since this
is a [lending
iterator,](https://blog.rust-lang.org/2022/11/03/Rust-1.65.0.html#generic-associated-types-gats)
these types all implement a new `LendingIterator` trait. We define this
ourselves because there is currently no widely-used crate that implements it.
观察修复的内容,可以找到大致有两类修复代码:
一类则是增加了生命周期的声明:
/// A field's metadata - pub struct Field { + // + // Borrowed from wherever the value is borrowed from. + pub struct Field<'a> { /// The field's name pub name: String, /// The field's value - pub value: Value, + pub value: Value<'a>, }
另一类则是增加了一些关于生命周期和幽灵数据的声明
#[derive(Debug)] - pub struct RowIterator(pub *mut _CassIterator); + pub struct RowIterator<'a>(*mut _CassIterator, PhantomData<&'a _Row>); /// skip code - impl<'a> Iterator for &'a RowIterator { - type Item = Value; + impl LendingIterator for RowIterator<'_> { + type Item<'a> = Value<'a> where Self: 'a; - fn next(&mut self) -> Option<<Self as Iterator>::Item> { + fn next(&mut self) -> Option<<Self as LendingIterator>::Item<'_>> { unsafe { match cass_iterator_next(self.0) { cass_false => None, cass_true => Some(Value::build(cass_iterator_get_column(self.0))), } } } }
可以看到,这里对类型RowIterator
新增了生命周期的定义,并且这个LendingIterator
似乎是一个新增的描述概念,作者同样添加到了README中:
## Lending iterator API (version 3.0) Version 3.0 fixes a soundness issue with the previous API. The iterators in the underlying Cassandra driver invalidate the current item when `next()` is called, and this was not reflected in the Rust binding prior to version 3. To deal with this, the various iterators (`ResultIterator`, `RowIterator`, `MapIterator`, `SetIterator`, `FieldIterator`, `UserTypeIterator`, `KeyspaceIterator`, `FunctionIterator`, `AggregateIterator`, `TableIterator`, `ColumnIterator`) no longer implement `std::iter::Iterator`. Instead, since this is a [lending iterator,](https://blog.rust-lang.org/2022/11/03/Rust-1.65.0.html#generic-associated-types-gats) these types all implement a new `LendingIterator` trait. We define this ourselves because there is currently no widely-used crate that implements it.
并且修复commit中,作者提到
Make ResultIterator a LendingIterator
换句话说,将这些迭代器修改为LendingIterator
,尤其是这个ResultIterator
,就能解决问题。那么总结以下,漏洞修复方案大概是:
- 将迭代器由Iterator修改为LendingIterator
- 将数据对象增加生命周期,并且对某些结构体增加幽灵成员以增加生命周期
整体修复全是基于Rust特性进行的操作。为了能够更好的了解这个修复过程发生了什么,我们需要了解rust中关于生命周期的一些概念。熟悉的同学可以直接跳到漏洞分析。