Closed
Description
You can use a reference cycle to leak a JoinGuard
and then the scoped thread can access freed memory:
use std::thread;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering::SeqCst;
use std::rc::Rc;
use std::cell::RefCell;
struct Evil<'a> {
link: RefCell<Option<Rc<Rc<Evil<'a>>>>>,
arm: thread::JoinGuard<'a, ()>
}
// This reliably observes an immutable reference mutating.
fn bad(g: &AtomicBool, v: &u64) {
let jg = thread::scoped(move || {
while !g.load(SeqCst) { }; // Wait for the reference to change
println!("{}", *v); // Observe it
g.store(false, SeqCst); // Safely exit without crashing
});
let e = Rc::new(Evil {
link: RefCell::new(None),
arm: jg
});
*e.link.borrow_mut() = Some(Rc::new(e.clone())); // Create a cycle
}
#[inline(never)] // Prevent DSE
fn helper(v: &mut Result<u64, &'static str>) {
let g = AtomicBool::new(false); // Used as a barrier to ensure reliable execution
if let &mut Ok(ref v) = v { bad(&g, &v) };
*v = Err("foo");
g.store(true, SeqCst);
while g.load(SeqCst) {};
}
fn main() {
helper(&mut Ok(4));
}