refactor: Make processes have cwd as a capability

This commit is contained in:
Ibuki Omatsu
2026-02-26 13:09:27 +00:00
committed by Jeremy Soller
parent 98e8716f48
commit bd9c6f1440
8 changed files with 314 additions and 78 deletions
+2 -3
View File
@@ -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<usize> {
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<FdGuard>,
+13
View File
@@ -61,6 +61,8 @@ pub struct ExtraInfo<'a> {
pub proc_fd: usize,
/// Namespace handle
pub ns_fd: Option<usize>,
/// CWD handle
pub cwd_fd: Option<usize>,
}
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<false> {
}
}
impl<const UPPER: bool> FdGuard<UPPER> {
#[inline]
pub fn openat<T: AsRef<str>>(
&self,
path: T,
flags: usize,
fcntl_flags: usize,
) -> Result<FdGuard<false>> {
syscall::openat(self.fd, path, flags, fcntl_flags).map(FdGuard::new)
}
#[inline]
pub fn dup(&self, buf: &[u8]) -> Result<FdGuard<false>> {
syscall::dup(self.fd, buf).map(FdGuard::new)
+22 -4
View File
@@ -445,7 +445,7 @@ pub fn setns(fd: usize) -> Option<FdGuardUpper> {
old_fd_guard
}
pub fn getns() -> Result<usize> {
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<T: AsRef<str>>(path: T, flags: usize) -> Result<usize> {
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<T: AsRef<str>>(
fd: usize,
path: T,
flags: usize,
fcntl_flags: usize,
) -> Result<usize> {
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<T: AsRef<str>>(path: T, flags: usize) -> Result<usize> {
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<FdGuardUpper> {
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());
+3
View File
@@ -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;
+12 -3
View File
@@ -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);
}
}
+4 -1
View File
@@ -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(
+3 -7
View File
@@ -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(())
}
+255 -60
View File
@@ -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<String> {
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::<Vec<_>>()
.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::<Vec<_>>()
.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<usize> {
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<String> {
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<usize> {
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<ReadGuard<'static, Option<Cwd>>> {
let guard = CWD.read();
if guard.as_ref().is_none() {
return Err(Error::new(ENOENT));
}
Ok(guard)
}
fn scheme_rooted_path(path: &str) -> Result<String> {
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<Strin
.unwrap_or(false);
Ok(if standard_scheme || legacy_scheme {
path
path.to_string()
} else {
let mut result = format!("/scheme/file{}", path);
@@ -81,23 +199,34 @@ fn canonicalize_with_cwd_internal(cwd: Option<&str>, path: &str) -> Result<Strin
})
}
// TODO: How much of this logic should be in redox-path?
fn canonicalize_with_cwd_internal(cwd: Option<&str>, path: &str) -> Result<String> {
let path = canonicalize_using_cwd(cwd, path).ok_or(Error::new(ENOENT))?;
scheme_rooted_path(&path)
}
pub fn canonicalize(path: &str) -> Result<String> {
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<str>,
pub fd: FdGuardUpper,
}
// TODO: arraystring?
static CWD: Mutex<Option<Box<str>>> = Mutex::new(None);
static CWD: RwLock<Option<Cwd>> = RwLock::new(None);
pub fn set_cwd_manual(cwd: Box<str>) {
pub fn set_cwd_manual(path: Box<str>, fd: FdGuardUpper) {
let _siglock = tmp_disable_signals();
*CWD.lock() = Some(cwd);
*CWD.write() = Some(Cwd { path, fd });
}
pub fn clone_cwd() -> Option<Box<str>> {
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<usize> {
// 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<usize> {
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<String> {
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<String> {
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(&current_abs, &link_target)?;
}
Err(e) => return Err(e),
}
// Sym reolve loop
for _ in 0..(MAX_LEVEL - 1) {
match open_absolute(&current_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(&current_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(&current_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]) };