Custom Implementations of scratchpad::Tracking Can Lead to Memory Corruption
Hello,
We are @purseclab, and we are fuzzing Rust crates to identify memory violation bugs. While analyzing this crate, we found that the methods Tracking::capacity, Tracking::set and Tracking::get are invoked within unsafe regions of the library. However, the Tracking trait is not marked as unsafe, meaning users may provide custom implementations under the assumption that the library handles all safety guarantees, including potential undefined behavior arising from those implementations.
The PoC below causes a heap buffer-overflow violation without using any unsafe code on the user side. The return values of Tracking::capacity and Tracking::get for the type StructB were discovered through fuzzing.
Note: the provided PoC may not be fully minimized. We leave the minimization process to the library developers, who have a more detailed understanding of the internal structures.
PoC:
#![forbid(unsafe_code)]
use scratchpad::*;
struct StructA(Vec<u8>);
struct StructB(String);
impl scratchpad::Tracking for StructB {
fn set(&mut self, _: usize, _: usize) {
// Do nothing
println!("@set");
}
fn capacity(&self) -> usize {
println!("@capacity");
12
}
fn get(&self, _: usize) -> usize {
println!("@get");
256
}
}
impl scratchpad::Buffer for StructA {
fn as_bytes_mut(&mut self) -> &mut [u8] {
println!("@as_bytes_mut");
&mut self.0[..]
}
fn as_bytes(&self) -> &[u8] {
println!("@as_bytes");
&self.0[..]
}
}
fn main() {
let structA = StructA(vec![111, 110, 108, 47, 115, 104, 97, 114, 101, 47, 97]);
let structB = StructB(String::from("BUG"));
let spad = scratchpad::Scratchpad::new(structA, structB);
let marker = spad.mark_back();
if let Ok(m) = marker {
let src_data: Vec<u8> = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
let src_data: [u8; 16] = src_data.try_into().unwrap();
println!("Before `allocate_slice_copy`");
let alloc = m.allocate_slice_copy::<[u8], _>(&src_data[..]);
println!("After `allocate_slice_copy`");
}
}
Bug Description:
The lack of the unsafe keyword from the trait Tracking can lead seemingly safe custom implementations to cause memory corruption. More specifically, we identified that the violation is triggered inside the Marker::allocate_slice_copy method, which calls Marker::allocate_array_uninitialized in lines 386-389.
https://github.com/okready/scratchpad/blob/c97b1855e98bbcf12ec1b746bf4f5fce5c865b9c/src/marker.rs#L374-L407
The Marker::allocate_array_uninitialized method then calls MarkerBack::allocate_memory, which in turn invokes user-defined trait methods.
https://github.com/okready/scratchpad/blob/c97b1855e98bbcf12ec1b746bf4f5fce5c865b9c/src/marker.rs#L1568-L1618
In particular, on line 1596 the potentially user-defined Tracking::get method is called, and its return value affects the buffer_end variable. This variable is then used to compute the start address on line 1606. The user-defined Tracking::set method (which may be a no-op) is called afterward (line 1615), and the resulting start is returned.
Finally, on line 397 of Marker::allocate_slice_copy, a heap buffer overflow occurs due to the ill-formed object returned by Marker::allocate_array_uninitialized.
Output:
@capacity
@capacity
@as_bytes
@set
Before `allocate_slice_copy`
@as_bytes_mut
@get
@set
=================================================================
==2695924==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x502000000100 at pc 0x55c768a5bc04 bp 0x7ffc754ebd20 sp 0x7ffc754eb4e0
WRITE of size 16 at 0x502000000100 thread T0
#0 0x55c768a5bc03 (/home/user/scratchpad_custom_traits/target/debug/scratchpad_custom_traits+0xc7c03) (BuildId: 0cc787794ffccd92)
#1 0x55c768a8eb96 (/home/user/scratchpad_custom_traits/target/debug/scratchpad_custom_traits+0xfab96) (BuildId: 0cc787794ffccd92)
#2 0x55c768a8d935 (/home/user/scratchpad_custom_traits/target/debug/scratchpad_custom_traits+0xf9935) (BuildId: 0cc787794ffccd92)
#3 0x55c768a8f98d (/home/user/scratchpad_custom_traits/target/debug/scratchpad_custom_traits+0xfb98d) (BuildId: 0cc787794ffccd92)
#4 0x55c768a8d67a (/home/user/scratchpad_custom_traits/target/debug/scratchpad_custom_traits+0xf967a) (BuildId: 0cc787794ffccd92)
#5 0x55c768a89655 (/home/user/scratchpad_custom_traits/target/debug/scratchpad_custom_traits+0xf5655) (BuildId: 0cc787794ffccd92)
#6 0x55c768a8aa2a (/home/user/scratchpad_custom_traits/target/debug/scratchpad_custom_traits+0xf6a2a) (BuildId: 0cc787794ffccd92)
#7 0x55c768a8e34d (/home/user/scratchpad_custom_traits/target/debug/scratchpad_custom_traits+0xfa34d) (BuildId: 0cc787794ffccd92)
#8 0x55c768a8a354 (/home/user/scratchpad_custom_traits/target/debug/scratchpad_custom_traits+0xf6354) (BuildId: 0cc787794ffccd92)
#9 0x55c768aa6b31 (/home/user/scratchpad_custom_traits/target/debug/scratchpad_custom_traits+0x112b31) (BuildId: 0cc787794ffccd92)
#10 0x55c768a8a1f8 (/home/user/scratchpad_custom_traits/target/debug/scratchpad_custom_traits+0xf61f8) (BuildId: 0cc787794ffccd92)
#11 0x55c768a898fd (/home/user/scratchpad_custom_traits/target/debug/scratchpad_custom_traits+0xf58fd) (BuildId: 0cc787794ffccd92)
#12 0x7f19d153dd8f (/lib/x86_64-linux-gnu/libc.so.6+0x29d8f) (BuildId: 490fef8403240c91833978d494d39e537409b92e)
#13 0x7f19d153de3f (/lib/x86_64-linux-gnu/libc.so.6+0x29e3f) (BuildId: 490fef8403240c91833978d494d39e537409b92e)
#14 0x55c7689da074 (/home/user/scratchpad_custom_traits/target/debug/scratchpad_custom_traits+0x46074) (BuildId: 0cc787794ffccd92)
0x502000000100 is located 160 bytes after 16-byte region [0x502000000050,0x502000000060)
freed by thread T0 here:
#0 0x55c768a5d456 (/home/user/scratchpad_custom_traits/target/debug/scratchpad_custom_traits+0xc9456) (BuildId: 0cc787794ffccd92)
#1 0x55c768a90646 (/home/user/scratchpad_custom_traits/target/debug/scratchpad_custom_traits+0xfc646) (BuildId: 0cc787794ffccd92)
#2 0x55c768a8af2a (/home/user/scratchpad_custom_traits/target/debug/scratchpad_custom_traits+0xf6f2a) (BuildId: 0cc787794ffccd92)
#3 0x55c768a8ad29 (/home/user/scratchpad_custom_traits/target/debug/scratchpad_custom_traits+0xf6d29) (BuildId: 0cc787794ffccd92)
#4 0x55c768a8aa2a (/home/user/scratchpad_custom_traits/target/debug/scratchpad_custom_traits+0xf6a2a) (BuildId: 0cc787794ffccd92)
#5 0x55c768aa6b31 (/home/user/scratchpad_custom_traits/target/debug/scratchpad_custom_traits+0x112b31) (BuildId: 0cc787794ffccd92)
#6 0x55c768a8a1f8 (/home/user/scratchpad_custom_traits/target/debug/scratchpad_custom_traits+0xf61f8) (BuildId: 0cc787794ffccd92)
#7 0x55c768a898fd (/home/user/scratchpad_custom_traits/target/debug/scratchpad_custom_traits+0xf58fd) (BuildId: 0cc787794ffccd92)
previously allocated by thread T0 here:
#0 0x55c768a5d6ef (/home/user/scratchpad_custom_traits/target/debug/scratchpad_custom_traits+0xc96ef) (BuildId: 0cc787794ffccd92)
#1 0x55c768a900af (/home/user/scratchpad_custom_traits/target/debug/scratchpad_custom_traits+0xfc0af) (BuildId: 0cc787794ffccd92)
#2 0x55c768a90340 (/home/user/scratchpad_custom_traits/target/debug/scratchpad_custom_traits+0xfc340) (BuildId: 0cc787794ffccd92)
#3 0x55c768a8feea (/home/user/scratchpad_custom_traits/target/debug/scratchpad_custom_traits+0xfbeea) (BuildId: 0cc787794ffccd92)
#4 0x55c768a8aa2a (/home/user/scratchpad_custom_traits/target/debug/scratchpad_custom_traits+0xf6a2a) (BuildId: 0cc787794ffccd92)
#5 0x55c768aa6b31 (/home/user/scratchpad_custom_traits/target/debug/scratchpad_custom_traits+0x112b31) (BuildId: 0cc787794ffccd92)
#6 0x55c768a8a1f8 (/home/user/scratchpad_custom_traits/target/debug/scratchpad_custom_traits+0xf61f8) (BuildId: 0cc787794ffccd92)
#7 0x55c768a898fd (/home/user/scratchpad_custom_traits/target/debug/scratchpad_custom_traits+0xf58fd) (BuildId: 0cc787794ffccd92)
SUMMARY: AddressSanitizer: heap-buffer-overflow (/home/user/scratchpad_custom_traits/target/debug/scratchpad_custom_traits+0xc7c03) (BuildId: 0cc787794ffccd92)
Shadow bytes around the buggy address:
0x501ffffffe80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x501fffffff00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x501fffffff80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x502000000000: fa fa 00 03 fa fa 03 fa fa fa fd fd fa fa fa fa
0x502000000080: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
=>0x502000000100:[fa]fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x502000000180: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x502000000200: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x502000000280: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x502000000300: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x502000000380: 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
==2695924==ABORTING
Proposed Fix:
Mark the Tracking trait as unsafe, since the library’s internal unsafe code relies on the correctness of its implementations.
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: scratchpad-1.3.1
- OS: Ubuntu 20.04.6 LTS