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. |
| pubfninsert_many<I:IntoIterator<Item = A::Item>>(&mutself,index:usize,iterable:I){ |
| let iter = iterable.into_iter(); |
| if index == self.len(){ |
| returnself.extend(iter); |
| } |
| |
| let(lower_size_bound, _) = iter.size_hint(); |
| assert!(lower_size_bound <= core::isize::MAXasusize);// 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(); |
| letmut 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); |
| letmut guard = DropOnPanic{ |
| start, |
| skip: index..(index + lower_size_bound), |
| len: old_len + lower_size_bound, |
| }; |
| |
| letmut num_added = 0; |
| for element in iter { |
| letmut 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;fnmain(){letmut v:SmallVec<[u8;0]> = SmallVec::new();// Spill on heap
v.push(123);// Allocate string on heaplet s = String::from("Hello!");println!("{}", s);// Prepare an iterator with small lower boundletmut 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 overlapassert!(v.as_ptr_range().contains(&s.as_ptr()));// String is corruptedprintln!("{}", 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