rustmemory-managementmemory-leaks

Why using Rust raw pointers in this case leads to use-after-free?


While modifying my code to run in into parallel, I came across this pattern of solution to pass a pointer to some structure to each thread. Each thread only access its index in the vector, so they never share the random number generation structure.

This code compiles fine because I say to the borrow checker to trust me.

use rand_mt::{Mt19937GenRand64, Mt64};
const NTHREADS: usize = 2;
const SEED: u64 = 14;

fn main() {
    let mut rng = Vec::new();
    let mut rngs = Vec::new();
    for i in 0..NTHREADS {
        rng.push(Mt64::new(SEED + i as u64));
        let rng_ptr: *mut Mt19937GenRand64 = &mut rng[i] as *mut _;
        rngs.push(unsafe{&mut *rng_ptr});
    }
    let rand: f64 = rngs[0].gen();
}

But this leads to use-after-free, and I wonder why. Shouldn't the rng vector live long enough that it is not deallocated? The weirdest part is that it does not lead to use after free for NTHREADS=1.


Solution

  • First of all, this is undefined behavior regardless of what the program does whenever NTHREADS is more than 1. rng.push and rng[i] both create mutable references to the entire rng, which invalidates all existing references stored in rngs.

    Your program encounters use-after-free when run because rng is reallocated when the second element is added. Vec in the current standard library allocates space for one element on the first push, then allocates space for two elements and deallocates the old region on the second push. Trying to use the pointer to the old region is a use-after-free.

    One way to fix this is to loop over rng after all the elements have been added:

    let rngs: Vec<&mut Mt64> = rng.iter_mut().collect();