Skip to content

New lint: dangerous use of vec.set_len() #4483

Closed
@Shnatsel

Description

@Shnatsel

I commonly see people call vec.set_len() to grow a vector and then fill the uninitialized data as a slice. This is dangerous because it exposes uninitialized memory to be read and dropped on panic. See Here's My Type, So Initialize Me Maybe for more details - anything that applies to mem::uninitialized also applies to regions exposed via vec.set_len().

Here are some problematic examples:

Creating Vec to create a slice

Wrong way:

let mut v: Vec<u8> = Vec::with_capacity(32);
unsafe {v.set_len(32)}; // definitely UB for most types, possibly UB for u8
write_something_to_slice(v.as_mut_slice());

Right way:

let mut v: Vec<u8> = vec![0; 32];
write_something_to_slice(v.as_mut_slice());

Doing this for any type where not every bit pattern is valid (like bool) or for Drop types is instant UB.

For types with any valid bit pattern it's less clear-cut, but still very dangerous and has already led to security vulnerabilites in real code. It's also a common pattern to pass such slices to std::io::Read::read(), which is unsound.

Note that for u8 and friends the use of vec![0; len] is already encouraged through some lints, see #3237, but there are currently no lints for unsafe variants of the code. Using vec![0; len] and reusing the vector for subsequent operations should be as fast as using uninit, but safe.

Appending to vector while borrowing its contents

Wrong way:

let mut v: Vec<bool> = vec![false, false, true, true];
v.reserve(4);
unsafe {v.set_len(8)};
for i in 0..4 {
    v[i+4] = ! v[i];
}

Right way:

let mut v: Vec<bool> = vec![false, false, true, true];
v.reserve(4);
let v_ptr = v.as_mut_ptr();
unsafe {
    for i in 0..4 {
        std::ptr::write(v_ptr.add(i+4), ! v[i]);
    }
    v.set_len(8);
}

Here's a real-world example of such code, albeit with u8: https://github.com/image-rs/inflate/blob/1a810dba8467fc9cf1e8c60155d3266790072220/src/lib.rs#L663

This is instant UB for types where not all bit patterns are valid. This is also an exploitable security vulnerability if this is done for Drop types, or if *v_ptr = value is used instead of ptr::write(v_ptr, value)

Sadly the wrong pattern is the best available pattern for types such as u8 (at least in the stdlib). Using ptr::write is not needed for such types, and you lose the bounds checks too if you use pointers. I've opened an RFC to address this, but it's not getting much attention.

Other patterns

I'm probably missing something, so please add other problematic examples.

cc @RalfJung

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-lintArea: New lintsE-mediumCall for participation: Medium difficulty level problem and requires some initial experience.L-correctnessLint: Belongs in the correctness lint group

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions