Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rust invalid pointer with Box::from_raw() Box::into_raw() round trip

Tags:

memory

rust

ffi

I'm banging my head against this supposedly simple usage of Box whilst trying to create some FFI helper code.

The sample here seems to give an error of free(): invalid pointer when used with a struct that has a field.

pub struct Handle(usize);

impl Handle {
    pub fn from<T>(obj: T) -> Self {
        let boxed = Box::new(obj);
        let mut ptr = Box::into_raw(boxed);
        Self::from_ptr_mut(&mut ptr)
    }

    pub fn from_ptr_mut<T>(ptr: &mut T) -> Self {
        Self(ptr as *mut T as usize)
    }

    pub fn to_box<T>(self) -> Box<T> {
        let obj: *mut T = self.to_ptr_mut();
        unsafe { Box::from_raw(obj) }
    }

    pub fn to_ptr_mut<T>(self) -> *mut T {
        self.0 as *mut T
    }
}

#[allow(dead_code)]
struct Crashes { value: u64 }

impl Drop for Crashes {
    fn drop(&mut self) {
        println!("Crashes dropped");
    }
}

fn crashes() {
    let t = Crashes { value: 12 };
    let a = Handle::from(t);
    let b = a.to_box::<Crashes>();
    drop(b);
}

struct Works;

impl Drop for Works {
    fn drop(&mut self) {
        println!("Works dropped");
    }
}

fn works() {
    let t = Works;
    let a = Handle::from(t);
    let b = a.to_box::<Works>();
    drop(b);
}

fn main() {
    works();
    crashes();
}

You can paste this into https://play.rust-lang.org/ and see how it throws aborts with the error free(): invalid pointer

The drop function seems to be called at the appropriate time, but the pointer seems to be somehow invalid

like image 398
Pruthvikar Avatar asked Jan 26 '23 13:01

Pruthvikar


1 Answers

You end up creating a double pointer here:

impl Handle {
    pub fn from<T>(obj: T) -> Self {
        let boxed = Box::new(obj);
        let mut ptr = Box::into_raw(boxed);
        Self::from_ptr_mut(&mut ptr)
    }

    pub fn from_ptr_mut<T>(ptr: &mut T) -> Self {
        Self(ptr as *mut T as usize)
    }
    ...
}

Box::into_raw returns a pointer, but then you take a mutable reference to that pointer, and store that address as a usize. You should just be using the *mut T as returned by Box::into_raw.

The reason that the non-working code with the double pointer compiles is that your from<T> and your from_ptr_mut<T> can take entirely different T parameters. If we consider the type T passed to from<T> to be a concrete type, then in this case you're calling from_ptr_mut<U> (where U is *mut T) with an argument of type &mut *mut T.

It should look like so:

impl Handle {
    pub fn from<T>(obj: T) -> Self {
        let boxed = Box::new(obj);
        let ptr = Box::into_raw(boxed);
        Self::from_ptr_mut(ptr)
    }

    pub fn from_ptr_mut<T>(ptr: *mut T) -> Self {
        Self(ptr as usize)
    }
    ...
}

Working example in the playground.


Even though we are in the realm of unsafe you can have the compiler do some of the work for you by making the parameter T be bound to your Handle struct. This way you will be statically prevented from loading a different type than was stored.

Playground example where Handle includes a PhantomData.

In this second example you don't have to tell the compiler which item you're retrieving a la a.to_box::<Crashes>(), which is good because you can't introduce undefined behavior by specifying the wrong type.

like image 75
turbulencetoo Avatar answered Jan 29 '23 10:01

turbulencetoo