diff --git a/src/backend/libc/fs/syscalls.rs b/src/backend/libc/fs/syscalls.rs index 662804bb0..a1e962174 100644 --- a/src/backend/libc/fs/syscalls.rs +++ b/src/backend/libc/fs/syscalls.rs @@ -1456,6 +1456,28 @@ pub(crate) fn statx( flags: AtFlags, mask: StatxFlags, ) -> io::Result { + // If a future Linux kernel adds more fields to `struct statx` and users + // passing flags unknown to rustix in `StatxFlags`, we could end up + // writing outside of the buffer. To prevent this possibility, we mask off + // any flags that we don't know about. + // + // This includes `STATX__RESERVED`, which has a value that we know, but + // which could take on arbitrary new meaning in the future. Linux currently + // rejects this flag with `EINVAL`, so we do the same. + // + // This doesn't rely on `STATX_ALL` because [it's deprecated] and already + // doesn't represent all the known flags. + // + // [it's deprecated]: https://patchwork.kernel.org/project/linux-fsdevel/patch/20200505095915.11275-7-mszeredi@redhat.com/ + #[cfg(not(any(target_os = "android", target_env = "musl")))] + const STATX__RESERVED: u32 = libc::STATX__RESERVED as u32; + #[cfg(any(target_os = "android", target_env = "musl"))] + const STATX__RESERVED: u32 = linux_raw_sys::general::STATX__RESERVED; + if (mask.bits() & STATX__RESERVED) == STATX__RESERVED { + return Err(io::Errno::INVAL); + } + let mask = mask & StatxFlags::all(); + let mut statx_buf = MaybeUninit::::uninit(); unsafe { ret(sys::statx( diff --git a/src/backend/linux_raw/fs/syscalls.rs b/src/backend/linux_raw/fs/syscalls.rs index f5b455dff..4cf0dcebb 100644 --- a/src/backend/linux_raw/fs/syscalls.rs +++ b/src/backend/linux_raw/fs/syscalls.rs @@ -38,7 +38,7 @@ use linux_raw_sys::general::{ __kernel_fsid_t, __kernel_timespec, open_how, statx, AT_EACCESS, AT_FDCWD, AT_REMOVEDIR, AT_SYMLINK_NOFOLLOW, F_ADD_SEALS, F_DUPFD, F_DUPFD_CLOEXEC, F_GETFD, F_GETFL, F_GETLEASE, F_GETOWN, F_GETPIPE_SZ, F_GETSIG, F_GET_SEALS, F_SETFD, F_SETFL, F_SETPIPE_SZ, SEEK_CUR, - SEEK_END, SEEK_SET, + SEEK_END, SEEK_SET, STATX__RESERVED, }; #[cfg(target_pointer_width = "32")] use { @@ -721,6 +721,24 @@ pub(crate) fn statx( flags: AtFlags, mask: StatxFlags, ) -> io::Result { + // If a future Linux kernel adds more fields to `struct statx` and users + // passing flags unknown to rustix in `StatxFlags`, we could end up + // writing outside of the buffer. To prevent this possibility, we mask off + // any flags that we don't know about. + // + // This includes `STATX__RESERVED`, which has a value that we know, but + // which could take on arbitrary new meaning in the future. Linux currently + // rejects this flag with `EINVAL`, so we do the same. + // + // This doesn't rely on `STATX_ALL` because [it's deprecated] and already + // doesn't represent all the known flags. + // + // [it's deprecated]: https://patchwork.kernel.org/project/linux-fsdevel/patch/20200505095915.11275-7-mszeredi@redhat.com/ + if (mask.bits() & STATX__RESERVED) == STATX__RESERVED { + return Err(io::Errno::INVAL); + } + let mask = mask & StatxFlags::all(); + unsafe { let mut statx_buf = MaybeUninit::::uninit(); ret(syscall!( diff --git a/tests/fs/main.rs b/tests/fs/main.rs index 279a517e4..0ef54eb53 100644 --- a/tests/fs/main.rs +++ b/tests/fs/main.rs @@ -37,5 +37,7 @@ mod readdir; mod renameat; #[cfg(not(any(target_os = "illumos", target_os = "redox", target_os = "wasi")))] mod statfs; +#[cfg(any(target_os = "android", target_os = "linux"))] +mod statx; mod utimensat; mod y2038; diff --git a/tests/fs/statx.rs b/tests/fs/statx.rs new file mode 100644 index 000000000..d02e9cf72 --- /dev/null +++ b/tests/fs/statx.rs @@ -0,0 +1,37 @@ +#[test] +fn test_statx_unknown_flags() { + use rustix::fs::{AtFlags, StatxFlags}; + + let f = std::fs::File::open(".").unwrap(); + + // It's ok (though still unwise) to construct flags values that have + // unknown bits. Exclude `STATX__RESERVED` here as that evokes an explicit + // failure; that's tested separately below. + let too_many_flags = + unsafe { StatxFlags::from_bits_unchecked(!0 & !linux_raw_sys::general::STATX__RESERVED) }; + + // It's also ok to pass such flags to `statx`. + let result = rustix::fs::statx(&f, "Cargo.toml", AtFlags::empty(), too_many_flags).unwrap(); + + // But, rustix should mask off bits it doesn't recognize, because these + // extra flags may tell future kernels to set extra fields beyond the + // extend of rustix's statx buffer. So make sure we didn't get extra + // fields. + assert_eq!(result.stx_mask & !StatxFlags::all().bits(), 0); +} + +#[test] +fn test_statx_reserved() { + use rustix::fs::{AtFlags, StatxFlags}; + + let f = std::fs::File::open(".").unwrap(); + + // It's ok (though still unwise) to construct a `STATX__RESERVED` flag + // value but `statx` should reliably fail with `INVAL`. + let reserved = + unsafe { StatxFlags::from_bits_unchecked(linux_raw_sys::general::STATX__RESERVED) }; + match rustix::fs::statx(&f, "Cargo.toml", AtFlags::empty(), reserved) { + Ok(_) => panic!("statx succeeded with `STATX__RESERVED`"), + Err(err) => assert_eq!(err, rustix::io::Errno::INVAL), + } +}