rustunsafedouble-free

Rust Uninitialized Vec Causes Double Free Error


This is a problem of potential undefined behavior of uninitialized memory in unsafe rust.

The code (playground) listed as follows:

#[deny(clippy::uninit_vec)]
unsafe fn uninitialized_vec<T>(size: usize) -> Vec<T> {
    let mut v: Vec<T> = Vec::with_capacity(size);
    unsafe { v.set_len(size) };
    v
}

fn case_1() {
    println!("=== Case 1 ===");
    let vec_a: Vec<Vec<usize>> = vec![vec![0]; 4];
    let _ = vec_a.clone();
    let _: Vec<Vec<usize>> = unsafe { uninitialized_vec(4) };
}

fn case_2() {
    println!("=== Case 2 ===");
    let vec_a: Vec<Vec<usize>> = vec![vec![0]; 4];
    {
        let _ = vec_a.clone();
    }
    let _: Vec<Vec<usize>> = unsafe { uninitialized_vec(4) };
}

fn main() {
    case_1();
    case_2();
}

From my common sense, the code itself does not intended to double free any variable. We just declared an uninitialized variable, but not using that variable. If this is not a bug, compiler optimization is probably the only reason that can explain this problem.

What I'm curious is that: whether this code actually triggers undefined behavior in unsafe rust, that compiler optimization may cause double free or other problems. And


Additionally, it is known that by using MaybeUninit properly may avoid double-free runtime error like this:

use std::mem::MaybeUninit;

unsafe fn uninitialized_vec<T>(size: usize) -> Vec<MaybeUninit<T>> {
    let mut v: Vec<MaybeUninit<T>> = Vec::with_capacity(size);
    unsafe { v.set_len(size) };
    v
}

fn main() {
    println!("=== This will not cause error ===");
    let vec_a: Vec<Vec<usize>> = vec![vec![0]; 12];
    {
        let _ = vec_a.clone();
    }
    let _: Vec<MaybeUninit<Vec<usize>>> = unsafe { uninitialized_vec(12) };
}

Still, MaybeUninit can be inconvenient in many circumstances. Subjectively, I prefer using Vec<T> instead of Vec<MaybeUninit<T>>, especially when I can make sure values of this uninitialized vector will be correctly filled later.


Solution

  • We just declared an uninitialized variable, but not using that variable

    This is incorrect. We are using this variable - namely, we are dropping the outer Vec, therefore dropping all the Vecs in it, and since they are uninitialized, we're trying to drop arbitrary memory.

    whether this code actually triggers undefined behavior in unsafe rust

    It does, according to documentation for set_len:

    Safety
    <...>
    The elements at old_len..new_len must be initialized.

    This requirement is explicitly violated.

    undefined behavior in unsafe rust, that compiler optimization may cause double free or other problems

    If you trigger undefined behavior, compilation process may do to your program anything at all, by definition. It's not "this code may be misoptimized in this way" - it's "you lied to the compiler, all bets are off".