Search code examples
rustffi

Why does a Box pointer passed to C and back to Rust segfault?


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);
}

Solution

  • 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.