Is there use-after-free in this case

Hello all,
I fall into a situation where I still do not understand why and how it works

struct X {
    pub raw_p: *mut u8,
    pub p: &'static [u8],
}

impl X {
    fn new() -> X {
        let raw_p = unsafe { libc::malloc(10) } as *mut u8;
        X { raw_p, p: unsafe { std::slice::from_raw_parts(raw_p, 10) } }
    }
}

impl Drop for X {
    fn drop(&mut self) {
        unsafe { libc::free(self.raw_p as *mut libc::c_void) }
    }
}

fn main() {
    let p;
    {
        let vx = X::new();
        p = vx.p;
    }
   // ... vx has been dropped ...
   // ... but still can use p !?
    for i in p {
        println!("{}", i);
    }
}

The code compiles but why X::p (which is a static reference) can be assigned?
Is there a use-after-free here?

Many thanks for any response.

'static reference is the one which can be valid for the whole program execution. When you created X by calling X::new(), you guaranteed that the reference you assign in new() does really hold this property. In safe code, Rust would check this for you and won’t allow for the incorrect assignment, but as you’ve gone unsafe, Rust has no way but to trust that you know what you’re doing.

(edit to clarify)
Look at the documentation: https://doc.rust-lang.org/std/slice/fn.from_raw_parts.html

The lifetime for the returned slice is inferred from its usage.

So, as you’ve assigned return value of from_raw_parts to 'static field, Rust must assume that it lives for that lifetime - he has no other way to limit it.

1 Like

Many thanks!!! It really makes sense to me. I though that Rust would forbid the assignment:

p: unsafe { std::slice::from_raw_parts(raw_p, 10) }

since p is a static reference.

Why so? You could very well ensure that pointer is in fact pointing to static memory - for example, by taking "some literal string".as_bytes() and then casting this &'static [u8] into *const (getting *mut is a bit harder, though, but from_raw_parts takes *const anyway). If raw_p is created this way, your assignment to p would be entirely legal.

1 Like

Thanks again for the clarification.

That’s because raw_p is *mut u8 then I though when raw_p is passed into from_raw_part, the function can not “make” a static reference.

Well, there’s an entry in Rustonomicon just for this case: https://doc.rust-lang.org/nomicon/coercions.html
We’re interested in first list:

Coercion is allowed between the following types:

  • Pointer Weakening:
    • *mut T to *const T

Since from_raw_parts, as we’ve already seen, takes * const, when we pass * mut to it, this is happily coerced to * const, since we’re not gaining anything new in this way - we’re only restricting usage, being sure that the memory we point to won’t be modified by the function. This is not something unusual - as you can see, any mutable pointer or reference can be easily coerced to immutable wherever we need it.

The signature of from_raw_parts is very illustrative here

pub unsafe fn from_raw_parts<'a, T>(data: *const T, len: usize) -> &'a [T]

Lifetime 'a is present on the output, but is absent from all inputs. That means that the caller of from_raw_parts can pick any lifetime they want. In other words, if you assign the result of from_raw_parts to &'x [T], that 'a would be set to 'x.

1 Like

This thread inspires me a different question: does 'static really means “the entire program”, or rather “there is no compiler-enforced constraint on the lifetime?” In other words, is the following, modified, program legal:

extern crate libc;

mod x {

    pub struct X {
        raw_p: *mut u8,
        p: &'static [u8],
    }

    impl X {
        pub fn new() -> X {
            let raw_p = unsafe { libc::malloc(10) } as *mut u8;
            X {
                raw_p,
                p: unsafe { std::slice::from_raw_parts(raw_p, 10) },
            }
        }

        #[inline(always)]
        pub fn p(&self) -> &[u8] {
            return self.p;
        }
    }

    impl Drop for X {
        fn drop(&mut self) {
            unsafe { libc::free(self.raw_p as *mut libc::c_void) }
        }
    }

}

fn main() {
    use x::X;
    {
        let vx = X::new();
        let _p = vx.p();
    }
}

?
Note that in this program, there is still a 'static lifetime for the slice. However, the only way to access it is by using a getter that ties its lifetime to that of &self, preventing the use-after-free in the OP.
Is that legal, or is the simple fact that we have a 'static lifetime that doesn’t actually live for the whole program duration already undefined behavior?

1 Like

There’s a c_vec crate that already does this.

That’s something I only clarified for myself recently.

'static doesn’t mean that a value lives forever. A String is 'static. What I’ve been working off of is that 'static means (to safe code) that you can hold onto the value as long as you want, and it will still be valid.

Note that in unsafe code, you can lie about lifetimes, so long as you don’t lie to safe code. If you use a &'static internally but only expose a bounded lifetime that’s guaranteed to be bound shorter than the actual lifetime of the data, it’s perfectly sound.

(Disclaimer: I’m not versed in official definitions and this is colloquially speaking from experience, and may be subtly incorrect.)

2 Likes