From bd9c6f144006726fb56b823dca0d2208a8e635f9 Mon Sep 17 00:00:00 2001 From: Ibuki Omatsu Date: Thu, 26 Feb 2026 13:09:27 +0000 Subject: [PATCH] refactor: Make processes have cwd as a capability --- redox-rt/src/lib.rs | 5 +- redox-rt/src/proc.rs | 13 ++ redox-rt/src/sys.rs | 26 ++- src/platform/auxv_defs.rs | 3 + src/platform/mod.rs | 15 +- src/platform/redox/exec.rs | 5 +- src/platform/redox/mod.rs | 10 +- src/platform/redox/path.rs | 315 ++++++++++++++++++++++++++++++------- 8 files changed, 314 insertions(+), 78 deletions(-) diff --git a/redox-rt/src/lib.rs b/redox-rt/src/lib.rs index 6732c4062f..b9e412c44f 100644 --- a/redox-rt/src/lib.rs +++ b/redox-rt/src/lib.rs @@ -265,15 +265,14 @@ pub fn current_proc_fd() -> &'static FdGuardUpper { info.proc_fd.as_ref().unwrap() } #[inline] -pub fn current_namespace_fd() -> usize { +pub fn current_namespace_fd() -> syscall::Result { DYNAMIC_PROC_INFO .lock() .ns_fd .as_ref() .map(|g| g.as_raw_fd()) - .unwrap_or(usize::MAX) + .ok_or(syscall::Error::new(syscall::ENOENT)) } - struct ChildHookCommonArgs { new_thr_fd: FdGuard, new_proc_fd: Option, diff --git a/redox-rt/src/proc.rs b/redox-rt/src/proc.rs index bda825a893..128ab306e2 100644 --- a/redox-rt/src/proc.rs +++ b/redox-rt/src/proc.rs @@ -61,6 +61,8 @@ pub struct ExtraInfo<'a> { pub proc_fd: usize, /// Namespace handle pub ns_fd: Option, + /// CWD handle + pub cwd_fd: Option, } pub fn fexec_impl( @@ -393,6 +395,8 @@ pub fn fexec_impl( push(AT_REDOX_PROC_FD)?; push(extrainfo.ns_fd.unwrap_or(usize::MAX))?; push(AT_REDOX_NS_FD)?; + push(extrainfo.cwd_fd.unwrap_or(usize::MAX))?; + push(AT_REDOX_CWD_FD)?; push(0)?; @@ -773,6 +777,15 @@ impl FdGuard { } } impl FdGuard { + #[inline] + pub fn openat>( + &self, + path: T, + flags: usize, + fcntl_flags: usize, + ) -> Result> { + syscall::openat(self.fd, path, flags, fcntl_flags).map(FdGuard::new) + } #[inline] pub fn dup(&self, buf: &[u8]) -> Result> { syscall::dup(self.fd, buf).map(FdGuard::new) diff --git a/redox-rt/src/sys.rs b/redox-rt/src/sys.rs index f9dae57adc..afb2ab9436 100644 --- a/redox-rt/src/sys.rs +++ b/redox-rt/src/sys.rs @@ -445,7 +445,7 @@ pub fn setns(fd: usize) -> Option { old_fd_guard } pub fn getns() -> Result { - let cur_ns = crate::current_namespace_fd(); + let cur_ns = crate::current_namespace_fd()?; if cur_ns == usize::MAX { Err(Error::new(ENODEV)) } else { @@ -458,7 +458,25 @@ pub fn open>(path: T, flags: usize) -> Result { unsafe { syscall::syscall5( syscall::SYS_OPENAT, - crate::current_namespace_fd(), + crate::current_namespace_fd()?, + path.as_ptr() as usize, + path.len(), + flags, + fcntl_flags, + ) + } +} +pub fn openat>( + fd: usize, + path: T, + flags: usize, + fcntl_flags: usize, +) -> Result { + let path = path.as_ref(); + unsafe { + syscall::syscall5( + syscall::SYS_OPENAT, + fd, path.as_ptr() as usize, path.len(), flags, @@ -471,7 +489,7 @@ pub fn unlink>(path: T, flags: usize) -> Result { unsafe { syscall::syscall4( syscall::SYS_UNLINKAT, - crate::current_namespace_fd(), + crate::current_namespace_fd()?, path.as_ptr() as usize, path.len(), flags, @@ -487,7 +505,7 @@ pub fn mkns(names: &[IoSlice]) -> Result { buf.extend_from_slice(&len.to_ne_bytes()); buf.extend_from_slice(name_bytes); } - FdGuard::new(syscall::dup(crate::current_namespace_fd(), &buf)?).to_upper() + FdGuard::new(syscall::dup(crate::current_namespace_fd()?, &buf)?).to_upper() } pub fn register_scheme_to_ns(ns_fd: usize, name: &str, cap_fd: usize) -> Result<()> { let mut buf = alloc::vec::Vec::from((NsDup::IssueRegister as usize).to_ne_bytes()); diff --git a/src/platform/auxv_defs.rs b/src/platform/auxv_defs.rs index fa0d7f3a73..748800aa44 100644 --- a/src/platform/auxv_defs.rs +++ b/src/platform/auxv_defs.rs @@ -55,3 +55,6 @@ pub const AT_REDOX_THR_FD: usize = 42; #[cfg(target_os = "redox")] pub const AT_REDOX_NS_FD: usize = 43; + +#[cfg(target_os = "redox")] +pub const AT_REDOX_CWD_FD: usize = 44; diff --git a/src/platform/mod.rs b/src/platform/mod.rs index c4250cfbd8..3bcb826463 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -352,6 +352,7 @@ pub unsafe fn init(auxvs: Box<[[usize; 2]]>) { pub unsafe fn init_inner(auxvs: Box<[[usize; 2]]>) { use self::auxv_defs::*; use crate::header::sys_stat::S_ISVTX; + use redox_rt::proc::FdGuard; use syscall::MODE_PERM; // TODO: Is it safe to assume setup_sighandler has been called at this point? @@ -362,14 +363,22 @@ pub unsafe fn init_inner(auxvs: Box<[[usize; 2]]>) { ) .expect("failed to sync signal pctl"); - if let (Some(cwd_ptr), Some(cwd_len)) = ( + if let (Some(cwd_ptr), Some(cwd_len), Some(cwd_fd)) = ( get_auxv(&auxvs, AT_REDOX_INITIAL_CWD_PTR), get_auxv(&auxvs, AT_REDOX_INITIAL_CWD_LEN), + get_auxv(&auxvs, AT_REDOX_CWD_FD), ) { let cwd_bytes: &'static [u8] = unsafe { core::slice::from_raw_parts(cwd_ptr as *const u8, cwd_len) }; - if let Ok(cwd) = core::str::from_utf8(cwd_bytes) { - self::sys::path::set_cwd_manual(cwd.into()); + if let (Ok(cwd_path), Some(cwd_fd)) = ( + core::str::from_utf8(cwd_bytes), + (cwd_fd != usize::MAX).then(|| { + FdGuard::new(cwd_fd) + .to_upper() + .expect("failed to move cwd fd to upper table") + }), + ) { + self::sys::path::set_cwd_manual(cwd_path.into(), cwd_fd); } } diff --git a/src/platform/redox/exec.rs b/src/platform/redox/exec.rs index d4b8dcf8e1..f9b0120766 100644 --- a/src/platform/redox/exec.rs +++ b/src/platform/redox/exec.rs @@ -201,7 +201,10 @@ pub fn execve( umask: redox_rt::sys::get_umask(), thr_fd: RtTcb::current().thread_fd().as_raw_fd(), proc_fd: redox_rt::current_proc_fd().as_raw_fd(), - ns_fd: Some(redox_rt::current_namespace_fd()), + ns_fd: redox_rt::current_namespace_fd().ok(), + cwd_fd: super::path::current_dir() + .ok() + .map(|fd| fd.as_ref().unwrap().fd.as_raw_fd()), }; fexec_impl( diff --git a/src/platform/redox/mod.rs b/src/platform/redox/mod.rs index e0b040d24d..88cb901bec 100644 --- a/src/platform/redox/mod.rs +++ b/src/platform/redox/mod.rs @@ -29,7 +29,7 @@ use crate::{ bits_time::timespec, errno::{ EBADF, EBADFD, EBADR, EEXIST, EFAULT, EFBIG, EINTR, EINVAL, EIO, ENAMETOOLONG, ENOENT, - ENOMEM, ENOSYS, EOPNOTSUPP, EPERM, ERANGE, + ENOMEM, ENOSYS, EOPNOTSUPP, EPERM, }, fcntl::{ self, AT_EMPTY_PATH, AT_FDCWD, AT_SYMLINK_NOFOLLOW, F_GETLK, F_OFD_GETLK, F_OFD_SETLK, @@ -265,11 +265,7 @@ impl Pal for Sys { } fn fchdir(fd: c_int) -> Result<()> { - let mut buf = [0; 4096]; - let res = syscall::fpath(fd as usize, &mut buf)?; - - let path = str::from_utf8(&buf[..res]).map_err(|_| Errno(EINVAL))?; - path::chdir(path)?; + path::fchdir(fd)?; Ok(()) } @@ -522,7 +518,7 @@ impl Pal for Sys { } fn getcwd(buf: Out<[u8]>) -> Result<()> { - path::getcwd(buf).ok_or(Errno(ERANGE))?; + path::getcwd(buf)?; Ok(()) } diff --git a/src/platform/redox/path.rs b/src/platform/redox/path.rs index d59f6b9e4c..1fa46a3d22 100644 --- a/src/platform/redox/path.rs +++ b/src/platform/redox/path.rs @@ -1,10 +1,12 @@ use alloc::{ boxed::Box, + collections::btree_set::BTreeSet, ffi::CString, string::{String, ToString}, + vec::Vec, }; use core::{ffi::c_int, str}; -use redox_rt::signal::tmp_disable_signals; +use redox_rt::{proc::FdGuardUpper, signal::tmp_disable_signals}; use syscall::{data::Stat, error::*, flag::*}; use super::{FdGuard, Pal, Sys, libcscheme}; @@ -13,11 +15,74 @@ use crate::{ fs::File, header::{fcntl, limits, sys_file}, out::Out, - sync::Mutex, + sync::rwlock::{ReadGuard, RwLock}, }; pub use redox_path::{RedoxPath, canonicalize_using_cwd}; +pub fn normalize_path<'a>(path: &'a str) -> Option<(bool, String)> { + let absolute = match RedoxPath::from_absolute(path) { + Some(absolute) => absolute, + None => return Some((true, partially_canonical(path)?)), + }; + let canonical = absolute.canonical()?; + Some((false, canonical.to_string())) +} + +pub fn normalize_scheme_rooted_path<'a>(path: &'a str) -> Option<(bool, String)> { + let absolute = match RedoxPath::from_absolute(path) { + Some(absolute) => absolute, + None => return Some((true, partially_canonical(path)?)), + }; + let canonical = absolute.canonical()?; + Some((false, scheme_rooted_path(&canonical.to_string()).ok()?)) +} + +fn partially_canonical(path: &str) -> Option { + let mut stack = Vec::new(); + let mut paths_to_check = BTreeSet::new(); + let mut up_counts = 0; + + for part in path.split('/') { + if part.is_empty() || part == "." { + continue; + } else if part == ".." { + if let Some(part) = stack.pop() { + paths_to_check.insert( + core::iter::repeat("..") + .take(up_counts) + .chain(stack.clone()) + .chain(core::iter::once(part)) + .collect::>() + .join("/"), + ); + } else { + up_counts += 1; + } + } else { + stack.push(part); + } + } + + for path in paths_to_check { + let _ = current_dir() + .ok()? + .as_ref() + .unwrap() + .fd + .openat(&path, O_STAT, 0) + .ok()?; + } + + Some( + core::iter::repeat("..") + .take(up_counts) + .chain(stack) + .collect::>() + .join("/"), + ) +} + // TODO: Define in syscall const PATH_MAX: usize = 4096; @@ -25,41 +90,94 @@ const PATH_MAX: usize = 4096; // same time forbid signal handlers from running in the meantime, to avoid reentrant deadlock. pub fn chdir(path: &str) -> Result<()> { let _siglock = tmp_disable_signals(); - let mut cwd_guard = CWD.lock(); + let mut cwd_guard = CWD.write(); + let (is_relative, path) = normalize_path(path).ok_or(Error::new(ENOENT))?; + if is_relative { + let fd = current_dir()? + .as_ref() + .unwrap() + .fd + .openat(&path, O_STAT, 0)? + .to_upper() + .unwrap(); + let mut stat = Stat::default(); + if fd.fstat(&mut stat).is_err() || (stat.st_mode & MODE_TYPE) != MODE_DIR { + return Err(Error::new(ENOTDIR)); + } - let canon = canonicalize_using_cwd(cwd_guard.as_deref(), path).ok_or(Error::new(ENOENT))?; - let canon_with_scheme = canonicalize_with_cwd_internal(cwd_guard.as_deref(), path)?; + let canon = canonicalize_using_cwd(cwd_guard.as_ref().map(|c| c.path.as_ref()), &path) + .ok_or(Error::new(ENOENT))?; + *cwd_guard = Some(Cwd { + path: canon.into_boxed_str(), + fd, + }); + } else { + let canon_with_scheme = scheme_rooted_path(&path)?; - let fd = redox_rt::sys::open(&canon_with_scheme, O_STAT | O_CLOEXEC)?; - let mut stat = Stat::default(); - if syscall::fstat(fd, &mut stat).is_err() || (stat.st_mode & MODE_TYPE) != MODE_DIR { - return Err(Error::new(ENOTDIR)); + let fd = FdGuard::open(&canon_with_scheme, O_STAT)? + .to_upper() + .unwrap(); + let mut stat = Stat::default(); + if fd.fstat(&mut stat).is_err() || (stat.st_mode & MODE_TYPE) != MODE_DIR { + return Err(Error::new(ENOTDIR)); + } + + *cwd_guard = Some(Cwd { + path: path.into_boxed_str(), + fd, + }); } - let _ = syscall::close(fd); - - *cwd_guard = Some(canon.into_boxed_str()); Ok(()) } -// getcwd is similarly both thread-safe and signal-safe. -pub fn getcwd(mut buf: Out<[u8]>) -> Option { - let _siglock = tmp_disable_signals(); - let cwd_guard = CWD.lock(); - let cwd = cwd_guard.as_deref().unwrap_or("").as_bytes(); +pub fn fchdir(fd: c_int) -> Result<()> { + let mut buf = [0; PATH_MAX]; + let res = syscall::fpath(fd as usize, &mut buf)?; - let [mut before, mut after] = buf.split_at_checked(cwd.len())?; - - before.copy_from_slice(&cwd); - after.zero(); - - Some(cwd.len()) + let path = core::str::from_utf8(&buf[..res]) + .map_err(|_| Errno(EINVAL))? + .to_string(); + let fd = FdGuard::new(syscall::fcntl( + fd as usize, + syscall::F_DUPFD, + syscall::UPPER_FDTBL_TAG, + )?) + .to_upper() + .unwrap(); + set_cwd_manual(path.into_boxed_str(), fd); + Ok(()) } -// TODO: How much of this logic should be in redox-path? -fn canonicalize_with_cwd_internal(cwd: Option<&str>, path: &str) -> Result { - let path = canonicalize_using_cwd(cwd, path).ok_or(Error::new(ENOENT))?; +// getcwd is similarly both thread-safe and signal-safe. +pub fn getcwd(mut buf: Out<[u8]>) -> Result { + let _siglock = tmp_disable_signals(); + let guard = CWD.read(); + let cwd = guard.as_ref().ok_or(Error::new(ENOENT))?; + let path_bytes = cwd.path.as_bytes(); + let [mut before, mut after] = buf + .split_at_checked(path_bytes.len()) + .ok_or(Error::new(ERANGE))?; + + before.copy_from_slice(path_bytes); + after.zero(); + + Ok(path_bytes.len()) +} + +// Get Cwd object +pub fn current_dir() -> Result>> { + let guard = CWD.read(); + + if guard.as_ref().is_none() { + return Err(Error::new(ENOENT)); + } + + Ok(guard) +} + +fn scheme_rooted_path(path: &str) -> Result { let standard_scheme = path == "/scheme" || path.starts_with("/scheme/"); let legacy_scheme = path .split("/") @@ -68,7 +186,7 @@ fn canonicalize_with_cwd_internal(cwd: Option<&str>, path: &str) -> Result, path: &str) -> Result, path: &str) -> Result { + let path = canonicalize_using_cwd(cwd, path).ok_or(Error::new(ENOENT))?; + scheme_rooted_path(&path) +} + pub fn canonicalize(path: &str) -> Result { let _siglock = tmp_disable_signals(); - let cwd_guard = CWD.lock(); - canonicalize_with_cwd_internal(cwd_guard.as_deref(), path) + let cwd_guard = CWD.read(); + canonicalize_with_cwd_internal(cwd_guard.as_ref().map(|c| c.path.as_ref()), path) +} + +pub struct Cwd { + pub path: Box, + pub fd: FdGuardUpper, } // TODO: arraystring? -static CWD: Mutex>> = Mutex::new(None); +static CWD: RwLock> = RwLock::new(None); -pub fn set_cwd_manual(cwd: Box) { +pub fn set_cwd_manual(path: Box, fd: FdGuardUpper) { let _siglock = tmp_disable_signals(); - *CWD.lock() = Some(cwd); + *CWD.write() = Some(Cwd { path, fd }); } pub fn clone_cwd() -> Option> { let _siglock = tmp_disable_signals(); - CWD.lock().clone() + CWD.read().as_ref().map(|cwd| cwd.path.clone()) } // TODO: Move to redox-rt, or maybe part of it? @@ -105,46 +234,106 @@ pub fn open(path: &str, flags: usize) -> Result { // TODO: SYMLOOP_MAX const MAX_LEVEL: usize = 64; - let mut resolve_buf = [0_u8; 4096]; - let mut path = path; + if path == "" { + return Err(Error::new(ENOENT)); + } - for _ in 0..MAX_LEVEL { - let canon = canonicalize_with_cwd_internal(CWD.lock().as_deref(), path)?; - - let open_res = if canon.starts_with(libcscheme::LIBC_SCHEME) { - libcscheme::open(&canon, flags) + let open_absolute = |path: &str| -> Result { + if path.starts_with(libcscheme::LIBC_SCHEME) { + libcscheme::open(path, flags) } else { - redox_rt::sys::open(&*canon, flags) + redox_rt::sys::open(path, flags) + } + }; + + let read_link_content = |path: &str, is_relative: bool| -> Result { + let mut resolve_buf = [0_u8; 4096]; + let resolve_flags = O_CLOEXEC | O_SYMLINK | O_RDONLY; + + let fd = if is_relative { + let fcntl_flags = resolve_flags & syscall::O_FCNTL_MASK; + current_dir()? + .as_ref() + .unwrap() + .fd + .openat(path, resolve_flags, fcntl_flags)? + } else { + FdGuard::open(path, resolve_flags)? }; - match open_res { + let count = fd.read(&mut resolve_buf)?; + if count == resolve_buf.len() { + // TODO: make resolve_buf PATH_MAX + 1 bytes? + return Err(Error::new(ENAMETOOLONG)); + } + // If the symbolic link path is non-UTF8, it cannot be opened, and is thus + // considered a "dangling symbolic link". + core::str::from_utf8(&resolve_buf[..count]) + .map_err(|_| Error::new(ENOENT)) + .map(|s| s.to_string()) + }; + + let calc_next_abs_path = |current_abs: &str, link_target: &str| -> Result { + let _siglock = tmp_disable_signals(); + let parent = get_parent_path(current_abs).ok_or(Error::new(ENOENT))?; + + canonicalize_using_cwd(Some(parent), link_target).ok_or(Error::new(ENOENT)) + }; + + let (is_relative, canon) = normalize_scheme_rooted_path(path).ok_or(Error::new(ENOENT))?; + let mut current_path_string: String; + + // First try + let initial_res = if is_relative { + let fcntl_flags = flags & syscall::O_FCNTL_MASK; + current_dir()? + .as_ref() + .unwrap() + .fd + .openat(&canon, flags, fcntl_flags) + .map(|fd: FdGuard| fd.take()) + } else { + open_absolute(&canon) + }; + + match initial_res { + Ok(fd) => return Ok(fd), + Err(e) if e == Error::new(EXDEV) => { + let link_target = read_link_content(&canon, is_relative)?; + + let _siglock = tmp_disable_signals(); + let cwd_guard = CWD.read(); + let current_abs = + canonicalize_using_cwd(cwd_guard.as_ref().map(|c| c.path.as_ref()), &canon) + .ok_or(Error::new(ENOENT))?; + + current_path_string = calc_next_abs_path(¤t_abs, &link_target)?; + } + Err(e) => return Err(e), + } + + // Sym reolve loop + for _ in 0..(MAX_LEVEL - 1) { + match open_absolute(¤t_path_string) { Ok(fd) => return Ok(fd), - Err(error) if error == Error::new(EXDEV) => { - let resolve_flags = O_CLOEXEC | O_SYMLINK | O_RDONLY; - let resolve_fd = FdGuard::new(redox_rt::sys::open(&*canon, resolve_flags)?); + Err(e) if e == Error::new(EXDEV) => { + let link_target = read_link_content(¤t_path_string, false)?; - let bytes_read = resolve_fd.read(&mut resolve_buf)?; - // TODO: make resolve_buf PATH_MAX + 1 bytes? - if bytes_read == resolve_buf.len() { - return Err(Error::new(ENAMETOOLONG)); - } - - // If the symbolic link path is non-UTF8, it cannot be opened, and is thus - // considered a "dangling symbolic link". - path = core::str::from_utf8(&resolve_buf[..bytes_read]) - .map_err(|_| Error::new(ENOENT))?; + current_path_string = calc_next_abs_path(¤t_path_string, &link_target)?; } - Err(other_error) => return Err(other_error), + Err(e) => return Err(e), } } + Err(Error::new(ELOOP)) } pub fn dir_path_and_fd_path(socket_path: &str) -> Result<(String, String)> { let _siglock = tmp_disable_signals(); - let cwd_guard = CWD.lock(); + let cwd_guard = CWD.read(); + let cwd_path = cwd_guard.as_ref().map(|c| c.path.as_ref()); - let full_path = canonicalize_with_cwd_internal(cwd_guard.as_deref(), socket_path)?; + let full_path = canonicalize_with_cwd_internal(cwd_path, socket_path)?; let redox_path = RedoxPath::from_absolute(&full_path).ok_or(Error::new(EINVAL))?; let (_, ref_path) = redox_path.as_parts().ok_or(Error::new(EINVAL))?; @@ -155,7 +344,7 @@ pub fn dir_path_and_fd_path(socket_path: &str) -> Result<(String, String)> { let dir_to_open = String::from(get_parent_path(&full_path).ok_or(Error::new(EINVAL))?); Ok((dir_to_open, ref_path.as_ref().to_string())) } else { - let full_path = canonicalize_with_cwd_internal(cwd_guard.as_deref(), ref_path.as_ref())?; + let full_path = canonicalize_with_cwd_internal(cwd_path, ref_path.as_ref())?; let redox_path = RedoxPath::from_absolute(&full_path).ok_or(Error::new(EINVAL))?; let (_, path) = redox_path.as_parts().ok_or(Error::new(EINVAL))?; let dir_to_open = String::from(get_parent_path(&full_path).ok_or(Error::new(EINVAL))?); @@ -217,7 +406,13 @@ pub(super) fn openat2_path(dirfd: c_int, path: &str, at_flags: c_int) -> Result< if dirfd == fcntl::AT_FDCWD { // The special constant AT_FDCWD indicates that we should use the cwd. let mut buf = [0; limits::PATH_MAX]; - let len = getcwd(Out::from_mut(&mut buf)).ok_or(Errno(ENAMETOOLONG))?; + let len = match getcwd(Out::from_mut(&mut buf)) { + Ok(len) => len, + Err(e) if e.errno == ERANGE => { + return Err(Errno(ENAMETOOLONG)); + } + Err(e) => return Err(Errno(e.errno)), + }; // SAFETY: Redox's cwd is stored as a str. let cwd = unsafe { str::from_utf8_unchecked(&buf[..len]) };