核心漏洞点
那漏洞到底出现在哪儿呢?回到我们分析的第一个点以及维护者提到的next
,这个漏洞应该是由于迭代器引发的,那么本质上应该是一个迭代器相关的点触发的问题。重新检查patch,会发现一个很容易忽略的点,在许多的example文件中,都出现了类似的修改
- for row in result.iter() {
+ let mut iter = result.iter();
+ while let Some(row) = iter.next() {
最初我以为这个修改无关痛痒,毕竟这个看起来只是用法不同。然而当我强行将其改成修改前的调用模式时,会提示如下的问题:
python
error[E0277]: `cassandra_cpp::cassandra::result::ResultIterator<'_>` is not an iterator
--> examples/simple2.rs:19:16
|
19 | for row in result.iter() {
| ^^^^^^^^^^^^^ `cassandra_cpp::cassandra::result::ResultIterator<'_>` is not an iterator
|
= help: the trait `Iterator` is not implemented for `cassandra_cpp::cassandra::result::ResultIterator<'_>`
= note: required for `cassandra_cpp::cassandra::result::ResultIterator<'_>` to implement `IntoIterator`
换句话说,这个写法会直接导致错误,因为修正后的ResultIterator
并没有去实现Iterator
的特征。实际上作者也进行了相关提醒:
python
/// An iterator over the results of a query. The result holds the data, so
/// the result must last for at least the lifetime of the iterator.
///
/// This is a lending iterator (you must stop using each item before you move to
/// the next), and so it does not implement `std::iter::Iterator`. The best way
/// to use it is as follows:
结合报错以及生命周期声明,这里会注意到几个特点
- 修复后的漏洞并没有继承Iterator,而是使用了自行定义的迭代器特征,所以才没办法使用
for-in-loop
ResultIterator
是一个C++中的对象,其中包含了一个Row
对象,而非指针ResultIterator
的生命周期和Row
的生命周期在Rust中并非强绑定关系
修复公告中强调ResultIterator
不在支持Iterator
而是LendingIterator
,观察其代码如下
impl<'a> Iterator for ResultIterator<'a> {
- type Item = Row<'a>;
- fn next(&mut self) -> Option<<Self as Iterator>::Item> {
+ impl LendingIterator for ResultIterator<'_> {
+ type Item<'a> = Row<'a> where Self: 'a;
+
+ fn next(&mut self) -> Option<<Self as LendingIterator>::Item<'_>> {
这个修改前 的代码具有一定的迷惑性,乍一看它和修改后一样,都保持了ResultIterator和Item指代的Row类型生命周期长度一致 ,只不过一个直接显示的指定生命周期,一个使用了Self
;一个使用Item
指定了带有生命周期的Row<'a>
,另一个声明了有生命周期的Item<'a>
。然而实际上,Row<'a>
的生命周期并非就是真的是Row对象。这里可以检查定义
/// A collection of column values. Read-only, so thread-safe.
- pub struct Row<'a>(*const _Row, PhantomData<&'a CassResult>);
+ //
+ // Borrowed immutably.
+ pub struct Row<'a>(*const _Row, PhantomData<&'a _Row>);
如果结合这段代码看,我们就能发现,修改前的ResultIterator
的生命周期,实际上和Row
中指定的CassResult
生命周期保持一致。CassResult
这个对象提供了接口获取ResultIterator
对象,他们之间的关系类似于
python
CassResult --- Create --> ResultIterator
|
+-- Create from self --> Row
从设计角度上看,也没太多问题,毕竟查询结果的每一行的生命周期与查询结果一致 是理所当然的。然而在实现过程中,Row自于ResultIterator,而这没有显示的指明Row与ResultIterator的关系 ,这就导致在修改前ResultIterator和Row在Rust中允许生命周期长度不同,而在C中这两个对象却来自于同一块内存 。这种场景中,一旦声明变量为Row
类型,并且生命周期长度超过了ResultIterator
,就会导致Row对象在ResultIterator被销毁后依然被使用。同时,由于生命周期声明错误,Rust编译器也会无法察觉当前问题,就会产生前文提到的UAF问题。
举个例子(这个代码只用于示范,无法运行)
let mut tmp_row = None;
let result = function.get_result();
{
for row in result.iter() {
if condition.satisfied():
tmp_row = Some(row)
break;
}
}
println!("here will cause problem {:?}", tmp_row);
实际上,这种代码在实际中很可能存在