Patch分析
公告中强调的ResultIterator
是漏洞分析的切入点,首先回顾这个迭代器的相关逻辑:
#[derive(Debug)] - pub struct ResultIterator<'a>(pub *mut _CassIterator, usize, PhantomData<&'a CassResult>); + pub struct ResultIterator<'a>(*mut _CassIterator, usize, PhantomData<&'a _CassResult>); - // The underlying C type has no thread-local state, but does not support access - // from multiple threads: https://datastax.github.io/cpp-driver/topics/#thread-safety - unsafe impl<'a> Send for ResultIterator<'a> {} + // The underlying C type has no thread-local state, and forbids only concurrent + // mutation/free: https://datastax.github.io/cpp-driver/topics/#thread-safety + unsafe impl Send for ResultIterator<'_> {} + unsafe impl Sync for ResultIterator<'_> {} impl<'a> Drop for ResultIterator<'a> { fn drop(&mut self) { unsafe { cass_iterator_free(self.0) } } } - 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<'_>> { unsafe { match cass_iterator_next(self.0) { cass_false => None, cass_true => Some(self.get_row()), } } } fn size_hint(&self) -> (usize, Option<usize>) { (0, Some(self.1)) } } - impl<'a> ResultIterator<'a> { - /// Gets the next row in the result set - pub fn get_row(&mut self) -> Row<'a> { + impl ResultIterator<'_> { + /// Gets the current row in the result set + pub fn get_row(&self) -> Row { unsafe { Row::build(cass_iterator_get_row(self.0)) } } }
重点关注其中的next
函数,我们会发现,代码修改前后都声明了Row
对象和这个ResultIterator
的生命周期,同时next
函数功能为调用ResultIterator
迭代器中实现的get_row
函数。
这边的LendingIterator
为库自身实现的一个接口,本质上和原先Iterator
写法类似,所以这里只是省略了没写,但是也是一样声明了生命周期,后面会提及
这个get_row
函数调用的函数cass_iterator_get_row
为一个CPP实现的函数,其细节如下
const CassRow* cass_iterator_get_row(const CassIterator* iterator) { if (iterator->type() != CASS_ITERATOR_TYPE_RESULT) { return NULL; } return CassRow::to(static_cast<const ResultIterator*>(iterator->from())->row()); }
这里的ResultIterator
是一个表示迭代器的类,其实现如下
class ResultIterator : public Iterator { public: ResultIterator(const ResultResponse* result) : Iterator(CASS_ITERATOR_TYPE_RESULT) , result_(result) , index_(-1) , row_(result) { decoder_ = (const_cast<ResultResponse*>(result))->row_decoder(); row_.values.reserve(result->column_count()); } virtual bool next() { // skip code } const Row* row() const { assert(index_ >= 0 && index_ < result_->row_count()); if (index_ > 0) { return &row_; } else { return &result_->first_row(); } } private: const ResultResponse* result_; Decoder decoder_; int32_t index_; Row row_; };
这里可以看到ResultIterator
对象中,存放了一个叫做Row
的对象,这个对象被创建的时候,对应的row_
对象也会被初始化,并且在名为row
的函数中,会根据当前的row_count
返回不同的指针。那么在这里我们可以得出第一条结论
ResultIterator 和 Row 处在同一片内存空间中,当 ResultIterator 被销毁的时候,Row也将被销毁
接下来,确认这个ResultIterator
在程序中是如何创建和使用的:
impl CassResult { /// Gets the number of rows for the specified result. // ... /// Creates a new iterator for the specified result. This can be /// used to iterate over rows in the result. pub fn iter(&self) -> ResultIterator { unsafe { ResultIterator( cass_iterator_from_result(self.0), cass_result_row_count(self.0), PhantomData, ) } } }
可以看到,迭代器对象由CassResult对象创建,这里的CaseResult对象指针正是前面ResultIterator对象创建时使用的指针:
ResultIterator(const ResultResponse* result) : Iterator(CASS_ITERATOR_TYPE_RESULT) , result_(result) // CaseResult pointer , index_(-1) , row_(result) // CaseResult pointer
于是,这里能得到第二个结论
CaseResult 的裸指针 传递给了 ResultIterator,并且ResultIterator中会使用 result_ 来操作对象
那么这里就能看到第一个问题:当 CaseResult 在 ResultIterator 销毁前被销毁,ResultIterator使用next的时候就将访问一个未初始化的内存。。。吗?尝试编写一个这样的poc
{ let result = get_result(); tmp_iter = result.iter(); } println!("Using tmp iter here {:?}", tmp_iter);
很容易就会发现编译器报错,说明rust编译器会检查这种问题。这要归功于 ResultIterator 声明的 PhantomData字段:
#[derive(Debug)] - pub struct ResultIterator<'a>(pub *mut _CassIterator, usize, PhantomData<&'a CassResult>); + pub struct ResultIterator<'a>(*mut _CassIterator, usize, PhantomData<&'a _CassResult>);
可以看到,无论修改前还是修改后,PhantomData
逻辑都是保留的,所以ResultIterator
的生命周期始终和CassREsult
保持同步,保护始终生效。换句话说,这个想法并非为当前报告中提及的漏洞点。