Is this type of raw pointer casting completely sound?

I'm wondering if casting *mut One -> *mut Two and then mutating through received pointer is sound.

Code (playground):

fn main() {
    let mut one = One {_f1: false, _f2: 0};
    let ptr: *mut Two = &mut one as *mut One as *mut Two;

    unsafe {
        ptr.write(Two {_f1: true, _f2: 1});
    }

    println!("{:?}", one);
}

#[derive(Debug)]
struct One {
    _f1: bool,
    _f2: u32
}

#[derive(Debug)]
struct Two {
    _f1: bool,
    _f2: u32
}

Intuitively it should be sound, because both structs have the same l̶a̶y̶o̶u̶t̶ ̶a̶n̶d size. Also, miri doesn't seem to complain. AFAIK, provenance also shouldn't be affected by a type change. Is there anything that I'm missing?

I do not recall that being a guarantee provided by the Rust compiler. Do you have a reference?

1 Like

You're right. I didn't mark my structs as repr(C) or repr(transparent) in my sample code, so let's remove the incorrect assumption the layout is the same for both.

So how do you know if the layouts are the same? For repr(C) types and repr(transparent) types, layout is precisely defined. But for your run-of-the-mill repr(Rust), it is not. Even different instances of the same generic type can have wildly different layout.

Source - The Rustonomicon

Just to be sure, run it a few times with randomized layout. Note that Miri doesn't have false positives, but it can have false negatives.

I don't think this is sound. Altough Rust defines bool as u8 and 0/1 values (which is more specific than in C), this is still not enough. The compiler reserves the right to reorder fields, and you're assuming that bool with padding will match layout of u32, but compiler could put padding either before or after the bool, and the 1 byte of u32 will be in a different place depending on endian.

2 Likes

(Same thing as what @kornel just said with more words.)


The potentially different layouts means it's unsound:

  • If they have different sizes or alignments or padding locations, say
  • If you end up writing a non-boolean value to the bool field byte (consider platform endianness too)
  • If you end up writing an uninitialized padding byte to any part of the u32 field

Even if you add some asserts about the size and alignment (which should compile out if your assumptions hold), the example (wherein the u32 only has 0 or 1 bytes regardless of endianness) is still unsound because these are all valid layouts...

bool [pad] [pad] [pad] four-u32-bytes
[pad] bool [pad] [pad] four-u32-bytes
[pad] [pad] bool [pad] four-u32-bytes
[pad] [pad] [pad] bool four-u32-bytes

// Same thing but with u32 on the front

...and so you could write a padding byte with an uninitialized value into one of the field bytes in the other struct.

2 Likes

By manually randomizing the layout:


#[derive(Debug)]
#[repr(C)]
struct One {
    _f2: u32,
    _f1: bool,
}

#[derive(Debug)]
#[repr(C)]
struct Two {
    _f1: bool,
    _pad: [MaybeUninit<u8>; 3],
    _f2: u32,
}

I get MIRI to complain about it:

error: Undefined Behavior: using uninitialized data, but this operation requires initialized memory
   --> /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/fmt/num.rs:466:5
    |
466 | /     impl_Display!(
467 | |         i8, u8, i16, u16, i32, u32, i64, u64, usize, isize
468 | |             as u64 via to_u64 named fmt_u64
469 | |     );
    | |_____^ using uninitialized data, but this operation requires initialized memory
    |
    = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
    = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
    = note: BACKTRACE:
    = note: inside `core::fmt::num::imp::<impl std::fmt::Display for u32>::fmt` at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/fmt/num.rs:284:38: 284:43
    = note: inside `core::fmt::num::<impl std::fmt::Debug for u32>::fmt` at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/fmt/num.rs:195:21: 195:47
    = note: inside closure at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/fmt/builders.rs:150:17: 150:36
    = note: inside `std::result::Result::<(), std::fmt::Error>::and_then::<(), {closure@std::fmt::DebugStruct<'_, '_>::field::{closure#0}}>` at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/result.rs:1320:22: 1320:27
    = note: inside `std::fmt::DebugStruct::<'_, '_>::field` at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/fmt/builders.rs:133:23: 152:11
    = note: inside `std::fmt::Formatter::<'_>::debug_struct_field2_finish` at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/fmt/mod.rs:1926:9: 1926:37
note: inside `<One as std::fmt::Debug>::fmt`
   --> src/main.rs:13:10
    |
13  | #[derive(Debug)]
    |          ^^^^^
    = note: inside `core::fmt::rt::Argument::<'_>::fmt` at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/fmt/rt.rs:142:9: 142:40
    = note: inside `std::fmt::write` at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/fmt/mod.rs:1117:17: 1117:40
    = note: inside `<std::io::StdoutLock<'_> as std::io::Write>::write_fmt` at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/io/mod.rs:1762:15: 1762:43
    = note: inside `<&std::io::Stdout as std::io::Write>::write_fmt` at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/io/stdio.rs:727:9: 727:36
    = note: inside `<std::io::Stdout as std::io::Write>::write_fmt` at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/io/stdio.rs:701:9: 701:33
    = note: inside `std::io::stdio::print_to::<std::io::Stdout>` at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/io/stdio.rs:1020:21: 1020:47
    = note: inside `std::io::_print` at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/io/stdio.rs:1097:5: 1097:37
note: inside `main`
   --> src/main.rs:10:5
    |
10  |     println!("{:?}", one);
    |     ^^^^^^^^^^^^^^^^^^^^^
    = note: this error originates in the macro `impl_Display` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)

https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=fd064b4ff773571a0251f92d019b2ef1

3 Likes

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.