Description
Hello,
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
AtomicBucket<T>
automatically implements Send/Sync unconditionally, due to the fact that Block<T>
unconditionally implements Send/Sync.
// https://docs.rs/metrics-util/0.6.2/src/metrics_util/bucket.rs.html#99-100
unsafe impl<T> Send for Block<T> {}
unsafe impl<T> Sync for Block<T> {}
Unconditional impl of Sync
allows users to create data race to T: !Sync
by using the AtomicBucket::data_with
API to concurrently access &T
from multiple threads.
Unconditional impl of Send
allows users to send T: !Send
to another thread by pushing T
to AtomicBucket<T>
and then moving it to another thread.
This issue exists in older versions too (v0.4.0-alpha.1),
but I used the newer version of the crate (v0.6.2) in the poc below,
as the older version fails to build on my pc at the moment..
Reproduction
Below is an example program that exhibits undefined behavior using safe APIs of metrics-util
.
Show Detail
#![forbid(unsafe_code)]
use metrics_util::AtomicBucket;
use std::cell::Cell;
use std::sync::Arc;
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
enum RefOrInt {
Ref(&'static u64),
Int(u64),
}
// The bug triggered in this poc exists in older versions too (v0.4.0-alpha.1),
// but I used the newer version of the crate as the older version fails to build
// on my pc at the time..
static SOME_INT: u64 = 123;
fn main() {
let cell = Cell::new(RefOrInt::Ref(&SOME_INT));
let bucket = Arc::new(AtomicBucket::new());
bucket.push(cell);
let bucket_clone = bucket.clone();
std::thread::spawn(move || {
let bucket = bucket_clone;
// Repeatedly write Ref(&addr) and Int(0xdeadbeef) into the cell.
bucket.data_with(|arr| {
for cell in arr.iter() {
loop {
cell.set(RefOrInt::Ref(&SOME_INT));
cell.set(RefOrInt::Int(0xdeadbeef));
}
}
});
});
bucket.data_with(|arr| {
for cell in arr.iter() {
loop {
if let RefOrInt::Ref(addr) = cell.get() {
if addr as *const u64 == &SOME_INT as *const u64 {
continue;
}
println!("Pointer is now {:p}", addr);
println!("Dereferencing addr will now segfault: {}", *addr);
}
}
}
});
}
Output:
Pointer is now 0xdeadbeef
Terminated with signal 11 (SIGSEGV)
Tested Environment
- Crate: metrics-util
- Version: 0.6.2
- OS: Ubuntu 18.04.5 LTS
- Rustc version: rustc 1.50.0 (cb75ad5db 2021-02-10)