Rust-clippy: New lint: transmute of Vec<T> into Vec<U> with different size or alignment is UB

Created on 6 Sep 2019  路  1Comment  路  Source: rust-lang/rust-clippy

The following snippet is undefined behavior and should crash on Windows:

fn main() {
    let u32_data: Vec<u32> = vec![0, 1];
    let u8_data: Vec<u8> = unsafe { std::mem::transmute(data) }; 
}

Excerpt from the docs:

ptr's T needs to have the same size and alignment as it was allocated with.

So unlike slices you cannot transmute a type with a greater size or alignment into a type with lesser size or alignment. Even though the above snippet goes from Vec<[u32]> to Vec<[u8]>, it is UB because sizes are different. Even going from Vec<[u32]> to Vec<[u8; 4]> is UB because the alignment of the latter is different.

This is also true when casting a pointer and then feeding it into Vec::from_raw_parts() as described in #4484. I.e. this issue is not specific to transmute(), any attempt to change size and/or alignment of the allocation is UB.

This also applies to other collections that own their allocations such as VecDeque, HashMap/HashSet, BTreeMap/BTreeSet, etc. although I have not seen any transmutes of these in practice.

In the particular case of Vec you can use .as_slice() and work with that. Once you have the slice this can be accomplished using .align_to() or via a pointer cast as described in transmute_ptr_to_ptr lint.

However, there is no way to transmute the collection itself without copying, and there never will be. You must copy the data in this case.

Most helpful comment

To make this issue even more serious, this can also lead to an exploitable buffer overflow:

fn main() {
    let data: Vec<u8> = vec![1,2,3,4];
    let optimal_data: Vec<u32> = unsafe { std::mem::transmute(data) };
    println!("{:?}", optimal_data); // prints one stable number and then 3 junk numbers
}

The vector is expected to have a backing allocation of at least len * size_of(T), which is violated by increasing size_of(T). This failure case could be addressed by implementing either this lint or #4484.

>All comments

To make this issue even more serious, this can also lead to an exploitable buffer overflow:

fn main() {
    let data: Vec<u8> = vec![1,2,3,4];
    let optimal_data: Vec<u32> = unsafe { std::mem::transmute(data) };
    println!("{:?}", optimal_data); // prints one stable number and then 3 junk numbers
}

The vector is expected to have a backing allocation of at least len * size_of(T), which is violated by increasing size_of(T). This failure case could be addressed by implementing either this lint or #4484.

Was this page helpful?
0 / 5 - 0 ratings