Some C code calls into the Rust open
call below which returns a pointer. Later the C code passes the exact same pointer back to the close
function which tries to drop (free) it. It segfaults in free(3)
. Why?
use std::os::raw::{c_int, c_void};
struct Handle;
extern "C" fn open(_readonly: c_int) -> *mut c_void {
let h = Handle;
let h = Box::new(h);
return Box::into_raw(h) as *mut c_void;
}
extern "C" fn close(h: *mut c_void) {
let h = unsafe { Box::from_raw(h) };
// XXX This segfaults - why?
drop(h);
}
In close
, you end up creating a Box<c_void>
instead of a Box<Handle>
because you didn't cast the *mut c_void
back to *mut Handle
before invoking Box::from_raw
.
fn close(h: *mut c_void) {
let h = unsafe { Box::from_raw(h as *mut Handle) };
// ^^^^^^^^^^^^^^
drop(h);
}
By the way, Box
doesn't actually allocate any memory for a zero-sized type (such as Handle
here) and uses a fixed, non-zero pointer value instead (which, in the current implementation, is the alignment of the type; a zero-sized type has an alignment of 1 by default). The destructor for a boxed zero-sized type knows not to try to deallocate memory at this fictitious memory address, but c_void
is not a zero-sized type (it has size 1), so the destructor for Box<c_void>
tries to free memory at address 0x1
, which causes the segfault.
If Handle
wasn't zero-sized, then the code may not crash, but it would still run the wrong destructor (it'd run c_void
's destructor, which does nothing), and this may cause memory leaks. A destructor runs Drop::drop
for the type if present, then drops the type's fields.