Skip to content

Fix the fstat/statat/etc. fallback when statx fails with EPERM. #666

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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