diff --git a/.gitmodules b/.gitmodules index aaee5b3c47c6abfc998f092f209276d44e895749..6d3198cb81972b6eeb6078749fb4b61d2f73c6fb 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 7a972425e5cd1a7575f927189116c34070d88d25..6b746cd562ada028b00cabd9dda80523140f7fdd 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]] @@ -297,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.3.0", +] + [[package]] name = "redox_syscall" version = "0.1.57" @@ -312,6 +322,15 @@ dependencies = [ "bitflags", ] +[[package]] +name = "redox_syscall" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca8ce969b6629faa6f60a71f84022b0217e5ca8bcaeb6d11654506a55ebf8fed" +dependencies = [ + "bitflags", +] + [[package]] name = "relibc" version = "0.2.5" @@ -324,12 +343,14 @@ dependencies = [ "lazy_static", "memchr", "memoffset", + "plain", "posix-regex", "ralloc", "rand", - "redox_syscall 0.2.15", + "redox-exec", + "redox_syscall 0.3.0", "sc", - "spin 0.9.2", + "spin 0.9.4", ] [[package]] @@ -361,9 +382,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 +442,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 +479,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 +500,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 +539,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 +552,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 +570,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 ba2c6ca44d5e69fde1e1e8243e8de18408e82347..3f163c02a2d49d1f3540be18a81d61991a503929 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] @@ -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" @@ -39,8 +40,9 @@ optional = true sc = "0.2.3" [target.'cfg(target_os = "redox")'.dependencies] -redox_syscall = "0.2.15" +redox_syscall = "0.3" spin = "0.9.0" +redox-exec = { path = "src/platform/redox/redox-exec" } [features] default = [] diff --git a/Makefile b/Makefile index 0358a308917cacdc850a36717a98e8d7d1b4443a..e3263879b080c4337827803c587a90acb9769d16 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 0000000000000000000000000000000000000000..a6036064ed63c19a61a235eacc5212db947658b0 --- /dev/null +++ b/renamesyms.sh @@ -0,0 +1,27 @@ +#!/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` +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" + +objcopy --redefine-syms="$sorted_file" "$target" + +rm -f "$sorted_file" diff --git a/rust-toolchain b/rust-toolchain index 2b782427dd79600e74d3c1c88e870a5ca9fa85b2..9eed51006fc6a182f0c8aa1eb88acfad9f5e869a 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1 +1 @@ -nightly-2021-06-15 +nightly-2022-03-18 diff --git a/src/crt0/src/lib.rs b/src/crt0/src/lib.rs index c92a897c0cc1da8eeca22a4a72eb9b7ad800fb48..ee596cadc557d5b49104837781b0382f2658755f 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/header/stdlib/mod.rs b/src/header/stdlib/mod.rs index db708bcebce0bfd75b748ad42b875c7c261d9b01..d48b67ac387513f70b0e053018f4bb629371ac6b 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/header/sys_auxv/mod.rs b/src/header/sys_auxv/mod.rs index 184eba1a76cbd1662c16a55e392b5c8363532275..6c4f9ccae1f6ae529ee332933a0580fc031f7526 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/ld_so/linker.rs b/src/ld_so/linker.rs index e8cf6ccf913a143c9836f1f0231126b7c97e76d3..a1a517f789f0588ff7de274f44f69f2f7ffe4347 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<DSO>) { + 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/mod.rs b/src/ld_so/mod.rs index 17aad17f45f6e3729af9ed6eb73007f227a310c8..73c8102e4cbfddb265d24e3a3b9e161f5a365388 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::<Master>(); tcb.copy_masters().expect_notls("failed to copy TLS master data"); tcb.activate(); + } //TODO: Warning on multiple TLS sections? diff --git a/src/ld_so/start.rs b/src/ld_so/start.rs index 25a1f16d0af132ff08a952a0b091c0f98369284d..e07afac109b77d444f46ef02e680e79a4f1f8c7e 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::<Vec<_>>(); + + 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/lib.rs b/src/lib.rs index f3883d827d224d93a6cb1523fd69a76f74f1915b..f03ce4c08b43a95c415a8a37dff6bbc048388a78 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/auxv_defs.rs b/src/platform/auxv_defs.rs new file mode 100644 index 0000000000000000000000000000000000000000..30a59962bb691f85b04a0eddef66ba813e2ea685 --- /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 35a35e4f70e5fad88fcaca2ce623d559d57aeced..6169a3b5919a57c8704c842994f02f0910cf1a25 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; @@ -54,8 +60,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<Item = *mut c_char> + '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/platform/pte.rs b/src/platform/pte.rs index fb779c6eba66e6da6f060a15f73f1e1ed11a5d44..b2a193f15d58fd30d8c07bd28f98461f23997b81 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/clone.rs b/src/platform/redox/clone.rs new file mode 100644 index 0000000000000000000000000000000000000000..4fcf0576bf4c78836bfd2f556d81e4d98fc54f0a --- /dev/null +++ b/src/platform/redox/clone.rs @@ -0,0 +1,327 @@ +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::<usize>().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())?); + + // 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))?; + + syscall::write(*new_name_fd, &buf)?; + + Ok(()) +} +#[cfg(target_arch = "x86_64")] +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")?); + 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)?; + } + + 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<usize> { + 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, __relibc_internal_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))?; + } + + // 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. + syscall::kill(new_pid, SIGCONT)?; + let _ = syscall::waitpid(new_pid, &mut 0, syscall::WUNTRACED | syscall::WCONTINUED); + + 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<usize> { + unsafe { + Error::demux(__relibc_internal_fork_wrapper()) + } +} + +fn fork_inner(initial_rsp: *mut usize) -> Result<usize> { + let (cur_filetable_fd, new_pid_fd, new_pid); + + { + let cur_pid_fd = FdGuard::new(syscall::open("thisproc:current/open_via_dup", O_CLOEXEC)?); + (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")?; + + { + 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). + { + 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")?); + + // 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::<usize>() * 4}>() { + let mut words = struct_bytes.array_chunks::<{size_of::<usize>()}>().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 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")?); + + 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)?; + } + // 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)?; + + // 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] +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!(" + .p2align 6 + .globl __relibc_internal_fork_wrapper + .type __relibc_internal_fork_wrapper, @function +__relibc_internal_fork_wrapper: + push rbp + mov rbp, rsp + + push rbx + push rbp + push r12 + push r13 + push r14 + push r15 + + sub rsp, 32 + + stmxcsr [rsp+16] + fnstcw [rsp+24] + + mov rdi, rsp + call __relibc_internal_fork_impl + jmp 2f + + .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 + + ldmxcsr [rsp+16] + fldcw [rsp+24] + + xor rax, rax + + .p2align 4 +2: + add rsp, 32 + pop r15 + pop r14 + pop r13 + pop r12 + pop rbp + pop rbx + + pop rbp + 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 + pop rsi + pop rdx + pop rcx + 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 + + ret + .size __relibc_internal_pte_clone_ret, . - __relibc_internal_pte_clone_ret +"); + +extern "sysv64" { + fn __relibc_internal_fork_wrapper() -> usize; + fn __relibc_internal_fork_ret(); + fn __relibc_internal_pte_clone_ret(); +} diff --git a/src/platform/redox/exec.rs b/src/platform/redox/exec.rs new file mode 100644 index 0000000000000000000000000000000000000000..99b561c68410ece5226d72536c2e27256690ccad --- /dev/null +++ b/src/platform/redox/exec.rs @@ -0,0 +1,248 @@ +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::data::Stat; +use syscall::flag::*; +use syscall::error::*; +use redox_exec::{FdGuard, FexecResult}; + +fn fexec_impl(file: File, path: &[u8], args: &[&[u8]], envs: &[&[u8]], total_args_envs_size: usize, interp_override: Option<redox_exec::InterpOverride>) -> Result<usize> { + 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 memory = FdGuard::new(syscall::open("memory:", 0)?); + + let addrspace_selection_fd = match redox_exec::fexec_impl(image_file, open_via_dup, &memory, path, args.iter().rev(), envs.iter().rev(), total_args_envs_size, interp_override)? { + 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. + 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)); + } + }; + drop(memory); + + // 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<redox_exec::InterpOverride>) -> Result<usize> { + // 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::<usize>() { + 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<T>(iter: impl IntoIterator<Item = T>) -> 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/extra.rs b/src/platform/redox/extra.rs index 4793096bdef7441d25695edfe3d2d8084e3a8c62..597e2c7eb6d5afb68abb97ae1e21b83812713c2f 100644 --- a/src/platform/redox/extra.rs +++ b/src/platform/redox/extra.rs @@ -1,5 +1,4 @@ -use core::{ptr, slice}; -use core::arch::global_asm; +use core::{mem::size_of, ptr, slice}; use crate::platform::{sys::e, types::*}; @@ -49,62 +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 } -extern "C" { - pub fn pte_clone_inner(stack: usize) -> usize; -} - -#[cfg(target_arch = "x86_64")] -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. - mov rsi, rdi - mov rax, {SYS_CLONE} - mov rdi, {flags} - - # Call clone syscall - syscall - - # Check if child or parent - test rax, rax - jnz 2f - - # Load registers - pop rax - pop rdi - pop rsi - pop rdx - pop rcx - pop r8 - pop r9 - - # Call entry point - call rax - - # Exit - mov rax, 1 - xor rdi, rdi - syscall - - # Invalid instruction on failure to exit - ud2 - - # Return PID if parent -2: - ret - - .size pte_clone_inner, . - pte_clone_inner - - ", - - 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), -); +pub use redox_exec::{create_set_addr_space_buf, FdGuard}; diff --git a/src/platform/redox/mod.rs b/src/platform/redox/mod.rs index 9597d15ba20214505cfc69d251466dbf55c3dadc..4f41c6d76e3faf6db7ab355ebc2d311516645d18 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}, @@ -33,7 +34,14 @@ 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; mod extra; mod ptrace; mod signal; @@ -205,140 +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 { - let mut file = match File::open(path, fcntl::O_RDONLY | fcntl::O_CLOEXEC) { - Ok(file) => file, - Err(_) => return -1, - }; - let fd = *file as usize; - - // Count arguments - let mut len = 0; - while !(*argv.offset(len)).is_null() { - len += 1; - } - - let mut args: Vec<[usize; 2]> = Vec::with_capacity(len as usize); - - // Read shebang (for example #!/bin/sh) - let interpreter = { - let mut reader = BufReader::new(&mut file); - - let mut shebang = [0; 2]; - let mut read = 0; - - while read < 2 { - match reader.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 - } - }; - let mut _interpreter_path = None; - if let Some(interpreter) = interpreter { - let cstring = match CString::new(interpreter) { - Ok(cstring) => cstring, - Err(_) => return -1, - }; - file = match File::open(&cstring, fcntl::O_RDONLY | fcntl::O_CLOEXEC) { - 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_ptr() as usize, path_ref.to_bytes().len()]); - } else { - if file.seek(SeekFrom::Start(0)).is_err() { - return -1; - } - } - - // Arguments - while !(*argv).is_null() { - let arg = *argv; - - let mut len = 0; - while *arg.offset(len) != 0 { - len += 1; - } - args.push([arg as usize, len as usize]); - argv = argv.offset(1); - } - - // Environment variables - let mut len = 0; - while !(*envp.offset(len)).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 len = 0; - while *env.offset(len) != 0 { - len += 1; - } - envs.push([env as usize, len as usize]); - envp = envp.offset(1); - } - - e(syscall::fexec(*file as usize, &args, &envs)) 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 { @@ -375,7 +253,7 @@ impl Pal for Sys { } fn fork() -> pid_t { - e(unsafe { syscall::clone(syscall::CloneFlags::empty()) }) as pid_t + e(clone::fork_impl()) as pid_t } fn fstat(fildes: c_int, buf: *mut stat) -> c_int { @@ -607,7 +485,7 @@ impl Pal for Sys { } fn getpagesize() -> usize { - 4096 + PAGE_SIZE } fn getpgid(pid: pid_t) -> pid_t { @@ -754,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), ), @@ -771,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 @@ -783,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 */ @@ -800,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 @@ -858,7 +736,7 @@ 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 + e(clone::pte_clone_impl(stack)) as pid_t } fn read(fd: c_int, buf: &mut [u8]) -> ssize_t { diff --git a/src/platform/redox/redox-exec/Cargo.toml b/src/platform/redox/redox-exec/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..bb1744cab53600494b48aa0a2ffc926bc72ddc75 --- /dev/null +++ b/src/platform/redox/redox-exec/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "redox-exec" +authors = ["4lDO2 <4lDO2@protonmail.com>"] +version = "0.1.0" +edition = "2021" +license = "MIT" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +redox_syscall = "0.3" +# 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 0000000000000000000000000000000000000000..a736c6016d4ae634295affad0abd66c1d2b3f389 --- /dev/null +++ b/src/platform/redox/redox-exec/src/lib.rs @@ -0,0 +1,412 @@ +#![no_std] + +#![feature(array_chunks, map_first_last)] + +extern crate alloc; + +use core::mem::size_of; + +use alloc::{ + boxed::Box, + collections::BTreeMap, + vec, +}; + +use syscall::{ + error::*, + flag::{MapFlags, SEEK_SET}, +}; + +#[cfg(target_arch = "x86_64")] +const PAGE_SIZE: usize = 4096; + +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<usize, usize>, +} + +pub fn fexec_impl<A, E>(image_file: FdGuard, open_via_dup: FdGuard, memory_scheme_fd: &FdGuard, path: &[u8], args: A, envs: E, total_args_envs_size: usize, mut interp_override: Option<InterpOverride>) -> Result<FexecResult> +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, 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 + // some misalignments, and then execute the SYS_EXEC syscall to replace the program memory + // entirely. + + let mut header_bytes = [0_u8; size_of::<Header>()]; + 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")?); + + // 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; + let pheaders_size = phentsize.saturating_mul(phnum).saturating_add(size_of::<Header>()); + + if pheaders_size > MAX_PH_SIZE { + return Err(Error::new(E2BIG)); + } + let mut phs_raw = vec! [0_u8; pheaders_size]; + phs_raw[..size_of::<Header>()].copy_from_slice(&header_bytes); + let phs = &mut phs_raw[size_of::<Header>()..]; + + // 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::<BTreeMap<_, _>>() + }, |o| core::mem::take(&mut o.tree)); + + const BUFSZ: usize = 1024 * 256; + let mut buf = vec! [0_u8; BUFSZ]; + + 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]; + 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)); + } + #[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)?; + + 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 => { + 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))?; + + 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))?; + } + 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); + } + } + _ => continue, + } + } + // 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; + + 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; + + let mut push = |word: usize| { + sp -= size_of::<usize>(); + write_all(*memory_fd, Some(sp as u64), &usize::to_ne_bytes(word)) + }; + + 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); + 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)?; + 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 + size_of::<Header>())?; + push(AT_PHDR)?; + push(interp_override.as_ref().map_or(header.e_phnum as usize, |o| o.at_phnum))?; + push(AT_PHNUM)?; + push(interp_override.as_ref().map_or(header.e_phentsize as usize, |o| o.at_phent))?; + push(AT_PHENT)?; + + let args_envs_size_aligned = (total_args_envs_size+PAGE_SIZE-1)/PAGE_SIZE*PAGE_SIZE; + let target_args_env_address = find_free_target_addr(&tree, args_envs_size_aligned).ok_or(Error::new(ENOMEM))?; + allocate_remote(&grants_fd, memory_scheme_fd, target_args_env_address, args_envs_size_aligned, MapFlags::PROT_READ | MapFlags::PROT_WRITE)?; + tree.insert(target_args_env_address, args_envs_size_aligned); + + 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)?; } + + { + 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, interp_override.as_ref().map_or(path, |o| &o.name)); + } + if interp_override.is_some() { + 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")?); + + let _ = syscall::write(*addrspace_selection_fd, &create_set_addr_space_buf(*grants_fd, header.e_entry as usize, sp)); + + Ok(FexecResult::Normal { addrspace_handle: addrspace_selection_fd }) +} +fn write_usizes<const N: usize>(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<u64>, 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<u64>, 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(()) +} + +// TODO: With the introduction of remote mmaps, remove this and let the kernel handle address +// allocation. +fn find_free_target_addr(tree: &BTreeMap<usize, usize>, size: usize) -> Option<usize> { + 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::<usize>() * 3] { + let mut buf = [0_u8; 3 * size_of::<usize>()]; + let mut chunks = buf.array_chunks_mut::<{size_of::<usize>()}>(); + *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 +} + +#[path = "../../../auxv_defs.rs"] +pub mod auxv_defs; + +use auxv_defs::*; diff --git a/src/start.rs b/src/start.rs index 8e715c47ec4e4f948b53cb3e0866f0f695f0b4cb..33d9d7e23fbdedafef7f7fbf7d5dcf214a53a47a 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,12 @@ extern "C" fn init_array() { alloc_init(); io_init(); + unsafe { + if platform::environ.is_null() { + platform::environ = __relibc_init_environ; + } + } + extern "C" { fn pthread_init(); } @@ -112,6 +127,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] @@ -146,15 +170,22 @@ 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::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();