use std::process::Command;
const MARKER: &str = "RUSTY_ALLOC_DOUBLE_FREE_CHILD";
fn child_double_free() -> ! {
let p = rusty_alloc::alloc::malloc(64);
assert!(!p.is_null(), "child: malloc failed");
unsafe { rusty_alloc::alloc::free(p) };
unsafe { rusty_alloc::alloc::free(p) };
eprintln!("child: second free RETURNED — double free was not detected");
std::process::exit(97);
}
#[cfg_attr(miri, ignore)]
#[test]
fn double_free_aborts_instead_of_corrupting() {
if std::env::var(MARKER).is_ok() {
child_double_free();
}
let exe = std::env::current_exe().expect("current_exe");
let out = Command::new(exe)
.env(MARKER, "1")
.args(["--exact", "double_free_aborts_instead_of_corrupting"])
.output()
.expect("spawn child");
assert!(
!out.status.success(),
"child exited successfully — a double free was accepted silently"
);
assert_ne!(
out.status.code(),
Some(97),
"the second free RETURNED: detection did not fire"
);
}
const ABANDON_MARKER: &str = "RUSTY_ALLOC_ABANDON_DOUBLE_FREE_CHILD";
#[cfg_attr(miri, ignore)]
#[test]
fn abandoned_double_free_aborts() {
if std::env::var(ABANDON_MARKER).is_ok() {
let addr = std::thread::spawn(|| {
let p = rusty_alloc::alloc::malloc(192);
assert!(!p.is_null());
p.expose_provenance()
})
.join()
.expect("allocator thread");
let p = core::ptr::with_exposed_provenance_mut::<u8>(addr);
unsafe {
rusty_alloc::alloc::free(p);
rusty_alloc::alloc::free(p);
}
eprintln!("child: second free of abandoned block RETURNED");
std::process::exit(97);
}
let exe = std::env::current_exe().expect("current_exe");
let out = Command::new(exe)
.env(ABANDON_MARKER, "1")
.args(["--exact", "abandoned_double_free_aborts"])
.output()
.expect("spawn child");
assert!(
!out.status.success(),
"abandoned double free was accepted silently"
);
assert_ne!(
out.status.code(),
Some(97),
"the second free of an abandoned block RETURNED"
);
}
const SET_DEFAULT_ABANDON_MARKER: &str = "RUSTY_ALLOC_SET_DEFAULT_ABANDON_DF_CHILD";
const FIRST_CLASS_ABANDON_MARKER: &str = "RUSTY_ALLOC_FIRST_CLASS_ABANDON_DF_CHILD";
#[cfg_attr(miri, ignore)]
#[test]
fn set_default_heap_abandoned_double_free_aborts() {
if std::env::var(SET_DEFAULT_ABANDON_MARKER).is_ok() {
let addr = std::thread::spawn(|| {
let h = rusty_alloc::init::create_heap(0, false, -1);
assert!(!h.is_null());
unsafe {
let _prev = rusty_alloc::init::set_default_heap(h);
let p = rusty_alloc::alloc::malloc(80);
assert!(!p.is_null());
p.expose_provenance()
}
})
.join()
.expect("set_default thread");
let p = core::ptr::with_exposed_provenance_mut::<u8>(addr);
unsafe {
rusty_alloc::alloc::free(p);
rusty_alloc::alloc::free(p);
}
eprintln!("child: second free after set_default_heap+exit RETURNED");
std::process::exit(97);
}
let exe = std::env::current_exe().expect("current_exe");
let out = Command::new(exe)
.env(SET_DEFAULT_ABANDON_MARKER, "1")
.args(["--exact", "set_default_heap_abandoned_double_free_aborts"])
.output()
.expect("spawn child");
assert!(
!out.status.success(),
"set_default abandoned double free was accepted silently"
);
assert_ne!(
out.status.code(),
Some(97),
"the second free after set_default_heap+exit RETURNED"
);
}
#[cfg_attr(miri, ignore)]
#[test]
fn first_class_heap_abandoned_double_free_aborts() {
if std::env::var(FIRST_CLASS_ABANDON_MARKER).is_ok() {
let addr = std::thread::spawn(|| {
let h = rusty_alloc::init::create_heap(0, false, -1);
assert!(!h.is_null());
let p = unsafe { rusty_alloc::alloc::heap_malloc(h, 64) };
assert!(!p.is_null());
p.expose_provenance()
})
.join()
.expect("first-class heap thread");
let p = core::ptr::with_exposed_provenance_mut::<u8>(addr);
unsafe {
rusty_alloc::alloc::free(p);
rusty_alloc::alloc::free(p);
}
eprintln!("child: second free of first-class abandoned block RETURNED");
std::process::exit(97);
}
let exe = std::env::current_exe().expect("current_exe");
let out = Command::new(exe)
.env(FIRST_CLASS_ABANDON_MARKER, "1")
.args(["--exact", "first_class_heap_abandoned_double_free_aborts"])
.output()
.expect("spawn child");
assert!(
!out.status.success(),
"first-class abandoned double free was accepted silently"
);
assert_ne!(
out.status.code(),
Some(97),
"the second free of a first-class abandoned block RETURNED"
);
}