Segfault when freeing memory allocated to trait objects through the Rust FFI

Hi everyone,

I am teaching myself about the Rust FFI and have created a small example library to better understand how to allocate and free memory associated with trait objects. The allocation and freeing of the memory occurs through a C-API. The problem is that running the test program that uses this library results in a segmentation fault.

I can isolate the problem to three lines of code, but I do not know why they result in a segmentation fault. I have also found that the problematic lines of code actually work when they are part of the binary program’s code and not part of the library. Could anyone please explain these observations to me?

Thanks!
-kmd

Steps to reproduce

  1. Create a new library crate with the command cargo new --lib ffi-test
  2. Change into the ffi-test directory and replace the contents of the file src/lib.rs with the following:

lib.rs

use std::boxed::Box;
use std::os::raw::c_void;

struct Foo { }

pub trait Bar {
    fn hello(&self) {
        println!("Hello world!");
    }
}

impl Bar for Foo { }

#[no_mangle]
pub extern "C" fn new() -> *mut c_void {
    let foo = Foo { };
    let bar = Box::new(foo) as Box<dyn Bar>;
    let bar = Box::new(bar);
    Box::into_raw(bar) as *mut c_void
}

#[no_mangle]
pub extern "C" fn free(bar: *mut c_void) {
    if bar.is_null() {
        return;
    }
    let bar = bar as *mut Box<dyn Bar>;
    unsafe{
        Box::from_raw(bar);
    }
}

  1. Create the file src/bin/main.rs with the following contents:

main.rs

use::ffi_test::{free, new};

fn main() {
    let c_pointer = new();
    free(c_pointer);
}
  1. Run the program with cargo run

Results

When I run the program the message Segmentation fault (core dumped) is printed to my terminal window.

If I comment out the following lines inside the free(bar: *mut c_void) function, then no segmentation fault occurs:

//unsafe{
//    Box::from_raw(bar);
//}

Furthermore, if I keep these lines commented out and use unsafe{ Box::from_raw(...) } from inside main.rs, then the program actually works. For example, if I change main.rs to look like this:

use::ffi_test::{Bar, free, new};

fn main() {
    let c_pointer = new();
    let pointer = c_pointer as *mut Box<dyn Bar>;
    unsafe {
        let bar = Box::from_raw(pointer);
        bar.hello();
    }
}

Then I see `Hello world!" printed to the screen.

Additional info

Note that inside the new() method I am creating a Box to the trait object Box<dyn Bar> and then converting it to a raw pointer. It is the pointer to the trait object that is passed through the API.

$ rustc --version
rustc 1.32.0 (9fda7c223 2019-01-16)

$ uname -a
Linux xxx 4.15.0-48-generic #51~16.04.1-Ubuntu SMP Fri Apr 5 12:01:12 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux

I’m still playing around with your code but it looks like you could be overwriting free (the one from the lib C) by doing #[no_mangle] extern "C" fn free()

EDIT: yep, renaming your free to bar_free fixed it for me.
The version where you don’t call free(ptr) in the main probably doesn’t bring the symbol in the result binary

1 Like

Ah whoops :crazy_face:

Thanks @ldesgoui . It looks like this was indeed the problem.