-
Notifications
You must be signed in to change notification settings - Fork 1
Description
Hello,
We are @purseclab, and we are fuzzing Rust crates to identify memory violation bugs. While analyzing this crate, we found that memory corruption can be triggered solely through calling safe APIs.
The PoC below causes a heap buffer-overflow memory violation.
PoC:
#![forbid(unsafe_code)]
use arenavec::*;
#[derive(PartialEq, PartialOrd)]
struct StructA(String);
fn main (){
let a_backing = arenavec::common::ArenaBacking::SystemAllocation;
let arena = arenavec::rc::Arena::init_capacity(a_backing, 136).unwrap();
let inner_ref = arena.inner();
let mut slice_vec = arenavec::common::SliceVec::with_capacity(inner_ref.clone(), 2);
let elem = StructA(String::from("BUG"));
slice_vec.push(elem);
let elem = StructA(String::from("BUG"));
slice_vec.push(elem);
let elem = StructA(String::from("BUG"));
slice_vec.push(elem);
let elem = StructA(String::from("BUG"));
slice_vec.push(elem); // Causes a Heap-Buffer-Overflow
}
Bug Description:
We believe the bug is caused by an incorrect implementation of the allocate_inner
method, which is reachable via SliceVec::reserve
.
Lines 256 to 281 in f931efb
pub fn reserve(&mut self, additional: usize) { | |
let ptr = self.slice.ptr; | |
let size = self.slice.len + additional; | |
if self.capacity >= size { | |
return; | |
} | |
let mut new_capacity = if self.capacity > 0 { self.capacity } else { 4 }; | |
while new_capacity < size { | |
new_capacity *= 2; | |
} | |
let new_ptr: NonNull<T> = self.slice.handle.allocate_or_extend(ptr, self.capacity, new_capacity); | |
if ptr != new_ptr { | |
unsafe { | |
ptr::copy_nonoverlapping(ptr.as_ptr(), new_ptr.as_ptr(), self.slice.len()); | |
} | |
self.slice.ptr = new_ptr; | |
} | |
self.capacity = new_capacity; | |
} |
Lines 693 to 729 in f931efb
pub(crate) fn allocate_inner<T>( | |
head: NonNull<u8>, | |
position: &Cell<usize>, | |
cap: usize, | |
count: usize) -> NonNull<T> | |
{ | |
let layout = Layout::new::<T>(); | |
let mask = layout.align() - 1; | |
let pos = position.get(); | |
debug_assert!(layout.align() >= (pos & mask)); | |
// let align = Ord::max(layout.align(), 64); | |
let mut skip = 64 - (pos & mask); | |
if skip == layout.align() { | |
skip = 0; | |
} | |
let additional = skip + layout.size() * count; | |
assert!( | |
pos + additional <= cap, | |
"arena overflow: {} > {}", | |
pos + additional, | |
cap | |
); | |
position.set(pos + additional); | |
let ret = unsafe { head.as_ptr().add(pos + skip) as *mut T }; | |
assert!((ret as usize) >= head.as_ptr() as usize); | |
assert!((ret as usize) < (head.as_ptr() as usize + cap)); | |
unsafe { NonNull::new_unchecked(ret) } | |
} |
It appears that although the capacity of the SliceVec
object is increased, the underlying memory is not, leading to writes beyond its bounds. The reserve
method is invoked within SliceVec::push
when the internal slice is not large enough to accommodate the new element.
Output:
=================================================================
==773800==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x525000001088 at pc 0x55771e7e12e4 bp 0x7ffe0180ffa0 sp 0x7ffe0180f760
WRITE of size 24 at 0x525000001088 thread T0
#0 0x55771e7e12e3 (/home/user/arenavec_push_bug/target/debug/arenavec_push_bug+0xc72e3) (BuildId: e50d13ff8fac804a)
#1 0x55771e80db2e (/home/user/arenavec_push_bug/target/debug/arenavec_push_bug+0xf3b2e) (BuildId: e50d13ff8fac804a)
#2 0x55771e80fdf4 (/home/user/arenavec_push_bug/target/debug/arenavec_push_bug+0xf5df4) (BuildId: e50d13ff8fac804a)
#3 0x55771e811303 (/home/user/arenavec_push_bug/target/debug/arenavec_push_bug+0xf7303) (BuildId: e50d13ff8fac804a)
#4 0x55771e81170a (/home/user/arenavec_push_bug/target/debug/arenavec_push_bug+0xf770a) (BuildId: e50d13ff8fac804a)
#5 0x55771e8124cd (/home/user/arenavec_push_bug/target/debug/arenavec_push_bug+0xf84cd) (BuildId: e50d13ff8fac804a)
#6 0x55771e80e924 (/home/user/arenavec_push_bug/target/debug/arenavec_push_bug+0xf4924) (BuildId: e50d13ff8fac804a)
#7 0x55771e82b2d1 (/home/user/arenavec_push_bug/target/debug/arenavec_push_bug+0x1112d1) (BuildId: e50d13ff8fac804a)
#8 0x55771e80e7c8 (/home/user/arenavec_push_bug/target/debug/arenavec_push_bug+0xf47c8) (BuildId: e50d13ff8fac804a)
#9 0x55771e8114fd (/home/user/arenavec_push_bug/target/debug/arenavec_push_bug+0xf74fd) (BuildId: e50d13ff8fac804a)
#10 0x7f146d806d8f (/lib/x86_64-linux-gnu/libc.so.6+0x29d8f) (BuildId: 490fef8403240c91833978d494d39e537409b92e)
#11 0x7f146d806e3f (/lib/x86_64-linux-gnu/libc.so.6+0x29e3f) (BuildId: 490fef8403240c91833978d494d39e537409b92e)
#12 0x55771e75fa04 (/home/user/arenavec_push_bug/target/debug/arenavec_push_bug+0x45a04) (BuildId: e50d13ff8fac804a)
0x525000001088 is located 0 bytes after 136-byte region [0x525000001000,0x525000001088)
allocated by thread T0 here:
#0 0x55771e7e3877 (/home/user/arenavec_push_bug/target/debug/arenavec_push_bug+0xc9877) (BuildId: e50d13ff8fac804a)
#1 0x55771e830390 (/home/user/arenavec_push_bug/target/debug/arenavec_push_bug+0x116390) (BuildId: e50d13ff8fac804a)
#2 0x55771e8139fe (/home/user/arenavec_push_bug/target/debug/arenavec_push_bug+0xf99fe) (BuildId: e50d13ff8fac804a)
#3 0x55771e8143ca (/home/user/arenavec_push_bug/target/debug/arenavec_push_bug+0xfa3ca) (BuildId: e50d13ff8fac804a)
#4 0x55771e810f75 (/home/user/arenavec_push_bug/target/debug/arenavec_push_bug+0xf6f75) (BuildId: e50d13ff8fac804a)
#5 0x55771e81170a (/home/user/arenavec_push_bug/target/debug/arenavec_push_bug+0xf770a) (BuildId: e50d13ff8fac804a)
#6 0x55771e82b2d1 (/home/user/arenavec_push_bug/target/debug/arenavec_push_bug+0x1112d1) (BuildId: e50d13ff8fac804a)
#7 0x55771e80e7c8 (/home/user/arenavec_push_bug/target/debug/arenavec_push_bug+0xf47c8) (BuildId: e50d13ff8fac804a)
#8 0x55771e8114fd (/home/user/arenavec_push_bug/target/debug/arenavec_push_bug+0xf74fd) (BuildId: e50d13ff8fac804a)
SUMMARY: AddressSanitizer: heap-buffer-overflow (/home/user/arenavec_push_bug/target/debug/arenavec_push_bug+0xc72e3) (BuildId: e50d13ff8fac804a)
Shadow bytes around the buggy address:
0x525000000e00: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x525000000e80: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x525000000f00: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x525000000f80: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x525000001000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x525000001080: 00[fa]fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x525000001100: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x525000001180: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x525000001200: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x525000001280: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x525000001300: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
==773800==ABORTING
How to Build and Run the PoC:
RUSTFLAGS="-Zsanitizer=address" cargo run
Details:
- Compiler Version: rustc 1.81.0-nightly (8337ba918 2024-06-12)
- Library Version: arenavec-0.1.1
- OS: Ubuntu 20.04.6 LTS