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();