Hello fellow Rustacean,
we (Rust group @sslab-gatech) found a memory-safety/soundness issue in this crate while scanning Rust code on crates.io for potential vulnerabilities.
Issue Description
|
/// Insert multiple elements at position `index`, shifting all following elements toward the |
|
/// back. |
|
pub fn insert_many<I: IntoIterator<Item = A::Item>>(&mut self, index: usize, iterable: I) { |
|
let iter = iterable.into_iter(); |
|
if index == self.len() { |
|
return self.extend(iter); |
|
} |
|
|
|
let (lower_size_bound, _) = iter.size_hint(); |
|
assert!(lower_size_bound <= core::isize::MAX as usize); // Ensure offset is indexable |
|
assert!(index + lower_size_bound >= index); // Protect against overflow |
|
self.reserve(lower_size_bound); |
|
|
|
unsafe { |
|
let old_len = self.len(); |
|
assert!(index <= old_len); |
|
let start = self.as_mut_ptr(); |
|
let mut ptr = start.add(index); |
|
|
|
// Move the trailing elements. |
|
ptr::copy(ptr, ptr.add(lower_size_bound), old_len - index); |
|
|
|
// In case the iterator panics, don't double-drop the items we just copied above. |
|
self.set_len(0); |
|
let mut guard = DropOnPanic { |
|
start, |
|
skip: index..(index + lower_size_bound), |
|
len: old_len + lower_size_bound, |
|
}; |
|
|
|
let mut num_added = 0; |
|
for element in iter { |
|
let mut cur = ptr.add(num_added); |
|
if num_added >= lower_size_bound { |
|
// Iterator provided more elements than the hint. Move trailing items again. |
|
self.reserve(1); |
|
let start = self.as_mut_ptr(); |
|
ptr = start.add(index); |
|
cur = ptr.add(num_added); |
|
ptr::copy(cur, cur.add(1), old_len - index); |
|
|
|
guard.start = start; |
|
guard.len += 1; |
|
guard.skip.end += 1; |
|
} |
|
ptr::write(cur, element); |
|
guard.skip.start += 1; |
|
num_added += 1; |
|
} |
|
mem::forget(guard); |
|
|
|
if num_added < lower_size_bound { |
|
// Iterator provided fewer elements than the hint |
|
ptr::copy( |
|
ptr.add(lower_size_bound), |
|
ptr.add(num_added), |
|
old_len - index, |
|
); |
|
} |
|
|
|
self.set_len(old_len + num_added); |
|
} |
insert_many() overflows the buffer when an iterator yields more items than the lower bound of size_hint().
The problem is in line 1044. reserve(n) reserves capacity for n more elements to be inserted. This is done by comparing the length and the capacity. Since the length of the buffer is set to 0 in line 1032, line 1044 will be always no-op and the following code will overflow the buffer.
Reproduction
Below is an example program that exhibits undefined behavior using safe APIs of smallvec.
#![forbid(unsafe_code)]
use smallvec::SmallVec;
fn main() {
let mut v: SmallVec<[u8; 0]> = SmallVec::new();
// Spill on heap
v.push(123);
// Allocate string on heap
let s = String::from("Hello!");
println!("{}", s);
// Prepare an iterator with small lower bound
let mut iter = (0u8..=255).filter(|n| n % 2 == 0);
assert_eq!(iter.size_hint().0, 0);
// Triggering the bug
v.insert_many(0, iter);
// Uh oh, heap overflow made smallvec and string to overlap
assert!(v.as_ptr_range().contains(&s.as_ptr()));
// String is corrupted
println!("{}", s);
}
Output:
Hello!
@BDFHJ
double free or corruption (out)
Terminated with signal 6 (SIGABRT)
Tested Environment
- Crate: smallvec
- Version: 1.6.0
- OS: Ubuntu 20.04.1 LTS
- Rustc version: rustc 1.48.0 (7eac88abb 2020-11-16)
Hello fellow Rustacean,
we (Rust group @sslab-gatech) found a memory-safety/soundness issue in this crate while scanning Rust code on crates.io for potential vulnerabilities.
Issue Description
rust-smallvec/src/lib.rs
Lines 1009 to 1070 in 9cf1176
insert_many()overflows the buffer when an iterator yields more items than the lower bound ofsize_hint().The problem is in line 1044.
reserve(n)reserves capacity fornmore elements to be inserted. This is done by comparing the length and the capacity. Since the length of the buffer is set to 0 in line 1032, line 1044 will be always no-op and the following code will overflow the buffer.Reproduction
Below is an example program that exhibits undefined behavior using safe APIs of
smallvec.Output:
Tested Environment