Help with miri error

Hi.

I want to transmute &[u8] to &A where A is a DST and just lays out some fixed prefix of the original slice:

/// `T` here is for `CoerceUnsized`
struct A<T: ?Sized = [u8]> {
    prefix: [u8; 2],
    tail: T,
}

My approach was quite strait forward — inspecting the pointer data of identical *const [u8] and *const A I realized that the *const A have it's length smaller by the length of the prefix field,
so to transmute &[u8] to &A I did the same — made slice length smaller by the length of the prefix and called transmute.

Some basic assertions shows that the resulting &A does indeed represents the original &[u8], but miri complains with "Undefined Behavior: trying to retag ..." (see the full error by invoking miri on playground).

The error seem cryptic to me. Is it actually UB to transmute?

it's UB, because the two types are of different sizes. don't transmute references, you must do the conversion and calculation with raw pointers.

only convert to references for the final properly constructed pointer.

Hi. Thanks for reply but both [u8] and A are Dynamically Sized Types, so the statement "it's UB, because the two types are of different sizes" seem to be built on top of the false assumption of the types being statically sized and hense false.

Or you meant some other two types? Could you please elaborate?

UPD: dynamic size of [u8], and A values are the same as shown in the playground I linked.

sorry about the wording.

pointers in rust have provenances, not just their memory address (and the metadata for DST). when you transmute a pointer to another pointer, fat or thin, if they point to data of different size (specially if the source is smaller than the target), the provenance would be incorrect, so the result is an invalid pointer [1].

in rust, only a raw pointer might have invalid value (though dereferencing it is still UB), but in your case, you are creating an invalid safe pointer &A, which is instant UB from my understanding.

in general, transmutation between safe references should be based on the memory layout compatibility of the pointee type, e.g. #[repr(transparent)] wrappers, not based on the layout of the pointer type, especially when DSTs are involved. transmutation of safe references must also consider lifetime constraints.


  1. what is a valid pointer â†Šī¸Ž

note, for slices, you can do it in stable, since the pointer metadata is known to be usize, and the standard library has special API for it. here's how to construct the raw pointer properly:

impl A<[u8]> {
    fn try_from_slice(s: &[u8]) -> Option<&Self> {
        let ptr = s.as_ptr();
        let len = s.len();
        // handling potential paddings with `offset_of!()`
        // offset_of!() doesn't support DST field (yet?)
        let tail_offset = std::mem::offset_of!(A::<[u8; 0]>, tail);
        // alternatively, use `prefix_size` instead of `tail_offset`, should be equivalent
        // let prefix_size = std::mem::size_of::<A<[u8; 0]>>();
        if len < tail_offset {
            return None;
        }

        let fat =
            std::ptr::slice_from_raw_parts(ptr, len - tail_offset);
        // SAFETY:
        // - [u8] and [u8; 0] have the same alignment, so the offset is correct
        // - tail is [u8], which has the same metadata, so pointer cast is valid
        unsafe { Some(&*(fat as *const Self)) }
    }
}

and playground:

1 Like

Thank you! miri is happy now

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.