From 5e1036bc158738c5fd173b1afa2b404e4ee61ec3 Mon Sep 17 00:00:00 2001 From: Anhad Singh Date: Thu, 4 Dec 2025 11:31:24 +1100 Subject: [PATCH] fix(platform/redox): fstatat This commit fixes multiple issues with `fstatat` implementation for Redox. 1. `flags` are now properly handled. The `flags` argument specifies AT_* flags not O_* flags. 2. Open with O_PATH to avoid actually *opening* the file which may block or cause permission issues. 3. Close the temporary file descriptor regardless of success of failure. Signed-off-by: Anhad Singh --- src/platform/redox/mod.rs | 46 ++++++++++++++++++++++++++++++++++----- 1 file changed, 41 insertions(+), 5 deletions(-) diff --git a/src/platform/redox/mod.rs b/src/platform/redox/mod.rs index e8a970337f..394d5282b5 100644 --- a/src/platform/redox/mod.rs +++ b/src/platform/redox/mod.rs @@ -314,11 +314,47 @@ impl Pal for Sys { } fn fstatat(dirfd: c_int, path: Option, buf: Out, flags: c_int) -> Result<()> { - let path = path - .and_then(|cs| str::from_utf8(cs.to_bytes()).ok()) - .ok_or(Errno(ENOENT))?; - let file = openat2(dirfd, path, flags, 0)?; - Sys::fstat(*file, buf) + // `path` should be non-null. + let path = path.ok_or(Errno(EFAULT))?; + let mut path = str::from_utf8(path.to_bytes()).ok().ok_or(Errno(EILSEQ))?; + + if path.is_empty() { + if flags & AT_EMPTY_PATH == AT_EMPTY_PATH { + if dirfd == AT_FDCWD { + path = "."; + } else { + return Sys::fstat(dirfd, buf); + } + } else { + // If the path is empty but `AT_EMPTY_PATH` is **not** set, bail out. + return Err(Errno(ENOENT)); + } + } + + // Use `O_PATH` to obtain a file descriptor without actually *opening* the file. This + // bypasses permission checks and avoids cases where opening a file is blocking operation + // (e.g., FIFOs). This gives a file descriptor where fstat(2) can be performed (and some + // other meta operations) but nothing else (e.g. read/write). + // + // `O_CLOEXEC` is used to avoid leaking file descriptors to child processes on exec(2). + // + // FIXME: Ideally we would want the file descriptor to not leak on fork(2) too because + // fstatat(2) should not have side effects. However, Redox does not currently support that, + // so we use `CLOEXEC` as a compromise. + let mut open_flags = fcntl::O_PATH | fcntl::O_CLOEXEC; + if flags & AT_SYMLINK_NOFOLLOW == AT_SYMLINK_NOFOLLOW { + open_flags |= fcntl::O_NOFOLLOW; + } + + let file = openat2(dirfd, path, open_flags, 0)?; + // Close the file descriptor after fstat(2) regardless of success or failure. + let fstat_res = Sys::fstat(*file, buf); + let close_res = syscall::close(file.fd as usize); + if let Err(err) = fstat_res { + return Err(err); + } + close_res?; + fstat_res } fn fstatvfs(fildes: c_int, mut buf: Out) -> Result<()> {