Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

rust

ffi

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);
}
like image 437
Rich Avatar asked Feb 08 '19 10:02

Rich


2 Answers

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.

like image 127
Francis Gagné Avatar answered Oct 27 '22 22:10

Francis Gagné


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

like image 34
mcarton Avatar answered Oct 28 '22 00:10

mcarton