6
votes

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);
}
2
Please use rustfmt to ensure that your code matches the style guidelineshellow
Please note, that this has nothing to do with extern "C". It segfaults nevertheless play.rust-lang.org/…hellow
How can I make the Box not be dropped? Although in the example my handle is empty, in the real code I want to return a handle which is passed back later to other functions, then freed in close.Rich
Can you give an example of how to call it? It seems like leak returns the inner T, but that can't be cast to the final void*Rich
@E4_net_or_something_like_that Box::into_raw "forgets" the Box, so the Box is not dropped when open returns.Francis Gagné

2 Answers

10
votes

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.

6
votes

The problem is you didn't cast the pointer back to a Handle pointer while transforming it back to a Box, and got a Box of the wrong type.

This works:

fn close(h: *mut c_void) {
    let h = unsafe { Box::from_raw(h as *mut Handle) };
    //                               ^^^^^^^^^^^^^^
    drop(h);
}

In your code, h is a std::boxed::Box<std::ffi::c_void>.