From 067a11362a17d11097bb8fe0e9caadb5bee185ad Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Sun, 7 May 2023 06:17:48 -0700 Subject: [PATCH] Fix the fstat/statat/etc. fallback when statx fails with EPERM. On some old non-y2038-safe container runtimes, `statx` fails with `EPERM`, so use `crate::fs::statx` instead of calling the statx syscall directly. This includes code to check whether it was a "real" `EPERM` we should return to the user, or an `EPERM` which indicates that statx isn't supported, allowing rustix to fall back to the old non-y2038-safe syscalls. Also, update the crate-level comment mentionintg that rustix does sometimes do dynamic feature detection in order to support y2038 and LFS. And fix a copy and paste in a comment in `renameat2`. --- src/backend/libc/fs/syscalls.rs | 13 +++++++----- src/backend/linux_raw/fs/syscalls.rs | 25 ++++++++++++++++------- src/backend/linux_raw/runtime/syscalls.rs | 3 +++ src/backend/linux_raw/time/syscalls.rs | 2 ++ src/fs/at.rs | 7 +++++++ src/lib.rs | 3 ++- 6 files changed, 40 insertions(+), 13 deletions(-) diff --git a/src/backend/libc/fs/syscalls.rs b/src/backend/libc/fs/syscalls.rs index 0202129ad..53f0f8d17 100644 --- a/src/backend/libc/fs/syscalls.rs +++ b/src/backend/libc/fs/syscalls.rs @@ -385,7 +385,7 @@ pub(crate) fn renameat2( new_path: &CStr, flags: RenameFlags, ) -> io::Result<()> { - // `getrandom` wasn't supported in glibc until 2.28. + // `renameat2` wasn't supported in glibc until 2.28. weak_or_syscall! { fn renameat2( olddirfd: c::c_int, @@ -442,14 +442,13 @@ pub(crate) fn symlinkat( #[cfg(not(target_os = "redox"))] pub(crate) fn statat(dirfd: BorrowedFd<'_>, path: &CStr, flags: AtFlags) -> io::Result { - // 32-bit and mips64 Linux: `struct stat64` is not y2038 compatible; use - // `statx`. + // See the comments in `fstat` about using `crate::fs::statx` here. #[cfg(all( any(target_os = "android", target_os = "linux"), any(target_pointer_width = "32", target_arch = "mips64"), ))] { - match statx(dirfd, path, flags, StatxFlags::BASIC_STATS) { + match crate::fs::statx(dirfd, path, flags, StatxFlags::BASIC_STATS) { Ok(x) => statx_to_stat(x), Err(io::Errno::NOSYS) => statat_old(dirfd, path, flags), Err(err) => Err(err), @@ -1108,12 +1107,16 @@ pub(crate) fn sync() { pub(crate) fn fstat(fd: BorrowedFd<'_>) -> io::Result { // 32-bit and mips64 Linux: `struct stat64` is not y2038 compatible; use // `statx`. + // + // And, some old platforms don't support `statx`, and some fail with a + // confusing error code, so we call `crate::fs::statx` to handle that. If + // `statx` isn't available, fall back to the buggy system call. #[cfg(all( any(target_os = "android", target_os = "linux"), any(target_pointer_width = "32", target_arch = "mips64"), ))] { - match statx(fd, cstr!(""), AtFlags::EMPTY_PATH, StatxFlags::BASIC_STATS) { + match crate::fs::statx(fd, cstr!(""), AtFlags::EMPTY_PATH, StatxFlags::BASIC_STATS) { Ok(x) => statx_to_stat(x), Err(io::Errno::NOSYS) => fstat_old(fd), Err(err) => Err(err), diff --git a/src/backend/linux_raw/fs/syscalls.rs b/src/backend/linux_raw/fs/syscalls.rs index 146c2dbf5..1593b22c0 100644 --- a/src/backend/linux_raw/fs/syscalls.rs +++ b/src/backend/linux_raw/fs/syscalls.rs @@ -442,9 +442,15 @@ pub(crate) fn sync() { #[inline] pub(crate) fn fstat(fd: BorrowedFd<'_>) -> io::Result { + // 32-bit and mips64 Linux: `struct stat64` is not y2038 compatible; use + // `statx`. + // + // And, some old platforms don't support `statx`, and some fail with a + // confusing error code, so we call `crate::fs::statx` to handle that. If + // `statx` isn't available, fall back to the buggy system call. #[cfg(any(target_pointer_width = "32", target_arch = "mips64"))] { - match statx(fd, cstr!(""), AtFlags::EMPTY_PATH, StatxFlags::BASIC_STATS) { + match crate::fs::statx(fd, cstr!(""), AtFlags::EMPTY_PATH, StatxFlags::BASIC_STATS) { Ok(x) => statx_to_stat(x), Err(io::Errno::NOSYS) => fstat_old(fd), Err(err) => Err(err), @@ -478,9 +484,10 @@ fn fstat_old(fd: BorrowedFd<'_>) -> io::Result { #[inline] pub(crate) fn stat(filename: &CStr) -> io::Result { + // See the comments in `fstat` about using `crate::fs::statx` here. #[cfg(any(target_pointer_width = "32", target_arch = "mips64"))] { - match statx( + match crate::fs::statx( crate::fs::cwd().as_fd(), filename, AtFlags::empty(), @@ -537,9 +544,10 @@ fn stat_old(filename: &CStr) -> io::Result { #[inline] pub(crate) fn statat(dirfd: BorrowedFd<'_>, filename: &CStr, flags: AtFlags) -> io::Result { + // See the comments in `fstat` about using `crate::fs::statx` here. #[cfg(any(target_pointer_width = "32", target_arch = "mips64"))] { - match statx(dirfd, filename, flags, StatxFlags::BASIC_STATS) { + match crate::fs::statx(dirfd, filename, flags, StatxFlags::BASIC_STATS) { Ok(x) => statx_to_stat(x), Err(io::Errno::NOSYS) => statat_old(dirfd, filename, flags), Err(err) => Err(err), @@ -591,9 +599,10 @@ fn statat_old(dirfd: BorrowedFd<'_>, filename: &CStr, flags: AtFlags) -> io::Res #[inline] pub(crate) fn lstat(filename: &CStr) -> io::Result { + // See the comments in `fstat` about using `crate::fs::statx` here. #[cfg(any(target_pointer_width = "32", target_arch = "mips64"))] { - match statx( + match crate::fs::statx( crate::fs::cwd().as_fd(), filename, AtFlags::SYMLINK_NOFOLLOW, @@ -1310,6 +1319,8 @@ fn _utimensat( // Assert that `Timestamps` has the expected layout. let _ = unsafe { transmute::(times.clone()) }; + // `utimensat_time64` was introduced in Linux 5.1. The old `utimensat` + // syscall is not y2038-compatible on 32-bit architectures. #[cfg(target_pointer_width = "32")] unsafe { match ret(syscall_readonly!( @@ -1400,9 +1411,9 @@ pub(crate) fn accessat( } // Linux's `faccessat` syscall doesn't have a flags argument, so if we have - // any flags, use the newer `faccessat2` which does. Unless we're on - // Android where using newer system calls can cause seccomp to abort the - // process. + // any flags, use the newer `faccessat2` introduced in Linux 5.8 which + // does. Unless we're on Android where using newer system calls can cause + // seccomp to abort the process. #[cfg(not(target_os = "android"))] if !flags.is_empty() { unsafe { diff --git a/src/backend/linux_raw/runtime/syscalls.rs b/src/backend/linux_raw/runtime/syscalls.rs index abd59720f..8f92085ef 100644 --- a/src/backend/linux_raw/runtime/syscalls.rs +++ b/src/backend/linux_raw/runtime/syscalls.rs @@ -193,6 +193,9 @@ pub(crate) fn sigtimedwait(set: &Sigset, timeout: Option) -> io::Resul let mut info = MaybeUninit::::uninit(); let timeout_ptr = optional_as_ptr(timeout.as_ref()); + // `rt_sigtimedwait_time64` was introduced in Linux 5.1. The old + // `rt_sigtimedwait` syscall is not y2038-compatible on 32-bit + // architectures. #[cfg(target_pointer_width = "32")] unsafe { match ret_c_int(syscall!( diff --git a/src/backend/linux_raw/time/syscalls.rs b/src/backend/linux_raw/time/syscalls.rs index 09a6dc37a..98b5dcc2c 100644 --- a/src/backend/linux_raw/time/syscalls.rs +++ b/src/backend/linux_raw/time/syscalls.rs @@ -63,6 +63,8 @@ unsafe fn clock_getres_old(which_clock: ClockId, result: &mut MaybeUninit<__kern #[cfg(feature = "time")] #[inline] pub(crate) fn clock_settime(which_clock: ClockId, timespec: __kernel_timespec) -> io::Result<()> { + // `clock_settime64` was introduced in Linux 5.1. The old `clock_settime` + // syscall is not y2038-compatible on 32-bit architectures. #[cfg(target_pointer_width = "32")] unsafe { match ret(syscall_readonly!( diff --git a/src/fs/at.rs b/src/fs/at.rs index f6e83127d..dfc7b6532 100644 --- a/src/fs/at.rs +++ b/src/fs/at.rs @@ -263,6 +263,13 @@ pub fn statat(dirfd: Fd, path: P, flags: AtFlags) -> io: /// `faccessat(dirfd, path, access, flags)`—Tests permissions for a file or /// directory. /// +/// On Linux before 5.8, this function uses the `faccessat` system call which +/// doesn't support any flags. This function emulates support for the +/// [`AtFlags::EACCESS`] flag by checking whether the uid and gid of the +/// process match the effective uid and gid, in which case the `EACCESS` flag +/// can be ignored. In Linux 5.8 and beyond `faccessat2` is used, which +/// supports flags. +/// /// # References /// - [POSIX] /// - [Linux] diff --git a/src/lib.rs b/src/lib.rs index f0e093585..f56b5d878 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -71,7 +71,8 @@ //! running under seccomp. //! //! Things they don't do include: -//! - Detecting whether functions are supported at runtime. +//! - Detecting whether functions are supported at runtime, except in specific +//! cases where new interfaces need to be detected to support y2038 and LFS. //! - Hiding significant differences between platforms. //! - Restricting ambient authorities. //! - Imposing sandboxing features such as filesystem path or network address