Skip to content

Commit

Permalink
Fix the fstat/statat/etc. fallback when statx fails with EPERM.
Browse files Browse the repository at this point in the history
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`.
  • Loading branch information
sunfishcode committed May 8, 2023
1 parent 43025d8 commit 067a113
Show file tree
Hide file tree
Showing 6 changed files with 40 additions and 13 deletions.
13 changes: 8 additions & 5 deletions src/backend/libc/fs/syscalls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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<Stat> {
// 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),
Expand Down Expand Up @@ -1108,12 +1107,16 @@ pub(crate) fn sync() {
pub(crate) fn fstat(fd: BorrowedFd<'_>) -> io::Result<Stat> {
// 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),
Expand Down
25 changes: 18 additions & 7 deletions src/backend/linux_raw/fs/syscalls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -442,9 +442,15 @@ pub(crate) fn sync() {

#[inline]
pub(crate) fn fstat(fd: BorrowedFd<'_>) -> io::Result<Stat> {
// 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),
Expand Down Expand Up @@ -478,9 +484,10 @@ fn fstat_old(fd: BorrowedFd<'_>) -> io::Result<Stat> {

#[inline]
pub(crate) fn stat(filename: &CStr) -> io::Result<Stat> {
// 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(),
Expand Down Expand Up @@ -537,9 +544,10 @@ fn stat_old(filename: &CStr) -> io::Result<Stat> {

#[inline]
pub(crate) fn statat(dirfd: BorrowedFd<'_>, filename: &CStr, flags: AtFlags) -> io::Result<Stat> {
// 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),
Expand Down Expand Up @@ -591,9 +599,10 @@ fn statat_old(dirfd: BorrowedFd<'_>, filename: &CStr, flags: AtFlags) -> io::Res

#[inline]
pub(crate) fn lstat(filename: &CStr) -> io::Result<Stat> {
// 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,
Expand Down Expand Up @@ -1310,6 +1319,8 @@ fn _utimensat(
// Assert that `Timestamps` has the expected layout.
let _ = unsafe { transmute::<Timestamps, [__kernel_timespec; 2]>(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!(
Expand Down Expand Up @@ -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 {
Expand Down
3 changes: 3 additions & 0 deletions src/backend/linux_raw/runtime/syscalls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,9 @@ pub(crate) fn sigtimedwait(set: &Sigset, timeout: Option<Timespec>) -> io::Resul
let mut info = MaybeUninit::<Siginfo>::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!(
Expand Down
2 changes: 2 additions & 0 deletions src/backend/linux_raw/time/syscalls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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!(
Expand Down
7 changes: 7 additions & 0 deletions src/fs/at.rs
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,13 @@ pub fn statat<P: path::Arg, Fd: AsFd>(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]
Expand Down
3 changes: 2 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 067a113

Please sign in to comment.