Repository navigation
Miri reports UB on MacOS when using fstat #1668
Description
Activity
(I don't really have a usecase for this, its just something I found and thought its worth recording at least)
Also occurs when cross-interpreting x86-64-unknown-freebsd and i686-unknown-linux-gnu with similar errors. I'm running this on macOS 26.5.2 and with tempfile 3.27.0 with the same testcase as Jefffrey.
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.04s Running unittests src/main.rs (target/miri/x86_64-unknown-freebsd/debug/build/fstat64-test/975e78630639fdcc/out/fstat64_test-975e78630639fdcc) running 1 test test tests::test123 ... error: Undefined Behavior: constructing invalid value of type libc::unix::bsd::freebsdlike::freebsd::freebsd12::stat: at .st_spare[18], encountered uninitialized memory, but expected an integer --> /Users/ryan/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rustix-1.1.4/src/backend/libc/fs/syscalls.rs:1570:20 | 1570 | let stat = stat.assume_init(); | ^^^^^^^^^^^^^^^^^^ Undefined Behavior occurred here | = 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: this is on thread `tests::test123` = note: stack backtrace: 0: rustix::backend::fs::syscalls::fstat at /Users/ryan/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rustix-1.1.4/src/backend/libc/fs/syscalls.rs:1570:20: 1570:38 1: rustix::fs::fd::fstat::<&std::fs::File> at /Users/ryan/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rustix-1.1.4/src/fs/fd.rs:157:5: 157:45 2: tempfile::file::imp::platform::reopen at /Users/ryan/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/tempfile-3.27.0/src/file/imp/unix.rs:83:20: 83:43 3: tempfile::NamedTempFile::reopen at /Users/ryan/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/tempfile-3.27.0/src/file/mod.rs:952:9: 952:63 4: tests::test123 at src/main.rs:10:9: 10:19 5: tests::test123::{closure#0} at src/main.rs:8:17: 8:17 note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace error: aborting due to 1 previous error error: test failed, to rerun pass `--bin fstat64-test` Caused by: process didn't exit successfully: `/Users/ryan/.rustup/toolchains/miri/bin/cargo-miri runner /Users/ryan/Documents/Engineering/Open-Source/miri/fstat64-test/target/miri/x86_64-unknown-freebsd/debug/build/fstat64-test/975e78630639fdcc/out/fstat64_test-975e78630639fdcc` (exit status: 1) note: test exited abnormally; to see the full output pass --no-capture to the harness.
This is ultimately an issue in
libcrather thanrustix. The proposed fix in rust-lang/libc#5497 changes the privatestatfields to usePadding, which should resolve the Undefined Behavior reported by Miri.Nope, this is unsoundness in
rustix. The full code is:unsafe { #[cfg(test)] static_assertions::assert_eq_size!(Stat, c::stat); let mut stat = MaybeUninit::<Stat>::uninit(); ret(c::fstat(borrowed_fd(fd), stat.as_mut_ptr().cast()))?; let stat = stat.assume_init(); #[cfg(apple)] let stat = fix_negative_stat_nsecs(stat); Ok(stat) }
It's making a bogus assertion that
statis fully initialized, whilefstatis absolutely no obligation to do this for things like padding fields. This is doing exactly what we say not to do at https://docs.rs/libc/latest/libc/#usage-guidelines:Never construct a libc struct with MaybeUninit::uninit(), initialize it, then call assume_init. Many structures have padding fields or may gain fields in the future, and it is far too easy to end up calling assume_init on partially initialized data.
Instead, use MaybeUninit::zeroed() or the Default implementations that are slowly being added. Alternatively, access fields only via raw pointer without ever using assume_init.
See also nix-rust/nix#2720, same class of unsoundness in
nix
Reproduction, testing via
tempfile(3.27.0):Running test:
It seems at this stat call:
rustix/src/backend/libc/fs/syscalls.rs
Lines 1568 to 1570 in 9640071
In the Miri shim they don't initialize fields
st_lspareandst_qspare:As the man page states they are reserved:
But above code assumes the struct (and all fields) are initialized