From 2186cd1fbd8a3cd8d8e5664773a6ed657ebdd833 Mon Sep 17 00:00:00 2001 From: 4lDO2 <4lDO2@protonmail.com> Date: Sat, 22 Jan 2022 13:38:13 +0100 Subject: [PATCH 01/23] Implement fexec in userspace. --- .gitmodules | 4 + Cargo.lock | 115 ++++++++------- Cargo.toml | 3 +- Makefile | 29 +--- renamesyms.sh | 22 +++ src/ld_so/tcb.rs | 16 +++ src/platform/redox/exec.rs | 271 ++++++++++++++++++++++++++++++++++++ src/platform/redox/extra.rs | 59 +++++++- src/platform/redox/mod.rs | 63 ++++++--- src/start.rs | 12 ++ 10 files changed, 499 insertions(+), 95 deletions(-) create mode 100755 renamesyms.sh create mode 100644 src/platform/redox/exec.rs diff --git a/.gitmodules b/.gitmodules index aaee5b3c47..6d3198cb81 100644 --- a/.gitmodules +++ b/.gitmodules @@ -13,3 +13,7 @@ [submodule "pthreads-emb"] path = pthreads-emb url = https://gitlab.redox-os.org/redox-os/pthreads-emb.git +[submodule "compiler-builtins"] + path = compiler-builtins + url = https://gitlab.redox-os.org/redox-os/compiler-builtins.git + branch = relibc_fix_dup_symbols diff --git a/Cargo.lock b/Cargo.lock index 7a972425e5..a4199296a8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -42,11 +42,11 @@ checksum = "2db2df1ebc842c41fd2c4ae5b5a577faf63bd5151b953db752fc686812bee318" dependencies = [ "clap", "log", - "proc-macro2 1.0.36", - "quote 1.0.16", + "proc-macro2 1.0.42", + "quote 1.0.20", "serde", "serde_json", - "syn 1.0.89", + "syn 1.0.98", "tempfile", "toml", ] @@ -108,9 +108,9 @@ version = "0.1.0" [[package]] name = "fastrand" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" +checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" dependencies = [ "instant", ] @@ -146,9 +146,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" +checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" [[package]] name = "lazy_static" @@ -165,33 +165,34 @@ version = "0.1.0" [[package]] name = "libc" -version = "0.2.121" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efaa7b300f3b5fe8eb6bf21ce3895e1751d9665086af2d64b42f19701015ff4f" +checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" [[package]] name = "lock_api" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88943dd7ef4a2e5a4bfa2753aaab3013e34ce2533d1996fb18ef591e315e2b3b" +checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" dependencies = [ + "autocfg", "scopeguard", ] [[package]] name = "log" -version = "0.4.16" +version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6389c490849ff5bc16be905ae24bc913a9c8892e19b2341dbc175e14c341c2b8" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" dependencies = [ "cfg-if", ] [[package]] name = "memchr" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "memoffset" @@ -204,9 +205,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" dependencies = [ "autocfg", ] @@ -227,16 +228,16 @@ version = "0.4.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" dependencies = [ - "unicode-xid 0.1.0", + "unicode-xid", ] [[package]] name = "proc-macro2" -version = "1.0.36" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" +checksum = "c278e965f1d8cf32d6e0e96de3d3e79712178ae67986d9cf9151f51e95aac89b" dependencies = [ - "unicode-xid 0.2.2", + "unicode-ident", ] [[package]] @@ -250,11 +251,11 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.16" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4af2ec4714533fcdf07e886f17025ace8b997b9ce51204ee69b6da831c3da57" +checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804" dependencies = [ - "proc-macro2 1.0.36", + "proc-macro2 1.0.42", ] [[package]] @@ -303,6 +304,14 @@ version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" +[[package]] +name = "redox_syscall" +version = "0.2.13" +source = "git+https://gitlab.redox-os.org/4lDO2/syscall.git?branch=userspace_fexec#676b758a4f8de3fa7f22d72f84c0f7b05152c5df" +dependencies = [ + "bitflags", +] + [[package]] name = "redox_syscall" version = "0.2.15" @@ -327,9 +336,9 @@ dependencies = [ "posix-regex", "ralloc", "rand", - "redox_syscall 0.2.15", + "redox_syscall 0.2.13", "sc", - "spin 0.9.2", + "spin 0.9.4", ] [[package]] @@ -361,9 +370,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" +checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" [[package]] name = "sc" @@ -421,29 +430,29 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.136" +version = "1.0.140" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" +checksum = "fc855a42c7967b7c369eb5860f7164ef1f6f81c20c7cc1141f2a604e18723b03" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.136" +version = "1.0.140" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" +checksum = "6f2122636b9fe3b81f1cb25099fcf2d3f542cdb1d45940d56c713158884a05da" dependencies = [ - "proc-macro2 1.0.36", - "quote 1.0.16", - "syn 1.0.89", + "proc-macro2 1.0.42", + "quote 1.0.20", + "syn 1.0.98", ] [[package]] name = "serde_json" -version = "1.0.79" +version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95" +checksum = "82c2c1fdcd807d1098552c5b9a36e425e42e9fbd7c6a37a8425f390f781f7fa7" dependencies = [ "itoa", "ryu", @@ -458,9 +467,9 @@ checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" [[package]] name = "spin" -version = "0.9.2" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "511254be0c5bcf062b019a6c89c01a664aa359ded62f78aa72c6fc137c0590e5" +checksum = "7f6002a767bff9e83f8eeecf883ecb8011875a21ae8da43bffb817a57e78cc09" dependencies = [ "lock_api", ] @@ -479,18 +488,18 @@ checksum = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5" dependencies = [ "proc-macro2 0.4.30", "quote 0.6.13", - "unicode-xid 0.1.0", + "unicode-xid", ] [[package]] name = "syn" -version = "1.0.89" +version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea297be220d52398dcc07ce15a209fce436d361735ac1db700cab3b6cdfb9f54" +checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" dependencies = [ - "proc-macro2 1.0.36", - "quote 1.0.16", - "unicode-xid 0.2.2", + "proc-macro2 1.0.42", + "quote 1.0.20", + "unicode-ident", ] [[package]] @@ -518,9 +527,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.5.8" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" +checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" dependencies = [ "serde", ] @@ -531,6 +540,12 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e92e959f029e4f8ee25d70d15ab58d2b46f98a17bc238b9265ff0c26f6f3d67f" +[[package]] +name = "unicode-ident" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15c61ba63f9235225a22310255a29b806b907c9b8c964bcbd0a2c70f3f2deea7" + [[package]] name = "unicode-width" version = "0.1.9" @@ -543,12 +558,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" -[[package]] -name = "unicode-xid" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" - [[package]] name = "vec_map" version = "0.8.2" diff --git a/Cargo.toml b/Cargo.toml index ba2c6ca44d..f63fe9fb57 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,7 +39,8 @@ optional = true sc = "0.2.3" [target.'cfg(target_os = "redox")'.dependencies] -redox_syscall = "0.2.15" +#redox_syscall = "0.2.15" +redox_syscall = { git = "https://gitlab.redox-os.org/4lDO2/syscall.git", branch = "userspace_fexec" } spin = "0.9.0" [features] diff --git a/Makefile b/Makefile index 0358a30891..e3263879b0 100644 --- a/Makefile +++ b/Makefile @@ -2,13 +2,11 @@ TARGET?=$(shell rustc -Z unstable-options --print target-spec-json | grep llvm-t CARGO?=cargo CARGO_TEST?=$(CARGO) -CARGOFLAGS?=-Z build-std=core,alloc,compiler_builtins +CARGO_COMMON_FLAGS=-Z build-std=core,alloc,compiler_builtins +CARGOFLAGS?=$(CARGO_COMMON_FLAGS) RUSTCFLAGS?= export OBJCOPY?=objcopy -# When using xargo, build it in local location -export XARGO_HOME=$(CURDIR)/target/xargo - BUILD="target/$(TARGET)" CARGOFLAGS+="--target=$(TARGET)" @@ -51,21 +49,7 @@ SRC=\ Cargo.* \ $(shell find src -type f) -# FIXME: Remove the following line. It's only required since xargo automatically links with compiler_builtins, which conflicts with the compiler_builtins that rustc always links with. -WEAKEN_SYMBOLS=\ - -W __divti3 \ - -W __fixdfti \ - -W __floattidf \ - -W __muloti4 \ - -W __udivti3 \ - -W __umodti3 \ - -W __rust_probestack \ - -W __rust_alloc \ - -W __rust_alloc_zeroed \ - -W __rust_dealloc \ - -W __rust_realloc \ - -W __rdl_oom \ - -W __rg_oom +BUILTINS_VERSION=0.1.70 .PHONY: all clean fmt install install-headers libs submodules test @@ -146,8 +130,7 @@ $(BUILD)/debug/libc.so: $(BUILD)/debug/librelibc.a $(BUILD)/pthreads-emb/libpthr $(BUILD)/debug/librelibc.a: $(SRC) CARGO_INCREMENTAL=0 $(CARGO) rustc $(CARGOFLAGS) -- --emit link=$@ $(RUSTCFLAGS) - # FIXME: Remove the following line. It's only required since xargo automatically links with compiler_builtins, which conflicts with the compiler_builtins that rustc always links with. - $(OBJCOPY) $@ $(WEAKEN_SYMBOLS) + ./renamesyms.sh $@ $(BUILD)/debug/deps/ touch $@ $(BUILD)/debug/crt0.o: $(SRC) @@ -185,7 +168,9 @@ $(BUILD)/release/libc.so: $(BUILD)/release/librelibc.a $(BUILD)/pthreads-emb/lib $(BUILD)/release/librelibc.a: $(SRC) CARGO_INCREMENTAL=0 $(CARGO) rustc --release $(CARGOFLAGS) -- --emit link=$@ $(RUSTCFLAGS) - $(OBJCOPY) $@ $(WEAKEN_SYMBOLS) + # TODO: Better to only allow a certain whitelisted set of symbols? Perhaps + # use some cbindgen hook, specify them manually, or grep for #[no_mangle]. + ./renamesyms.sh $@ $(BUILD)/release/deps/ touch $@ $(BUILD)/release/crt0.o: $(SRC) diff --git a/renamesyms.sh b/renamesyms.sh new file mode 100755 index 0000000000..0b534cf579 --- /dev/null +++ b/renamesyms.sh @@ -0,0 +1,22 @@ +#!/bin/sh +target=$1 +deps_dir=$2 + +if [ -z "$target" ] || [ -z "$deps_dir" ]; then + echo "Usage:\n\t./renamesyms.sh TARGET DEPS_DIR" + exit 1 +fi + +symbols_file=`mktemp` + +for dep in `find $deps_dir -type f -name "*.rlib"`; do + nm --format=posix -g "$dep" 2>/dev/null | sed 's/.*:.*//g' | awk '{if ($2 == "T") print $1}' | sed 's/^\(.*\)$/\1 __relibc_\1/g' >> $symbols_file +done + +sorted_file=`mktemp` +sort -u "$symbols_file" > "$sorted_file" +rm -f "$symbols_file" + +objcopy --redefine-syms="$sorted_file" "$target" + +rm -f "$sorted_file" diff --git a/src/ld_so/tcb.rs b/src/ld_so/tcb.rs index 3826322fa7..59f0b90da2 100644 --- a/src/ld_so/tcb.rs +++ b/src/ld_so/tcb.rs @@ -168,6 +168,22 @@ impl Tcb { Self::os_arch_activate(self.tcb_ptr as usize); } + /// Deactive TLS, used before exec() on Redox to not trick target executable into thinking TLS + /// is already initialized as if it was a thread. + #[cfg(all(target_os = "redox", target_arch = "x86_64"))] + pub unsafe fn deactivate() { + let mut env = syscall::EnvRegisters::default(); + + let file = syscall::open("thisproc:current/regs/env", syscall::O_CLOEXEC | syscall::O_WRONLY) + .expect_notls("failed to open handle for process registers"); + + env.fsbase = 0; + env.gsbase = 0; + + let _ = syscall::write(file, &mut env) + .expect_notls("failed to read fsbase"); + } + /// Mapping with correct flags for TCB and TLS unsafe fn map(size: usize) -> Result<&'static mut [u8]> { let ptr = sys_mman::mmap( diff --git a/src/platform/redox/exec.rs b/src/platform/redox/exec.rs new file mode 100644 index 0000000000..9c1cb8fc13 --- /dev/null +++ b/src/platform/redox/exec.rs @@ -0,0 +1,271 @@ +use core::convert::TryFrom; + +use alloc::{ + collections::{btree_map::Entry, BTreeMap}, + vec::Vec, +}; + +use syscall::{ + data::ExecMemRange, + error::{Error, Result, ENOEXEC, ENOMEM}, + flag::{AT_ENTRY, AT_NULL, AT_PHDR, AT_PHENT, AT_PHNUM, MapFlags}, +}; + +fn read_all(fd: usize, offset: u64, buf: &mut [u8]) -> Result<()> { + syscall::lseek(fd, offset as isize, syscall::SEEK_SET).unwrap(); + + let mut total_bytes_read = 0; + + while total_bytes_read < buf.len() { + total_bytes_read += match syscall::read(fd, &mut buf[total_bytes_read..])? { + 0 => return Err(Error::new(ENOEXEC)), + bytes_read => bytes_read, + } + } + Ok(()) +} + +fn find_free_target_addr(tree: &BTreeMap, size: usize) -> Option { + let mut iterator = tree.iter().peekable(); + + // Ignore the space between zero and the first region, to avoid null pointers. + while let Some((cur_address, entry)) = iterator.next() { + let end = *cur_address + entry.size; + + if let Some((next_address, _)) = iterator.peek() { + if **next_address - end > size { + return Some(end); + } + } + // No need to check last entry, since the stack will always be put at the highest + // possible address. + } + + None +} +struct TreeEntry { + size: usize, // always a page-size multiple + flags: MapFlags, + accessible_addr: *mut u8, // also always a page-size multiple +} +impl Drop for TreeEntry { + fn drop(&mut self) { + unsafe { + if !self.accessible_addr.is_null() { + let _ = syscall::funmap(self.accessible_addr as usize, self.size); + } + } + } +} + +#[cfg(target_arch = "x86_64")] +const PAGE_SIZE: usize = 4096; + +const FD_ANONYMOUS: usize = !0; + +pub fn fexec_impl(fd: usize, path: &[u8], args: &[&[u8]], envs: &[&[u8]], args_envs_size_without_nul: usize) -> Result { + let total_args_envs_size = args_envs_size_without_nul + args.len() + envs.len(); + + // Here, we do the minimum part of loading an application, which is what the kernel used to do. + // We load the executable into memory (albeit at different offsets in this executable), fix + // some misalignments, and then execute the SYS_EXEC syscall to replace the program memory + // entirely. + + // TODO: setuid/setgid + // TODO: Introduce RAII guards to all owned allocations so that no leaks occur in case of + // errors. + + use goblin::elf::header::header64::Header; + + let mut header_bytes = [0_u8; core::mem::size_of::
()]; + + read_all(fd, 0, &mut header_bytes)?; + + let header = Header::from_bytes(&header_bytes); + + let instruction_ptr = usize::try_from(header.e_entry).map_err(|_| Error::new(ENOEXEC))?; + + let mut tree = BTreeMap::::new(); + + use goblin::elf64::program_header::{self, ProgramHeader}; + + let phdrs_size = (header.e_phnum as usize) * (header.e_phentsize as usize); + let phdrs_size_aligned = (phdrs_size + PAGE_SIZE - 1) / PAGE_SIZE * PAGE_SIZE; + let phdrs_mem = unsafe { syscall::fmap(FD_ANONYMOUS, &syscall::Map { offset: 0, size: phdrs_size_aligned, address: 0, flags: MapFlags::PROT_WRITE | MapFlags::MAP_PRIVATE })? }; + read_all(fd, header.e_phoff, unsafe { core::slice::from_raw_parts_mut(phdrs_mem as *mut u8, phdrs_size) })?; + + let phdrs = unsafe { core::slice::from_raw_parts(phdrs_mem as *const ProgramHeader, header.e_phnum as usize) }; + + for segment in phdrs { + let mut flags = syscall::PROT_READ; + + // W ^ X. If it is executable, do not allow it to be writable, even if requested + if segment.p_flags & program_header::PF_X == program_header::PF_X { + flags |= syscall::PROT_EXEC; + } else if segment.p_flags & program_header::PF_W == program_header::PF_W { + flags |= syscall::PROT_WRITE; + } + + match segment.p_type { + program_header::PT_LOAD => { + let voff = segment.p_vaddr as usize % PAGE_SIZE; + let vaddr = segment.p_vaddr as usize - voff; + let size = + (segment.p_memsz as usize + voff + PAGE_SIZE - 1) / PAGE_SIZE * PAGE_SIZE; + + if segment.p_filesz > segment.p_memsz { + return Err(Error::new(ENOEXEC)); + } + + let mem = match tree + .range_mut(..=vaddr) + .next_back() + .filter(|(other_vaddr, entry)| **other_vaddr + entry.size > vaddr) + { + None => unsafe { + let mem = syscall::fmap( + FD_ANONYMOUS, + &syscall::Map { + offset: 0, + address: 0, + size, + flags: syscall::PROT_WRITE, + }, + ) + .map_err(|_| Error::new(ENOMEM))? + as *mut u8; + tree.insert( + vaddr, + TreeEntry { + size, + flags, + accessible_addr: mem, + }, + ); + mem + }, + Some(( + _, + &mut TreeEntry { + flags: ref mut f, + accessible_addr, + .. + }, + )) => { + *f |= flags; + accessible_addr + } + }; + read_all(fd, segment.p_offset, unsafe { + core::slice::from_raw_parts_mut(mem.add(voff), segment.p_filesz as usize) + })?; + } + _ => (), + } + } + let (stack_base, mut stack_mem) = unsafe { + let stack_base = syscall::fmap(FD_ANONYMOUS, &syscall::Map { offset: 0, size: STACK_SIZE, address: 0, flags: MapFlags::PROT_WRITE | MapFlags::PROT_READ | MapFlags::MAP_PRIVATE })? as *mut u8; + let stack_mem = stack_base.add(STACK_SIZE).sub(256); + + (stack_base, stack_mem) + }; + + tree.insert(STACK_TOP - STACK_SIZE, TreeEntry { + size: STACK_SIZE, + flags: MapFlags::PROT_READ | MapFlags::PROT_WRITE | MapFlags::MAP_PRIVATE, + accessible_addr: stack_base, + }); + let mut stack_mem = stack_mem.cast::(); + + let target_phdr_address = find_free_target_addr(&tree, phdrs_size_aligned).ok_or(Error::new(ENOMEM))?; + tree.insert(target_phdr_address, TreeEntry { + size: phdrs_size_aligned, + accessible_addr: phdrs_mem as *mut u8, + flags: MapFlags::PROT_READ | MapFlags::MAP_PRIVATE, + }); + + let mut sp = STACK_TOP - 256; + + let mut push = |word: usize| unsafe { + sp -= core::mem::size_of::(); + stack_mem = stack_mem.sub(1); + stack_mem.write(word); + }; + + push(0); + push(AT_NULL); + push(instruction_ptr); + push(AT_ENTRY); + push(target_phdr_address); + push(AT_PHDR); + push(header.e_phnum as usize); + push(AT_PHNUM); + push(header.e_phentsize as usize); + push(AT_PHENT); + + let args_envs_size_aligned = (total_args_envs_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))?; + + unsafe { + let map = syscall::Map { + offset: 0, + flags: MapFlags::PROT_READ | MapFlags::PROT_WRITE | MapFlags::MAP_PRIVATE, + address: 0, + size: args_envs_size_aligned, + }; + let ptr = syscall::fmap(FD_ANONYMOUS, &map)? as *mut u8; + let args_envs_region = core::slice::from_raw_parts_mut(ptr, total_args_envs_size); + let mut offset = 0; + + for collection in &[envs, args] { + push(0); + + for source_slice in collection.iter().rev().copied() { + push(target_args_env_address + offset); + args_envs_region[offset..offset + source_slice.len()].copy_from_slice(source_slice); + offset += source_slice.len() + 1; + } + } + + tree.insert(target_args_env_address, TreeEntry { + accessible_addr: ptr, + size: args_envs_size_aligned, + flags: MapFlags::PROT_READ | MapFlags::MAP_PRIVATE, + }); + } + push(args.len()); + + const STACK_TOP: usize = (1 << 47); + const STACK_SIZE: usize = 1024 * 1024; + + let memranges = tree + .into_iter() + .map(|(address, mut tree_entry)| { + // Prevent use-after-free + let old_address = core::mem::replace(&mut tree_entry.accessible_addr, core::ptr::null_mut()) as usize; + + ExecMemRange { + address, + size: tree_entry.size, + flags: tree_entry.flags.bits(), + old_address, + } + }) + .collect::>(); + + /*unsafe { + let stack = &*(stack_mem as *const crate::start::Stack); + + }*/ + + unsafe { crate::ld_so::tcb::Tcb::deactivate(); } + + // TODO: Restore old name if exec failed? + if let Ok(fd) = syscall::open("thisproc:current/name", syscall::O_WRONLY) { + let _ = syscall::write(fd, path); + let _ = syscall::close(fd); + } + + syscall::exec(&memranges, instruction_ptr, sp)?; + unreachable!(); +} diff --git a/src/platform/redox/extra.rs b/src/platform/redox/extra.rs index 4793096bde..93fa489e47 100644 --- a/src/platform/redox/extra.rs +++ b/src/platform/redox/extra.rs @@ -1,6 +1,8 @@ use core::{ptr, slice}; use core::arch::global_asm; +use syscall::data::CloneInfo; + use crate::platform::{sys::e, types::*}; #[no_mangle] @@ -50,13 +52,66 @@ pub unsafe extern "C" fn redox_physunmap(virtual_address: *mut c_void) -> c_int } extern "C" { - pub fn pte_clone_inner(stack: usize) -> usize; + pub fn pte_clone_inner(info: *const CloneInfo) -> usize; } #[cfg(target_arch = "x86_64")] global_asm!(" .globl pte_clone_inner .type pte_clone_inner, @function + .p2align 6", + // Parameters: in RDI +"pte_clone_inner: + mov rax, {SYS_CLONE} + mov rsi, rdi + mov rdi, {CLONE_FLAGS} + mov rdx, {INFO_LEN}", + // Call clone(flags, info_ptr, info_len) syscall + "syscall + + # Check if child or parent + test rax, rax + jnz .parent + + # Load registers + pop rax + pop rdi + pop rsi + pop rdx + pop rcx + pop r8 + pop r9 + + # Call entry point + call rax + + # Exit + mov rax, {SYS_EXIT} + xor rdi, rdi + syscall + + # Invalid instruction on failure to exit + ud2 + + # Return PID if parent +.parent: + ret + ", + SYS_EXIT = const(syscall::SYS_EXIT), + SYS_CLONE = const(syscall::SYS_CLONE), + CLONE_FLAGS = const( + syscall::CLONE_VM.bits() + | syscall::CLONE_FS.bits() + | syscall::CLONE_FILES.bits() + | syscall::CLONE_SIGHAND.bits() + | syscall::CLONE_STACK.bits() + ), + INFO_LEN = const(core::mem::size_of::()), +); + +/*global_asm!(" + .globl pte_clone_inner + .type pte_clone_inner, @function pte_clone_inner: # Move the 1st argument `stack` of this function into the second argument to clone. @@ -107,4 +162,4 @@ pte_clone_inner: | syscall::CLONE_STACK.bits() ), SYS_CLONE = const(syscall::SYS_CLONE), -); +);*/ diff --git a/src/platform/redox/mod.rs b/src/platform/redox/mod.rs index 9597d15ba2..61695cbfc9 100644 --- a/src/platform/redox/mod.rs +++ b/src/platform/redox/mod.rs @@ -3,7 +3,7 @@ use core::arch::asm; use syscall::{ self, - data::{Map, Stat as redox_stat, StatVfs as redox_statvfs, TimeSpec as redox_timespec}, + data::{CloneInfo, Map, Stat as redox_stat, StatVfs as redox_statvfs, TimeSpec as redox_timespec}, PtraceEvent, Result, }; @@ -34,6 +34,7 @@ static mut BRK_CUR: *mut c_void = ptr::null_mut(); static mut BRK_END: *mut c_void = ptr::null_mut(); mod epoll; +mod exec; mod extra; mod ptrace; mod signal; @@ -220,7 +221,7 @@ impl Pal for Sys { len += 1; } - let mut args: Vec<[usize; 2]> = Vec::with_capacity(len as usize); + let mut args: Vec<&[u8]> = Vec::with_capacity(len as usize); // Read shebang (for example #!/bin/sh) let interpreter = { @@ -301,44 +302,50 @@ impl Pal for Sys { // Make sure path is kept alive long enough, and push it to the arguments _interpreter_path = Some(cstring); let path_ref = _interpreter_path.as_ref().unwrap(); - args.push([path_ref.as_ptr() as usize, path_ref.to_bytes().len()]); + args.push(path_ref.as_bytes()); } else { if file.seek(SeekFrom::Start(0)).is_err() { return -1; } } - // Arguments - while !(*argv).is_null() { - let arg = *argv; + let mut args_envs_size_without_nul = 0; + // Arguments + while !argv.read().is_null() { + let arg = argv.read(); + + // TODO: Optimized strlen? let mut len = 0; - while *arg.offset(len) != 0 { + while arg.add(len).read() != 0 { len += 1; } - args.push([arg as usize, len as usize]); + args.push(core::slice::from_raw_parts(arg as *const u8, len)); + args_envs_size_without_nul += len; argv = argv.offset(1); } // Environment variables let mut len = 0; - while !(*envp.offset(len)).is_null() { + while !envp.add(len).read().is_null() { len += 1; } - let mut envs: Vec<[usize; 2]> = Vec::with_capacity(len as usize); - while !(*envp).is_null() { - let env = *envp; + let mut envs: Vec<&[u8]> = Vec::with_capacity(len); + while !envp.read().is_null() { + let env = envp.read(); + // TODO: Optimized strlen? let mut len = 0; - while *env.offset(len) != 0 { + while env.add(len).read() != 0 { len += 1; } - envs.push([env as usize, len as usize]); - envp = envp.offset(1); + envs.push(core::slice::from_raw_parts(env as *const u8, len)); + args_envs_size_without_nul += len; + envp = envp.add(1); } - e(syscall::fexec(*file as usize, &args, &envs)) as c_int + e(self::exec::fexec_impl(*file as usize, path.to_bytes(), &args, &envs, args_envs_size_without_nul)) as c_int } fn fchdir(fd: c_int) -> c_int { @@ -858,7 +865,29 @@ impl Pal for Sys { #[cfg(target_arch = "x86_64")] unsafe fn pte_clone(stack: *mut usize) -> pid_t { - e(syscall::Error::demux(extra::pte_clone_inner(stack as usize))) as pid_t + let flags = syscall::CLONE_VM + | syscall::CLONE_FS + | syscall::CLONE_FILES + | syscall::CLONE_SIGHAND + | syscall::CLONE_STACK; + let flags = flags.bits(); + + use syscall::{Map, MapFlags}; + + const SIGSTACK_SIZE: usize = 1024 * 256; + + // TODO: Put sigstack at high addresses? + let target_sigstack = match syscall::fmap(!0, &Map { address: 0, flags: MapFlags::PROT_READ | MapFlags::PROT_WRITE | MapFlags::MAP_PRIVATE, offset: 0, size: SIGSTACK_SIZE }) { + Ok(s) => s + SIGSTACK_SIZE, + Err(err) => return e(Err(err)) as pid_t, + }; + + let info = CloneInfo { + target_stack: stack as usize, + target_sigstack, + }; + + e(syscall::Error::demux(extra::pte_clone_inner(&info))) as pid_t } fn read(fd: c_int, buf: &mut [u8]) -> ssize_t { diff --git a/src/start.rs b/src/start.rs index 8e715c47ec..95d0837308 100644 --- a/src/start.rs +++ b/src/start.rs @@ -112,6 +112,15 @@ fn io_init() { stdio::stderr = stdio::default_stderr.get(); } } +fn setup_sigstack() { + use syscall::{Map, MapFlags}; + const SIGSTACK_SIZE: usize = 1024 * 256; + let sigstack = unsafe { syscall::fmap(!0, &Map { address: 0, offset: 0, flags: MapFlags::MAP_PRIVATE | MapFlags::PROT_READ | MapFlags::PROT_WRITE, size: SIGSTACK_SIZE }) }.expect("failed to allocate sigstack") + SIGSTACK_SIZE; + + let fd = syscall::open("thisproc:current/sigstack", syscall::O_WRONLY | syscall::O_CLOEXEC).expect("failed to open thisproc:current/sigstack"); + syscall::write(fd, &usize::to_ne_bytes(sigstack)).expect("failed to write to thisproc:current/sigstack"); + let _ = syscall::close(fd); +} #[inline(never)] #[no_mangle] @@ -156,6 +165,9 @@ pub unsafe extern "C" fn relibc_start(sp: &'static Stack) -> ! { platform::inner_environ = copy_string_array(envp, len); platform::environ = platform::inner_environ.as_mut_ptr(); + // Setup signal stack, otherwise we cannot handle any signals besides SIG_IGN/SIG_DFL behavior. + setup_sigstack(); + init_array(); // Run preinit array From c95d276af9602e0d8c947750d233226bb83d7094 Mon Sep 17 00:00:00 2001 From: 4lDO2 <4lDO2@protonmail.com> Date: Sun, 12 Jun 2022 19:19:13 +0200 Subject: [PATCH 02/23] Allow POSIX's `environ` to be set. Rust's libstd for example uses environ when using the `envs` builder method for spawning processes, and therefore relibc cannot simply assume environ will always point to the internal relibc env var Vec. --- src/header/stdlib/mod.rs | 131 ++++++++++++++++++--------------------- src/platform/mod.rs | 20 +++++- src/start.rs | 4 +- 3 files changed, 80 insertions(+), 75 deletions(-) diff --git a/src/header/stdlib/mod.rs b/src/header/stdlib/mod.rs index db708bcebc..d48b67ac38 100644 --- a/src/header/stdlib/mod.rs +++ b/src/header/stdlib/mod.rs @@ -324,16 +324,7 @@ pub extern "C" fn gcvt(value: c_double, ndigit: c_int, buf: *mut c_char) -> *mut } unsafe fn find_env(search: *const c_char) -> Option<(usize, *mut c_char)> { - for (i, item) in platform::inner_environ.iter().enumerate() { - let mut item = *item; - if item.is_null() { - assert_eq!( - i, - platform::inner_environ.len() - 1, - "an early null pointer in environ vector" - ); - break; - } + for (i, mut item) in platform::environ_iter().enumerate() { let mut search = search; loop { let end_of_query = *search == 0 || *search == b'=' as c_char; @@ -341,7 +332,7 @@ unsafe fn find_env(search: *const c_char) -> Option<(usize, *mut c_char)> { if *item == b'=' as c_char || end_of_query { if *item == b'=' as c_char && end_of_query { // Both keys env here - return Some((i, item.offset(1))); + return Some((i, item.add(1))); } else { break; } @@ -351,10 +342,11 @@ unsafe fn find_env(search: *const c_char) -> Option<(usize, *mut c_char)> { break; } - item = item.offset(1); - search = search.offset(1); + item = item.add(1); + search = search.add(1); } } + None } @@ -694,22 +686,33 @@ pub extern "C" fn ptsname(fildes: c_int) -> *mut c_char { unimplemented!(); } +unsafe fn put_new_env(insert: *mut c_char) { + // XXX: Another problem is that `environ` can be set to any pointer, which means there is a + // chance of a memory leak. But we can check if it was the same as before, like musl does. + if platform::environ == platform::OUR_ENVIRON.as_mut_ptr() { + *platform::OUR_ENVIRON.last_mut().unwrap() = insert; + platform::OUR_ENVIRON.push(core::ptr::null_mut()); + // Likely a no-op but is needed due to Stacked Borrows. + platform::environ = platform::OUR_ENVIRON.as_mut_ptr(); + } else { + platform::OUR_ENVIRON.clear(); + platform::OUR_ENVIRON.extend(platform::environ_iter()); + platform::OUR_ENVIRON.push(insert); + platform::OUR_ENVIRON.push(core::ptr::null_mut()); + platform::environ = platform::OUR_ENVIRON.as_mut_ptr(); + } +} + #[no_mangle] pub unsafe extern "C" fn putenv(insert: *mut c_char) -> c_int { assert_ne!(insert, ptr::null_mut(), "putenv(NULL)"); if let Some((i, _)) = find_env(insert) { - //TODO: find out why this crashes: platform::free(platform::inner_environ[i] as *mut c_void); - platform::inner_environ[i] = insert; + // XXX: The POSIX manual states that environment variables can be *set* via the `environ` + // global variable. While we can check if a pointer belongs to our allocator, or check + // `environ` against a vector which we control, it is likely not worth the effort. + platform::environ.add(i).write(insert); } else { - let i = platform::inner_environ.len() - 1; - assert_eq!( - platform::inner_environ[i], - ptr::null_mut(), - "environ did not end with null" - ); - platform::inner_environ[i] = insert; - platform::inner_environ.push(ptr::null_mut()); - platform::environ = platform::inner_environ.as_mut_ptr(); + put_new_env(insert); } 0 } @@ -850,69 +853,45 @@ pub unsafe extern "C" fn seed48(seed16v: *mut c_ushort) -> *mut c_ushort { rand48::SEED48_XSUBI.as_mut_ptr() } +unsafe fn copy_kv(existing: *mut c_char, key: *const c_char, value: *const c_char, key_len: usize, value_len: usize) { + core::ptr::copy_nonoverlapping(key, existing, key_len); + core::ptr::write(existing.add(key_len), b'=' as c_char); + core::ptr::copy_nonoverlapping(value, existing.add(key_len + 1), value_len); +} + #[no_mangle] pub unsafe extern "C" fn setenv( mut key: *const c_char, mut value: *const c_char, overwrite: c_int, ) -> c_int { - let mut key_len = 0; - while *key.offset(key_len) != 0 { - key_len += 1; - } - let mut value_len = 0; - while *value.offset(value_len) != 0 { - value_len += 1; - } + let key_len = strlen(key); + let value_len = strlen(value); - let index = if let Some((i, existing)) = find_env(key) { + if let Some((i, existing)) = find_env(key) { if overwrite == 0 { return 0; } - let mut existing_len = 0; - while *existing.offset(existing_len) != 0 { - existing_len += 1; - } + let existing_len = strlen(existing); if existing_len >= value_len { // Reuse existing element's allocation - for i in 0..=value_len { - *existing.offset(i) = *value.offset(i); - } - return 0; + core::ptr::copy_nonoverlapping(value, existing, value_len); } else { - i + // Reuse platform::environ slot, but allocate a new pointer. + let mut ptr = platform::alloc(key_len as usize + 1 + value_len as usize) as *mut c_char; + copy_kv(ptr, key, value, key_len, value_len); + platform::environ.add(i).write(ptr); } } else { - let i = platform::inner_environ.len() - 1; - assert_eq!( - platform::inner_environ[i], - ptr::null_mut(), - "environ did not end with null" - ); - platform::inner_environ.push(ptr::null_mut()); - platform::environ = platform::inner_environ.as_mut_ptr(); - i - }; + // Expand platform::environ and allocate a new pointer. + let mut ptr = platform::alloc(key_len as usize + 1 + value_len as usize) as *mut c_char; + copy_kv(ptr, key, value, key_len, value_len); + put_new_env(ptr); + } //platform::free(platform::inner_environ[index] as *mut c_void); - let mut ptr = platform::alloc(key_len as usize + 1 + value_len as usize) as *mut c_char; - platform::inner_environ[index] = ptr; - - while *key != 0 { - *ptr = *key; - ptr = ptr.offset(1); - key = key.offset(1); - } - *ptr = b'=' as c_char; - ptr = ptr.offset(1); - while *value != 0 { - *ptr = *value; - ptr = ptr.offset(1); - value = value.offset(1); - } - *ptr = 0; 0 } @@ -1174,9 +1153,19 @@ pub extern "C" fn unlockpt(fildes: c_int) -> c_int { #[no_mangle] pub unsafe extern "C" fn unsetenv(key: *const c_char) -> c_int { if let Some((i, _)) = find_env(key) { - // No need to worry about updating the pointer, this does not - // reallocate in any way. And the final null is already shifted back. - platform::inner_environ.remove(i); + if platform::environ == platform::OUR_ENVIRON.as_mut_ptr() { + // No need to worry about updating the pointer, this does not + // reallocate in any way. And the final null is already shifted back. + platform::OUR_ENVIRON.remove(i); + + // My UB paranoia. + platform::environ = platform::OUR_ENVIRON.as_mut_ptr(); + } else { + platform::OUR_ENVIRON.clear(); + platform::OUR_ENVIRON.extend(platform::environ_iter().enumerate().filter(|&(j, _)| j != i).map(|(_, v)| v)); + platform::OUR_ENVIRON.push(core::ptr::null_mut()); + platform::environ = platform::OUR_ENVIRON.as_mut_ptr(); + } } 0 } diff --git a/src/platform/mod.rs b/src/platform/mod.rs index 35a35e4f70..890f83245d 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -54,8 +54,24 @@ pub static mut program_invocation_short_name: *mut c_char = ptr::null_mut(); #[allow(non_upper_case_globals)] #[no_mangle] pub static mut environ: *mut *mut c_char = ptr::null_mut(); -#[allow(non_upper_case_globals)] -pub static mut inner_environ: Vec<*mut c_char> = Vec::new(); + +pub static mut OUR_ENVIRON: Vec<*mut c_char> = Vec::new(); + +pub fn environ_iter() -> impl Iterator + 'static { + unsafe { + let mut ptrs = environ; + + core::iter::from_fn(move || { + let ptr = ptrs.read(); + if ptr.is_null() { + None + } else { + ptrs = ptrs.add(1); + Some(ptr) + } + }) + } +} pub trait WriteByte: fmt::Write { fn write_u8(&mut self, byte: u8) -> fmt::Result; diff --git a/src/start.rs b/src/start.rs index 95d0837308..cee25f33d9 100644 --- a/src/start.rs +++ b/src/start.rs @@ -162,8 +162,8 @@ pub unsafe extern "C" fn relibc_start(sp: &'static Stack) -> ! { while !(*envp.add(len)).is_null() { len += 1; } - platform::inner_environ = copy_string_array(envp, len); - platform::environ = platform::inner_environ.as_mut_ptr(); + platform::OUR_ENVIRON = copy_string_array(envp, len); + platform::environ = platform::OUR_ENVIRON.as_mut_ptr(); // Setup signal stack, otherwise we cannot handle any signals besides SIG_IGN/SIG_DFL behavior. setup_sigstack(); From 37cc4e5383cc7cedf11661460c6851fd5ec1dd6f Mon Sep 17 00:00:00 2001 From: 4lDO2 <4lDO2@protonmail.com> Date: Tue, 14 Jun 2022 10:55:35 +0200 Subject: [PATCH 03/23] WIP: Add setuid/setgid support. --- src/platform/redox/mod.rs | 190 +++++++++++++++++++++++--------------- 1 file changed, 117 insertions(+), 73 deletions(-) diff --git a/src/platform/redox/mod.rs b/src/platform/redox/mod.rs index 61695cbfc9..907de338f5 100644 --- a/src/platform/redox/mod.rs +++ b/src/platform/redox/mod.rs @@ -14,10 +14,11 @@ use crate::{ dirent::dirent, errno::{EINVAL, EIO, ENOMEM, EPERM, ERANGE}, fcntl, + string::strlen, sys_mman::{MAP_ANONYMOUS, PROT_READ, PROT_WRITE}, sys_random, sys_resource::{rlimit, RLIM_INFINITY}, - sys_stat::stat, + sys_stat::{stat, S_ISGID, S_ISUID}, sys_statvfs::statvfs, sys_time::{timeval, timezone}, sys_utsname::{utsname, UTSLENGTH}, @@ -65,6 +66,14 @@ pub fn e(sys: Result) -> usize { } } } +fn flatten_with_nul(iter: impl IntoIterator) -> Box<[u8]> where T: AsRef<[u8]> { + let mut vec = Vec::new(); + for item in iter { + vec.extend(item.as_ref()); + vec.push(b'\0'); + } + vec.into_boxed_slice() +} pub struct Sys; @@ -215,81 +224,87 @@ impl Pal for Sys { }; let fd = *file as usize; + // With execve now being implemented in userspace, we need to check ourselves that this + // file is actually executable. While checking for read permission is unnecessary as the + // scheme will not allow us to read otherwise, the execute bit is completely unenforced. We + // have the permission to mmap executable memory and fill it with the program even if it is + // unset, so the best we can do is check that nothing is executed by accident. + // + // TODO: At some point we might have capabilities limiting the ability to allocate + // executable memory, and in that case we might use the `escalate:` scheme as we already do + // when the binary needs setuid/setgid. + + let mut stat = redox_stat::default(); + if e(syscall::fstat(fd, &mut stat)) == !0 { + return -1; + } + let uid = e(syscall::getuid()); + if uid == !0 { + return -1; + } + let gid = e(syscall::getuid()); + if gid == !0 { + return -1; + } + + let mode = if uid == stat.st_uid as usize { + (stat.st_mode >> 3 * 2) & 0o7 + } else if gid == stat.st_gid as usize { + (stat.st_mode >> 3 * 1) & 0o7 + } else { + stat.st_mode & 0o7 + }; + + if mode & 0o1 == 0o0 { + errno = EPERM; + return -1; + } + let wants_setugid = stat.st_mode & ((S_ISUID | S_ISGID) as u16) != 0; + // Count arguments let mut len = 0; - while !(*argv.offset(len)).is_null() { + while !(*argv.add(len)).is_null() { len += 1; } - let mut args: Vec<&[u8]> = Vec::with_capacity(len as usize); + let mut args: Vec<&[u8]> = Vec::with_capacity(len); // Read shebang (for example #!/bin/sh) - let interpreter = { - let mut reader = BufReader::new(&mut file); - - let mut shebang = [0; 2]; + let mut _interpreter_path = None; + let is_interpreted = { let mut read = 0; + let mut shebang = [0; 2]; while read < 2 { - match reader.read(&mut shebang) { + match file.read(&mut shebang) { Ok(0) => break, Ok(i) => read += i, Err(_) => return -1, } } - - if &shebang == b"#!" { - // So, this file is interpreted. - // That means the actual file descriptor passed to `fexec` won't be this file. - // So we need to check ourselves that this file is actually be executable. - - let mut stat = redox_stat::default(); - if e(syscall::fstat(fd, &mut stat)) == !0 { - return -1; - } - let uid = e(syscall::getuid()); - if uid == !0 { - return -1; - } - let gid = e(syscall::getuid()); - if gid == !0 { - return -1; - } - - let mode = if uid == stat.st_uid as usize { - (stat.st_mode >> 3 * 2) & 0o7 - } else if gid == stat.st_gid as usize { - (stat.st_mode >> 3 * 1) & 0o7 - } else { - stat.st_mode & 0o7 - }; - - if mode & 0o1 == 0o0 { - errno = EPERM; - return -1; - } - - // Then, read the actual interpreter: - let mut interpreter = Vec::new(); - match reader.read_until(b'\n', &mut interpreter) { - Err(_) => return -1, - Ok(_) => { - if interpreter.ends_with(&[b'\n']) { - interpreter.pop().unwrap(); - } - // TODO: Returning the interpreter here is actually a - // hack. Preferrably we should reassign `file =` - // directly from here. Just wait until NLL comes - // around... - Some(interpreter) - } - } - } else { - None - } + shebang == *b"#!" }; - let mut _interpreter_path = None; - if let Some(interpreter) = interpreter { + // 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. + // + // 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 + // too. + + if is_interpreted { + // So, this file is interpreted. + // Then, read the actual interpreter: + let mut interpreter = Vec::new(); + if BufReader::new(&mut file).read_until(b'\n', &mut interpreter).is_err() { + return -1; + } + if interpreter.ends_with(&[b'\n']) { + interpreter.pop().unwrap(); + } let cstring = match CString::new(interpreter) { Ok(cstring) => cstring, Err(_) => return -1, @@ -308,21 +323,16 @@ impl Pal for Sys { return -1; } } - let mut args_envs_size_without_nul = 0; // Arguments while !argv.read().is_null() { let arg = argv.read(); - // TODO: Optimized strlen? - let mut len = 0; - while arg.add(len).read() != 0 { - len += 1; - } + let len = strlen(arg); args.push(core::slice::from_raw_parts(arg as *const u8, len)); args_envs_size_without_nul += len; - argv = argv.offset(1); + argv = argv.add(1); } // Environment variables @@ -335,17 +345,51 @@ impl Pal for Sys { while !envp.read().is_null() { let env = envp.read(); - // TODO: Optimized strlen? - let mut len = 0; - while env.add(len).read() != 0 { - len += 1; - } + let len = strlen(env); envs.push(core::slice::from_raw_parts(env as *const u8, len)); args_envs_size_without_nul += len; envp = envp.add(1); } - e(self::exec::fexec_impl(*file as usize, path.to_bytes(), &args, &envs, args_envs_size_without_nul)) as c_int + if !is_interpreted && wants_setugid { + let name = CStr::from_bytes_with_nul(b"escalate:\0").expect("string should be valid"); + // We are now going to invoke `escalate:` rather than loading the program ourselves. + let mut escalate_fd = match File::open(name, fcntl::O_WRONLY | fcntl::O_CLOEXEC) { + Ok(f) => f, + Err(_) => return -1, + }; + + // First, we write the path. + // + // TODO: For improved security, use a hypothetical SYS_DUP_FORWARD syscall to give the + // scheme our file descriptor. It can check through the kernel-overwritten stat.st_dev + // field that it pertains to a "trusted" scheme (i.e. of at least the privilege the + // new uid/gid has), although for now only root can open schemes. Passing a file + // descriptor and not a path will allow escalated to run in a limited namespace. + // + // TODO: Plus, at this point fexecve is not implemented (but specified in + // POSIX.1-2008), and to avoid bad syscalls such as fpath passing a file descriptor + // would be better. + escalate_fd.write_all(path.to_bytes()); + + // Second, we write the flattened args and envs with NUL characters separating + // individual items. + if escalate_fd.write_all(&flatten_with_nul(args)).is_err() { + return -1; + } + if escalate_fd.write_all(&flatten_with_nul(envs)).is_err() { + return -1; + } + + // escalated will take care of the rest when responding to SYS_CLOSE. + if escalate_fd.write(&[]).is_err() { + return -1; + } + + unreachable!() + } else { + e(self::exec::fexec_impl(*file as usize, path.to_bytes(), &args, &envs, args_envs_size_without_nul)) as c_int + } } fn fchdir(fd: c_int) -> c_int { From a7c817d81a4837a7ecefcccc3facdeb1d498e8d5 Mon Sep 17 00:00:00 2001 From: 4lDO2 <4lDO2@protonmail.com> Date: Thu, 16 Jun 2022 14:51:44 +0200 Subject: [PATCH 04/23] WIP: Support setuid/setgid --- src/ld_so/mod.rs | 1 + src/platform/redox/exec.rs | 12 ++++++++---- src/platform/redox/mod.rs | 37 +++++++++++++++++++++++++++++++++---- 3 files changed, 42 insertions(+), 8 deletions(-) diff --git a/src/ld_so/mod.rs b/src/ld_so/mod.rs index 17aad17f45..73c8102e4c 100644 --- a/src/ld_so/mod.rs +++ b/src/ld_so/mod.rs @@ -124,6 +124,7 @@ pub fn static_init(sp: &'static Stack) { tcb.masters_len = mem::size_of::(); tcb.copy_masters().expect_notls("failed to copy TLS master data"); tcb.activate(); + } //TODO: Warning on multiple TLS sections? diff --git a/src/platform/redox/exec.rs b/src/platform/redox/exec.rs index 9c1cb8fc13..1b99048652 100644 --- a/src/platform/redox/exec.rs +++ b/src/platform/redox/exec.rs @@ -11,6 +11,8 @@ use syscall::{ flag::{AT_ENTRY, AT_NULL, AT_PHDR, AT_PHENT, AT_PHNUM, MapFlags}, }; +use crate::fs::File; + fn read_all(fd: usize, offset: u64, buf: &mut [u8]) -> Result<()> { syscall::lseek(fd, offset as isize, syscall::SEEK_SET).unwrap(); @@ -63,7 +65,8 @@ const PAGE_SIZE: usize = 4096; const FD_ANONYMOUS: usize = !0; -pub fn fexec_impl(fd: usize, path: &[u8], args: &[&[u8]], envs: &[&[u8]], args_envs_size_without_nul: usize) -> Result { +pub fn fexec_impl(file: File, path: &[u8], args: &[&[u8]], envs: &[&[u8]], args_envs_size_without_nul: usize) -> Result { + let fd = *file as usize; let total_args_envs_size = args_envs_size_without_nul + args.len() + envs.len(); // Here, we do the minimum part of loading an application, which is what the kernel used to do. @@ -261,10 +264,11 @@ pub fn fexec_impl(fd: usize, path: &[u8], args: &[&[u8]], envs: &[&[u8]], args_e unsafe { crate::ld_so::tcb::Tcb::deactivate(); } // TODO: Restore old name if exec failed? - if let Ok(fd) = syscall::open("thisproc:current/name", syscall::O_WRONLY) { - let _ = syscall::write(fd, path); - let _ = syscall::close(fd); + if let Ok(name_fd) = syscall::open("thisproc:current/name", syscall::O_WRONLY) { + let _ = syscall::write(name_fd, path); + let _ = syscall::close(name_fd); } + drop(file); syscall::exec(&memranges, instruction_ptr, sp)?; unreachable!(); diff --git a/src/platform/redox/mod.rs b/src/platform/redox/mod.rs index 907de338f5..266ffec106 100644 --- a/src/platform/redox/mod.rs +++ b/src/platform/redox/mod.rs @@ -218,7 +218,9 @@ impl Pal for Sys { mut argv: *const *mut c_char, mut envp: *const *mut c_char, ) -> c_int { - let mut file = match File::open(path, fcntl::O_RDONLY | fcntl::O_CLOEXEC) { + // NOTE: We must omit O_CLOEXEC and close manually, otherwise it will be closed before we + // have even read it! + let mut file = match File::open(path, fcntl::O_RDONLY) { Ok(file) => file, Err(_) => return -1, }; @@ -309,7 +311,7 @@ impl Pal for Sys { Ok(cstring) => cstring, Err(_) => return -1, }; - file = match File::open(&cstring, fcntl::O_RDONLY | fcntl::O_CLOEXEC) { + file = match File::open(&cstring, fcntl::O_RDONLY) { Ok(file) => file, Err(_) => return -1, }; @@ -351,6 +353,32 @@ impl Pal for Sys { envp = envp.add(1); } + // Close all O_CLOEXEC file descriptors. TODO: close_range? + { + let name = CStr::from_bytes_with_nul(b"thisproc:current/files\0").expect("string should be valid"); + let files_fd = match File::open(name, fcntl::O_RDONLY) { + Ok(f) => f, + Err(_) => return -1, + }; + for line in BufReader::new(files_fd).lines() { + let line = match line { + Ok(l) => l, + Err(_) => break, + }; + let fd = match line.parse::() { + Ok(f) => f, + Err(_) => continue, + }; + + let flags = Self::fcntl(fd as c_int, fcntl::F_GETFD, 0); + if flags != -1 { + if flags & fcntl::O_CLOEXEC == fcntl::O_CLOEXEC { + let _ = Self::close(fd as c_int); + } + } + } + } + if !is_interpreted && wants_setugid { let name = CStr::from_bytes_with_nul(b"escalate:\0").expect("string should be valid"); // We are now going to invoke `escalate:` rather than loading the program ourselves. @@ -368,7 +396,7 @@ impl Pal for Sys { // descriptor and not a path will allow escalated to run in a limited namespace. // // TODO: Plus, at this point fexecve is not implemented (but specified in - // POSIX.1-2008), and to avoid bad syscalls such as fpath passing a file descriptor + // POSIX.1-2008), and to avoid bad syscalls such as fpath, passing a file descriptor // would be better. escalate_fd.write_all(path.to_bytes()); @@ -382,13 +410,14 @@ impl Pal for Sys { } // escalated will take care of the rest when responding to SYS_CLOSE. + // FIXME: close escalate_fd if escalate_fd.write(&[]).is_err() { return -1; } unreachable!() } else { - e(self::exec::fexec_impl(*file as usize, path.to_bytes(), &args, &envs, args_envs_size_without_nul)) as c_int + e(self::exec::fexec_impl(file, path.to_bytes(), &args, &envs, args_envs_size_without_nul)) as c_int } } From bb45466a4e83bbb39510c19a79fab46b7f716153 Mon Sep 17 00:00:00 2001 From: 4lDO2 <4lDO2@protonmail.com> Date: Wed, 6 Jul 2022 09:11:10 +0200 Subject: [PATCH 05/23] Implement clone in userspace. --- Cargo.lock | 1 + Cargo.toml | 1 + src/lib.rs | 1 + src/platform/pte.rs | 1 - src/platform/redox/exec.rs | 284 +++++++++++++----------------- src/platform/redox/extra.rs | 337 ++++++++++++++++++++++++++---------- src/platform/redox/mod.rs | 28 +-- 7 files changed, 368 insertions(+), 285 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a4199296a8..9c1bc72414 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -333,6 +333,7 @@ dependencies = [ "lazy_static", "memchr", "memoffset", + "plain", "posix-regex", "ralloc", "rand", diff --git a/Cargo.toml b/Cargo.toml index f63fe9fb57..e7c0d78753 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ memoffset = "0.5.1" posix-regex = { path = "posix-regex", features = ["no_std"] } rand = { version = "0.5.5", default-features = false } memchr = { version = "2.2.0", default-features = false } +plain = "0.2" [dependencies.goblin] version = "0.0.21" diff --git a/src/lib.rs b/src/lib.rs index f3883d827d..f03ce4c08b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,7 @@ #![allow(non_upper_case_globals)] #![allow(unused_variables)] #![feature(allocator_api)] +#![feature(array_chunks)] #![feature(asm_const)] #![feature(box_into_pin)] #![feature(c_variadic)] diff --git a/src/platform/pte.rs b/src/platform/pte.rs index fb779c6eba..b2a193f15d 100644 --- a/src/platform/pte.rs +++ b/src/platform/pte.rs @@ -118,7 +118,6 @@ pub unsafe extern "C" fn pte_osThreadCreate( if stack_base as isize == -1 { return PTE_OS_GENERAL_FAILURE; } - ptr::write_bytes(stack_base as *mut u8, 0, stack_size); let stack_end = stack_base.add(stack_size); let mut stack = stack_end as *mut usize; { diff --git a/src/platform/redox/exec.rs b/src/platform/redox/exec.rs index 1b99048652..f2231068b4 100644 --- a/src/platform/redox/exec.rs +++ b/src/platform/redox/exec.rs @@ -1,4 +1,5 @@ use core::convert::TryFrom; +use super::extra::FdGuard; use alloc::{ collections::{btree_map::Entry, BTreeMap}, @@ -7,14 +8,16 @@ use alloc::{ use syscall::{ data::ExecMemRange, - error::{Error, Result, ENOEXEC, ENOMEM}, - flag::{AT_ENTRY, AT_NULL, AT_PHDR, AT_PHENT, AT_PHNUM, MapFlags}, + error::*, + flag::{AT_ENTRY, AT_NULL, AT_PHDR, AT_PHENT, AT_PHNUM, MapFlags, O_WRONLY, SEEK_SET}, }; use crate::fs::File; -fn read_all(fd: usize, offset: u64, buf: &mut [u8]) -> Result<()> { - syscall::lseek(fd, offset as isize, syscall::SEEK_SET).unwrap(); +fn read_all(fd: usize, offset: Option, buf: &mut [u8]) -> Result<()> { + if let Some(offset) = offset { + syscall::lseek(fd, offset as isize, syscall::SEEK_SET).unwrap(); + } let mut total_bytes_read = 0; @@ -27,12 +30,12 @@ fn read_all(fd: usize, offset: u64, buf: &mut [u8]) -> Result<()> { Ok(()) } -fn find_free_target_addr(tree: &BTreeMap, size: usize) -> Option { +fn find_free_target_addr(tree: &BTreeMap, size: usize) -> Option { let mut iterator = tree.iter().peekable(); // Ignore the space between zero and the first region, to avoid null pointers. - while let Some((cur_address, entry)) = iterator.next() { - let end = *cur_address + entry.size; + while let Some((cur_address, entry_size)) = iterator.next() { + let end = *cur_address + entry_size; if let Some((next_address, _)) = iterator.peek() { if **next_address - end > size { @@ -45,20 +48,6 @@ fn find_free_target_addr(tree: &BTreeMap, size: usize) -> Opti None } -struct TreeEntry { - size: usize, // always a page-size multiple - flags: MapFlags, - accessible_addr: *mut u8, // also always a page-size multiple -} -impl Drop for TreeEntry { - fn drop(&mut self) { - unsafe { - if !self.accessible_addr.is_null() { - let _ = syscall::funmap(self.accessible_addr as usize, self.size); - } - } - } -} #[cfg(target_arch = "x86_64")] const PAGE_SIZE: usize = 4096; @@ -66,6 +55,8 @@ const PAGE_SIZE: usize = 4096; const FD_ANONYMOUS: usize = !0; pub fn fexec_impl(file: File, path: &[u8], args: &[&[u8]], envs: &[&[u8]], args_envs_size_without_nul: usize) -> Result { + use goblin::elf64::{header::Header, program_header::program_header64::{ProgramHeader, PT_LOAD, PF_W, PF_X}}; + let fd = *file as usize; let total_args_envs_size = args_envs_size_without_nul + args.len() + envs.len(); @@ -78,198 +69,155 @@ pub fn fexec_impl(file: File, path: &[u8], args: &[&[u8]], envs: &[&[u8]], args_ // TODO: Introduce RAII guards to all owned allocations so that no leaks occur in case of // errors. - use goblin::elf::header::header64::Header; - let mut header_bytes = [0_u8; core::mem::size_of::
()]; - - read_all(fd, 0, &mut header_bytes)?; - + read_all(fd, Some(0), &mut header_bytes)?; let header = Header::from_bytes(&header_bytes); + let grants_fd = { + let current_addrspace_fd = FdGuard::new(syscall::open("thisproc:current/addrspace", 0)?); + FdGuard::new(syscall::dup(*current_addrspace_fd, b"empty")?) + }; + let memory_fd = FdGuard::new(syscall::dup(*grants_fd, b"mem")?); + let instruction_ptr = usize::try_from(header.e_entry).map_err(|_| Error::new(ENOEXEC))?; - let mut tree = BTreeMap::::new(); + // Never allow more than 1 MiB of program headers. TODO: Capabilities again? + const MAX_PH_SIZE: usize = 1024 * 1024; + let phentsize = u64::from(header.e_phentsize) as usize; + let phnum = u64::from(header.e_phnum) as usize; + let pheaders_size = phentsize.saturating_mul(phnum); - use goblin::elf64::program_header::{self, ProgramHeader}; + if pheaders_size > MAX_PH_SIZE { + return Err(Error::new(E2BIG)); + } + let mut phs = vec! [0_u8; pheaders_size]; - let phdrs_size = (header.e_phnum as usize) * (header.e_phentsize as usize); - let phdrs_size_aligned = (phdrs_size + PAGE_SIZE - 1) / PAGE_SIZE * PAGE_SIZE; - let phdrs_mem = unsafe { syscall::fmap(FD_ANONYMOUS, &syscall::Map { offset: 0, size: phdrs_size_aligned, address: 0, flags: MapFlags::PROT_WRITE | MapFlags::MAP_PRIVATE })? }; - read_all(fd, header.e_phoff, unsafe { core::slice::from_raw_parts_mut(phdrs_mem as *mut u8, phdrs_size) })?; + let mut tree = BTreeMap::new(); + tree.insert(0, PAGE_SIZE); - let phdrs = unsafe { core::slice::from_raw_parts(phdrs_mem as *const ProgramHeader, header.e_phnum as usize) }; + const BUFSZ: usize = 16384; + let mut buf = vec! [0_u8; BUFSZ]; - for segment in phdrs { + read_all(*file as usize, Some(header.e_phoff), &mut phs).map_err(|_| Error::new(EIO))?; + + for ph_idx in 0..phnum { + let ph_bytes = &phs[ph_idx * phentsize..(ph_idx + 1) * phentsize]; + let segment: &ProgramHeader = plain::from_bytes(ph_bytes).map_err(|_| Error::new(EINVAL))?; let mut flags = syscall::PROT_READ; // W ^ X. If it is executable, do not allow it to be writable, even if requested - if segment.p_flags & program_header::PF_X == program_header::PF_X { + if segment.p_flags & PF_X == PF_X { flags |= syscall::PROT_EXEC; - } else if segment.p_flags & program_header::PF_W == program_header::PF_W { + } else if segment.p_flags & PF_W == PF_W { flags |= syscall::PROT_WRITE; } - match segment.p_type { - program_header::PT_LOAD => { - let voff = segment.p_vaddr as usize % PAGE_SIZE; - let vaddr = segment.p_vaddr as usize - voff; - let size = - (segment.p_memsz as usize + voff + PAGE_SIZE - 1) / PAGE_SIZE * PAGE_SIZE; + let voff = segment.p_vaddr as usize % PAGE_SIZE; + let vaddr = segment.p_vaddr as usize - voff; + let size = + (segment.p_memsz as usize + voff + PAGE_SIZE - 1) / PAGE_SIZE * PAGE_SIZE; - if segment.p_filesz > segment.p_memsz { - return Err(Error::new(ENOEXEC)); - } + if segment.p_filesz > segment.p_memsz { + return Err(Error::new(ENOEXEC)); + } + if segment.p_type == PT_LOAD { + mprotect_remote(*grants_fd, vaddr, size, flags)?; + syscall::lseek(*file as usize, segment.p_offset as isize, SEEK_SET).map_err(|_| Error::new(EIO))?; + syscall::lseek(*memory_fd, segment.p_vaddr as isize, SEEK_SET).map_err(|_| Error::new(EIO))?; - let mem = match tree - .range_mut(..=vaddr) - .next_back() - .filter(|(other_vaddr, entry)| **other_vaddr + entry.size > vaddr) - { - None => unsafe { - let mem = syscall::fmap( - FD_ANONYMOUS, - &syscall::Map { - offset: 0, - address: 0, - size, - flags: syscall::PROT_WRITE, - }, - ) - .map_err(|_| Error::new(ENOMEM))? - as *mut u8; - tree.insert( - vaddr, - TreeEntry { - size, - flags, - accessible_addr: mem, - }, - ); - mem - }, - Some(( - _, - &mut TreeEntry { - flags: ref mut f, - accessible_addr, - .. - }, - )) => { - *f |= flags; - accessible_addr - } - }; - read_all(fd, segment.p_offset, unsafe { - core::slice::from_raw_parts_mut(mem.add(voff), segment.p_filesz as usize) - })?; + for size in core::iter::repeat(BUFSZ).take((segment.p_filesz as usize) / BUFSZ).chain(Some((segment.p_filesz as usize) % BUFSZ)) { + read_all(*file as usize, None, &mut buf[..size]).map_err(|_| Error::new(EIO))?; + let _ = syscall::write(*memory_fd, &buf[..size]).map_err(|_| Error::new(EIO))?; + } + + if !tree.range(..=vaddr).next_back().filter(|(start, size)| **start + **size > vaddr).is_some() { + tree.insert(vaddr, size); } - _ => (), } } - let (stack_base, mut stack_mem) = unsafe { - let stack_base = syscall::fmap(FD_ANONYMOUS, &syscall::Map { offset: 0, size: STACK_SIZE, address: 0, flags: MapFlags::PROT_WRITE | MapFlags::PROT_READ | MapFlags::MAP_PRIVATE })? as *mut u8; - let stack_mem = stack_base.add(STACK_SIZE).sub(256); + // Setup a stack starting from the very end of the address space, and then growing downwards. + const STACK_TOP: usize = 1 << 47; + const STACK_SIZE: usize = 1024 * 1024; - (stack_base, stack_mem) - }; - - tree.insert(STACK_TOP - STACK_SIZE, TreeEntry { - size: STACK_SIZE, - flags: MapFlags::PROT_READ | MapFlags::PROT_WRITE | MapFlags::MAP_PRIVATE, - accessible_addr: stack_base, - }); - let mut stack_mem = stack_mem.cast::(); - - let target_phdr_address = find_free_target_addr(&tree, phdrs_size_aligned).ok_or(Error::new(ENOMEM))?; - tree.insert(target_phdr_address, TreeEntry { - size: phdrs_size_aligned, - accessible_addr: phdrs_mem as *mut u8, - flags: MapFlags::PROT_READ | MapFlags::MAP_PRIVATE, - }); + mprotect_remote(*grants_fd, STACK_TOP - STACK_SIZE, STACK_SIZE, MapFlags::PROT_READ | MapFlags::PROT_WRITE)?; + tree.insert(STACK_TOP - STACK_SIZE, STACK_SIZE); let mut sp = STACK_TOP - 256; - let mut push = |word: usize| unsafe { + let mut push = |word: usize| { sp -= core::mem::size_of::(); - stack_mem = stack_mem.sub(1); - stack_mem.write(word); + let _ = syscall::lseek(*memory_fd, sp as isize, SEEK_SET)?; + let _ = syscall::write(*memory_fd, &usize::to_ne_bytes(word))?; + Ok(()) }; - push(0); - push(AT_NULL); - push(instruction_ptr); - push(AT_ENTRY); - push(target_phdr_address); - push(AT_PHDR); - push(header.e_phnum as usize); - push(AT_PHNUM); - push(header.e_phentsize as usize); - push(AT_PHENT); + let pheaders_size_aligned = (pheaders_size+PAGE_SIZE-1)/PAGE_SIZE*PAGE_SIZE; + let pheaders = find_free_target_addr(&tree, pheaders_size_aligned).ok_or(Error::new(ENOMEM))?; + tree.insert(pheaders, pheaders_size_aligned); + mprotect_remote(*grants_fd, pheaders, pheaders_size_aligned, MapFlags::PROT_READ)?; + + syscall::lseek(*memory_fd, pheaders as isize, SEEK_SET).map_err(|_| Error::new(EIO))?; + syscall::write(*memory_fd, &phs).map_err(|_| Error::new(EIO))?; + + push(0)?; + push(AT_NULL)?; + push(header.e_entry as usize)?; + push(AT_ENTRY)?; + push(pheaders)?; + push(AT_PHDR)?; + push(header.e_phnum as usize)?; + push(AT_PHNUM)?; + push(header.e_phentsize as usize)?; + push(AT_PHENT)?; let args_envs_size_aligned = (total_args_envs_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))?; + mprotect_remote(*grants_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); - unsafe { - let map = syscall::Map { - offset: 0, - flags: MapFlags::PROT_READ | MapFlags::PROT_WRITE | MapFlags::MAP_PRIVATE, - address: 0, - size: args_envs_size_aligned, - }; - let ptr = syscall::fmap(FD_ANONYMOUS, &map)? as *mut u8; - let args_envs_region = core::slice::from_raw_parts_mut(ptr, total_args_envs_size); - let mut offset = 0; + let mut offset = 0; - for collection in &[envs, args] { - push(0); + let mut argc = 0; - for source_slice in collection.iter().rev().copied() { - push(target_args_env_address + offset); - args_envs_region[offset..offset + source_slice.len()].copy_from_slice(source_slice); - offset += source_slice.len() + 1; - } + for (collection, is_args) in [(envs, false), (args, true)] { + push(0)?; + + for source_slice in collection.iter().rev() { + if is_args { argc += 1; } + push(target_args_env_address + offset)?; + + syscall::lseek(*memory_fd, (target_args_env_address + offset) as isize, SEEK_SET).map_err(|_| Error::new(EIO))?; + let _ = syscall::write(*memory_fd, source_slice).map_err(|_| Error::new(EIO))?; + offset += source_slice.len() + 1; } - - tree.insert(target_args_env_address, TreeEntry { - accessible_addr: ptr, - size: args_envs_size_aligned, - flags: MapFlags::PROT_READ | MapFlags::MAP_PRIVATE, - }); } - push(args.len()); - const STACK_TOP: usize = (1 << 47); - const STACK_SIZE: usize = 1024 * 1024; - - let memranges = tree - .into_iter() - .map(|(address, mut tree_entry)| { - // Prevent use-after-free - let old_address = core::mem::replace(&mut tree_entry.accessible_addr, core::ptr::null_mut()) as usize; - - ExecMemRange { - address, - size: tree_entry.size, - flags: tree_entry.flags.bits(), - old_address, - } - }) - .collect::>(); - - /*unsafe { - let stack = &*(stack_mem as *const crate::start::Stack); - - }*/ + push(argc)?; unsafe { crate::ld_so::tcb::Tcb::deactivate(); } // TODO: Restore old name if exec failed? - if let Ok(name_fd) = syscall::open("thisproc:current/name", syscall::O_WRONLY) { + if let Ok(name_fd) = syscall::open("thisproc:current/name", O_WRONLY) { let _ = syscall::write(name_fd, path); let _ = syscall::close(name_fd); } drop(file); - syscall::exec(&memranges, instruction_ptr, sp)?; + let addrspace_selection_fd = FdGuard::new(syscall::open("thisproc:current/current-addrspace", O_WRONLY)?); + + let mut buf = [0_u8; 24]; + buf[..8].copy_from_slice(&usize::to_ne_bytes(*grants_fd)); + buf[8..16].copy_from_slice(&usize::to_ne_bytes(sp)); + buf[16..24].copy_from_slice(&usize::to_ne_bytes(header.e_entry as usize)); + + let _ = syscall::write(*addrspace_selection_fd, &buf); unreachable!(); } +fn mprotect_remote(socket: usize, addr: usize, len: usize, flags: MapFlags) -> Result<()> { + let mut grants_buf = [0_u8; 24]; + grants_buf[..8].copy_from_slice(&usize::to_ne_bytes(addr)); + grants_buf[8..16].copy_from_slice(&usize::to_ne_bytes(len)); + grants_buf[16..24].copy_from_slice(&usize::to_ne_bytes(flags.bits())); + syscall::write(socket, &grants_buf)?; + Ok(()) +} diff --git a/src/platform/redox/extra.rs b/src/platform/redox/extra.rs index 93fa489e47..dc7f46868b 100644 --- a/src/platform/redox/extra.rs +++ b/src/platform/redox/extra.rs @@ -1,7 +1,10 @@ -use core::{ptr, slice}; +use core::{mem, ptr, slice}; use core::arch::global_asm; -use syscall::data::CloneInfo; +use syscall::data::Map; +use syscall::flag::{MapFlags, O_CLOEXEC}; +use syscall::error::{Error, Result, EINVAL, ENAMETOOLONG}; +use syscall::SIGCONT; use crate::platform::{sys::e, types::*}; @@ -51,115 +54,267 @@ pub unsafe extern "C" fn redox_physunmap(virtual_address: *mut c_void) -> c_int e(syscall::physunmap(virtual_address as usize)) as c_int } -extern "C" { - pub fn pte_clone_inner(info: *const CloneInfo) -> usize; +pub struct FdGuard { + fd: usize, + taken: bool, +} +impl FdGuard { + pub fn new(fd: usize) -> Self { + Self { + fd, taken: false, + } + } + pub fn take(&mut self) -> usize { + self.taken = true; + self.fd + } +} +impl core::ops::Deref for FdGuard { + type Target = usize; + + fn deref(&self) -> &Self::Target { + &self.fd + } } +impl Drop for FdGuard { + fn drop(&mut self) { + if !self.taken { + let _ = syscall::close(self.fd); + } + } +} + +fn new_context() -> Result<(FdGuard, usize)> { + // Create a new context (fields such as uid/gid will be inherited from the current context). + let fd = FdGuard::new(syscall::open("thisproc:new/open_via_dup", O_CLOEXEC)?); + + // Extract pid. + let mut buffer = [0_u8; 64]; + let len = syscall::fpath(*fd, &mut buffer)?; + let buffer = buffer.get(..len).ok_or(Error::new(ENAMETOOLONG))?; + + let colon_idx = buffer.iter().position(|c| *c == b':').ok_or(Error::new(EINVAL))?; + let slash_idx = buffer.iter().skip(colon_idx).position(|c| *c == b'/').ok_or(Error::new(EINVAL))? + colon_idx; + let pid_bytes = buffer.get(colon_idx + 1..slash_idx).ok_or(Error::new(EINVAL))?; + let pid_str = core::str::from_utf8(pid_bytes).map_err(|_| Error::new(EINVAL))?; + let pid = pid_str.parse::().map_err(|_| Error::new(EINVAL))?; + + Ok((fd, pid)) +} + +fn copy_str(cur_pid_fd: usize, new_pid_fd: usize, key: &str) -> Result<()> { + let cur_name_fd = FdGuard::new(syscall::dup(cur_pid_fd, key.as_bytes())?); + let new_name_fd = FdGuard::new(syscall::dup(new_pid_fd, key.as_bytes())?); + + let mut buf = [0_u8; 256]; + let len = syscall::read(*cur_name_fd, &mut buf)?; + let buf = buf.get(..len).ok_or(Error::new(ENAMETOOLONG))?; + + syscall::write(*new_name_fd, &buf)?; + + Ok(()) +} #[cfg(target_arch = "x86_64")] -global_asm!(" - .globl pte_clone_inner - .type pte_clone_inner, @function - .p2align 6", - // Parameters: in RDI -"pte_clone_inner: - mov rax, {SYS_CLONE} - mov rsi, rdi - mov rdi, {CLONE_FLAGS} - mov rdx, {INFO_LEN}", - // Call clone(flags, info_ptr, info_len) syscall - "syscall +fn copy_float_env_regs(cur_pid_fd: usize, new_pid_fd: usize) -> Result<()> { + // Copy environment registers. + { + let cur_env_regs_fd = FdGuard::new(syscall::dup(cur_pid_fd, b"regs/env")?); + let new_env_regs_fd = FdGuard::new(syscall::dup(new_pid_fd, b"regs/env")?); - # Check if child or parent - test rax, rax - jnz .parent + let mut env_regs = syscall::EnvRegisters::default(); + let _ = syscall::read(*cur_env_regs_fd, &mut env_regs)?; + let _ = syscall::write(*new_env_regs_fd, &env_regs)?; + } + // Copy float registers. + { + let cur_float_regs_fd = FdGuard::new(syscall::dup(cur_pid_fd, b"regs/float")?); + let new_float_regs_fd = FdGuard::new(syscall::dup(new_pid_fd, b"regs/float")?); - # Load registers - pop rax - pop rdi - pop rsi - pop rdx - pop rcx - pop r8 - pop r9 + let mut float_regs = syscall::FloatRegisters::default(); + let _ = syscall::read(*cur_float_regs_fd, &mut float_regs)?; + let _ = syscall::write(*new_float_regs_fd, &float_regs)?; + } - # Call entry point - call rax + Ok(()) +} - # Exit - mov rax, {SYS_EXIT} - xor rdi, rdi - syscall +/// Spawns a new context sharing the same address space as the current one (i.e. a new thread). +pub unsafe fn pte_clone_impl(stack: *mut usize) -> Result { + let cur_pid_fd = FdGuard::new(syscall::open("thisproc:current/open_via_dup", O_CLOEXEC)?); + let (new_pid_fd, new_pid) = new_context()?; - # Invalid instruction on failure to exit - ud2 + // Allocate a new signal stack. + { + let sigstack_fd = FdGuard::new(syscall::dup(*new_pid_fd, b"sigstack")?); - # Return PID if parent -.parent: - ret - ", - SYS_EXIT = const(syscall::SYS_EXIT), - SYS_CLONE = const(syscall::SYS_CLONE), - CLONE_FLAGS = const( - syscall::CLONE_VM.bits() - | syscall::CLONE_FS.bits() - | syscall::CLONE_FILES.bits() - | syscall::CLONE_SIGHAND.bits() - | syscall::CLONE_STACK.bits() - ), - INFO_LEN = const(core::mem::size_of::()), -); + const SIGSTACK_SIZE: usize = 1024 * 256; -/*global_asm!(" - .globl pte_clone_inner - .type pte_clone_inner, @function + // TODO: Put sigstack at high addresses? + let target_sigstack = syscall::fmap(!0, &Map { address: 0, flags: MapFlags::PROT_READ | MapFlags::PROT_WRITE | MapFlags::MAP_PRIVATE, offset: 0, size: SIGSTACK_SIZE })? + SIGSTACK_SIZE; -pte_clone_inner: - # Move the 1st argument `stack` of this function into the second argument to clone. - mov rsi, rdi - mov rax, {SYS_CLONE} - mov rdi, {flags} + let _ = syscall::write(*sigstack_fd, &usize::to_ne_bytes(target_sigstack))?; + } - # Call clone syscall - syscall + copy_str(*cur_pid_fd, *new_pid_fd, "name")?; + copy_str(*cur_pid_fd, *new_pid_fd, "cwd")?; - # Check if child or parent - test rax, rax - jnz 2f + // Reuse existing address space + { + let cur_addr_space_fd = FdGuard::new(syscall::dup(*cur_pid_fd, b"addrspace")?); + let new_addr_space_sel_fd = FdGuard::new(syscall::dup(*new_pid_fd, b"current-addrspace")?); - # Load registers - pop rax - pop rdi - pop rsi - pop rdx - pop rcx - pop r8 - pop r9 + let buf = create_set_addr_space_buf(*cur_addr_space_fd, pte_clone_ret as usize, stack as usize); + let _ = syscall::write(*new_addr_space_sel_fd, &buf)?; + } - # Call entry point - call rax + // Reuse file table + { + let cur_filetable_fd = FdGuard::new(syscall::dup(*cur_pid_fd, b"filetable")?); + let new_filetable_sel_fd = FdGuard::new(syscall::dup(*new_pid_fd, b"current-filetable")?); - # Exit - mov rax, 1 - xor rdi, rdi - syscall + let _ = syscall::write(*new_filetable_sel_fd, &usize::to_ne_bytes(*cur_filetable_fd))?; + } - # Invalid instruction on failure to exit - ud2 - # Return PID if parent + copy_float_env_regs(*cur_pid_fd, *new_pid_fd)?; + + // Unblock context. + syscall::kill(new_pid, SIGCONT); + + Ok(0) +} +fn create_set_addr_space_buf(space: usize, ip: usize, sp: usize) -> [u8; mem::size_of::() * 3] { + let mut buf = [0_u8; 3 * mem::size_of::()]; + let mut chunks = buf.array_chunks_mut::<{mem::size_of::()}>(); + *chunks.next().unwrap() = usize::to_ne_bytes(space); + *chunks.next().unwrap() = usize::to_ne_bytes(sp); + *chunks.next().unwrap() = usize::to_ne_bytes(ip); + buf +} +/// Spawns a new context which will not share the same address space as the current one. File +/// descriptors from other schemes are reobtained with `dup`, and grants referencing such file +/// descriptors are reobtained through `fmap`. Other mappings are kept but duplicated using CoW. +pub fn fork_impl() -> Result { + unsafe { + Error::demux(fork_wrapper()) + } +} + +fn fork_inner(initial_rsp: *mut usize) -> Result { + let new_pid = { + let cur_pid_fd = FdGuard::new(syscall::open("thisproc:current/open_via_dup", O_CLOEXEC)?); + let (new_pid_fd, new_pid) = new_context()?; + + // Do not allocate new signal stack, but copy existing address (all memory will be re-mapped + // CoW later). + { + let cur_sigstack_fd = FdGuard::new(syscall::dup(*cur_pid_fd, b"sigstack")?); + let new_sigstack_fd = FdGuard::new(syscall::dup(*new_pid_fd, b"sigstack")?); + + let mut sigstack_buf = usize::to_ne_bytes(0); + + let _ = syscall::read(*cur_sigstack_fd, &mut sigstack_buf); + let _ = syscall::write(*new_sigstack_fd, &sigstack_buf); + } + + copy_str(*cur_pid_fd, *new_pid_fd, "name")?; + copy_str(*cur_pid_fd, *new_pid_fd, "cwd")?; + + // CoW-duplicate address space. + { + let cur_addr_space_fd = FdGuard::new(syscall::dup(*cur_pid_fd, b"addrspace")?); + + // FIXME: Find mappings which use external file descriptors + + let new_addr_space_fd = FdGuard::new(syscall::dup(*cur_addr_space_fd, b"exclusive")?); + let new_addr_space_sel_fd = FdGuard::new(syscall::dup(*new_pid_fd, b"current-addrspace")?); + + let buf = create_set_addr_space_buf(*new_addr_space_fd, fork_ret as usize, initial_rsp as usize); + let _ = syscall::write(*new_addr_space_sel_fd, &buf)?; + } + + // Copy existing files into new file table, but do not reuse the same file table (i.e. new + // parent FDs will not show up for the child). + { + let cur_filetable_fd = FdGuard::new(syscall::dup(*cur_pid_fd, b"filetable")?); + // TODO: Use cross_scheme_links or something similar to avoid copying the file table in the + // kernel. + let new_filetable_fd = FdGuard::new(syscall::dup(*cur_filetable_fd, b"copy")?); + let new_filetable_sel_fd = FdGuard::new(syscall::dup(*new_pid_fd, b"current-filetable")?); + + let _ = syscall::write(*new_filetable_sel_fd, &usize::to_ne_bytes(*new_filetable_fd)); + } + copy_float_env_regs(*cur_pid_fd, *new_pid_fd)?; + + new_pid + }; + + // Unblock context. + syscall::kill(new_pid, SIGCONT); + + Ok(new_pid) +} +#[no_mangle] +unsafe extern "sysv64" fn __relibc_internal_fork_impl(initial_rsp: *mut usize) -> usize { + Error::mux(fork_inner(initial_rsp)) +} + +core::arch::global_asm!(" + .p2align 6 + .globl fork_wrapper + .type fork_wrapper, @function +fork_wrapper: + push rbp + mov rbp, rsp + + push rbx + push rbp + push r12 + push r13 + push r14 + push r15 + + mov rdi, rsp + call __relibc_internal_fork_impl + jmp 2f + +fork_ret: + xor rax, rax 2: + pop r15 + pop r14 + pop r13 + pop r12 + pop rbp + pop rbx + + pop rbp ret + .size fork_wrapper, . - fork_wrapper - .size pte_clone_inner, . - pte_clone_inner + .globl pte_clone_ret + .type pte_clone_ret, @function +pte_clone_ret: - ", + # Load registers + pop rax + pop rdi + pop rsi + pop rdx + pop rcx + pop r8 + pop r9 - flags = const( - syscall::CLONE_VM.bits() - | syscall::CLONE_FS.bits() - | syscall::CLONE_FILES.bits() - | syscall::CLONE_SIGHAND.bits() - | syscall::CLONE_STACK.bits() - ), - SYS_CLONE = const(syscall::SYS_CLONE), -);*/ + # Call entry point + call rax + + ret + .size pte_clone_ret, . - pte_clone_ret +"); + +extern "sysv64" { + fn fork_wrapper() -> usize; + fn fork_ret(); + fn pte_clone_ret(); +} diff --git a/src/platform/redox/mod.rs b/src/platform/redox/mod.rs index 266ffec106..a51b56dce8 100644 --- a/src/platform/redox/mod.rs +++ b/src/platform/redox/mod.rs @@ -355,7 +355,7 @@ impl Pal for Sys { // Close all O_CLOEXEC file descriptors. TODO: close_range? { - let name = CStr::from_bytes_with_nul(b"thisproc:current/files\0").expect("string should be valid"); + let name = CStr::from_bytes_with_nul(b"thisproc:current/filetable\0").expect("string should be valid"); let files_fd = match File::open(name, fcntl::O_RDONLY) { Ok(f) => f, Err(_) => return -1, @@ -455,7 +455,7 @@ impl Pal for Sys { } fn fork() -> pid_t { - e(unsafe { syscall::clone(syscall::CloneFlags::empty()) }) as pid_t + e(extra::fork_impl()) as pid_t } fn fstat(fildes: c_int, buf: *mut stat) -> c_int { @@ -938,29 +938,7 @@ impl Pal for Sys { #[cfg(target_arch = "x86_64")] unsafe fn pte_clone(stack: *mut usize) -> pid_t { - let flags = syscall::CLONE_VM - | syscall::CLONE_FS - | syscall::CLONE_FILES - | syscall::CLONE_SIGHAND - | syscall::CLONE_STACK; - let flags = flags.bits(); - - use syscall::{Map, MapFlags}; - - const SIGSTACK_SIZE: usize = 1024 * 256; - - // TODO: Put sigstack at high addresses? - let target_sigstack = match syscall::fmap(!0, &Map { address: 0, flags: MapFlags::PROT_READ | MapFlags::PROT_WRITE | MapFlags::MAP_PRIVATE, offset: 0, size: SIGSTACK_SIZE }) { - Ok(s) => s + SIGSTACK_SIZE, - Err(err) => return e(Err(err)) as pid_t, - }; - - let info = CloneInfo { - target_stack: stack as usize, - target_sigstack, - }; - - e(syscall::Error::demux(extra::pte_clone_inner(&info))) as pid_t + e(extra::pte_clone_impl(stack)) as pid_t } fn read(fd: c_int, buf: &mut [u8]) -> ssize_t { From 777a82b50f13cdd2d2b17481b604ff39470b3232 Mon Sep 17 00:00:00 2001 From: 4lDO2 <4lDO2@protonmail.com> Date: Thu, 7 Jul 2022 12:00:56 +0200 Subject: [PATCH 06/23] Refactor out clone, fix some fd leaks. --- src/platform/redox/clone.rs | 278 ++++++++++++++++++++++++++++++++++++ src/platform/redox/exec.rs | 52 ++++--- src/platform/redox/extra.rs | 240 +------------------------------ src/platform/redox/mod.rs | 13 +- 4 files changed, 324 insertions(+), 259 deletions(-) create mode 100644 src/platform/redox/clone.rs diff --git a/src/platform/redox/clone.rs b/src/platform/redox/clone.rs new file mode 100644 index 0000000000..8877ef91e1 --- /dev/null +++ b/src/platform/redox/clone.rs @@ -0,0 +1,278 @@ +use core::arch::global_asm; +use core::mem::size_of; + +use alloc::boxed::Box; +use alloc::vec::Vec; + +use syscall::data::Map; +use syscall::flag::{MapFlags, O_CLOEXEC}; +use syscall::error::{Error, Result, EINVAL, ENAMETOOLONG}; +use syscall::SIGCONT; + +use super::extra::{create_set_addr_space_buf, FdGuard}; + +fn new_context() -> Result<(FdGuard, usize)> { + // Create a new context (fields such as uid/gid will be inherited from the current context). + let fd = FdGuard::new(syscall::open("thisproc:new/open_via_dup", O_CLOEXEC)?); + + // Extract pid. + let mut buffer = [0_u8; 64]; + let len = syscall::fpath(*fd, &mut buffer)?; + let buffer = buffer.get(..len).ok_or(Error::new(ENAMETOOLONG))?; + + let colon_idx = buffer.iter().position(|c| *c == b':').ok_or(Error::new(EINVAL))?; + let slash_idx = buffer.iter().skip(colon_idx).position(|c| *c == b'/').ok_or(Error::new(EINVAL))? + colon_idx; + let pid_bytes = buffer.get(colon_idx + 1..slash_idx).ok_or(Error::new(EINVAL))?; + let pid_str = core::str::from_utf8(pid_bytes).map_err(|_| Error::new(EINVAL))?; + let pid = pid_str.parse::().map_err(|_| Error::new(EINVAL))?; + + Ok((fd, pid)) +} + +fn copy_str(cur_pid_fd: usize, new_pid_fd: usize, key: &str) -> Result<()> { + let cur_name_fd = FdGuard::new(syscall::dup(cur_pid_fd, key.as_bytes())?); + let new_name_fd = FdGuard::new(syscall::dup(new_pid_fd, key.as_bytes())?); + + let mut buf = [0_u8; 256]; + let len = syscall::read(*cur_name_fd, &mut buf)?; + let buf = buf.get(..len).ok_or(Error::new(ENAMETOOLONG))?; + + syscall::write(*new_name_fd, &buf)?; + + Ok(()) +} +#[cfg(target_arch = "x86_64")] +fn copy_float_env_regs(cur_pid_fd: usize, new_pid_fd: usize) -> Result<()> { + // Copy environment registers. + { + let cur_env_regs_fd = FdGuard::new(syscall::dup(cur_pid_fd, b"regs/env")?); + let new_env_regs_fd = FdGuard::new(syscall::dup(new_pid_fd, b"regs/env")?); + + let mut env_regs = syscall::EnvRegisters::default(); + let _ = syscall::read(*cur_env_regs_fd, &mut env_regs)?; + let _ = syscall::write(*new_env_regs_fd, &env_regs)?; + } + // Copy float registers. + { + let cur_float_regs_fd = FdGuard::new(syscall::dup(cur_pid_fd, b"regs/float")?); + let new_float_regs_fd = FdGuard::new(syscall::dup(new_pid_fd, b"regs/float")?); + + let mut float_regs = syscall::FloatRegisters::default(); + let _ = syscall::read(*cur_float_regs_fd, &mut float_regs)?; + let _ = syscall::write(*new_float_regs_fd, &float_regs)?; + } + + Ok(()) +} + +/// Spawns a new context sharing the same address space as the current one (i.e. a new thread). +pub unsafe fn pte_clone_impl(stack: *mut usize) -> Result { + let cur_pid_fd = FdGuard::new(syscall::open("thisproc:current/open_via_dup", O_CLOEXEC)?); + let (new_pid_fd, new_pid) = new_context()?; + + // Allocate a new signal stack. + { + let sigstack_fd = FdGuard::new(syscall::dup(*new_pid_fd, b"sigstack")?); + + const SIGSTACK_SIZE: usize = 1024 * 256; + + // TODO: Put sigstack at high addresses? + let target_sigstack = syscall::fmap(!0, &Map { address: 0, flags: MapFlags::PROT_READ | MapFlags::PROT_WRITE | MapFlags::MAP_PRIVATE, offset: 0, size: SIGSTACK_SIZE })? + SIGSTACK_SIZE; + + let _ = syscall::write(*sigstack_fd, &usize::to_ne_bytes(target_sigstack))?; + } + + copy_str(*cur_pid_fd, *new_pid_fd, "name")?; + copy_str(*cur_pid_fd, *new_pid_fd, "cwd")?; + + // Reuse existing address space + { + let cur_addr_space_fd = FdGuard::new(syscall::dup(*cur_pid_fd, b"addrspace")?); + let new_addr_space_sel_fd = FdGuard::new(syscall::dup(*new_pid_fd, b"current-addrspace")?); + + let buf = create_set_addr_space_buf(*cur_addr_space_fd, pte_clone_ret as usize, stack as usize); + let _ = syscall::write(*new_addr_space_sel_fd, &buf)?; + } + + // Reuse file table + { + let cur_filetable_fd = FdGuard::new(syscall::dup(*cur_pid_fd, b"filetable")?); + let new_filetable_sel_fd = FdGuard::new(syscall::dup(*new_pid_fd, b"current-filetable")?); + + let _ = syscall::write(*new_filetable_sel_fd, &usize::to_ne_bytes(*cur_filetable_fd))?; + } + + copy_float_env_regs(*cur_pid_fd, *new_pid_fd)?; + + // Unblock context. + syscall::kill(new_pid, SIGCONT)?; + + Ok(0) +} +/// Spawns a new context which will not share the same address space as the current one. File +/// descriptors from other schemes are reobtained with `dup`, and grants referencing such file +/// descriptors are reobtained through `fmap`. Other mappings are kept but duplicated using CoW. +pub fn fork_impl() -> Result { + unsafe { + Error::demux(fork_wrapper()) + } +} + +fn fork_inner(initial_rsp: *mut usize) -> Result { + let new_pid = { + let cur_pid_fd = FdGuard::new(syscall::open("thisproc:current/open_via_dup", O_CLOEXEC)?); + let (new_pid_fd, new_pid) = new_context()?; + + // Do not allocate new signal stack, but copy existing address (all memory will be re-mapped + // CoW later). + { + let cur_sigstack_fd = FdGuard::new(syscall::dup(*cur_pid_fd, b"sigstack")?); + let new_sigstack_fd = FdGuard::new(syscall::dup(*new_pid_fd, b"sigstack")?); + + let mut sigstack_buf = usize::to_ne_bytes(0); + + let _ = syscall::read(*cur_sigstack_fd, &mut sigstack_buf); + let _ = syscall::write(*new_sigstack_fd, &sigstack_buf); + } + + copy_str(*cur_pid_fd, *new_pid_fd, "name")?; + copy_str(*cur_pid_fd, *new_pid_fd, "cwd")?; + + // CoW-duplicate address space. + { + let cur_addr_space_fd = FdGuard::new(syscall::dup(*cur_pid_fd, b"addrspace")?); + + // FIXME: Find mappings which use external file descriptors + + let new_addr_space_fd = FdGuard::new(syscall::dup(*cur_addr_space_fd, b"exclusive")?); + + let mut buf = vec! [0_u8; 4096]; + let mut bytes_read = 0; + + loop { + let new_bytes_read = syscall::read(*cur_addr_space_fd, &mut buf[bytes_read..])?; + + if new_bytes_read == 0 { break } + + bytes_read += new_bytes_read; + } + let bytes = &buf[..bytes_read]; + + for struct_bytes in bytes.array_chunks::<{size_of::() * 4}>() { + let mut words = struct_bytes.array_chunks::<{size_of::()}>().copied().map(usize::from_ne_bytes); + + let addr = words.next().unwrap(); + let size = words.next().unwrap(); + let flags = words.next().unwrap(); + let offset = words.next().unwrap(); + + if flags & 0x8000_0000 == 0 { + continue; + } + let map_flags = MapFlags::from_bits_truncate(flags); + + let mapped_address = unsafe { + let fd = FdGuard::new(syscall::dup(*cur_addr_space_fd, format!("grant-{:x}", addr).as_bytes())?); + syscall::fmap(*fd, &syscall::Map { address: 0, size, flags: map_flags, offset })? + }; + + let mut buf = [0_u8; size_of::() * 4]; + let mut chunks = buf.array_chunks_mut::<{size_of::()}>(); + *chunks.next().unwrap() = usize::to_ne_bytes(addr); + *chunks.next().unwrap() = usize::to_ne_bytes(size); + *chunks.next().unwrap() = usize::to_ne_bytes(map_flags.bits()); + *chunks.next().unwrap() = usize::to_ne_bytes(mapped_address); + + let _ = syscall::write(*new_addr_space_fd, &buf)?; + } + let new_addr_space_sel_fd = FdGuard::new(syscall::dup(*new_pid_fd, b"current-addrspace")?); + + let buf = create_set_addr_space_buf(*new_addr_space_fd, fork_ret as usize, initial_rsp as usize); + let _ = syscall::write(*new_addr_space_sel_fd, &buf)?; + } + + // Copy existing files into new file table, but do not reuse the same file table (i.e. new + // parent FDs will not show up for the child). + { + let cur_filetable_fd = FdGuard::new(syscall::dup(*cur_pid_fd, b"filetable")?); + // TODO: Use cross_scheme_links or something similar to avoid copying the file table in the + // kernel. + let new_filetable_fd = FdGuard::new(syscall::dup(*cur_filetable_fd, b"copy")?); + let new_filetable_sel_fd = FdGuard::new(syscall::dup(*new_pid_fd, b"current-filetable")?); + + let _ = syscall::write(*new_filetable_sel_fd, &usize::to_ne_bytes(*new_filetable_fd)); + } + copy_float_env_regs(*cur_pid_fd, *new_pid_fd)?; + + new_pid + }; + + // Unblock context. + syscall::kill(new_pid, SIGCONT)?; + + Ok(new_pid) +} +#[no_mangle] +unsafe extern "sysv64" fn __relibc_internal_fork_impl(initial_rsp: *mut usize) -> usize { + Error::mux(fork_inner(initial_rsp)) +} + +#[no_mangle] +core::arch::global_asm!(" + .p2align 6 + .globl fork_wrapper + .type fork_wrapper, @function +fork_wrapper: + push rbp + mov rbp, rsp + + push rbx + push rbp + push r12 + push r13 + push r14 + push r15 + + mov rdi, rsp + call __relibc_internal_fork_impl + jmp 2f + +fork_ret: + xor rax, rax +2: + pop r15 + pop r14 + pop r13 + pop r12 + pop rbp + pop rbx + + pop rbp + ret + .size fork_wrapper, . - fork_wrapper + + .globl pte_clone_ret + .type pte_clone_ret, @function +pte_clone_ret: + + # Load registers + pop rax + pop rdi + pop rsi + pop rdx + pop rcx + pop r8 + pop r9 + + # Call entry point + call rax + + ret + .size pte_clone_ret, . - pte_clone_ret +"); + +extern "sysv64" { + fn fork_wrapper() -> usize; + fn fork_ret(); + fn pte_clone_ret(); +} diff --git a/src/platform/redox/exec.rs b/src/platform/redox/exec.rs index f2231068b4..77c03b7cc6 100644 --- a/src/platform/redox/exec.rs +++ b/src/platform/redox/exec.rs @@ -1,5 +1,5 @@ use core::convert::TryFrom; -use super::extra::FdGuard; +use super::extra::{create_set_addr_space_buf, FdGuard}; use alloc::{ collections::{btree_map::Entry, BTreeMap}, @@ -16,7 +16,7 @@ use crate::fs::File; fn read_all(fd: usize, offset: Option, buf: &mut [u8]) -> Result<()> { if let Some(offset) = offset { - syscall::lseek(fd, offset as isize, syscall::SEEK_SET).unwrap(); + syscall::lseek(fd, offset as isize, SEEK_SET)?; } let mut total_bytes_read = 0; @@ -29,6 +29,21 @@ fn read_all(fd: usize, offset: Option, buf: &mut [u8]) -> Result<()> { } Ok(()) } +fn write_all(fd: usize, offset: Option, buf: &[u8]) -> Result<()> { + if let Some(offset) = offset { + syscall::lseek(fd, offset as isize, SEEK_SET)?; + } + + let mut total_bytes_written = 0; + + while total_bytes_written < buf.len() { + total_bytes_written += match syscall::write(fd, &buf[total_bytes_written..])? { + 0 => return Err(Error::new(EIO)), + bytes_written => bytes_written, + } + } + Ok(()) +} fn find_free_target_addr(tree: &BTreeMap, size: usize) -> Option { let mut iterator = tree.iter().peekable(); @@ -55,6 +70,14 @@ const PAGE_SIZE: usize = 4096; const FD_ANONYMOUS: usize = !0; pub fn fexec_impl(file: File, path: &[u8], args: &[&[u8]], envs: &[&[u8]], args_envs_size_without_nul: usize) -> Result { + let addrspace_selection_fd = fexec_impl_inner(file, path, args, envs, args_envs_size_without_nul)?; + + // Dropping this FD will cause the address space switch. + drop(addrspace_selection_fd); + + unreachable!(); +} +fn fexec_impl_inner(file: File, path: &[u8], args: &[&[u8]], envs: &[&[u8]], args_envs_size_without_nul: usize) -> Result { use goblin::elf64::{header::Header, program_header::program_header64::{ProgramHeader, PT_LOAD, PF_W, PF_X}}; let fd = *file as usize; @@ -65,7 +88,6 @@ pub fn fexec_impl(file: File, path: &[u8], args: &[&[u8]], envs: &[&[u8]], args_ // some misalignments, and then execute the SYS_EXEC syscall to replace the program memory // entirely. - // TODO: setuid/setgid // TODO: Introduce RAII guards to all owned allocations so that no leaks occur in case of // errors. @@ -146,9 +168,7 @@ pub fn fexec_impl(file: File, path: &[u8], args: &[&[u8]], envs: &[&[u8]], args_ let mut push = |word: usize| { sp -= core::mem::size_of::(); - let _ = syscall::lseek(*memory_fd, sp as isize, SEEK_SET)?; - let _ = syscall::write(*memory_fd, &usize::to_ne_bytes(word))?; - Ok(()) + write_all(*memory_fd, Some(sp as u64), &usize::to_ne_bytes(word)) }; let pheaders_size_aligned = (pheaders_size+PAGE_SIZE-1)/PAGE_SIZE*PAGE_SIZE; @@ -156,8 +176,7 @@ pub fn fexec_impl(file: File, path: &[u8], args: &[&[u8]], envs: &[&[u8]], args_ tree.insert(pheaders, pheaders_size_aligned); mprotect_remote(*grants_fd, pheaders, pheaders_size_aligned, MapFlags::PROT_READ)?; - syscall::lseek(*memory_fd, pheaders as isize, SEEK_SET).map_err(|_| Error::new(EIO))?; - syscall::write(*memory_fd, &phs).map_err(|_| Error::new(EIO))?; + write_all(*memory_fd, Some(pheaders as u64), &phs)?; push(0)?; push(AT_NULL)?; @@ -186,8 +205,7 @@ pub fn fexec_impl(file: File, path: &[u8], args: &[&[u8]], envs: &[&[u8]], args_ if is_args { argc += 1; } push(target_args_env_address + offset)?; - syscall::lseek(*memory_fd, (target_args_env_address + offset) as isize, SEEK_SET).map_err(|_| Error::new(EIO))?; - let _ = syscall::write(*memory_fd, source_slice).map_err(|_| Error::new(EIO))?; + write_all(*memory_fd, Some((target_args_env_address + offset) as u64), source_slice)?; offset += source_slice.len() + 1; } } @@ -197,21 +215,15 @@ pub fn fexec_impl(file: File, path: &[u8], args: &[&[u8]], envs: &[&[u8]], args_ unsafe { crate::ld_so::tcb::Tcb::deactivate(); } // TODO: Restore old name if exec failed? - if let Ok(name_fd) = syscall::open("thisproc:current/name", O_WRONLY) { - let _ = syscall::write(name_fd, path); - let _ = syscall::close(name_fd); + if let Ok(name_fd) = syscall::open("thisproc:current/name", O_WRONLY).map(FdGuard::new) { + let _ = syscall::write(*name_fd, path); } - drop(file); let addrspace_selection_fd = FdGuard::new(syscall::open("thisproc:current/current-addrspace", O_WRONLY)?); - let mut buf = [0_u8; 24]; - buf[..8].copy_from_slice(&usize::to_ne_bytes(*grants_fd)); - buf[8..16].copy_from_slice(&usize::to_ne_bytes(sp)); - buf[16..24].copy_from_slice(&usize::to_ne_bytes(header.e_entry as usize)); + let _ = syscall::write(*addrspace_selection_fd, &create_set_addr_space_buf(*grants_fd, header.e_entry as usize, sp)); - let _ = syscall::write(*addrspace_selection_fd, &buf); - unreachable!(); + Ok(addrspace_selection_fd) } fn mprotect_remote(socket: usize, addr: usize, len: usize, flags: MapFlags) -> Result<()> { let mut grants_buf = [0_u8; 24]; diff --git a/src/platform/redox/extra.rs b/src/platform/redox/extra.rs index dc7f46868b..3e3ba46d3c 100644 --- a/src/platform/redox/extra.rs +++ b/src/platform/redox/extra.rs @@ -1,10 +1,4 @@ -use core::{mem, ptr, slice}; -use core::arch::global_asm; - -use syscall::data::Map; -use syscall::flag::{MapFlags, O_CLOEXEC}; -use syscall::error::{Error, Result, EINVAL, ENAMETOOLONG}; -use syscall::SIGCONT; +use core::{mem::size_of, ptr, slice}; use crate::platform::{sys::e, types::*}; @@ -84,237 +78,11 @@ impl Drop for FdGuard { } } } - -fn new_context() -> Result<(FdGuard, usize)> { - // Create a new context (fields such as uid/gid will be inherited from the current context). - let fd = FdGuard::new(syscall::open("thisproc:new/open_via_dup", O_CLOEXEC)?); - - // Extract pid. - let mut buffer = [0_u8; 64]; - let len = syscall::fpath(*fd, &mut buffer)?; - let buffer = buffer.get(..len).ok_or(Error::new(ENAMETOOLONG))?; - - let colon_idx = buffer.iter().position(|c| *c == b':').ok_or(Error::new(EINVAL))?; - let slash_idx = buffer.iter().skip(colon_idx).position(|c| *c == b'/').ok_or(Error::new(EINVAL))? + colon_idx; - let pid_bytes = buffer.get(colon_idx + 1..slash_idx).ok_or(Error::new(EINVAL))?; - let pid_str = core::str::from_utf8(pid_bytes).map_err(|_| Error::new(EINVAL))?; - let pid = pid_str.parse::().map_err(|_| Error::new(EINVAL))?; - - Ok((fd, pid)) -} - -fn copy_str(cur_pid_fd: usize, new_pid_fd: usize, key: &str) -> Result<()> { - let cur_name_fd = FdGuard::new(syscall::dup(cur_pid_fd, key.as_bytes())?); - let new_name_fd = FdGuard::new(syscall::dup(new_pid_fd, key.as_bytes())?); - - let mut buf = [0_u8; 256]; - let len = syscall::read(*cur_name_fd, &mut buf)?; - let buf = buf.get(..len).ok_or(Error::new(ENAMETOOLONG))?; - - syscall::write(*new_name_fd, &buf)?; - - Ok(()) -} -#[cfg(target_arch = "x86_64")] -fn copy_float_env_regs(cur_pid_fd: usize, new_pid_fd: usize) -> Result<()> { - // Copy environment registers. - { - let cur_env_regs_fd = FdGuard::new(syscall::dup(cur_pid_fd, b"regs/env")?); - let new_env_regs_fd = FdGuard::new(syscall::dup(new_pid_fd, b"regs/env")?); - - let mut env_regs = syscall::EnvRegisters::default(); - let _ = syscall::read(*cur_env_regs_fd, &mut env_regs)?; - let _ = syscall::write(*new_env_regs_fd, &env_regs)?; - } - // Copy float registers. - { - let cur_float_regs_fd = FdGuard::new(syscall::dup(cur_pid_fd, b"regs/float")?); - let new_float_regs_fd = FdGuard::new(syscall::dup(new_pid_fd, b"regs/float")?); - - let mut float_regs = syscall::FloatRegisters::default(); - let _ = syscall::read(*cur_float_regs_fd, &mut float_regs)?; - let _ = syscall::write(*new_float_regs_fd, &float_regs)?; - } - - Ok(()) -} - -/// Spawns a new context sharing the same address space as the current one (i.e. a new thread). -pub unsafe fn pte_clone_impl(stack: *mut usize) -> Result { - let cur_pid_fd = FdGuard::new(syscall::open("thisproc:current/open_via_dup", O_CLOEXEC)?); - let (new_pid_fd, new_pid) = new_context()?; - - // Allocate a new signal stack. - { - let sigstack_fd = FdGuard::new(syscall::dup(*new_pid_fd, b"sigstack")?); - - const SIGSTACK_SIZE: usize = 1024 * 256; - - // TODO: Put sigstack at high addresses? - let target_sigstack = syscall::fmap(!0, &Map { address: 0, flags: MapFlags::PROT_READ | MapFlags::PROT_WRITE | MapFlags::MAP_PRIVATE, offset: 0, size: SIGSTACK_SIZE })? + SIGSTACK_SIZE; - - let _ = syscall::write(*sigstack_fd, &usize::to_ne_bytes(target_sigstack))?; - } - - copy_str(*cur_pid_fd, *new_pid_fd, "name")?; - copy_str(*cur_pid_fd, *new_pid_fd, "cwd")?; - - // Reuse existing address space - { - let cur_addr_space_fd = FdGuard::new(syscall::dup(*cur_pid_fd, b"addrspace")?); - let new_addr_space_sel_fd = FdGuard::new(syscall::dup(*new_pid_fd, b"current-addrspace")?); - - let buf = create_set_addr_space_buf(*cur_addr_space_fd, pte_clone_ret as usize, stack as usize); - let _ = syscall::write(*new_addr_space_sel_fd, &buf)?; - } - - // Reuse file table - { - let cur_filetable_fd = FdGuard::new(syscall::dup(*cur_pid_fd, b"filetable")?); - let new_filetable_sel_fd = FdGuard::new(syscall::dup(*new_pid_fd, b"current-filetable")?); - - let _ = syscall::write(*new_filetable_sel_fd, &usize::to_ne_bytes(*cur_filetable_fd))?; - } - - - copy_float_env_regs(*cur_pid_fd, *new_pid_fd)?; - - // Unblock context. - syscall::kill(new_pid, SIGCONT); - - Ok(0) -} -fn create_set_addr_space_buf(space: usize, ip: usize, sp: usize) -> [u8; mem::size_of::() * 3] { - let mut buf = [0_u8; 3 * mem::size_of::()]; - let mut chunks = buf.array_chunks_mut::<{mem::size_of::()}>(); +pub fn create_set_addr_space_buf(space: usize, ip: usize, sp: usize) -> [u8; size_of::() * 3] { + let mut buf = [0_u8; 3 * size_of::()]; + let mut chunks = buf.array_chunks_mut::<{size_of::()}>(); *chunks.next().unwrap() = usize::to_ne_bytes(space); *chunks.next().unwrap() = usize::to_ne_bytes(sp); *chunks.next().unwrap() = usize::to_ne_bytes(ip); buf } -/// Spawns a new context which will not share the same address space as the current one. File -/// descriptors from other schemes are reobtained with `dup`, and grants referencing such file -/// descriptors are reobtained through `fmap`. Other mappings are kept but duplicated using CoW. -pub fn fork_impl() -> Result { - unsafe { - Error::demux(fork_wrapper()) - } -} - -fn fork_inner(initial_rsp: *mut usize) -> Result { - let new_pid = { - let cur_pid_fd = FdGuard::new(syscall::open("thisproc:current/open_via_dup", O_CLOEXEC)?); - let (new_pid_fd, new_pid) = new_context()?; - - // Do not allocate new signal stack, but copy existing address (all memory will be re-mapped - // CoW later). - { - let cur_sigstack_fd = FdGuard::new(syscall::dup(*cur_pid_fd, b"sigstack")?); - let new_sigstack_fd = FdGuard::new(syscall::dup(*new_pid_fd, b"sigstack")?); - - let mut sigstack_buf = usize::to_ne_bytes(0); - - let _ = syscall::read(*cur_sigstack_fd, &mut sigstack_buf); - let _ = syscall::write(*new_sigstack_fd, &sigstack_buf); - } - - copy_str(*cur_pid_fd, *new_pid_fd, "name")?; - copy_str(*cur_pid_fd, *new_pid_fd, "cwd")?; - - // CoW-duplicate address space. - { - let cur_addr_space_fd = FdGuard::new(syscall::dup(*cur_pid_fd, b"addrspace")?); - - // FIXME: Find mappings which use external file descriptors - - let new_addr_space_fd = FdGuard::new(syscall::dup(*cur_addr_space_fd, b"exclusive")?); - let new_addr_space_sel_fd = FdGuard::new(syscall::dup(*new_pid_fd, b"current-addrspace")?); - - let buf = create_set_addr_space_buf(*new_addr_space_fd, fork_ret as usize, initial_rsp as usize); - let _ = syscall::write(*new_addr_space_sel_fd, &buf)?; - } - - // Copy existing files into new file table, but do not reuse the same file table (i.e. new - // parent FDs will not show up for the child). - { - let cur_filetable_fd = FdGuard::new(syscall::dup(*cur_pid_fd, b"filetable")?); - // TODO: Use cross_scheme_links or something similar to avoid copying the file table in the - // kernel. - let new_filetable_fd = FdGuard::new(syscall::dup(*cur_filetable_fd, b"copy")?); - let new_filetable_sel_fd = FdGuard::new(syscall::dup(*new_pid_fd, b"current-filetable")?); - - let _ = syscall::write(*new_filetable_sel_fd, &usize::to_ne_bytes(*new_filetable_fd)); - } - copy_float_env_regs(*cur_pid_fd, *new_pid_fd)?; - - new_pid - }; - - // Unblock context. - syscall::kill(new_pid, SIGCONT); - - Ok(new_pid) -} -#[no_mangle] -unsafe extern "sysv64" fn __relibc_internal_fork_impl(initial_rsp: *mut usize) -> usize { - Error::mux(fork_inner(initial_rsp)) -} - -core::arch::global_asm!(" - .p2align 6 - .globl fork_wrapper - .type fork_wrapper, @function -fork_wrapper: - push rbp - mov rbp, rsp - - push rbx - push rbp - push r12 - push r13 - push r14 - push r15 - - mov rdi, rsp - call __relibc_internal_fork_impl - jmp 2f - -fork_ret: - xor rax, rax -2: - pop r15 - pop r14 - pop r13 - pop r12 - pop rbp - pop rbx - - pop rbp - ret - .size fork_wrapper, . - fork_wrapper - - .globl pte_clone_ret - .type pte_clone_ret, @function -pte_clone_ret: - - # Load registers - pop rax - pop rdi - pop rsi - pop rdx - pop rcx - pop r8 - pop r9 - - # Call entry point - call rax - - ret - .size pte_clone_ret, . - pte_clone_ret -"); - -extern "sysv64" { - fn fork_wrapper() -> usize; - fn fork_ret(); - fn pte_clone_ret(); -} diff --git a/src/platform/redox/mod.rs b/src/platform/redox/mod.rs index a51b56dce8..ab5deedb94 100644 --- a/src/platform/redox/mod.rs +++ b/src/platform/redox/mod.rs @@ -34,6 +34,7 @@ use super::{errno, types::*, Pal, Read}; static mut BRK_CUR: *mut c_void = ptr::null_mut(); static mut BRK_END: *mut c_void = ptr::null_mut(); +mod clone; mod epoll; mod exec; mod extra; @@ -355,6 +356,10 @@ impl Pal for Sys { // Close all O_CLOEXEC file descriptors. TODO: close_range? { + // NOTE: This approach of implementing O_CLOEXEC will not work in multithreaded + // scenarios. While execve() is undefined according to POSIX if there exist sibling + // threads, it could still be allowed by keeping certain file descriptors and instead + // set the active file table. let name = CStr::from_bytes_with_nul(b"thisproc:current/filetable\0").expect("string should be valid"); let files_fd = match File::open(name, fcntl::O_RDONLY) { Ok(f) => f, @@ -398,7 +403,9 @@ impl Pal for Sys { // TODO: Plus, at this point fexecve is not implemented (but specified in // POSIX.1-2008), and to avoid bad syscalls such as fpath, passing a file descriptor // would be better. - escalate_fd.write_all(path.to_bytes()); + if escalate_fd.write_all(path.to_bytes()).is_err() { + return -1; + } // Second, we write the flattened args and envs with NUL characters separating // individual items. @@ -455,7 +462,7 @@ impl Pal for Sys { } fn fork() -> pid_t { - e(extra::fork_impl()) as pid_t + e(clone::fork_impl()) as pid_t } fn fstat(fildes: c_int, buf: *mut stat) -> c_int { @@ -938,7 +945,7 @@ impl Pal for Sys { #[cfg(target_arch = "x86_64")] unsafe fn pte_clone(stack: *mut usize) -> pid_t { - e(extra::pte_clone_impl(stack)) as pid_t + e(clone::pte_clone_impl(stack)) as pid_t } fn read(fd: c_int, buf: &mut [u8]) -> ssize_t { From df8e2deddcdcc5621ea194e20cd3a5858d1e0aae Mon Sep 17 00:00:00 2001 From: 4lDO2 <4lDO2@protonmail.com> Date: Thu, 7 Jul 2022 12:20:44 +0200 Subject: [PATCH 07/23] Fix file descriptor leak in fork(). --- src/platform/redox/clone.rs | 55 +++++++++++++++++++++++++------------ src/platform/redox/exec.rs | 3 -- 2 files changed, 38 insertions(+), 20 deletions(-) diff --git a/src/platform/redox/clone.rs b/src/platform/redox/clone.rs index 8877ef91e1..398541bfe6 100644 --- a/src/platform/redox/clone.rs +++ b/src/platform/redox/clone.rs @@ -119,9 +119,11 @@ pub fn fork_impl() -> Result { } fn fork_inner(initial_rsp: *mut usize) -> Result { - let new_pid = { + let (cur_filetable_fd, new_pid_fd, new_pid); + + { let cur_pid_fd = FdGuard::new(syscall::open("thisproc:current/open_via_dup", O_CLOEXEC)?); - let (new_pid_fd, new_pid) = new_context()?; + (new_pid_fd, new_pid) = new_context()?; // Do not allocate new signal stack, but copy existing address (all memory will be re-mapped // CoW later). @@ -138,6 +140,18 @@ fn fork_inner(initial_rsp: *mut usize) -> Result { copy_str(*cur_pid_fd, *new_pid_fd, "name")?; copy_str(*cur_pid_fd, *new_pid_fd, "cwd")?; + // Copy existing files into new file table, but do not reuse the same file table (i.e. new + // parent FDs will not show up for the child). + { + cur_filetable_fd = FdGuard::new(syscall::dup(*cur_pid_fd, b"filetable")?); + + // This must be done before the address space is copied. + unsafe { + initial_rsp.write(*cur_filetable_fd); + initial_rsp.add(1).write(*new_pid_fd); + } + } + // CoW-duplicate address space. { let cur_addr_space_fd = FdGuard::new(syscall::dup(*cur_pid_fd, b"addrspace")?); @@ -190,22 +204,18 @@ fn fork_inner(initial_rsp: *mut usize) -> Result { let buf = create_set_addr_space_buf(*new_addr_space_fd, fork_ret as usize, initial_rsp as usize); let _ = syscall::write(*new_addr_space_sel_fd, &buf)?; } - - // Copy existing files into new file table, but do not reuse the same file table (i.e. new - // parent FDs will not show up for the child). - { - let cur_filetable_fd = FdGuard::new(syscall::dup(*cur_pid_fd, b"filetable")?); - // TODO: Use cross_scheme_links or something similar to avoid copying the file table in the - // kernel. - let new_filetable_fd = FdGuard::new(syscall::dup(*cur_filetable_fd, b"copy")?); - let new_filetable_sel_fd = FdGuard::new(syscall::dup(*new_pid_fd, b"current-filetable")?); - - let _ = syscall::write(*new_filetable_sel_fd, &usize::to_ne_bytes(*new_filetable_fd)); - } copy_float_env_regs(*cur_pid_fd, *new_pid_fd)?; - - new_pid - }; + } + // Copy the file table. We do this last to ensure that all previously used file descriptors are + // closed. The only exception -- the filetable selection fd and the current filetable fd -- + // will be closed by the child process. + { + // TODO: Use cross_scheme_links or something similar to avoid copying the file table in the + // kernel. + let new_filetable_fd = FdGuard::new(syscall::dup(*cur_filetable_fd, b"copy")?); + let new_filetable_sel_fd = FdGuard::new(syscall::dup(*new_pid_fd, b"current-filetable")?); + let _ = syscall::write(*new_filetable_sel_fd, &usize::to_ne_bytes(*new_filetable_fd)); + } // Unblock context. syscall::kill(new_pid, SIGCONT)?; @@ -216,6 +226,11 @@ fn fork_inner(initial_rsp: *mut usize) -> Result { unsafe extern "sysv64" fn __relibc_internal_fork_impl(initial_rsp: *mut usize) -> usize { Error::mux(fork_inner(initial_rsp)) } +#[no_mangle] +unsafe extern "sysv64" fn __relibc_internal_fork_hook(cur_filetable_fd: usize, new_pid_fd: usize) { + let _ = syscall::close(cur_filetable_fd); + let _ = syscall::close(new_pid_fd); +} #[no_mangle] core::arch::global_asm!(" @@ -233,13 +248,19 @@ fork_wrapper: push r14 push r15 + sub rsp, 16 + mov rdi, rsp call __relibc_internal_fork_impl jmp 2f fork_ret: + mov rdi, [rsp] + mov rsi, [rsp + 8] + call __relibc_internal_fork_hook xor rax, rax 2: + add rsp, 16 pop r15 pop r14 pop r13 diff --git a/src/platform/redox/exec.rs b/src/platform/redox/exec.rs index 77c03b7cc6..bdda06dae5 100644 --- a/src/platform/redox/exec.rs +++ b/src/platform/redox/exec.rs @@ -88,9 +88,6 @@ fn fexec_impl_inner(file: File, path: &[u8], args: &[&[u8]], envs: &[&[u8]], arg // some misalignments, and then execute the SYS_EXEC syscall to replace the program memory // entirely. - // TODO: Introduce RAII guards to all owned allocations so that no leaks occur in case of - // errors. - let mut header_bytes = [0_u8; core::mem::size_of::
()]; read_all(fd, Some(0), &mut header_bytes)?; let header = Header::from_bytes(&header_bytes); From 1476cdeb8f6a497a5a6659eba6763142ebe9e099 Mon Sep 17 00:00:00 2001 From: 4lDO2 <4lDO2@protonmail.com> Date: Thu, 7 Jul 2022 19:27:11 +0200 Subject: [PATCH 08/23] Split fexec_impl into crate, used by escalated. --- Cargo.lock | 10 + Cargo.toml | 3 +- src/ld_so/tcb.rs | 16 -- src/platform/redox/exec.rs | 232 +----------------- src/platform/redox/extra.rs | 39 +-- src/platform/redox/redox-exec/Cargo.toml | 15 ++ src/platform/redox/redox-exec/src/lib.rs | 291 +++++++++++++++++++++++ 7 files changed, 329 insertions(+), 277 deletions(-) create mode 100644 src/platform/redox/redox-exec/Cargo.toml create mode 100644 src/platform/redox/redox-exec/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 9c1bc72414..4ce7a7c5d1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -298,6 +298,15 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" +[[package]] +name = "redox-exec" +version = "0.1.0" +dependencies = [ + "goblin", + "plain", + "redox_syscall 0.2.13", +] + [[package]] name = "redox_syscall" version = "0.1.57" @@ -337,6 +346,7 @@ dependencies = [ "posix-regex", "ralloc", "rand", + "redox-exec", "redox_syscall 0.2.13", "sc", "spin 0.9.4", diff --git a/Cargo.toml b/Cargo.toml index e7c0d78753..e2fa243e32 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,7 @@ name = "relibc" crate-type = ["staticlib"] [workspace] -members = ["src/crt0", "src/crti", "src/crtn", "src/ld_so"] +members = ["src/crt0", "src/crti", "src/crtn", "src/ld_so", "src/platform/redox/redox-exec"] exclude = ["core_io", "ralloc", "tests"] [build-dependencies] @@ -43,6 +43,7 @@ sc = "0.2.3" #redox_syscall = "0.2.15" redox_syscall = { git = "https://gitlab.redox-os.org/4lDO2/syscall.git", branch = "userspace_fexec" } spin = "0.9.0" +redox-exec = { path = "src/platform/redox/redox-exec" } [features] default = [] diff --git a/src/ld_so/tcb.rs b/src/ld_so/tcb.rs index 59f0b90da2..3826322fa7 100644 --- a/src/ld_so/tcb.rs +++ b/src/ld_so/tcb.rs @@ -168,22 +168,6 @@ impl Tcb { Self::os_arch_activate(self.tcb_ptr as usize); } - /// Deactive TLS, used before exec() on Redox to not trick target executable into thinking TLS - /// is already initialized as if it was a thread. - #[cfg(all(target_os = "redox", target_arch = "x86_64"))] - pub unsafe fn deactivate() { - let mut env = syscall::EnvRegisters::default(); - - let file = syscall::open("thisproc:current/regs/env", syscall::O_CLOEXEC | syscall::O_WRONLY) - .expect_notls("failed to open handle for process registers"); - - env.fsbase = 0; - env.gsbase = 0; - - let _ = syscall::write(file, &mut env) - .expect_notls("failed to read fsbase"); - } - /// Mapping with correct flags for TCB and TLS unsafe fn map(size: usize) -> Result<&'static mut [u8]> { let ptr = sys_mman::mmap( diff --git a/src/platform/redox/exec.rs b/src/platform/redox/exec.rs index bdda06dae5..ac3672bb37 100644 --- a/src/platform/redox/exec.rs +++ b/src/platform/redox/exec.rs @@ -1,232 +1,20 @@ -use core::convert::TryFrom; -use super::extra::{create_set_addr_space_buf, FdGuard}; - -use alloc::{ - collections::{btree_map::Entry, BTreeMap}, - vec::Vec, -}; - -use syscall::{ - data::ExecMemRange, - error::*, - flag::{AT_ENTRY, AT_NULL, AT_PHDR, AT_PHENT, AT_PHNUM, MapFlags, O_WRONLY, SEEK_SET}, -}; - use crate::fs::File; -fn read_all(fd: usize, offset: Option, buf: &mut [u8]) -> Result<()> { - if let Some(offset) = offset { - syscall::lseek(fd, offset as isize, SEEK_SET)?; - } - - let mut total_bytes_read = 0; - - while total_bytes_read < buf.len() { - total_bytes_read += match syscall::read(fd, &mut buf[total_bytes_read..])? { - 0 => return Err(Error::new(ENOEXEC)), - bytes_read => bytes_read, - } - } - Ok(()) -} -fn write_all(fd: usize, offset: Option, buf: &[u8]) -> Result<()> { - if let Some(offset) = offset { - syscall::lseek(fd, offset as isize, SEEK_SET)?; - } - - let mut total_bytes_written = 0; - - while total_bytes_written < buf.len() { - total_bytes_written += match syscall::write(fd, &buf[total_bytes_written..])? { - 0 => return Err(Error::new(EIO)), - bytes_written => bytes_written, - } - } - Ok(()) -} - -fn find_free_target_addr(tree: &BTreeMap, size: usize) -> Option { - let mut iterator = tree.iter().peekable(); - - // Ignore the space between zero and the first region, to avoid null pointers. - while let Some((cur_address, entry_size)) = iterator.next() { - let end = *cur_address + entry_size; - - if let Some((next_address, _)) = iterator.peek() { - if **next_address - end > size { - return Some(end); - } - } - // No need to check last entry, since the stack will always be put at the highest - // possible address. - } - - None -} - -#[cfg(target_arch = "x86_64")] -const PAGE_SIZE: usize = 4096; - -const FD_ANONYMOUS: usize = !0; +use syscall::error::Result; +use redox_exec::FdGuard; pub fn fexec_impl(file: File, path: &[u8], args: &[&[u8]], envs: &[&[u8]], args_envs_size_without_nul: usize) -> Result { - let addrspace_selection_fd = fexec_impl_inner(file, path, args, envs, args_envs_size_without_nul)?; + let fd = *file; + core::mem::forget(file); + let image_file = FdGuard::new(fd as usize); + + let open_via_dup = FdGuard::new(syscall::open("thisproc:current/open_via_dup", 0)?); + + let total_args_envs_size = args_envs_size_without_nul + args.len() + envs.len(); + let addrspace_selection_fd = redox_exec::fexec_impl(image_file, open_via_dup, path, args.iter().rev(), envs.iter().rev(), total_args_envs_size)?; // Dropping this FD will cause the address space switch. drop(addrspace_selection_fd); unreachable!(); } -fn fexec_impl_inner(file: File, path: &[u8], args: &[&[u8]], envs: &[&[u8]], args_envs_size_without_nul: usize) -> Result { - use goblin::elf64::{header::Header, program_header::program_header64::{ProgramHeader, PT_LOAD, PF_W, PF_X}}; - - let fd = *file as usize; - let total_args_envs_size = args_envs_size_without_nul + args.len() + envs.len(); - - // Here, we do the minimum part of loading an application, which is what the kernel used to do. - // We load the executable into memory (albeit at different offsets in this executable), fix - // some misalignments, and then execute the SYS_EXEC syscall to replace the program memory - // entirely. - - let mut header_bytes = [0_u8; core::mem::size_of::
()]; - read_all(fd, Some(0), &mut header_bytes)?; - let header = Header::from_bytes(&header_bytes); - - let grants_fd = { - let current_addrspace_fd = FdGuard::new(syscall::open("thisproc:current/addrspace", 0)?); - FdGuard::new(syscall::dup(*current_addrspace_fd, b"empty")?) - }; - let memory_fd = FdGuard::new(syscall::dup(*grants_fd, b"mem")?); - - let instruction_ptr = usize::try_from(header.e_entry).map_err(|_| Error::new(ENOEXEC))?; - - // Never allow more than 1 MiB of program headers. TODO: Capabilities again? - const MAX_PH_SIZE: usize = 1024 * 1024; - let phentsize = u64::from(header.e_phentsize) as usize; - let phnum = u64::from(header.e_phnum) as usize; - let pheaders_size = phentsize.saturating_mul(phnum); - - if pheaders_size > MAX_PH_SIZE { - return Err(Error::new(E2BIG)); - } - let mut phs = vec! [0_u8; pheaders_size]; - - let mut tree = BTreeMap::new(); - tree.insert(0, PAGE_SIZE); - - const BUFSZ: usize = 16384; - let mut buf = vec! [0_u8; BUFSZ]; - - read_all(*file as usize, Some(header.e_phoff), &mut phs).map_err(|_| Error::new(EIO))?; - - for ph_idx in 0..phnum { - let ph_bytes = &phs[ph_idx * phentsize..(ph_idx + 1) * phentsize]; - let segment: &ProgramHeader = plain::from_bytes(ph_bytes).map_err(|_| Error::new(EINVAL))?; - let mut flags = syscall::PROT_READ; - - // W ^ X. If it is executable, do not allow it to be writable, even if requested - if segment.p_flags & PF_X == PF_X { - flags |= syscall::PROT_EXEC; - } else if segment.p_flags & PF_W == PF_W { - flags |= syscall::PROT_WRITE; - } - - let voff = segment.p_vaddr as usize % PAGE_SIZE; - let vaddr = segment.p_vaddr as usize - voff; - let size = - (segment.p_memsz as usize + voff + PAGE_SIZE - 1) / PAGE_SIZE * PAGE_SIZE; - - if segment.p_filesz > segment.p_memsz { - return Err(Error::new(ENOEXEC)); - } - if segment.p_type == PT_LOAD { - mprotect_remote(*grants_fd, vaddr, size, flags)?; - syscall::lseek(*file as usize, segment.p_offset as isize, SEEK_SET).map_err(|_| Error::new(EIO))?; - syscall::lseek(*memory_fd, segment.p_vaddr as isize, SEEK_SET).map_err(|_| Error::new(EIO))?; - - for size in core::iter::repeat(BUFSZ).take((segment.p_filesz as usize) / BUFSZ).chain(Some((segment.p_filesz as usize) % BUFSZ)) { - read_all(*file as usize, None, &mut buf[..size]).map_err(|_| Error::new(EIO))?; - let _ = syscall::write(*memory_fd, &buf[..size]).map_err(|_| Error::new(EIO))?; - } - - if !tree.range(..=vaddr).next_back().filter(|(start, size)| **start + **size > vaddr).is_some() { - tree.insert(vaddr, size); - } - } - } - // Setup a stack starting from the very end of the address space, and then growing downwards. - const STACK_TOP: usize = 1 << 47; - const STACK_SIZE: usize = 1024 * 1024; - - mprotect_remote(*grants_fd, STACK_TOP - STACK_SIZE, STACK_SIZE, MapFlags::PROT_READ | MapFlags::PROT_WRITE)?; - tree.insert(STACK_TOP - STACK_SIZE, STACK_SIZE); - - let mut sp = STACK_TOP - 256; - - let mut push = |word: usize| { - sp -= core::mem::size_of::(); - write_all(*memory_fd, Some(sp as u64), &usize::to_ne_bytes(word)) - }; - - let pheaders_size_aligned = (pheaders_size+PAGE_SIZE-1)/PAGE_SIZE*PAGE_SIZE; - let pheaders = find_free_target_addr(&tree, pheaders_size_aligned).ok_or(Error::new(ENOMEM))?; - tree.insert(pheaders, pheaders_size_aligned); - mprotect_remote(*grants_fd, pheaders, pheaders_size_aligned, MapFlags::PROT_READ)?; - - write_all(*memory_fd, Some(pheaders as u64), &phs)?; - - push(0)?; - push(AT_NULL)?; - push(header.e_entry as usize)?; - push(AT_ENTRY)?; - push(pheaders)?; - push(AT_PHDR)?; - push(header.e_phnum as usize)?; - push(AT_PHNUM)?; - push(header.e_phentsize as usize)?; - push(AT_PHENT)?; - - let args_envs_size_aligned = (total_args_envs_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))?; - mprotect_remote(*grants_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); - - let mut offset = 0; - - let mut argc = 0; - - for (collection, is_args) in [(envs, false), (args, true)] { - push(0)?; - - for source_slice in collection.iter().rev() { - if is_args { argc += 1; } - push(target_args_env_address + offset)?; - - write_all(*memory_fd, Some((target_args_env_address + offset) as u64), source_slice)?; - offset += source_slice.len() + 1; - } - } - - push(argc)?; - - unsafe { crate::ld_so::tcb::Tcb::deactivate(); } - - // TODO: Restore old name if exec failed? - if let Ok(name_fd) = syscall::open("thisproc:current/name", O_WRONLY).map(FdGuard::new) { - let _ = syscall::write(*name_fd, path); - } - - let addrspace_selection_fd = FdGuard::new(syscall::open("thisproc:current/current-addrspace", O_WRONLY)?); - - let _ = syscall::write(*addrspace_selection_fd, &create_set_addr_space_buf(*grants_fd, header.e_entry as usize, sp)); - - Ok(addrspace_selection_fd) -} -fn mprotect_remote(socket: usize, addr: usize, len: usize, flags: MapFlags) -> Result<()> { - let mut grants_buf = [0_u8; 24]; - grants_buf[..8].copy_from_slice(&usize::to_ne_bytes(addr)); - grants_buf[8..16].copy_from_slice(&usize::to_ne_bytes(len)); - grants_buf[16..24].copy_from_slice(&usize::to_ne_bytes(flags.bits())); - syscall::write(socket, &grants_buf)?; - Ok(()) -} diff --git a/src/platform/redox/extra.rs b/src/platform/redox/extra.rs index 3e3ba46d3c..597e2c7eb6 100644 --- a/src/platform/redox/extra.rs +++ b/src/platform/redox/extra.rs @@ -48,41 +48,4 @@ pub unsafe extern "C" fn redox_physunmap(virtual_address: *mut c_void) -> c_int e(syscall::physunmap(virtual_address as usize)) as c_int } -pub struct FdGuard { - fd: usize, - taken: bool, -} -impl FdGuard { - pub fn new(fd: usize) -> Self { - Self { - fd, taken: false, - } - } - pub fn take(&mut self) -> usize { - self.taken = true; - self.fd - } -} -impl core::ops::Deref for FdGuard { - type Target = usize; - - fn deref(&self) -> &Self::Target { - &self.fd - } -} - -impl Drop for FdGuard { - fn drop(&mut self) { - if !self.taken { - let _ = syscall::close(self.fd); - } - } -} -pub fn create_set_addr_space_buf(space: usize, ip: usize, sp: usize) -> [u8; size_of::() * 3] { - let mut buf = [0_u8; 3 * size_of::()]; - let mut chunks = buf.array_chunks_mut::<{size_of::()}>(); - *chunks.next().unwrap() = usize::to_ne_bytes(space); - *chunks.next().unwrap() = usize::to_ne_bytes(sp); - *chunks.next().unwrap() = usize::to_ne_bytes(ip); - buf -} +pub use redox_exec::{create_set_addr_space_buf, FdGuard}; diff --git a/src/platform/redox/redox-exec/Cargo.toml b/src/platform/redox/redox-exec/Cargo.toml new file mode 100644 index 0000000000..afe990a0a3 --- /dev/null +++ b/src/platform/redox/redox-exec/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "redox-exec" +authors = ["4lDO2 <4lDO2@protonmail.com>"] +version = "0.1.0" +edition = "2018" +license = "MIT" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +#redox_syscall = "0.2" +redox_syscall = { git = "https://gitlab.redox-os.org/4lDO2/syscall.git", branch = "userspace_fexec" } +# TODO: Update +goblin = { version = "0.0.21", default-features = false, features = ["elf32", "elf64", "endian_fd"] } +plain = "0.2" diff --git a/src/platform/redox/redox-exec/src/lib.rs b/src/platform/redox/redox-exec/src/lib.rs new file mode 100644 index 0000000000..aba5125cd3 --- /dev/null +++ b/src/platform/redox/redox-exec/src/lib.rs @@ -0,0 +1,291 @@ +#![no_std] + +#![feature(array_chunks)] + +extern crate alloc; + +use core::convert::TryFrom; +use core::mem::size_of; + +use alloc::{ + collections::{btree_map::Entry, BTreeMap}, + vec::Vec, + vec, +}; + +use syscall::{ + error::*, + flag::{AT_ENTRY, AT_NULL, AT_PHDR, AT_PHENT, AT_PHNUM, MapFlags, O_WRONLY, SEEK_SET}, +}; + +#[cfg(target_arch = "x86_64")] +const PAGE_SIZE: usize = 4096; + +pub fn fexec_impl(image_file: FdGuard, open_via_dup: FdGuard, path: &[u8], args: A, envs: E, total_args_envs_size: usize) -> Result +where + A: IntoIterator, + E: IntoIterator, + A::Item: AsRef<[u8]>, + E::Item: AsRef<[u8]>, +{ + use goblin::elf64::{header::Header, program_header::program_header64::{ProgramHeader, PT_LOAD, PF_W, PF_X}}; + + // Here, we do the minimum part of loading an application, which is what the kernel used to do. + // We load the executable into memory (albeit at different offsets in this executable), fix + // some misalignments, and then execute the SYS_EXEC syscall to replace the program memory + // entirely. + + let mut header_bytes = [0_u8; size_of::
()]; + read_all(*image_file, Some(0), &mut header_bytes)?; + let header = Header::from_bytes(&header_bytes); + + let grants_fd = { + let current_addrspace_fd = FdGuard::new(syscall::dup(*open_via_dup, b"addrspace")?); + FdGuard::new(syscall::dup(*current_addrspace_fd, b"empty")?) + }; + let memory_fd = FdGuard::new(syscall::dup(*grants_fd, b"mem")?); + + let instruction_ptr = usize::try_from(header.e_entry).map_err(|_| Error::new(ENOEXEC))?; + + // Never allow more than 1 MiB of program headers. TODO: Capabilities again? + const MAX_PH_SIZE: usize = 1024 * 1024; + let phentsize = u64::from(header.e_phentsize) as usize; + let phnum = u64::from(header.e_phnum) as usize; + let pheaders_size = phentsize.saturating_mul(phnum); + + if pheaders_size > MAX_PH_SIZE { + return Err(Error::new(E2BIG)); + } + let mut phs = vec! [0_u8; pheaders_size]; + + let mut tree = BTreeMap::new(); + tree.insert(0, PAGE_SIZE); + + const BUFSZ: usize = 16384; + let mut buf = vec! [0_u8; BUFSZ]; + + read_all(*image_file as usize, Some(header.e_phoff), &mut phs).map_err(|_| Error::new(EIO))?; + + for ph_idx in 0..phnum { + let ph_bytes = &phs[ph_idx * phentsize..(ph_idx + 1) * phentsize]; + let segment: &ProgramHeader = plain::from_bytes(ph_bytes).map_err(|_| Error::new(EINVAL))?; + let mut flags = syscall::PROT_READ; + + // W ^ X. If it is executable, do not allow it to be writable, even if requested + if segment.p_flags & PF_X == PF_X { + flags |= syscall::PROT_EXEC; + } else if segment.p_flags & PF_W == PF_W { + flags |= syscall::PROT_WRITE; + } + + let voff = segment.p_vaddr as usize % PAGE_SIZE; + let vaddr = segment.p_vaddr as usize - voff; + let size = + (segment.p_memsz as usize + voff + PAGE_SIZE - 1) / PAGE_SIZE * PAGE_SIZE; + + if segment.p_filesz > segment.p_memsz { + return Err(Error::new(ENOEXEC)); + } + if segment.p_type == PT_LOAD { + mprotect_remote(*grants_fd, vaddr, size, flags)?; + syscall::lseek(*image_file as usize, segment.p_offset as isize, SEEK_SET).map_err(|_| Error::new(EIO))?; + syscall::lseek(*memory_fd, segment.p_vaddr as isize, SEEK_SET).map_err(|_| Error::new(EIO))?; + + for size in core::iter::repeat(BUFSZ).take((segment.p_filesz as usize) / BUFSZ).chain(Some((segment.p_filesz as usize) % BUFSZ)) { + read_all(*image_file as usize, None, &mut buf[..size]).map_err(|_| Error::new(EIO))?; + let _ = syscall::write(*memory_fd, &buf[..size]).map_err(|_| Error::new(EIO))?; + } + + if !tree.range(..=vaddr).next_back().filter(|(start, size)| **start + **size > vaddr).is_some() { + tree.insert(vaddr, size); + } + } + } + // Setup a stack starting from the very end of the address space, and then growing downwards. + const STACK_TOP: usize = 1 << 47; + const STACK_SIZE: usize = 1024 * 1024; + + mprotect_remote(*grants_fd, STACK_TOP - STACK_SIZE, STACK_SIZE, MapFlags::PROT_READ | MapFlags::PROT_WRITE)?; + tree.insert(STACK_TOP - STACK_SIZE, STACK_SIZE); + + let mut sp = STACK_TOP - 256; + + let mut push = |word: usize| { + sp -= size_of::(); + write_all(*memory_fd, Some(sp as u64), &usize::to_ne_bytes(word)) + }; + + let pheaders_size_aligned = (pheaders_size+PAGE_SIZE-1)/PAGE_SIZE*PAGE_SIZE; + let pheaders = find_free_target_addr(&tree, pheaders_size_aligned).ok_or(Error::new(ENOMEM))?; + tree.insert(pheaders, pheaders_size_aligned); + mprotect_remote(*grants_fd, pheaders, pheaders_size_aligned, MapFlags::PROT_READ)?; + + write_all(*memory_fd, Some(pheaders as u64), &phs)?; + + push(0)?; + push(AT_NULL)?; + push(header.e_entry as usize)?; + push(AT_ENTRY)?; + push(pheaders)?; + push(AT_PHDR)?; + push(header.e_phnum as usize)?; + push(AT_PHNUM)?; + push(header.e_phentsize as usize)?; + push(AT_PHENT)?; + + let args_envs_size_aligned = (total_args_envs_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))?; + mprotect_remote(*grants_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); + + let mut offset = 0; + + let mut argc = 0; + + { + let mut append = |source_slice: &[u8]| { + let address = target_args_env_address + offset; + write_all(*memory_fd, Some(address as u64), source_slice)?; + offset += source_slice.len() + 1; + Ok(address) + }; + + push(0)?; + + for env in envs { + push(append(env.as_ref())?)?; + } + + push(0)?; + + for arg in args { + push(append(arg.as_ref())?)?; + argc += 1; + } + } + + push(argc)?; + + unsafe { deactivate_tcb(*open_via_dup)?; } + + // TODO: Restore old name if exec failed? + if let Ok(name_fd) = syscall::dup(*open_via_dup, b"name").map(FdGuard::new) { + let _ = syscall::write(*name_fd, path); + } + + let addrspace_selection_fd = FdGuard::new(syscall::dup(*open_via_dup, b"current-addrspace")?); + + let _ = syscall::write(*addrspace_selection_fd, &create_set_addr_space_buf(*grants_fd, header.e_entry as usize, sp)); + + Ok(addrspace_selection_fd) +} +fn mprotect_remote(socket: usize, addr: usize, len: usize, flags: MapFlags) -> Result<()> { + let mut grants_buf = [0_u8; 24]; + grants_buf[..8].copy_from_slice(&usize::to_ne_bytes(addr)); + grants_buf[8..16].copy_from_slice(&usize::to_ne_bytes(len)); + grants_buf[16..24].copy_from_slice(&usize::to_ne_bytes(flags.bits())); + syscall::write(socket, &grants_buf)?; + Ok(()) +} +fn read_all(fd: usize, offset: Option, buf: &mut [u8]) -> Result<()> { + if let Some(offset) = offset { + syscall::lseek(fd, offset as isize, SEEK_SET)?; + } + + let mut total_bytes_read = 0; + + while total_bytes_read < buf.len() { + total_bytes_read += match syscall::read(fd, &mut buf[total_bytes_read..])? { + 0 => return Err(Error::new(ENOEXEC)), + bytes_read => bytes_read, + } + } + Ok(()) +} +fn write_all(fd: usize, offset: Option, buf: &[u8]) -> Result<()> { + if let Some(offset) = offset { + syscall::lseek(fd, offset as isize, SEEK_SET)?; + } + + let mut total_bytes_written = 0; + + while total_bytes_written < buf.len() { + total_bytes_written += match syscall::write(fd, &buf[total_bytes_written..])? { + 0 => return Err(Error::new(EIO)), + bytes_written => bytes_written, + } + } + Ok(()) +} + +fn find_free_target_addr(tree: &BTreeMap, size: usize) -> Option { + let mut iterator = tree.iter().peekable(); + + // Ignore the space between zero and the first region, to avoid null pointers. + while let Some((cur_address, entry_size)) = iterator.next() { + let end = *cur_address + entry_size; + + if let Some((next_address, _)) = iterator.peek() { + if **next_address - end > size { + return Some(end); + } + } + // No need to check last entry, since the stack will always be put at the highest + // possible address. + } + + None +} +/// Deactive TLS, used before exec() on Redox to not trick target executable into thinking TLS +/// is already initialized as if it was a thread. +#[cfg(all(target_os = "redox", target_arch = "x86_64"))] +pub unsafe fn deactivate_tcb(open_via_dup: usize) -> Result<()> { + let mut env = syscall::EnvRegisters::default(); + + let file = FdGuard::new(syscall::dup(open_via_dup, b"regs/env")?); + + env.fsbase = 0; + env.gsbase = 0; + + let _ = syscall::write(*file, &mut env)?; + Ok(()) +} + +pub struct FdGuard { + fd: usize, + taken: bool, +} +impl FdGuard { + pub fn new(fd: usize) -> Self { + Self { + fd, taken: false, + } + } + pub fn take(&mut self) -> usize { + self.taken = true; + self.fd + } +} +impl core::ops::Deref for FdGuard { + type Target = usize; + + fn deref(&self) -> &Self::Target { + &self.fd + } +} + +impl Drop for FdGuard { + fn drop(&mut self) { + if !self.taken { + let _ = syscall::close(self.fd); + } + } +} +pub fn create_set_addr_space_buf(space: usize, ip: usize, sp: usize) -> [u8; size_of::() * 3] { + let mut buf = [0_u8; 3 * size_of::()]; + let mut chunks = buf.array_chunks_mut::<{size_of::()}>(); + *chunks.next().unwrap() = usize::to_ne_bytes(space); + *chunks.next().unwrap() = usize::to_ne_bytes(sp); + *chunks.next().unwrap() = usize::to_ne_bytes(ip); + buf +} From bac2509ccd74c5ee97c1c1c891164878c6e68e80 Mon Sep 17 00:00:00 2001 From: 4lDO2 <4lDO2@protonmail.com> Date: Fri, 8 Jul 2022 12:27:17 +0200 Subject: [PATCH 09/23] Fix leaks for setuid/setgid too. --- src/platform/redox/mod.rs | 19 +++++++++++++------ src/platform/redox/redox-exec/src/lib.rs | 4 ++-- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/platform/redox/mod.rs b/src/platform/redox/mod.rs index ab5deedb94..b456e85d75 100644 --- a/src/platform/redox/mod.rs +++ b/src/platform/redox/mod.rs @@ -385,9 +385,12 @@ impl Pal for Sys { } if !is_interpreted && wants_setugid { + // Make sure the last file descriptor not covered by O_CLOEXEC is not leaked. + drop(file); + let name = CStr::from_bytes_with_nul(b"escalate:\0").expect("string should be valid"); // We are now going to invoke `escalate:` rather than loading the program ourselves. - let mut escalate_fd = match File::open(name, fcntl::O_WRONLY | fcntl::O_CLOEXEC) { + let mut escalate_fd = match File::open(name, fcntl::O_WRONLY) { Ok(f) => f, Err(_) => return -1, }; @@ -408,7 +411,7 @@ impl Pal for Sys { } // Second, we write the flattened args and envs with NUL characters separating - // individual items. + // individual items. This can be copied directly into the new executable's memory. if escalate_fd.write_all(&flatten_with_nul(args)).is_err() { return -1; } @@ -416,10 +419,14 @@ impl Pal for Sys { return -1; } - // escalated will take care of the rest when responding to SYS_CLOSE. - // FIXME: close escalate_fd - if escalate_fd.write(&[]).is_err() { - return -1; + // 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 + // errors. + let fd = *escalate_fd as usize; + core::mem::forget(escalate_fd); + + if let Err(err) = syscall::close(fd) { + return e(Err(err)) as c_int; } unreachable!() diff --git a/src/platform/redox/redox-exec/src/lib.rs b/src/platform/redox/redox-exec/src/lib.rs index aba5125cd3..aecb2b1bcc 100644 --- a/src/platform/redox/redox-exec/src/lib.rs +++ b/src/platform/redox/redox-exec/src/lib.rs @@ -61,7 +61,7 @@ where let mut tree = BTreeMap::new(); tree.insert(0, PAGE_SIZE); - const BUFSZ: usize = 16384; + const BUFSZ: usize = 65536; let mut buf = vec! [0_u8; BUFSZ]; read_all(*image_file as usize, Some(header.e_phoff), &mut phs).map_err(|_| Error::new(EIO))?; @@ -91,7 +91,7 @@ where syscall::lseek(*image_file as usize, segment.p_offset as isize, SEEK_SET).map_err(|_| Error::new(EIO))?; syscall::lseek(*memory_fd, segment.p_vaddr as isize, SEEK_SET).map_err(|_| Error::new(EIO))?; - for size in core::iter::repeat(BUFSZ).take((segment.p_filesz as usize) / BUFSZ).chain(Some((segment.p_filesz as usize) % BUFSZ)) { + for size in core::iter::repeat(buf.len()).take((segment.p_filesz as usize) / buf.len()).chain(Some((segment.p_filesz as usize) % buf.len())) { read_all(*image_file as usize, None, &mut buf[..size]).map_err(|_| Error::new(EIO))?; let _ = syscall::write(*memory_fd, &buf[..size]).map_err(|_| Error::new(EIO))?; } From 05446070ecc2a491990253caffe1ba9a71d77e84 Mon Sep 17 00:00:00 2001 From: 4lDO2 <4lDO2@protonmail.com> Date: Tue, 12 Jul 2022 14:37:39 +0200 Subject: [PATCH 10/23] Use same rust-toolchain version as elsewhere. --- rust-toolchain | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust-toolchain b/rust-toolchain index 2b782427dd..9eed51006f 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1 +1 @@ -nightly-2021-06-15 +nightly-2022-03-18 From 3701ea3be70dd420efd93e61758cf0759ca18071 Mon Sep 17 00:00:00 2001 From: 4lDO2 <4lDO2@protonmail.com> Date: Tue, 12 Jul 2022 14:38:46 +0200 Subject: [PATCH 11/23] Conflict-proof #[global_allocator] symbols too. The #[global_allocator] used by relibc is only relibc's business. Symbols should therefore not leak if another applications for example wants to override the system allocator (and for bootstrap, which links to relibc but uses its own #[global_allocator] based on libc functions). --- renamesyms.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/renamesyms.sh b/renamesyms.sh index 0b534cf579..a6036064ed 100755 --- a/renamesyms.sh +++ b/renamesyms.sh @@ -8,11 +8,16 @@ if [ -z "$target" ] || [ -z "$deps_dir" ]; then fi symbols_file=`mktemp` +special_syms="__rg_alloc __rg_dealloc __rg_realloc __rg_alloc_zeroed" for dep in `find $deps_dir -type f -name "*.rlib"`; do nm --format=posix -g "$dep" 2>/dev/null | sed 's/.*:.*//g' | awk '{if ($2 == "T") print $1}' | sed 's/^\(.*\)$/\1 __relibc_\1/g' >> $symbols_file done +for special_sym in $special_syms; do + echo "$special_sym __relibc_$special_sym" >> $symbols_file +done + sorted_file=`mktemp` sort -u "$symbols_file" > "$sorted_file" rm -f "$symbols_file" From b930cc98d02c92a9025972a88a0662003b3c3b98 Mon Sep 17 00:00:00 2001 From: 4lDO2 <4lDO2@protonmail.com> Date: Tue, 12 Jul 2022 14:42:06 +0200 Subject: [PATCH 12/23] Correctly preserve and set MXCSR and FCW. --- src/crt0/src/lib.rs | 12 +++++++++++ src/platform/redox/clone.rs | 26 +++++++++++------------- src/platform/redox/redox-exec/src/lib.rs | 4 +--- 3 files changed, 25 insertions(+), 17 deletions(-) diff --git a/src/crt0/src/lib.rs b/src/crt0/src/lib.rs index c92a897c0c..ee596cadc5 100644 --- a/src/crt0/src/lib.rs +++ b/src/crt0/src/lib.rs @@ -8,10 +8,22 @@ use core::arch::global_asm; #[cfg(target_arch = "x86_64")] global_asm!(" .globl _start + .type _start, @function _start: mov rdi, rsp and rsp, 0xFFFFFFFFFFFFFFF0 + + sub rsp, 8 + + mov DWORD PTR [rsp], 0x00001F80 + ldmxcsr [rsp] + mov WORD PTR [rsp], 0x031F + fldcw [rsp] + + add rsp, 8 + call relibc_start + .size _start, . - _start "); #[cfg(target_arch = "aarch64")] global_asm!(" diff --git a/src/platform/redox/clone.rs b/src/platform/redox/clone.rs index 398541bfe6..c409b6901c 100644 --- a/src/platform/redox/clone.rs +++ b/src/platform/redox/clone.rs @@ -42,7 +42,7 @@ fn copy_str(cur_pid_fd: usize, new_pid_fd: usize, key: &str) -> Result<()> { Ok(()) } #[cfg(target_arch = "x86_64")] -fn copy_float_env_regs(cur_pid_fd: usize, new_pid_fd: usize) -> Result<()> { +fn copy_env_regs(cur_pid_fd: usize, new_pid_fd: usize) -> Result<()> { // Copy environment registers. { let cur_env_regs_fd = FdGuard::new(syscall::dup(cur_pid_fd, b"regs/env")?); @@ -52,15 +52,6 @@ fn copy_float_env_regs(cur_pid_fd: usize, new_pid_fd: usize) -> Result<()> { let _ = syscall::read(*cur_env_regs_fd, &mut env_regs)?; let _ = syscall::write(*new_env_regs_fd, &env_regs)?; } - // Copy float registers. - { - let cur_float_regs_fd = FdGuard::new(syscall::dup(cur_pid_fd, b"regs/float")?); - let new_float_regs_fd = FdGuard::new(syscall::dup(new_pid_fd, b"regs/float")?); - - let mut float_regs = syscall::FloatRegisters::default(); - let _ = syscall::read(*cur_float_regs_fd, &mut float_regs)?; - let _ = syscall::write(*new_float_regs_fd, &float_regs)?; - } Ok(()) } @@ -102,7 +93,7 @@ pub unsafe fn pte_clone_impl(stack: *mut usize) -> Result { let _ = syscall::write(*new_filetable_sel_fd, &usize::to_ne_bytes(*cur_filetable_fd))?; } - copy_float_env_regs(*cur_pid_fd, *new_pid_fd)?; + copy_env_regs(*cur_pid_fd, *new_pid_fd)?; // Unblock context. syscall::kill(new_pid, SIGCONT)?; @@ -204,7 +195,7 @@ fn fork_inner(initial_rsp: *mut usize) -> Result { let buf = create_set_addr_space_buf(*new_addr_space_fd, fork_ret as usize, initial_rsp as usize); let _ = syscall::write(*new_addr_space_sel_fd, &buf)?; } - copy_float_env_regs(*cur_pid_fd, *new_pid_fd)?; + copy_env_regs(*cur_pid_fd, *new_pid_fd)?; } // Copy the file table. We do this last to ensure that all previously used file descriptors are // closed. The only exception -- the filetable selection fd and the current filetable fd -- @@ -248,7 +239,10 @@ fork_wrapper: push r14 push r15 - sub rsp, 16 + sub rsp, 32 + + stmxcsr [rsp+16] + fnstcw [rsp+24] mov rdi, rsp call __relibc_internal_fork_impl @@ -258,9 +252,13 @@ fork_ret: mov rdi, [rsp] mov rsi, [rsp + 8] call __relibc_internal_fork_hook + + ldmxcsr [rsp+16] + fldcw [rsp+24] + xor rax, rax 2: - add rsp, 16 + add rsp, 32 pop r15 pop r14 pop r13 diff --git a/src/platform/redox/redox-exec/src/lib.rs b/src/platform/redox/redox-exec/src/lib.rs index aecb2b1bcc..f40e5535bd 100644 --- a/src/platform/redox/redox-exec/src/lib.rs +++ b/src/platform/redox/redox-exec/src/lib.rs @@ -45,9 +45,7 @@ where }; let memory_fd = FdGuard::new(syscall::dup(*grants_fd, b"mem")?); - let instruction_ptr = usize::try_from(header.e_entry).map_err(|_| Error::new(ENOEXEC))?; - - // Never allow more than 1 MiB of program headers. TODO: Capabilities again? + // Never allow more than 1 MiB of program headers. const MAX_PH_SIZE: usize = 1024 * 1024; let phentsize = u64::from(header.e_phentsize) as usize; let phnum = u64::from(header.e_phnum) as usize; From 8ac0626be74e00f84ba18fca94e2e61cd3b48068 Mon Sep 17 00:00:00 2001 From: 4lDO2 <4lDO2@protonmail.com> Date: Mon, 18 Jul 2022 14:09:48 +0200 Subject: [PATCH 13/23] Handle sigactions properly in execve and fork. --- src/platform/redox/clone.rs | 17 +++++++++++++++++ src/platform/redox/redox-exec/src/lib.rs | 8 ++++++++ 2 files changed, 25 insertions(+) diff --git a/src/platform/redox/clone.rs b/src/platform/redox/clone.rs index c409b6901c..a01e8c63e7 100644 --- a/src/platform/redox/clone.rs +++ b/src/platform/redox/clone.rs @@ -93,6 +93,15 @@ pub unsafe fn pte_clone_impl(stack: *mut usize) -> Result { let _ = syscall::write(*new_filetable_sel_fd, &usize::to_ne_bytes(*cur_filetable_fd))?; } + // Reuse sigactions (on Linux, CLONE_THREAD requires CLONE_SIGHAND which implies the sigactions + // table is reused). + { + let cur_sigaction_fd = FdGuard::new(syscall::dup(*cur_pid_fd, b"sigactions")?); + let new_sigaction_sel_fd = FdGuard::new(syscall::dup(*new_pid_fd, b"current-sigactions")?); + + let _ = syscall::write(*new_sigaction_sel_fd, &usize::to_ne_bytes(*cur_sigaction_fd))?; + } + copy_env_regs(*cur_pid_fd, *new_pid_fd)?; // Unblock context. @@ -131,6 +140,14 @@ fn fork_inner(initial_rsp: *mut usize) -> Result { 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")?); + let new_sigaction_fd = FdGuard::new(syscall::dup(*cur_sigaction_fd, b"copy")?); + let new_sigaction_sel_fd = FdGuard::new(syscall::dup(*new_pid_fd, b"current-sigactions")?); + + let _ = syscall::write(*new_sigaction_sel_fd, &usize::to_ne_bytes(*new_sigaction_fd))?; + } + // Copy existing files into new file table, but do not reuse the same file table (i.e. new // parent FDs will not show up for the child). { diff --git a/src/platform/redox/redox-exec/src/lib.rs b/src/platform/redox/redox-exec/src/lib.rs index f40e5535bd..13f0c10f49 100644 --- a/src/platform/redox/redox-exec/src/lib.rs +++ b/src/platform/redox/redox-exec/src/lib.rs @@ -166,6 +166,14 @@ where unsafe { deactivate_tcb(*open_via_dup)?; } + { + let current_sigaction_fd = FdGuard::new(syscall::dup(*open_via_dup, b"sigactions")?); + let empty_sigaction_fd = FdGuard::new(syscall::dup(*current_sigaction_fd, b"empty")?); + let sigaction_selection_fd = FdGuard::new(syscall::dup(*open_via_dup, b"current-sigactions")?); + + let _ = syscall::write(*sigaction_selection_fd, &usize::to_ne_bytes(*empty_sigaction_fd))?; + } + // TODO: Restore old name if exec failed? if let Ok(name_fd) = syscall::dup(*open_via_dup, b"name").map(FdGuard::new) { let _ = syscall::write(*name_fd, path); From 24ecd6ee16660651881a7ff218dc5293b95d7a26 Mon Sep 17 00:00:00 2001 From: 4lDO2 <4lDO2@protonmail.com> Date: Mon, 18 Jul 2022 16:04:09 +0200 Subject: [PATCH 14/23] Fix waitpid deadlock. --- src/platform/redox/clone.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/platform/redox/clone.rs b/src/platform/redox/clone.rs index a01e8c63e7..62e2385d9a 100644 --- a/src/platform/redox/clone.rs +++ b/src/platform/redox/clone.rs @@ -228,6 +228,12 @@ fn fork_inner(initial_rsp: *mut usize) -> Result { // Unblock context. syscall::kill(new_pid, SIGCONT)?; + // XXX: Killing with SIGCONT will put (pid, 65536) at key (pid, pgid) into the waitpid of this + // context. This means that if pgid is changed (as it is in ion for example), the pgid message + // in syscall::exit() will not be inserted as the key comparator thinks they're equal as their + // PIDs are. So, we have to call this to clear the waitpid queue to prevent deadlocks. + let _ = syscall::waitpid(new_pid, &mut 0, syscall::WUNTRACED | syscall::WCONTINUED); + Ok(new_pid) } #[no_mangle] From db16393fd7f8c77151733be1abaf804d2b140425 Mon Sep 17 00:00:00 2001 From: 4lDO2 <4lDO2@protonmail.com> Date: Fri, 22 Jul 2022 17:25:44 +0200 Subject: [PATCH 15/23] Manually init FPU context in pte_clone. --- src/platform/redox/clone.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/platform/redox/clone.rs b/src/platform/redox/clone.rs index 62e2385d9a..ee2c8c8cf9 100644 --- a/src/platform/redox/clone.rs +++ b/src/platform/redox/clone.rs @@ -296,7 +296,6 @@ fork_ret: .globl pte_clone_ret .type pte_clone_ret, @function pte_clone_ret: - # Load registers pop rax pop rdi @@ -306,6 +305,15 @@ pte_clone_ret: pop r8 pop r9 + sub rsp, 8 + + mov DWORD PTR [rsp], 0x00001F80 + ldmxcsr [rsp] + mov WORD PTR [rsp], 0x031F + fldcw [rsp] + + add rsp, 8 + # Call entry point call rax From 049a5156d70829c5ee4be49b710bf0e530f36f05 Mon Sep 17 00:00:00 2001 From: 4lDO2 <4lDO2@protonmail.com> Date: Sat, 23 Jul 2022 00:15:18 +0200 Subject: [PATCH 16/23] Implement PT_INTERP, and thus dynamic linking. With this change, gcc can now successfully compile a tiny program that printfs an integer returned from a function, from a dynamically linked library that it compiled as well. Rustc however, is orders of magnitude more complex, and the next step is to fix constructors which require access to `environ`, in ld.so --- src/header/sys_auxv/mod.rs | 28 +-- src/platform/auxv_defs.rs | 27 +++ src/platform/mod.rs | 6 + src/platform/redox/exec.rs | 235 +++++++++++++++++++++- src/platform/redox/mod.rs | 242 ++--------------------- src/platform/redox/redox-exec/src/lib.rs | 115 ++++++++--- 6 files changed, 363 insertions(+), 290 deletions(-) create mode 100644 src/platform/auxv_defs.rs diff --git a/src/header/sys_auxv/mod.rs b/src/header/sys_auxv/mod.rs index 184eba1a76..6c4f9ccae1 100644 --- a/src/header/sys_auxv/mod.rs +++ b/src/header/sys_auxv/mod.rs @@ -2,33 +2,7 @@ use crate::platform::types::*; -pub const AT_NULL: usize = 0; /* End of vector */ -pub const AT_IGNORE: usize = 1; /* Entry should be ignored */ -pub const AT_EXECFD: usize = 2; /* File descriptor of program */ -pub const AT_PHDR: usize = 3; /* Program headers for program */ -pub const AT_PHENT: usize = 4; /* Size of program header entry */ -pub const AT_PHNUM: usize = 5; /* Number of program headers */ -pub const AT_PAGESZ: usize = 6; /* System page size */ -pub const AT_BASE: usize = 7; /* Base address of interpreter */ -pub const AT_FLAGS: usize = 8; /* Flags */ -pub const AT_ENTRY: usize = 9; /* Entry point of program */ -pub const AT_NOTELF: usize = 10; /* Program is not ELF */ -pub const AT_UID: usize = 11; /* Real uid */ -pub const AT_EUID: usize = 12; /* Effective uid */ -pub const AT_GID: usize = 13; /* Real gid */ -pub const AT_EGID: usize = 14; /* Effective gid */ -pub const AT_CLKTCK: usize = 17; /* Frequency of times() */ -pub const AT_PLATFORM: usize = 15; /* String identifying platform. */ -pub const AT_HWCAP: usize = 16; /* Machine-dependent hints about */ -pub const AT_FPUCW: usize = 18; /* Used FPU control word. */ -pub const AT_DCACHEBSIZE: usize = 19; /* Data cache block size. */ -pub const AT_ICACHEBSIZE: usize = 20; /* Instruction cache block size. */ -pub const AT_UCACHEBSIZE: usize = 21; /* Unified cache block size. */ -pub const AT_IGNOREPPC: usize = 22; /* Entry should be ignored. */ -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. */ +pub use crate::platform::auxv_defs::*; #[no_mangle] pub extern "C" fn getauxval(_t: c_ulong) -> c_ulong { diff --git a/src/platform/auxv_defs.rs b/src/platform/auxv_defs.rs new file mode 100644 index 0000000000..30a59962bb --- /dev/null +++ b/src/platform/auxv_defs.rs @@ -0,0 +1,27 @@ +pub const AT_NULL: usize = 0; /* End of vector */ +pub const AT_IGNORE: usize = 1; /* Entry should be ignored */ +pub const AT_EXECFD: usize = 2; /* File descriptor of program */ +pub const AT_PHDR: usize = 3; /* Program headers for program */ +pub const AT_PHENT: usize = 4; /* Size of program header entry */ +pub const AT_PHNUM: usize = 5; /* Number of program headers */ +pub const AT_PAGESZ: usize = 6; /* System page size */ +pub const AT_BASE: usize = 7; /* Base address of interpreter */ +pub const AT_FLAGS: usize = 8; /* Flags */ +pub const AT_ENTRY: usize = 9; /* Entry point of program */ +pub const AT_NOTELF: usize = 10; /* Program is not ELF */ +pub const AT_UID: usize = 11; /* Real uid */ +pub const AT_EUID: usize = 12; /* Effective uid */ +pub const AT_GID: usize = 13; /* Real gid */ +pub const AT_EGID: usize = 14; /* Effective gid */ +pub const AT_CLKTCK: usize = 17; /* Frequency of times() */ +pub const AT_PLATFORM: usize = 15; /* String identifying platform. */ +pub const AT_HWCAP: usize = 16; /* Machine-dependent hints about */ +pub const AT_FPUCW: usize = 18; /* Used FPU control word. */ +pub const AT_DCACHEBSIZE: usize = 19; /* Data cache block size. */ +pub const AT_ICACHEBSIZE: usize = 20; /* Instruction cache block size. */ +pub const AT_UCACHEBSIZE: usize = 21; /* Unified cache block size. */ +pub const AT_IGNOREPPC: usize = 22; /* Entry should be ignored. */ +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. */ diff --git a/src/platform/mod.rs b/src/platform/mod.rs index 890f83245d..6169a3b591 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -34,6 +34,12 @@ mod pte; pub use self::rlb::{Line, RawLineBuffer}; pub mod rlb; +#[cfg(target_os = "linux")] +pub mod auxv_defs; + +#[cfg(target_os = "redox")] +pub use redox_exec::auxv_defs; + use self::types::*; pub mod types; diff --git a/src/platform/redox/exec.rs b/src/platform/redox/exec.rs index ac3672bb37..c551eb3133 100644 --- a/src/platform/redox/exec.rs +++ b/src/platform/redox/exec.rs @@ -1,20 +1,245 @@ +use crate::c_str::{CStr, CString}; +use crate::core_io::{BufReader, prelude::*, SeekFrom}; use crate::fs::File; +use crate::header::{fcntl, string::strlen}; +use crate::platform::{sys::{S_ISUID, S_ISGID}, types::*}; -use syscall::error::Result; -use redox_exec::FdGuard; +use syscall::data::Stat; +use syscall::flag::*; +use syscall::error::*; +use redox_exec::{FdGuard, FexecResult}; -pub fn fexec_impl(file: File, path: &[u8], args: &[&[u8]], envs: &[&[u8]], args_envs_size_without_nul: usize) -> Result { +fn fexec_impl(file: File, path: &[u8], args: &[&[u8]], envs: &[&[u8]], total_args_envs_size: usize, interp_override: Option) -> Result { let fd = *file; core::mem::forget(file); let image_file = FdGuard::new(fd as usize); let open_via_dup = FdGuard::new(syscall::open("thisproc:current/open_via_dup", 0)?); - let total_args_envs_size = args_envs_size_without_nul + args.len() + envs.len(); - let addrspace_selection_fd = redox_exec::fexec_impl(image_file, open_via_dup, path, args.iter().rev(), envs.iter().rev(), total_args_envs_size)?; + let addrspace_selection_fd = match redox_exec::fexec_impl(image_file, open_via_dup, path, args.iter().rev(), envs.iter().rev(), total_args_envs_size, interp_override)? { + FexecResult::Normal { addrspace_handle } => addrspace_handle, + FexecResult::Interp { image_file, open_via_dup, path, interp_override: new_interp_override } => { + drop(image_file); + drop(open_via_dup); + + // According to elf(5), PT_INTERP requires that the interpreter path be + // null-terminated. Violating this should therefore give the "format error" ENOEXEC. + let path_cstr = CStr::from_bytes_with_nul(&path).map_err(|_| Error::new(ENOEXEC))?; + + return execve(path_cstr, ArgEnv::Parsed { total_args_envs_size, args, envs }, Some(new_interp_override)); + } + }; // Dropping this FD will cause the address space switch. drop(addrspace_selection_fd); unreachable!(); } +pub enum ArgEnv<'a> { + C { argv: *const *mut c_char, envp: *const *mut c_char }, + Parsed { args: &'a [&'a [u8]], envs: &'a [&'a [u8]], total_args_envs_size: usize }, +} +pub fn execve(path: &CStr, arg_env: ArgEnv, interp_override: Option) -> Result { + // NOTE: We must omit O_CLOEXEC and close manually, otherwise it will be closed before we + // have even read it! + let mut image_file = File::open(path, O_RDONLY as c_int).map_err(|_| Error::new(ENOENT))?; + + // With execve now being implemented in userspace, we need to check ourselves that this + // file is actually executable. While checking for read permission is unnecessary as the + // scheme will not allow us to read otherwise, the execute bit is completely unenforced. We + // have the permission to mmap executable memory and fill it with the program even if it is + // unset, so the best we can do is check that nothing is executed by accident. + // + // TODO: At some point we might have capabilities limiting the ability to allocate + // executable memory, and in that case we might use the `escalate:` scheme as we already do + // when the binary needs setuid/setgid. + + let mut stat = Stat::default(); + syscall::fstat(*image_file as usize, &mut stat)?; + let uid = syscall::getuid()?; + let gid = syscall::getuid()?; + + let mode = if uid == stat.st_uid as usize { + (stat.st_mode >> 3 * 2) & 0o7 + } else if gid == stat.st_gid as usize { + (stat.st_mode >> 3 * 1) & 0o7 + } else { + stat.st_mode & 0o7 + }; + + if mode & 0o1 == 0o0 { + return Err(Error::new(EPERM)); + } + let wants_setugid = stat.st_mode & ((S_ISUID | S_ISGID) as u16) != 0; + + // Count arguments + let mut len = 0; + + match arg_env { + ArgEnv::C { argv, .. } => unsafe { + while !(*argv.add(len)).is_null() { + len += 1; + } + } + ArgEnv::Parsed { args, .. } => len = args.len(), + } + + let mut args: Vec<&[u8]> = Vec::with_capacity(len); + + // Read shebang (for example #!/bin/sh) + let mut _interpreter_path = None; + let is_interpreted = { + let mut read = 0; + let mut shebang = [0; 2]; + + while read < 2 { + match image_file.read(&mut shebang).map_err(|_| Error::new(ENOEXEC))? { + 0 => break, + i => read += i, + } + } + 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. + // + // 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 + // too. + + if is_interpreted { + // TODO: Does this support prepending args to the interpreter? E.g. + // #!/usr/bin/env python3 + + // So, this file is interpreted. + // Then, read the actual interpreter: + let mut interpreter = Vec::new(); + BufReader::new(&mut image_file).read_until(b'\n', &mut interpreter).map_err(|_| Error::new(EIO))?; + if interpreter.ends_with(&[b'\n']) { + interpreter.pop().unwrap(); + } + let cstring = CString::new(interpreter).map_err(|_| Error::new(ENOEXEC))?; + image_file = File::open(&cstring, O_RDONLY as c_int).map_err(|_| Error::new(ENOENT))?; + + // Make sure path is kept alive long enough, and push it to the arguments + _interpreter_path = Some(cstring); + let path_ref = _interpreter_path.as_ref().unwrap(); + args.push(path_ref.as_bytes()); + } else { + image_file.seek(SeekFrom::Start(0)).map_err(|_| Error::new(EIO))?; + } + + let (total_args_envs_size, args, envs): (usize, Vec<_>, Vec<_>) = match arg_env { + ArgEnv::C { mut argv, mut envp } => unsafe { + let mut args_envs_size_without_nul = 0; + + // Arguments + while !argv.read().is_null() { + let arg = argv.read(); + + let len = strlen(arg); + args.push(core::slice::from_raw_parts(arg as *const u8, len)); + args_envs_size_without_nul += len; + argv = argv.add(1); + } + + // Environment variables + let mut len = 0; + while !envp.add(len).read().is_null() { + len += 1; + } + + let mut envs: Vec<&[u8]> = Vec::with_capacity(len); + while !envp.read().is_null() { + let env = envp.read(); + + let len = strlen(env); + envs.push(core::slice::from_raw_parts(env as *const u8, len)); + args_envs_size_without_nul += len; + envp = envp.add(1); + } + (args_envs_size_without_nul + args.len() + envs.len(), args, envs) + } + ArgEnv::Parsed { args: new_args, envs, total_args_envs_size } => { + let prev_size: usize = args.iter().map(|a| a.len()).sum(); + args.extend(new_args); + (total_args_envs_size + prev_size, args, Vec::from(envs)) + } + }; + + + // Close all O_CLOEXEC file descriptors. TODO: close_range? + { + // NOTE: This approach of implementing O_CLOEXEC will not work in multithreaded + // scenarios. While execve() is undefined according to POSIX if there exist sibling + // threads, it could still be allowed by keeping certain file descriptors and instead + // set the active file table. + let files_fd = File::new(syscall::open("thisproc:current/filetable", O_RDONLY)? as c_int); + for line in BufReader::new(files_fd).lines() { + let line = match line { + Ok(l) => l, + Err(_) => break, + }; + let fd = match line.parse::() { + Ok(f) => f, + Err(_) => continue, + }; + + let flags = syscall::fcntl(fd, F_GETFD, 0)?; + + if flags & O_CLOEXEC == O_CLOEXEC { + let _ = syscall::close(fd); + } + } + } + + if !is_interpreted && wants_setugid { + // Make sure the last file descriptor not covered by O_CLOEXEC is not leaked. + drop(image_file); + + // We are now going to invoke `escalate:` rather than loading the program ourselves. + let escalate_fd = FdGuard::new(syscall::open("escalate:", O_WRONLY)?); + + // First, we write the path. + // + // TODO: For improved security, use a hypothetical SYS_DUP_FORWARD syscall to give the + // scheme our file descriptor. It can check through the kernel-overwritten stat.st_dev + // field that it pertains to a "trusted" scheme (i.e. of at least the privilege the + // new uid/gid has), although for now only root can open schemes. Passing a file + // descriptor and not a path will allow escalated to run in a limited namespace. + // + // TODO: Plus, at this point fexecve is not implemented (but specified in + // POSIX.1-2008), and to avoid bad syscalls such as fpath, passing a file descriptor + // would be better. + let _ = syscall::write(*escalate_fd, path.to_bytes()); + + // Second, we write the flattened args and envs with NUL characters separating + // 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))?; + + // 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 + // errors. + let fd = *escalate_fd as usize; + core::mem::forget(escalate_fd); + + syscall::close(fd)?; + + unreachable!() + } else { + fexec_impl(image_file, path.to_bytes(), &args, &envs, total_args_envs_size, interp_override) + } +} +fn flatten_with_nul(iter: impl IntoIterator) -> Box<[u8]> where T: AsRef<[u8]> { + let mut vec = Vec::new(); + for item in iter { + vec.extend(item.as_ref()); + vec.push(b'\0'); + } + vec.into_boxed_slice() +} diff --git a/src/platform/redox/mod.rs b/src/platform/redox/mod.rs index b456e85d75..67c9392e71 100644 --- a/src/platform/redox/mod.rs +++ b/src/platform/redox/mod.rs @@ -34,6 +34,11 @@ use super::{errno, types::*, Pal, Read}; static mut BRK_CUR: *mut c_void = ptr::null_mut(); static mut BRK_END: *mut c_void = ptr::null_mut(); +const PAGE_SIZE: usize = 4096; +fn round_up_to_page_size(val: usize) -> usize { + (val + PAGE_SIZE - 1) / PAGE_SIZE * PAGE_SIZE +} + mod clone; mod epoll; mod exec; @@ -67,14 +72,6 @@ pub fn e(sys: Result) -> usize { } } } -fn flatten_with_nul(iter: impl IntoIterator) -> Box<[u8]> where T: AsRef<[u8]> { - let mut vec = Vec::new(); - for item in iter { - vec.extend(item.as_ref()); - vec.push(b'\0'); - } - vec.into_boxed_slice() -} pub struct Sys; @@ -216,223 +213,10 @@ impl Pal for Sys { unsafe fn execve( path: &CStr, - mut argv: *const *mut c_char, - mut envp: *const *mut c_char, + argv: *const *mut c_char, + envp: *const *mut c_char, ) -> c_int { - // NOTE: We must omit O_CLOEXEC and close manually, otherwise it will be closed before we - // have even read it! - let mut file = match File::open(path, fcntl::O_RDONLY) { - Ok(file) => file, - Err(_) => return -1, - }; - let fd = *file as usize; - - // With execve now being implemented in userspace, we need to check ourselves that this - // file is actually executable. While checking for read permission is unnecessary as the - // scheme will not allow us to read otherwise, the execute bit is completely unenforced. We - // have the permission to mmap executable memory and fill it with the program even if it is - // unset, so the best we can do is check that nothing is executed by accident. - // - // TODO: At some point we might have capabilities limiting the ability to allocate - // executable memory, and in that case we might use the `escalate:` scheme as we already do - // when the binary needs setuid/setgid. - - let mut stat = redox_stat::default(); - if e(syscall::fstat(fd, &mut stat)) == !0 { - return -1; - } - let uid = e(syscall::getuid()); - if uid == !0 { - return -1; - } - let gid = e(syscall::getuid()); - if gid == !0 { - return -1; - } - - let mode = if uid == stat.st_uid as usize { - (stat.st_mode >> 3 * 2) & 0o7 - } else if gid == stat.st_gid as usize { - (stat.st_mode >> 3 * 1) & 0o7 - } else { - stat.st_mode & 0o7 - }; - - if mode & 0o1 == 0o0 { - errno = EPERM; - return -1; - } - let wants_setugid = stat.st_mode & ((S_ISUID | S_ISGID) as u16) != 0; - - // Count arguments - let mut len = 0; - while !(*argv.add(len)).is_null() { - len += 1; - } - - let mut args: Vec<&[u8]> = Vec::with_capacity(len); - - // Read shebang (for example #!/bin/sh) - let mut _interpreter_path = None; - let is_interpreted = { - let mut read = 0; - let mut shebang = [0; 2]; - - while read < 2 { - match file.read(&mut shebang) { - Ok(0) => break, - Ok(i) => read += i, - Err(_) => return -1, - } - } - 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. - // - // 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 - // too. - - if is_interpreted { - // So, this file is interpreted. - // Then, read the actual interpreter: - let mut interpreter = Vec::new(); - if BufReader::new(&mut file).read_until(b'\n', &mut interpreter).is_err() { - return -1; - } - if interpreter.ends_with(&[b'\n']) { - interpreter.pop().unwrap(); - } - let cstring = match CString::new(interpreter) { - Ok(cstring) => cstring, - Err(_) => return -1, - }; - file = match File::open(&cstring, fcntl::O_RDONLY) { - Ok(file) => file, - Err(_) => return -1, - }; - - // Make sure path is kept alive long enough, and push it to the arguments - _interpreter_path = Some(cstring); - let path_ref = _interpreter_path.as_ref().unwrap(); - args.push(path_ref.as_bytes()); - } else { - if file.seek(SeekFrom::Start(0)).is_err() { - return -1; - } - } - let mut args_envs_size_without_nul = 0; - - // Arguments - while !argv.read().is_null() { - let arg = argv.read(); - - let len = strlen(arg); - args.push(core::slice::from_raw_parts(arg as *const u8, len)); - args_envs_size_without_nul += len; - argv = argv.add(1); - } - - // Environment variables - let mut len = 0; - while !envp.add(len).read().is_null() { - len += 1; - } - - let mut envs: Vec<&[u8]> = Vec::with_capacity(len); - while !envp.read().is_null() { - let env = envp.read(); - - let len = strlen(env); - envs.push(core::slice::from_raw_parts(env as *const u8, len)); - args_envs_size_without_nul += len; - envp = envp.add(1); - } - - // Close all O_CLOEXEC file descriptors. TODO: close_range? - { - // NOTE: This approach of implementing O_CLOEXEC will not work in multithreaded - // scenarios. While execve() is undefined according to POSIX if there exist sibling - // threads, it could still be allowed by keeping certain file descriptors and instead - // set the active file table. - let name = CStr::from_bytes_with_nul(b"thisproc:current/filetable\0").expect("string should be valid"); - let files_fd = match File::open(name, fcntl::O_RDONLY) { - Ok(f) => f, - Err(_) => return -1, - }; - for line in BufReader::new(files_fd).lines() { - let line = match line { - Ok(l) => l, - Err(_) => break, - }; - let fd = match line.parse::() { - Ok(f) => f, - Err(_) => continue, - }; - - let flags = Self::fcntl(fd as c_int, fcntl::F_GETFD, 0); - if flags != -1 { - if flags & fcntl::O_CLOEXEC == fcntl::O_CLOEXEC { - let _ = Self::close(fd as c_int); - } - } - } - } - - if !is_interpreted && wants_setugid { - // Make sure the last file descriptor not covered by O_CLOEXEC is not leaked. - drop(file); - - let name = CStr::from_bytes_with_nul(b"escalate:\0").expect("string should be valid"); - // We are now going to invoke `escalate:` rather than loading the program ourselves. - let mut escalate_fd = match File::open(name, fcntl::O_WRONLY) { - Ok(f) => f, - Err(_) => return -1, - }; - - // First, we write the path. - // - // TODO: For improved security, use a hypothetical SYS_DUP_FORWARD syscall to give the - // scheme our file descriptor. It can check through the kernel-overwritten stat.st_dev - // field that it pertains to a "trusted" scheme (i.e. of at least the privilege the - // new uid/gid has), although for now only root can open schemes. Passing a file - // descriptor and not a path will allow escalated to run in a limited namespace. - // - // TODO: Plus, at this point fexecve is not implemented (but specified in - // POSIX.1-2008), and to avoid bad syscalls such as fpath, passing a file descriptor - // would be better. - if escalate_fd.write_all(path.to_bytes()).is_err() { - return -1; - } - - // Second, we write the flattened args and envs with NUL characters separating - // individual items. This can be copied directly into the new executable's memory. - if escalate_fd.write_all(&flatten_with_nul(args)).is_err() { - return -1; - } - if escalate_fd.write_all(&flatten_with_nul(envs)).is_err() { - return -1; - } - - // 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 - // errors. - let fd = *escalate_fd as usize; - core::mem::forget(escalate_fd); - - if let Err(err) = syscall::close(fd) { - return e(Err(err)) as c_int; - } - - unreachable!() - } else { - e(self::exec::fexec_impl(file, path.to_bytes(), &args, &envs, args_envs_size_without_nul)) as c_int - } + e(self::exec::execve(path, self::exec::ArgEnv::C { argv, envp }, None)) as c_int } fn fchdir(fd: c_int) -> c_int { @@ -701,7 +485,7 @@ impl Pal for Sys { } fn getpagesize() -> usize { - 4096 + PAGE_SIZE } fn getpgid(pid: pid_t) -> pid_t { @@ -848,7 +632,7 @@ impl Pal for Sys { ) -> *mut c_void { let map = Map { offset: off as usize, - size: len, + size: round_up_to_page_size(len), flags: syscall::MapFlags::from_bits_truncate( ((prot as usize) << 16) | ((flags as usize) & 0xFFFF), ), @@ -865,7 +649,7 @@ impl Pal for Sys { unsafe fn mprotect(addr: *mut c_void, len: usize, prot: c_int) -> c_int { e(syscall::mprotect( addr as usize, - len, + round_up_to_page_size(len), syscall::MapFlags::from_bits((prot as usize) << 16) .expect("mprotect: invalid bit pattern"), )) as c_int @@ -877,7 +661,7 @@ impl Pal for Sys { /* TODO e(syscall::msync( addr as usize, - len, + round_up_to_page_size(len), flags )) as c_int */ @@ -894,7 +678,7 @@ impl Pal for Sys { } unsafe fn munmap(addr: *mut c_void, len: usize) -> c_int { - if e(syscall::funmap(addr as usize, len)) == !0 { + if e(syscall::funmap(addr as usize, round_up_to_page_size(len))) == !0 { return !0; } 0 diff --git a/src/platform/redox/redox-exec/src/lib.rs b/src/platform/redox/redox-exec/src/lib.rs index 13f0c10f49..1146b0ede0 100644 --- a/src/platform/redox/redox-exec/src/lib.rs +++ b/src/platform/redox/redox-exec/src/lib.rs @@ -1,34 +1,46 @@ #![no_std] -#![feature(array_chunks)] +#![feature(array_chunks, map_first_last)] extern crate alloc; -use core::convert::TryFrom; use core::mem::size_of; use alloc::{ - collections::{btree_map::Entry, BTreeMap}, - vec::Vec, + boxed::Box, + collections::BTreeMap, vec, }; use syscall::{ error::*, - flag::{AT_ENTRY, AT_NULL, AT_PHDR, AT_PHENT, AT_PHNUM, MapFlags, O_WRONLY, SEEK_SET}, + flag::{MapFlags, SEEK_SET}, }; #[cfg(target_arch = "x86_64")] const PAGE_SIZE: usize = 4096; -pub fn fexec_impl(image_file: FdGuard, open_via_dup: FdGuard, path: &[u8], args: A, envs: E, total_args_envs_size: usize) -> Result +pub enum FexecResult { + Normal { addrspace_handle: FdGuard }, + Interp { path: Box<[u8]>, image_file: FdGuard, open_via_dup: FdGuard, interp_override: InterpOverride }, +} +pub struct InterpOverride { + phs: Box<[u8]>, + at_entry: usize, + at_phnum: usize, + at_phent: usize, + name: Box<[u8]>, + tree: BTreeMap, +} + +pub fn fexec_impl(image_file: FdGuard, open_via_dup: FdGuard, path: &[u8], args: A, envs: E, total_args_envs_size: usize, mut interp_override: Option) -> Result where A: IntoIterator, E: IntoIterator, A::Item: AsRef<[u8]>, E::Item: AsRef<[u8]>, { - use goblin::elf64::{header::Header, program_header::program_header64::{ProgramHeader, PT_LOAD, PF_W, PF_X}}; + use goblin::elf64::{header::Header, program_header::program_header64::{ProgramHeader, PT_LOAD, PT_INTERP, PF_W, PF_X}}; // Here, we do the minimum part of loading an application, which is what the kernel used to do. // We load the executable into memory (albeit at different offsets in this executable), fix @@ -49,20 +61,24 @@ where const MAX_PH_SIZE: usize = 1024 * 1024; let phentsize = u64::from(header.e_phentsize) as usize; let phnum = u64::from(header.e_phnum) as usize; - let pheaders_size = phentsize.saturating_mul(phnum); + let pheaders_size = phentsize.saturating_mul(phnum).saturating_add(size_of::
()); if pheaders_size > MAX_PH_SIZE { return Err(Error::new(E2BIG)); } - let mut phs = vec! [0_u8; pheaders_size]; + let mut phs_raw = vec! [0_u8; pheaders_size]; + phs_raw[..size_of::
()].copy_from_slice(&header_bytes); + let phs = &mut phs_raw[size_of::
()..]; - let mut tree = BTreeMap::new(); - tree.insert(0, PAGE_SIZE); + // TODO: Remove clone, but this would require more as_refs and as_muts + let mut tree = interp_override.as_mut().map_or_else(|| { + core::iter::once((0, PAGE_SIZE)).collect::>() + }, |o| core::mem::take(&mut o.tree)); - const BUFSZ: usize = 65536; + const BUFSZ: usize = 1024 * 256; let mut buf = vec! [0_u8; BUFSZ]; - read_all(*image_file as usize, Some(header.e_phoff), &mut phs).map_err(|_| Error::new(EIO))?; + read_all(*image_file as usize, Some(header.e_phoff), phs).map_err(|_| Error::new(EIO))?; for ph_idx in 0..phnum { let ph_bytes = &phs[ph_idx * phentsize..(ph_idx + 1) * phentsize]; @@ -84,19 +100,42 @@ where if segment.p_filesz > segment.p_memsz { return Err(Error::new(ENOEXEC)); } - if segment.p_type == PT_LOAD { - mprotect_remote(*grants_fd, vaddr, size, flags)?; - syscall::lseek(*image_file as usize, segment.p_offset as isize, SEEK_SET).map_err(|_| Error::new(EIO))?; - syscall::lseek(*memory_fd, segment.p_vaddr as isize, SEEK_SET).map_err(|_| Error::new(EIO))?; + #[forbid(unreachable_patterns)] + match segment.p_type { + // PT_INTERP must come before any PT_LOAD, so we don't have to iterate twice. + PT_INTERP => { + let mut interp = vec! [0_u8; segment.p_filesz as usize]; + read_all(*image_file as usize, Some(segment.p_offset), &mut interp)?; - for size in core::iter::repeat(buf.len()).take((segment.p_filesz as usize) / buf.len()).chain(Some((segment.p_filesz as usize) % buf.len())) { - read_all(*image_file as usize, None, &mut buf[..size]).map_err(|_| Error::new(EIO))?; - let _ = syscall::write(*memory_fd, &buf[..size]).map_err(|_| Error::new(EIO))?; + return Ok(FexecResult::Interp { + path: interp.into_boxed_slice(), + image_file, + open_via_dup, + interp_override: InterpOverride { + at_entry: header.e_entry as usize, + at_phnum: phnum, + at_phent: phentsize, + phs: phs_raw.into_boxed_slice(), + name: path.into(), + tree, + } + }); } + PT_LOAD => { + mprotect_remote(*grants_fd, vaddr, size, flags)?; + syscall::lseek(*image_file as usize, segment.p_offset as isize, SEEK_SET).map_err(|_| Error::new(EIO))?; + syscall::lseek(*memory_fd, segment.p_vaddr as isize, SEEK_SET).map_err(|_| Error::new(EIO))?; - if !tree.range(..=vaddr).next_back().filter(|(start, size)| **start + **size > vaddr).is_some() { - tree.insert(vaddr, size); + for size in core::iter::repeat(buf.len()).take((segment.p_filesz as usize) / buf.len()).chain(Some((segment.p_filesz as usize) % buf.len())) { + read_all(*image_file as usize, None, &mut buf[..size]).map_err(|_| Error::new(EIO))?; + let _ = syscall::write(*memory_fd, &buf[..size]).map_err(|_| Error::new(EIO))?; + } + + if !tree.range(..=vaddr).next_back().filter(|(start, size)| **start + **size > vaddr).is_some() { + tree.insert(vaddr, size); + } } + _ => continue, } } // Setup a stack starting from the very end of the address space, and then growing downwards. @@ -113,22 +152,31 @@ where write_all(*memory_fd, Some(sp as u64), &usize::to_ne_bytes(word)) }; - let pheaders_size_aligned = (pheaders_size+PAGE_SIZE-1)/PAGE_SIZE*PAGE_SIZE; + let pheaders_to_convey = if let Some(ref r#override) = interp_override { + &*r#override.phs + } else { + &*phs_raw + }; + let pheaders_size_aligned = (pheaders_to_convey.len()+PAGE_SIZE-1)/PAGE_SIZE*PAGE_SIZE; let pheaders = find_free_target_addr(&tree, pheaders_size_aligned).ok_or(Error::new(ENOMEM))?; tree.insert(pheaders, pheaders_size_aligned); mprotect_remote(*grants_fd, pheaders, pheaders_size_aligned, MapFlags::PROT_READ)?; - write_all(*memory_fd, Some(pheaders as u64), &phs)?; + write_all(*memory_fd, Some(pheaders as u64), &pheaders_to_convey)?; push(0)?; push(AT_NULL)?; push(header.e_entry as usize)?; + if let Some(ref r#override) = interp_override { + push(AT_BASE)?; + push(r#override.at_entry)?; + } push(AT_ENTRY)?; - push(pheaders)?; + push(pheaders + size_of::
())?; push(AT_PHDR)?; - push(header.e_phnum as usize)?; + push(interp_override.as_ref().map_or(header.e_phnum as usize, |o| o.at_phnum))?; push(AT_PHNUM)?; - push(header.e_phentsize as usize)?; + 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; @@ -176,14 +224,18 @@ where // TODO: Restore old name if exec failed? if let Ok(name_fd) = syscall::dup(*open_via_dup, b"name").map(FdGuard::new) { - let _ = syscall::write(*name_fd, path); + let _ = syscall::write(*name_fd, interp_override.as_ref().map_or(path, |o| &o.name)); + } + { + let mmap_min_fd = FdGuard::new(syscall::dup(*open_via_dup, b"mmap-min-addr")?); + let _ = syscall::write(*mmap_min_fd, &usize::to_ne_bytes(tree.iter().rev().nth(1).map_or(0, |(off, len)| *off + *len))); } let addrspace_selection_fd = FdGuard::new(syscall::dup(*open_via_dup, b"current-addrspace")?); let _ = syscall::write(*addrspace_selection_fd, &create_set_addr_space_buf(*grants_fd, header.e_entry as usize, sp)); - Ok(addrspace_selection_fd) + Ok(FexecResult::Normal { addrspace_handle: addrspace_selection_fd }) } fn mprotect_remote(socket: usize, addr: usize, len: usize, flags: MapFlags) -> Result<()> { let mut grants_buf = [0_u8; 24]; @@ -295,3 +347,8 @@ pub fn create_set_addr_space_buf(space: usize, ip: usize, sp: usize) -> [u8; siz *chunks.next().unwrap() = usize::to_ne_bytes(ip); buf } + +#[path = "../../../auxv_defs.rs"] +pub mod auxv_defs; + +use auxv_defs::*; From 4e5ccbffd165bb1c0a019928ac72f2f75adfd906 Mon Sep 17 00:00:00 2001 From: 4lDO2 <4lDO2@protonmail.com> Date: Sat, 23 Jul 2022 16:24:02 +0200 Subject: [PATCH 17/23] Allow constructors to access env vars. --- src/ld_so/linker.rs | 8 ++++++++ src/ld_so/start.rs | 16 ++++++++++++++++ src/start.rs | 33 +++++++++++++++++++++++++-------- 3 files changed, 49 insertions(+), 8 deletions(-) diff --git a/src/ld_so/linker.rs b/src/ld_so/linker.rs index e8cf6ccf91..a1a517f789 100644 --- a/src/ld_so/linker.rs +++ b/src/ld_so/linker.rs @@ -472,7 +472,15 @@ impl Linker { } fn run_init(&self, objects: &Vec) { + use crate::platform::{self, types::*}; + for obj in objects.iter().rev() { + if let Some((symbol, true)) = obj.get_sym("__relibc_init_environ") { + unsafe { + symbol.as_ptr().cast::<*mut *mut c_char>().write(platform::environ); + } + } + obj.run_init(); } } diff --git a/src/ld_so/start.rs b/src/ld_so/start.rs index 25a1f16d0a..e07afac109 100644 --- a/src/ld_so/start.rs +++ b/src/ld_so/start.rs @@ -167,6 +167,22 @@ pub extern "C" fn relibc_ld_so_start(sp: &'static mut Stack, ld_entry: usize) -> (argv, envs, auxv) }; + unsafe { + crate::platform::OUR_ENVIRON = envs.iter().map(|(k, v)| { + let mut var = Vec::with_capacity(k.len() + v.len() + 2); + var.extend(k.as_bytes()); + var.push(b'='); + var.extend(v.as_bytes()); + var.push(b'\0'); + let mut var = var.into_boxed_slice(); + let ptr = var.as_mut_ptr(); + core::mem::forget(var); + ptr.cast() + }).chain(core::iter::once(core::ptr::null_mut())).collect::>(); + + 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 } else { diff --git a/src/start.rs b/src/start.rs index cee25f33d9..9ec0eb76de 100644 --- a/src/start.rs +++ b/src/start.rs @@ -67,7 +67,16 @@ static INIT_ARRAY: [extern "C" fn(); 1] = [init_array]; static mut init_complete: bool = false; +#[used] +#[no_mangle] +static mut __relibc_init_environ: *mut *mut c_char = ptr::null_mut(); + fn alloc_init() { + unsafe { + if init_complete { + return; + } + } unsafe { if let Some(tcb) = ld_so::tcb::Tcb::current() { if tcb.mspace != 0 { @@ -96,6 +105,10 @@ extern "C" fn init_array() { alloc_init(); io_init(); + unsafe { + platform::environ = __relibc_init_environ; + } + extern "C" { fn pthread_init(); } @@ -155,15 +168,19 @@ pub unsafe extern "C" fn relibc_start(sp: &'static Stack) -> ! { platform::program_invocation_name = *arg; platform::program_invocation_short_name = libgen::basename(*arg); } - - // Set up envp - let envp = sp.envp(); - let mut len = 0; - while !(*envp.add(len)).is_null() { - len += 1; + // We check for NULL here since ld.so might already have initialized it for us, and we don't + // want to overwrite it if constructors in .init_array of dependency libraries have called + // setenv. + if platform::environ.is_null() { + // Set up envp + let envp = sp.envp(); + let mut len = 0; + while !(*envp.add(len)).is_null() { + len += 1; + } + platform::OUR_ENVIRON = copy_string_array(envp, len); + platform::environ = platform::OUR_ENVIRON.as_mut_ptr(); } - platform::OUR_ENVIRON = copy_string_array(envp, len); - platform::environ = platform::OUR_ENVIRON.as_mut_ptr(); // Setup signal stack, otherwise we cannot handle any signals besides SIG_IGN/SIG_DFL behavior. setup_sigstack(); From b58ad5bf13e02662ea273c8d83598290e5f80e5b Mon Sep 17 00:00:00 2001 From: 4lDO2 <4lDO2@protonmail.com> Date: Sat, 23 Jul 2022 16:24:33 +0200 Subject: [PATCH 18/23] In clone, use better asm function names and align. --- src/platform/redox/clone.rs | 39 +++++++++++++++++++++++-------------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/src/platform/redox/clone.rs b/src/platform/redox/clone.rs index ee2c8c8cf9..45060f54da 100644 --- a/src/platform/redox/clone.rs +++ b/src/platform/redox/clone.rs @@ -81,7 +81,7 @@ pub unsafe fn pte_clone_impl(stack: *mut usize) -> Result { let cur_addr_space_fd = FdGuard::new(syscall::dup(*cur_pid_fd, b"addrspace")?); let new_addr_space_sel_fd = FdGuard::new(syscall::dup(*new_pid_fd, b"current-addrspace")?); - let buf = create_set_addr_space_buf(*cur_addr_space_fd, pte_clone_ret as usize, stack as usize); + let buf = create_set_addr_space_buf(*cur_addr_space_fd, __relibc_internal_pte_clone_ret as usize, stack as usize); let _ = syscall::write(*new_addr_space_sel_fd, &buf)?; } @@ -106,6 +106,7 @@ pub unsafe fn pte_clone_impl(stack: *mut usize) -> Result { // Unblock context. syscall::kill(new_pid, SIGCONT)?; + let _ = syscall::waitpid(new_pid, &mut 0, syscall::WUNTRACED | syscall::WCONTINUED); Ok(0) } @@ -114,7 +115,7 @@ pub unsafe fn pte_clone_impl(stack: *mut usize) -> Result { /// descriptors are reobtained through `fmap`. Other mappings are kept but duplicated using CoW. pub fn fork_impl() -> Result { unsafe { - Error::demux(fork_wrapper()) + Error::demux(__relibc_internal_fork_wrapper()) } } @@ -209,7 +210,7 @@ fn fork_inner(initial_rsp: *mut usize) -> Result { } let new_addr_space_sel_fd = FdGuard::new(syscall::dup(*new_pid_fd, b"current-addrspace")?); - let buf = create_set_addr_space_buf(*new_addr_space_fd, fork_ret as usize, initial_rsp as usize); + let buf = create_set_addr_space_buf(*new_addr_space_fd, __relibc_internal_fork_ret as usize, initial_rsp as usize); let _ = syscall::write(*new_addr_space_sel_fd, &buf)?; } copy_env_regs(*cur_pid_fd, *new_pid_fd)?; @@ -249,9 +250,9 @@ unsafe extern "sysv64" fn __relibc_internal_fork_hook(cur_filetable_fd: usize, n #[no_mangle] core::arch::global_asm!(" .p2align 6 - .globl fork_wrapper - .type fork_wrapper, @function -fork_wrapper: + .globl __relibc_internal_fork_wrapper + .type __relibc_internal_fork_wrapper, @function +__relibc_internal_fork_wrapper: push rbp mov rbp, rsp @@ -271,7 +272,11 @@ fork_wrapper: call __relibc_internal_fork_impl jmp 2f -fork_ret: + .size __relibc_internal_fork_wrapper, . - __relibc_internal_fork_wrapper + + .p2align 6 + .type __relibc_internal_fork_ret, @function +__relibc_internal_fork_ret: mov rdi, [rsp] mov rsi, [rsp + 8] call __relibc_internal_fork_hook @@ -280,6 +285,8 @@ fork_ret: fldcw [rsp+24] xor rax, rax + + .p2align 4 2: add rsp, 32 pop r15 @@ -291,11 +298,13 @@ fork_ret: pop rbp ret - .size fork_wrapper, . - fork_wrapper - .globl pte_clone_ret - .type pte_clone_ret, @function -pte_clone_ret: + .size __relibc_internal_fork_ret, . - __relibc_internal_fork_ret + + .globl __relibc_internal_pte_clone_ret + .type __relibc_internal_pte_clone_ret, @function + .p2align 6 +__relibc_internal_pte_clone_ret: # Load registers pop rax pop rdi @@ -318,11 +327,11 @@ pte_clone_ret: call rax ret - .size pte_clone_ret, . - pte_clone_ret + .size __relibc_internal_pte_clone_ret, . - __relibc_internal_pte_clone_ret "); extern "sysv64" { - fn fork_wrapper() -> usize; - fn fork_ret(); - fn pte_clone_ret(); + fn __relibc_internal_fork_wrapper() -> usize; + fn __relibc_internal_fork_ret(); + fn __relibc_internal_pte_clone_ret(); } From ed196516a2044fe1a1b88b2983f0cf2f649e46b6 Mon Sep 17 00:00:00 2001 From: 4lDO2 <4lDO2@protonmail.com> Date: Tue, 26 Jul 2022 15:48:51 +0200 Subject: [PATCH 19/23] Only inherit environ from ldso if previously NULL. --- src/start.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/start.rs b/src/start.rs index 9ec0eb76de..33d9d7e23f 100644 --- a/src/start.rs +++ b/src/start.rs @@ -106,7 +106,9 @@ extern "C" fn init_array() { io_init(); unsafe { - platform::environ = __relibc_init_environ; + if platform::environ.is_null() { + platform::environ = __relibc_init_environ; + } } extern "C" { From d32dd52d3bd2e972d5f3202580577fc977fd6a19 Mon Sep 17 00:00:00 2001 From: 4lDO2 <4lDO2@protonmail.com> Date: Tue, 26 Jul 2022 15:49:30 +0200 Subject: [PATCH 20/23] Use 2021 edition in redox-exec --- src/platform/redox/redox-exec/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/redox/redox-exec/Cargo.toml b/src/platform/redox/redox-exec/Cargo.toml index afe990a0a3..37c846216d 100644 --- a/src/platform/redox/redox-exec/Cargo.toml +++ b/src/platform/redox/redox-exec/Cargo.toml @@ -2,7 +2,7 @@ name = "redox-exec" authors = ["4lDO2 <4lDO2@protonmail.com>"] version = "0.1.0" -edition = "2018" +edition = "2021" license = "MIT" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html From 985b2a59a92682f3cb447d3385d96b37390838b3 Mon Sep 17 00:00:00 2001 From: 4lDO2 <4lDO2@protonmail.com> Date: Tue, 26 Jul 2022 15:50:00 +0200 Subject: [PATCH 21/23] Use better interfaces in exec/clone --- src/platform/redox/clone.rs | 16 +---- src/platform/redox/exec.rs | 5 +- src/platform/redox/mod.rs | 2 +- src/platform/redox/redox-exec/src/lib.rs | 82 ++++++++++++++++++++---- 4 files changed, 77 insertions(+), 28 deletions(-) diff --git a/src/platform/redox/clone.rs b/src/platform/redox/clone.rs index 45060f54da..4fcf0576bf 100644 --- a/src/platform/redox/clone.rs +++ b/src/platform/redox/clone.rs @@ -33,6 +33,7 @@ fn copy_str(cur_pid_fd: usize, new_pid_fd: usize, key: &str) -> Result<()> { let cur_name_fd = FdGuard::new(syscall::dup(cur_pid_fd, key.as_bytes())?); let new_name_fd = FdGuard::new(syscall::dup(new_pid_fd, key.as_bytes())?); + // TODO: Max path size? let mut buf = [0_u8; 256]; let len = syscall::read(*cur_name_fd, &mut buf)?; let buf = buf.get(..len).ok_or(Error::new(ENAMETOOLONG))?; @@ -194,19 +195,8 @@ fn fork_inner(initial_rsp: *mut usize) -> Result { } let map_flags = MapFlags::from_bits_truncate(flags); - let mapped_address = unsafe { - let fd = FdGuard::new(syscall::dup(*cur_addr_space_fd, format!("grant-{:x}", addr).as_bytes())?); - syscall::fmap(*fd, &syscall::Map { address: 0, size, flags: map_flags, offset })? - }; - - let mut buf = [0_u8; size_of::() * 4]; - let mut chunks = buf.array_chunks_mut::<{size_of::()}>(); - *chunks.next().unwrap() = usize::to_ne_bytes(addr); - *chunks.next().unwrap() = usize::to_ne_bytes(size); - *chunks.next().unwrap() = usize::to_ne_bytes(map_flags.bits()); - *chunks.next().unwrap() = usize::to_ne_bytes(mapped_address); - - let _ = syscall::write(*new_addr_space_fd, &buf)?; + let grant_fd = FdGuard::new(syscall::dup(*cur_addr_space_fd, format!("grant-{:x}", addr).as_bytes())?); + redox_exec::mmap_remote(&new_addr_space_fd, &grant_fd, offset, addr, size, map_flags)?; } let new_addr_space_sel_fd = FdGuard::new(syscall::dup(*new_pid_fd, b"current-addrspace")?); diff --git a/src/platform/redox/exec.rs b/src/platform/redox/exec.rs index c551eb3133..99b561c684 100644 --- a/src/platform/redox/exec.rs +++ b/src/platform/redox/exec.rs @@ -15,12 +15,14 @@ fn fexec_impl(file: File, path: &[u8], args: &[&[u8]], envs: &[&[u8]], total_arg let image_file = FdGuard::new(fd as usize); 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, 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, interp_override)? { FexecResult::Normal { addrspace_handle } => addrspace_handle, FexecResult::Interp { image_file, open_via_dup, path, interp_override: new_interp_override } => { drop(image_file); drop(open_via_dup); + drop(memory); // According to elf(5), PT_INTERP requires that the interpreter path be // null-terminated. Violating this should therefore give the "format error" ENOEXEC. @@ -29,6 +31,7 @@ fn fexec_impl(file: File, path: &[u8], args: &[&[u8]], envs: &[&[u8]], total_arg return execve(path_cstr, ArgEnv::Parsed { total_args_envs_size, args, envs }, Some(new_interp_override)); } }; + drop(memory); // Dropping this FD will cause the address space switch. drop(addrspace_selection_fd); diff --git a/src/platform/redox/mod.rs b/src/platform/redox/mod.rs index 67c9392e71..4f41c6d76e 100644 --- a/src/platform/redox/mod.rs +++ b/src/platform/redox/mod.rs @@ -3,7 +3,7 @@ use core::arch::asm; use syscall::{ self, - data::{CloneInfo, Map, Stat as redox_stat, StatVfs as redox_statvfs, TimeSpec as redox_timespec}, + data::{Map, Stat as redox_stat, StatVfs as redox_statvfs, TimeSpec as redox_timespec}, PtraceEvent, Result, }; diff --git a/src/platform/redox/redox-exec/src/lib.rs b/src/platform/redox/redox-exec/src/lib.rs index 1146b0ede0..658f524bba 100644 --- a/src/platform/redox/redox-exec/src/lib.rs +++ b/src/platform/redox/redox-exec/src/lib.rs @@ -33,7 +33,7 @@ pub struct InterpOverride { tree: BTreeMap, } -pub fn fexec_impl(image_file: FdGuard, open_via_dup: FdGuard, path: &[u8], args: A, envs: E, total_args_envs_size: usize, mut interp_override: Option) -> Result +pub fn fexec_impl(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) -> Result where A: IntoIterator, E: IntoIterator, @@ -122,7 +122,7 @@ where }); } PT_LOAD => { - mprotect_remote(*grants_fd, vaddr, size, flags)?; + allocate_remote(&grants_fd, memory_scheme_fd, vaddr, size, syscall::PROT_READ | syscall::PROT_WRITE)?; syscall::lseek(*image_file as usize, segment.p_offset as isize, SEEK_SET).map_err(|_| Error::new(EIO))?; syscall::lseek(*memory_fd, segment.p_vaddr as isize, SEEK_SET).map_err(|_| Error::new(EIO))?; @@ -130,6 +130,7 @@ where read_all(*image_file as usize, None, &mut buf[..size]).map_err(|_| Error::new(EIO))?; let _ = syscall::write(*memory_fd, &buf[..size]).map_err(|_| Error::new(EIO))?; } + mprotect_remote(&grants_fd, vaddr, size, flags)?; if !tree.range(..=vaddr).next_back().filter(|(start, size)| **start + **size > vaddr).is_some() { tree.insert(vaddr, size); @@ -142,7 +143,7 @@ where const STACK_TOP: usize = 1 << 47; const STACK_SIZE: usize = 1024 * 1024; - mprotect_remote(*grants_fd, STACK_TOP - STACK_SIZE, STACK_SIZE, MapFlags::PROT_READ | MapFlags::PROT_WRITE)?; + allocate_remote(&grants_fd, memory_scheme_fd, STACK_TOP - STACK_SIZE, STACK_SIZE, MapFlags::PROT_READ | MapFlags::PROT_WRITE)?; tree.insert(STACK_TOP - STACK_SIZE, STACK_SIZE); let mut sp = STACK_TOP - 256; @@ -160,9 +161,9 @@ where let pheaders_size_aligned = (pheaders_to_convey.len()+PAGE_SIZE-1)/PAGE_SIZE*PAGE_SIZE; let pheaders = find_free_target_addr(&tree, pheaders_size_aligned).ok_or(Error::new(ENOMEM))?; tree.insert(pheaders, pheaders_size_aligned); - mprotect_remote(*grants_fd, pheaders, pheaders_size_aligned, MapFlags::PROT_READ)?; - + allocate_remote(&grants_fd, memory_scheme_fd, pheaders, pheaders_size_aligned, MapFlags::PROT_READ | MapFlags::PROT_WRITE)?; write_all(*memory_fd, Some(pheaders as u64), &pheaders_to_convey)?; + mprotect_remote(&grants_fd, pheaders, pheaders_size_aligned, MapFlags::PROT_READ)?; push(0)?; push(AT_NULL)?; @@ -181,7 +182,7 @@ where let args_envs_size_aligned = (total_args_envs_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))?; - mprotect_remote(*grants_fd, target_args_env_address, args_envs_size_aligned, MapFlags::PROT_READ | MapFlags::PROT_WRITE)?; + 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); let mut offset = 0; @@ -226,7 +227,7 @@ where if let Ok(name_fd) = syscall::dup(*open_via_dup, b"name").map(FdGuard::new) { let _ = syscall::write(*name_fd, interp_override.as_ref().map_or(path, |o| &o.name)); } - { + if interp_override.is_some() { let mmap_min_fd = FdGuard::new(syscall::dup(*open_via_dup, b"mmap-min-addr")?); let _ = syscall::write(*mmap_min_fd, &usize::to_ne_bytes(tree.iter().rev().nth(1).map_or(0, |(off, len)| *off + *len))); } @@ -237,14 +238,67 @@ where Ok(FexecResult::Normal { addrspace_handle: addrspace_selection_fd }) } -fn mprotect_remote(socket: usize, addr: usize, len: usize, flags: MapFlags) -> Result<()> { - let mut grants_buf = [0_u8; 24]; - grants_buf[..8].copy_from_slice(&usize::to_ne_bytes(addr)); - grants_buf[8..16].copy_from_slice(&usize::to_ne_bytes(len)); - grants_buf[16..24].copy_from_slice(&usize::to_ne_bytes(flags.bits())); - syscall::write(socket, &grants_buf)?; +fn write_usizes(fd: &FdGuard, usizes: [usize; N]) -> Result<()> { + let _ = syscall::write(**fd, unsafe { plain::as_bytes(&usizes) }); Ok(()) } +fn allocate_remote(addrspace_fd: &FdGuard, memory_scheme_fd: &FdGuard, dst_addr: usize, len: usize, flags: MapFlags) -> Result<()> { + mmap_remote(addrspace_fd, memory_scheme_fd, 0, dst_addr, len, flags) +} +pub fn mmap_remote(addrspace_fd: &FdGuard, fd: &FdGuard, offset: usize, dst_addr: usize, len: usize, flags: MapFlags) -> Result<()> { + write_usizes(addrspace_fd, [ + // op + syscall::flag::ADDRSPACE_OP_MMAP, + // fd + **fd, + // "offset" + offset, + // address + dst_addr, + // size + len, + // flags + (flags | MapFlags::MAP_FIXED_NOREPLACE).bits(), + ]) +} +pub fn mprotect_remote(addrspace_fd: &FdGuard, addr: usize, len: usize, flags: MapFlags) -> Result<()> { + write_usizes(addrspace_fd, [ + // op + syscall::flag::ADDRSPACE_OP_MPROTECT, + // address + addr, + // size + len, + // flags + flags.bits(), + ]) +} +pub fn munmap_remote(addrspace_fd: &FdGuard, addr: usize, len: usize) -> Result<()> { + write_usizes(addrspace_fd, [ + // op + syscall::flag::ADDRSPACE_OP_MUNMAP, + // address + addr, + // size + len, + ]) +} +pub fn munmap_transfer(src: &FdGuard, dst: &FdGuard, src_addr: usize, dst_addr: usize, len: usize, flags: MapFlags) -> Result<()> { + write_usizes(dst, [ + // op + syscall::flag::ADDRSPACE_OP_TRANSFER, + // fd + **src, + // "offset" (source address) + src_addr, + // address + dst_addr, + // size + len, + // flags + (flags | MapFlags::MAP_FIXED_NOREPLACE).bits(), + ]) +} fn read_all(fd: usize, offset: Option, buf: &mut [u8]) -> Result<()> { if let Some(offset) = offset { syscall::lseek(fd, offset as isize, SEEK_SET)?; @@ -276,6 +330,8 @@ fn write_all(fd: usize, offset: Option, buf: &[u8]) -> Result<()> { Ok(()) } +// TODO: With the introduction of remote mmaps, remove this and let the kernel handle address +// allocation. fn find_free_target_addr(tree: &BTreeMap, size: usize) -> Option { let mut iterator = tree.iter().peekable(); From 13cbac293be5d27e69a18a8d41c85ea9876fcf3d Mon Sep 17 00:00:00 2001 From: 4lDO2 <4lDO2@protonmail.com> Date: Tue, 26 Jul 2022 21:05:32 +0200 Subject: [PATCH 22/23] Fix write to mmap-min-addr --- src/platform/redox/redox-exec/src/lib.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/platform/redox/redox-exec/src/lib.rs b/src/platform/redox/redox-exec/src/lib.rs index 658f524bba..a736c6016d 100644 --- a/src/platform/redox/redox-exec/src/lib.rs +++ b/src/platform/redox/redox-exec/src/lib.rs @@ -228,8 +228,10 @@ where let _ = syscall::write(*name_fd, interp_override.as_ref().map_or(path, |o| &o.name)); } if interp_override.is_some() { - let mmap_min_fd = FdGuard::new(syscall::dup(*open_via_dup, b"mmap-min-addr")?); - let _ = syscall::write(*mmap_min_fd, &usize::to_ne_bytes(tree.iter().rev().nth(1).map_or(0, |(off, len)| *off + *len))); + let mmap_min_fd = FdGuard::new(syscall::dup(*grants_fd, b"mmap-min-addr")?); + let last_addr = tree.iter().rev().nth(1).map_or(0, |(off, len)| *off + *len); + let aligned_last_addr = (last_addr + PAGE_SIZE - 1) / PAGE_SIZE * PAGE_SIZE; + let _ = syscall::write(*mmap_min_fd, &usize::to_ne_bytes(aligned_last_addr)); } let addrspace_selection_fd = FdGuard::new(syscall::dup(*open_via_dup, b"current-addrspace")?); From f438a4259ab31b9173410e31bb6f42fb89f71168 Mon Sep 17 00:00:00 2001 From: 4lDO2 <4lDO2@protonmail.com> Date: Wed, 27 Jul 2022 17:38:12 +0200 Subject: [PATCH 23/23] Use redox_syscall 0.3 from crates.io --- Cargo.lock | 13 +++++++------ Cargo.toml | 3 +-- src/platform/redox/redox-exec/Cargo.toml | 3 +-- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4ce7a7c5d1..6b746cd562 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -304,7 +304,7 @@ version = "0.1.0" dependencies = [ "goblin", "plain", - "redox_syscall 0.2.13", + "redox_syscall 0.3.0", ] [[package]] @@ -315,17 +315,18 @@ checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" [[package]] name = "redox_syscall" -version = "0.2.13" -source = "git+https://gitlab.redox-os.org/4lDO2/syscall.git?branch=userspace_fexec#676b758a4f8de3fa7f22d72f84c0f7b05152c5df" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534cfe58d6a18cc17120fbf4635d53d14691c1fe4d951064df9bd326178d7d5a" dependencies = [ "bitflags", ] [[package]] name = "redox_syscall" -version = "0.2.15" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534cfe58d6a18cc17120fbf4635d53d14691c1fe4d951064df9bd326178d7d5a" +checksum = "ca8ce969b6629faa6f60a71f84022b0217e5ca8bcaeb6d11654506a55ebf8fed" dependencies = [ "bitflags", ] @@ -347,7 +348,7 @@ dependencies = [ "ralloc", "rand", "redox-exec", - "redox_syscall 0.2.13", + "redox_syscall 0.3.0", "sc", "spin 0.9.4", ] diff --git a/Cargo.toml b/Cargo.toml index e2fa243e32..3f163c02a2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,8 +40,7 @@ optional = true sc = "0.2.3" [target.'cfg(target_os = "redox")'.dependencies] -#redox_syscall = "0.2.15" -redox_syscall = { git = "https://gitlab.redox-os.org/4lDO2/syscall.git", branch = "userspace_fexec" } +redox_syscall = "0.3" spin = "0.9.0" redox-exec = { path = "src/platform/redox/redox-exec" } diff --git a/src/platform/redox/redox-exec/Cargo.toml b/src/platform/redox/redox-exec/Cargo.toml index 37c846216d..bb1744cab5 100644 --- a/src/platform/redox/redox-exec/Cargo.toml +++ b/src/platform/redox/redox-exec/Cargo.toml @@ -8,8 +8,7 @@ license = "MIT" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -#redox_syscall = "0.2" -redox_syscall = { git = "https://gitlab.redox-os.org/4lDO2/syscall.git", branch = "userspace_fexec" } +redox_syscall = "0.3" # TODO: Update goblin = { version = "0.0.21", default-features = false, features = ["elf32", "elf64", "endian_fd"] } plain = "0.2"