Move cwd from kernel to relibc.

This commit is contained in:
4lDO2
2022-08-12 11:28:21 +02:00
parent 4b90afeb2a
commit 54b3f873f8
9 changed files with 263 additions and 61 deletions
+5 -18
View File
@@ -11,7 +11,7 @@ use alloc::{
use crate::{
c_str::CStr,
header::{sys_auxv::AT_NULL, unistd},
platform::{new_mspace, types::c_char},
platform::{get_auxvs, get_auxv, new_mspace, types::c_char},
start::Stack,
sync::mutex::Mutex,
ALLOCATOR,
@@ -57,19 +57,6 @@ unsafe fn get_env(mut ptr: *const usize) -> (BTreeMap<String, String>, *const us
return (envs, ptr);
}
unsafe fn get_auxv(mut ptr: *const usize) -> BTreeMap<usize, usize> {
//traverse the stack and collect argument environment variables
let mut auxv = BTreeMap::new();
while *ptr != AT_NULL {
let kind = *ptr;
ptr = ptr.add(1);
let value = *ptr;
ptr = ptr.add(1);
auxv.insert(kind, value);
}
return auxv;
}
unsafe fn adjust_stack(sp: &'static mut Stack) {
let mut argv = sp.argv() as *mut usize;
@@ -163,7 +150,7 @@ pub extern "C" fn relibc_ld_so_start(sp: &'static mut Stack, ld_entry: usize) ->
let argv_start = sp.argv() as *mut usize;
let (argv, argv_end) = get_argv(argv_start);
let (envs, envs_end) = get_env(argv_end.add(1));
let auxv = get_auxv(envs_end.add(1));
let auxv = get_auxvs(envs_end.add(1));
(argv, envs, auxv)
};
@@ -183,8 +170,8 @@ pub extern "C" fn relibc_ld_so_start(sp: &'static mut Stack, ld_entry: usize) ->
crate::platform::environ = crate::platform::OUR_ENVIRON.as_mut_ptr();
}
let is_manual = if let Some(img_entry) = auxv.get(&AT_ENTRY) {
*img_entry == ld_entry
let is_manual = if let Some(img_entry) = get_auxv(&auxv, AT_ENTRY) {
img_entry == ld_entry
} else {
true
};
@@ -229,7 +216,7 @@ pub extern "C" fn relibc_ld_so_start(sp: &'static mut Stack, ld_entry: usize) ->
let base_addr = {
let mut base = None;
if !is_manual && cfg!(not(target_os = "redox")) {
let phdr = *auxv.get(&AT_PHDR).unwrap();
let phdr = get_auxv(&auxv, AT_PHDR).unwrap();
if phdr != 0 {
base = Some(phdr - SIZEOF_EHDR);
}
+6
View File
@@ -25,3 +25,9 @@ pub const AT_BASE_PLATFORM: usize = 24; /* String identifying real platforms.*/
pub const AT_RANDOM: usize = 25; /* Address of 16 random bytes. */
pub const AT_HWCAP2: usize = 26; /* More machine-dependent hints about*/
pub const AT_EXECFN: usize = 31; /* Filename of executable. */
#[cfg(target_os = "redox")]
// XXX: The name AT_CWD is already used in openat... for a completely different purpose.
pub const AT_REDOX_INITIALCWD_PTR: usize = 32;
#[cfg(target_os = "redox")]
pub const AT_REDOX_INITIALCWD_LEN: usize = 33;
+39
View File
@@ -1,4 +1,5 @@
use crate::io::{self, Read, Write};
use alloc::boxed::Box;
use alloc::vec::Vec;
use core::{fmt, ptr};
@@ -261,3 +262,41 @@ impl<T: Write> Write for CountingWriter<T> {
self.inner.flush()
}
}
// TODO: Set a global variable once get_auxvs is called, and then implement getauxval based on
// get_auxv.
#[cold]
pub unsafe fn get_auxvs(mut ptr: *const usize) -> Box<[[usize; 2]]> {
//traverse the stack and collect argument environment variables
let mut auxvs = Vec::new();
while *ptr != self::auxv_defs::AT_NULL {
let kind = ptr.read();
ptr = ptr.add(1);
let value = ptr.read();
ptr = ptr.add(1);
auxvs.push([kind, value]);
}
auxvs.sort_unstable_by_key(|[kind, _]| *kind);
auxvs.into_boxed_slice()
}
pub fn get_auxv(auxvs: &[[usize; 2]], key: usize) -> Option<usize> {
auxvs.binary_search_by_key(&key, |[entry_key, _]| *entry_key).ok().map(|idx| auxvs[idx][1])
}
#[cold]
#[cfg(target_os = "redox")]
pub fn init(auxvs: Box<[[usize; 2]]>) {
use self::auxv_defs::*;
if let (Some(cwd_ptr), Some(cwd_len)) = (get_auxv(&auxvs, AT_REDOX_INITIALCWD_PTR), get_auxv(&auxvs, AT_REDOX_INITIALCWD_LEN)) {
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::setcwd_manual(cwd.into());
}
}
}
#[cfg(not(target_os = "redox"))]
pub fn init(auxvs: Box<[[usize; 2]]>) {}
-1
View File
@@ -31,7 +31,6 @@ pub unsafe fn pte_clone_impl(stack: *mut usize) -> Result<usize> {
}
copy_str(*cur_pid_fd, *new_pid_fd, "name")?;
copy_str(*cur_pid_fd, *new_pid_fd, "cwd")?;
// Reuse existing address space
{
+13 -10
View File
@@ -7,9 +7,9 @@ use crate::platform::{sys::{S_ISUID, S_ISGID}, types::*};
use syscall::data::Stat;
use syscall::flag::*;
use syscall::error::*;
use redox_exec::{FdGuard, FexecResult};
use redox_exec::{FdGuard, ExtraInfo, FexecResult};
fn fexec_impl(file: File, path: &[u8], args: &[&[u8]], envs: &[&[u8]], total_args_envs_size: usize, interp_override: Option<redox_exec::InterpOverride>) -> Result<usize> {
fn fexec_impl(file: File, path: &[u8], args: &[&[u8]], envs: &[&[u8]], total_args_envs_size: usize, extrainfo: &ExtraInfo, interp_override: Option<redox_exec::InterpOverride>) -> Result<usize> {
let fd = *file;
core::mem::forget(file);
let image_file = FdGuard::new(fd as usize);
@@ -17,7 +17,7 @@ fn fexec_impl(file: File, path: &[u8], args: &[&[u8]], envs: &[&[u8]], total_arg
let open_via_dup = FdGuard::new(syscall::open("thisproc:current/open_via_dup", 0)?);
let memory = FdGuard::new(syscall::open("memory:", 0)?);
let addrspace_selection_fd = match redox_exec::fexec_impl(image_file, open_via_dup, &memory, path, args.iter().rev(), envs.iter().rev(), total_args_envs_size, interp_override)? {
let addrspace_selection_fd = match redox_exec::fexec_impl(image_file, open_via_dup, &memory, path, args.iter().rev(), envs.iter().rev(), total_args_envs_size, extrainfo, interp_override)? {
FexecResult::Normal { addrspace_handle } => addrspace_handle,
FexecResult::Interp { image_file, open_via_dup, path, interp_override: new_interp_override } => {
drop(image_file);
@@ -75,6 +75,8 @@ pub fn execve(path: &CStr, arg_env: ArgEnv, interp_override: Option<redox_exec::
}
let wants_setugid = stat.st_mode & ((S_ISUID | S_ISGID) as u16) != 0;
let cwd: Box<[u8]> = super::path::clone_cwd().unwrap_or_default().into();
// Count arguments
let mut len = 0;
@@ -103,12 +105,11 @@ pub fn execve(path: &CStr, arg_env: ArgEnv, interp_override: Option<redox_exec::
}
shebang == *b"#!"
};
// Since the fexec implementation is almost fully done in userspace, the kernel can no
// longer set UID/GID accordingly, and this code checking for them before using
// hypothetical interfaces to upgrade UID/GID, can not be trusted. So we ask the
// `escalate:` scheme for help. Note that `escalate:` can be deliberately excluded from the
// scheme namespace to deny privilege escalation (such as su/sudo/doas) for untrusted
// processes.
// Since the fexec implementation is almost fully done in userspace, the kernel can no longer
// set UID/GID accordingly, and this code checking for them before using interfaces to upgrade
// UID/GID, can not be trusted. So we ask the `escalate:` scheme for help. Note that
// `escalate:` can be deliberately excluded from the scheme namespace to deny privilege
// escalation (such as su/sudo/doas) for untrusted processes.
//
// According to execve(2), Linux and most other UNIXes ignore setuid/setgid for interpreted
// executables and thereby simply keep the privileges as is. For compatibility we do that
@@ -224,6 +225,7 @@ pub fn execve(path: &CStr, arg_env: ArgEnv, interp_override: Option<redox_exec::
// individual items. This can be copied directly into the new executable's memory.
let _ = syscall::write(*escalate_fd, &flatten_with_nul(args))?;
let _ = syscall::write(*escalate_fd, &flatten_with_nul(envs))?;
let _ = syscall::write(*escalate_fd, &cwd);
// Closing will notify the scheme, and from that point we will no longer have control
// over this process (unless it fails). We do this manually since drop cannot handle
@@ -235,7 +237,8 @@ pub fn execve(path: &CStr, arg_env: ArgEnv, interp_override: Option<redox_exec::
unreachable!()
} else {
fexec_impl(image_file, path.to_bytes(), &args, &envs, total_args_envs_size, interp_override)
let extrainfo = ExtraInfo { cwd: Some(&cwd) };
fexec_impl(image_file, path.to_bytes(), &args, &envs, total_args_envs_size, &extrainfo, interp_override)
}
}
fn flatten_with_nul<T>(iter: impl IntoIterator<Item = T>) -> Box<[u8]> where T: AsRef<[u8]> {
+27 -28
View File
@@ -29,6 +29,8 @@ use crate::{
io::{self, prelude::*, BufReader, SeekFrom},
};
pub use redox_exec::FdGuard;
use super::{errno, types::*, Pal, Read};
static mut BRK_CUR: *mut c_void = ptr::null_mut();
@@ -43,6 +45,7 @@ mod clone;
mod epoll;
mod exec;
mod extra;
pub(crate) mod path;
mod ptrace;
mod signal;
mod socket;
@@ -61,6 +64,8 @@ macro_rules! path_from_c_str {
}};
}
use self::path::canonicalize;
pub fn e(sys: Result<usize>) -> usize {
match sys {
Ok(ok) => ok,
@@ -163,7 +168,7 @@ impl Pal for Sys {
fn chdir(path: &CStr) -> c_int {
let path = path_from_c_str!(path);
e(syscall::chdir(path)) as c_int
e(path::chdir(path).map(|()| 0)) as c_int
}
fn chmod(path: &CStr, mode: mode_t) -> c_int {
@@ -354,29 +359,20 @@ impl Pal for Sys {
}
fn getcwd(buf: *mut c_char, size: size_t) -> *mut c_char {
// TODO: Not using MaybeUninit seems a little unsafe
let buf_slice = unsafe { slice::from_raw_parts_mut(buf as *mut u8, size as usize) };
if !buf_slice.is_empty() {
let nonnull_size = buf_slice.len() - 1;
let read = e(syscall::getcwd(&mut buf_slice[..nonnull_size]));
if read == !0 {
ptr::null_mut()
} else if read == nonnull_size {
unsafe {
errno = ERANGE;
}
ptr::null_mut()
} else {
for b in &mut buf_slice[read..] {
*b = 0;
}
buf
}
} else {
unsafe {
errno = EINVAL;
}
ptr::null_mut()
if buf_slice.is_empty() {
unsafe { errno = EINVAL; }
return ptr::null_mut();
}
if path::getcwd(buf_slice).is_none() {
unsafe { errno = ERANGE; }
return ptr::null_mut()
}
buf
}
fn getdents(fd: c_int, mut dirents: *mut dirent, max_bytes: usize) -> c_int {
@@ -708,10 +704,13 @@ impl Pal for Sys {
fn open(path: &CStr, oflag: c_int, mode: mode_t) -> c_int {
let path = path_from_c_str!(path);
e(syscall::open(
path,
((oflag as usize) & 0xFFFF_0000) | ((mode as usize) & 0xFFFF),
)) as c_int
e(canonicalize(path).and_then(|canon| {
syscall::open(
&canon,
((oflag as usize) & 0xFFFF_0000) | ((mode as usize) & 0xFFFF),
)
})) as c_int
}
fn pipe2(fds: &mut [c_int], flags: c_int) -> c_int {
@@ -764,7 +763,7 @@ impl Pal for Sys {
fn rmdir(path: &CStr) -> c_int {
let path = path_from_c_str!(path);
e(syscall::rmdir(path)) as c_int
e(canonicalize(path).and_then(|path| syscall::rmdir(&path))) as c_int
}
fn sched_yield() -> c_int {
@@ -891,7 +890,7 @@ impl Pal for Sys {
fn unlink(path: &CStr) -> c_int {
let path = path_from_c_str!(path);
e(syscall::unlink(path)) as c_int
e(canonicalize(path).and_then(|path| syscall::unlink(&path))) as c_int
}
fn waitpid(mut pid: pid_t, stat_loc: *mut c_int, options: c_int) -> pid_t {
+156
View File
@@ -0,0 +1,156 @@
use syscall::error::*;
use syscall::flag::*;
use alloc::borrow::ToOwned;
use alloc::boxed::Box;
use alloc::string::String;
use alloc::vec::Vec;
use crate::sync::Mutex;
// TODO: Define in syscall
const PATH_MAX: usize = 4096;
/// Make a relative path absolute.
///
/// Given a cwd of "scheme:/path", this his function will turn "foo" into "scheme:/path/foo".
/// "/foo" will turn into "scheme:/foo". "bar:/foo" will be used directly, as it is already
/// absolute
pub fn canonicalize_using_cwd(cwd_opt: Option<&str>, path: &str) -> Option<String> {
let mut canon = if path.find(':').is_none() {
let cwd = cwd_opt?;
let path_start = cwd.find(':')? + 1;
let mut canon = if !path.starts_with('/') {
let mut c = cwd.to_owned();
if ! c.ends_with('/') {
c.push('/');
}
c
} else {
cwd[..path_start].to_owned()
};
canon.push_str(&path);
canon
} else {
path.to_owned()
};
// NOTE: assumes the scheme does not include anything like "../" or "./"
let mut result = {
let parts = canon.split('/')
.rev()
.scan(0, |nskip, part| {
if part == "." {
Some(None)
} else if part == ".." {
*nskip += 1;
Some(None)
} else if *nskip > 0 {
*nskip -= 1;
Some(None)
} else {
Some(Some(part))
}
})
.filter_map(|x| x)
.filter(|x| !x.is_empty())
.collect::<Vec<_>>();
parts
.iter()
.rev()
.fold(String::new(), |mut string, &part| {
string.push_str(part);
string.push('/');
string
})
};
result.pop(); // remove extra '/'
// replace with the root of the scheme if it's empty
Some(if result.is_empty() {
let pos = canon.find(':')
.map_or(canon.len(), |p| p + 1);
canon.truncate(pos);
canon
} else {
result
})
}
// XXX: chdir is not marked thread-safe (MT-safe) by POSIX. But on Linux it is simply run as a
// syscall and is therefore atomic, which is presumably why Rust's libstd doesn't synchronize
// access to this.
//
// https://internals.rust-lang.org/t/synchronized-ffi-access-to-posix-environment-variable-functions/15475
//
// chdir is however signal-safe, forbidding the use of locks. We therefore call sigprocmask before
// and after acquiring the locks. (TODO: ArcSwap? That will need to be ported to no_std first,
// though).
pub fn chdir(path: &str) -> Result<()> {
let _siglock = SignalMask::lock();
let mut cwd_guard = CWD.lock();
let canonicalized = canonicalize_using_cwd(cwd_guard.as_deref(), path).ok_or(Error::new(ENOENT))?;
*cwd_guard = Some(canonicalized.into_boxed_str());
// TODO: Check that the dir exists and is a directory.
Ok(())
}
pub fn clone_cwd() -> Option<Box<str>> {
let _siglock = SignalMask::lock();
CWD.lock().clone()
}
// TODO: MaybeUninit
pub fn getcwd(buf: &mut [u8]) -> Option<usize> {
let _siglock = SignalMask::lock();
let cwd_guard = CWD.lock();
let cwd = cwd_guard.as_deref().unwrap_or("").as_bytes();
// But is already checked not to be empty.
if buf.len() - 1 < cwd.len() { return None; }
buf[..cwd.len()].copy_from_slice(&cwd);
buf[cwd.len()..].fill(0_u8);
Some(cwd.len())
}
// TODO: Move cwd from kernel to libc. It is passed via auxiliary vectors.
pub fn canonicalize(path: &str) -> Result<String> {
let _siglock = SignalMask::lock();
let cwd = CWD.lock();
canonicalize_using_cwd(cwd.as_deref(), path).ok_or(Error::new(ENOENT))
}
// TODO: arraystring?
static CWD: Mutex<Option<Box<str>>> = Mutex::new(None);
pub fn setcwd_manual(cwd: Box<str>) {
let _siglock = SignalMask::lock();
*CWD.lock() = Some(cwd);
}
/// RAII guard able to magically fix signal unsafety, by disabling signals during a critical
/// section.
pub struct SignalMask {
oldset: [u64; 2],
}
impl SignalMask {
pub fn lock() -> Self {
let mut oldset = [0; 2];
syscall::sigprocmask(syscall::SIG_SETMASK, Some(&[!0, !0]), Some(&mut oldset)).expect("failed to run sigprocmask");
Self { oldset }
}
}
impl Drop for SignalMask {
fn drop(&mut self) {
let _ = syscall::sigprocmask(syscall::SIG_SETMASK, Some(&self.oldset), None);
}
}
+14 -3
View File
@@ -34,7 +34,11 @@ pub struct InterpOverride {
tree: BTreeMap<usize, usize>,
}
pub fn fexec_impl<A, E>(image_file: FdGuard, open_via_dup: FdGuard, memory_scheme_fd: &FdGuard, path: &[u8], args: A, envs: E, total_args_envs_size: usize, mut interp_override: Option<InterpOverride>) -> Result<FexecResult>
pub struct ExtraInfo<'a> {
pub cwd: Option<&'a [u8]>,
}
pub fn fexec_impl<A, E>(image_file: FdGuard, open_via_dup: FdGuard, memory_scheme_fd: &FdGuard, path: &[u8], args: A, envs: E, total_args_envs_size: usize, extrainfo: &ExtraInfo, mut interp_override: Option<InterpOverride>) -> Result<FexecResult>
where
A: IntoIterator,
E: IntoIterator,
@@ -178,7 +182,8 @@ where
push(interp_override.as_ref().map_or(header.e_phentsize as usize, |o| o.at_phent))?;
push(AT_PHENT)?;
let args_envs_size_aligned = (total_args_envs_size+PAGE_SIZE-1)/PAGE_SIZE*PAGE_SIZE;
let total_args_envs_auxvpointee_size = total_args_envs_size + extrainfo.cwd.map_or(0, |s| s.len() + 1);
let args_envs_size_aligned = (total_args_envs_auxvpointee_size+PAGE_SIZE-1)/PAGE_SIZE*PAGE_SIZE;
let target_args_env_address = find_free_target_addr(&tree, args_envs_size_aligned).ok_or(Error::new(ENOMEM))?;
allocate_remote(&grants_fd, memory_scheme_fd, target_args_env_address, args_envs_size_aligned, MapFlags::PROT_READ | MapFlags::PROT_WRITE)?;
tree.insert(target_args_env_address, args_envs_size_aligned);
@@ -195,6 +200,13 @@ where
Ok(address)
};
if let Some(cwd) = extrainfo.cwd {
push(append(cwd)?)?;
push(AT_REDOX_INITIALCWD_PTR)?;
push(cwd.len())?;
push(AT_REDOX_INITIALCWD_LEN)?;
}
push(0)?;
for env in envs {
@@ -424,7 +436,6 @@ fn fork_inner(initial_rsp: *mut usize) -> Result<usize> {
}
copy_str(*cur_pid_fd, *new_pid_fd, "name")?;
copy_str(*cur_pid_fd, *new_pid_fd, "cwd")?;
{
let cur_sigaction_fd = FdGuard::new(syscall::dup(*cur_pid_fd, b"sigactions")?);
+3 -1
View File
@@ -4,7 +4,7 @@ use core::{intrinsics, ptr};
use crate::{
header::{libgen, stdio, stdlib},
ld_so,
platform::{self, new_mspace, types::*, Pal, Sys},
platform::{self, get_auxvs, new_mspace, types::*, Pal, Sys},
ALLOCATOR,
};
@@ -185,6 +185,8 @@ pub unsafe extern "C" fn relibc_start(sp: &'static Stack) -> ! {
platform::OUR_ENVIRON = copy_string_array(envp, len);
platform::environ = platform::OUR_ENVIRON.as_mut_ptr();
}
let auxvs = get_auxvs(sp.auxv().cast());
crate::platform::init(auxvs);
// Setup signal stack, otherwise we cannot handle any signals besides SIG_IGN/SIG_DFL behavior.
#[cfg(target_os = "redox")]