From fd6e1bae450aee44cbe612b1026df995ce6c50da Mon Sep 17 00:00:00 2001 From: ClawSeven Date: Mon, 29 Apr 2024 19:17:01 +0800 Subject: [PATCH] [libos] Implement sub-crates including uring_callback/keyable-arc/object-id/untrusted-alloc etc --- src/libos/Cargo.toml | 11 +- src/libos/Makefile | 8 +- src/libos/crates/Cargo.toml | 24 + src/libos/crates/common.mk | 7 + src/libos/crates/errno/src/lib.rs | 13 +- src/libos/crates/io-uring-callback/Cargo.lock | 400 +++++++++ src/libos/crates/io-uring-callback/Cargo.toml | 29 + src/libos/crates/io-uring-callback/README.md | 15 + .../io-uring-callback/examples/sgx/.gitignore | 11 + .../io-uring-callback/examples/sgx/Makefile | 161 ++++ .../io-uring-callback/examples/sgx/README.md | 14 + .../examples/sgx/app/Cargo.toml | 11 + .../examples/sgx/app/build.rs | 33 + .../examples/sgx/app/rust-toolchain | 1 + .../examples/sgx/app/src/main.rs | 81 ++ .../examples/sgx/bin/readme.txt | 1 + .../examples/sgx/enclave/Cargo.toml | 23 + .../examples/sgx/enclave/Enclave.config.xml | 13 + .../examples/sgx/enclave/Enclave.edl | 36 + .../examples/sgx/enclave/Enclave.lds | 9 + .../examples/sgx/enclave/Enclave_private.pem | 39 + .../examples/sgx/enclave/Makefile | 38 + .../examples/sgx/enclave/rust-toolchain | 1 + .../examples/sgx/enclave/src/lib.rs | 334 +++++++ .../sgx/enclave/x86_64-unknown-linux-sgx.json | 31 + .../examples/sgx/lib/readme.txt | 1 + .../io-uring-callback/examples/tcp_echo.rs | 251 ++++++ .../crates/io-uring-callback/rust-toolchain | 1 + .../crates/io-uring-callback/src/io_handle.rs | 192 +++++ src/libos/crates/io-uring-callback/src/lib.rs | 812 ++++++++++++++++++ src/libos/crates/keyable-arc/Cargo.toml | 10 + src/libos/crates/keyable-arc/src/lib.rs | 348 ++++++++ src/libos/crates/object-id/Cargo.toml | 9 + src/libos/crates/object-id/src/lib.rs | 62 ++ .../crates/sgx-untrusted-alloc/.gitignore | 2 + .../crates/sgx-untrusted-alloc/Cargo.toml | 22 + .../crates/sgx-untrusted-alloc/README.md | 10 + .../crates/sgx-untrusted-alloc/src/box_.rs | 190 ++++ .../crates/sgx-untrusted-alloc/src/lib.rs | 95 ++ .../src/maybe_untrusted.rs | 130 +++ .../crates/sgx-untrusted-alloc/src/prelude.rs | 5 + .../untrusted_allocator/free_space_manager.rs | 163 ++++ .../src/untrusted_allocator/mod.rs | 111 +++ .../src/untrusted_allocator/vm_area.rs | 63 ++ .../untrusted_allocator/vm_chunk_manager.rs | 158 ++++ .../src/untrusted_allocator/vm_range.rs | 159 ++++ .../src/untrusted_allocator/vm_util.rs | 9 + 47 files changed, 4139 insertions(+), 8 deletions(-) create mode 100644 src/libos/crates/Cargo.toml create mode 100644 src/libos/crates/common.mk create mode 100644 src/libos/crates/io-uring-callback/Cargo.lock create mode 100644 src/libos/crates/io-uring-callback/Cargo.toml create mode 100644 src/libos/crates/io-uring-callback/README.md create mode 100644 src/libos/crates/io-uring-callback/examples/sgx/.gitignore create mode 100644 src/libos/crates/io-uring-callback/examples/sgx/Makefile create mode 100644 src/libos/crates/io-uring-callback/examples/sgx/README.md create mode 100644 src/libos/crates/io-uring-callback/examples/sgx/app/Cargo.toml create mode 100644 src/libos/crates/io-uring-callback/examples/sgx/app/build.rs create mode 100644 src/libos/crates/io-uring-callback/examples/sgx/app/rust-toolchain create mode 100644 src/libos/crates/io-uring-callback/examples/sgx/app/src/main.rs create mode 100644 src/libos/crates/io-uring-callback/examples/sgx/bin/readme.txt create mode 100644 src/libos/crates/io-uring-callback/examples/sgx/enclave/Cargo.toml create mode 100644 src/libos/crates/io-uring-callback/examples/sgx/enclave/Enclave.config.xml create mode 100644 src/libos/crates/io-uring-callback/examples/sgx/enclave/Enclave.edl create mode 100644 src/libos/crates/io-uring-callback/examples/sgx/enclave/Enclave.lds create mode 100644 src/libos/crates/io-uring-callback/examples/sgx/enclave/Enclave_private.pem create mode 100644 src/libos/crates/io-uring-callback/examples/sgx/enclave/Makefile create mode 100644 src/libos/crates/io-uring-callback/examples/sgx/enclave/rust-toolchain create mode 100644 src/libos/crates/io-uring-callback/examples/sgx/enclave/src/lib.rs create mode 100644 src/libos/crates/io-uring-callback/examples/sgx/enclave/x86_64-unknown-linux-sgx.json create mode 100644 src/libos/crates/io-uring-callback/examples/sgx/lib/readme.txt create mode 100644 src/libos/crates/io-uring-callback/examples/tcp_echo.rs create mode 100644 src/libos/crates/io-uring-callback/rust-toolchain create mode 100644 src/libos/crates/io-uring-callback/src/io_handle.rs create mode 100644 src/libos/crates/io-uring-callback/src/lib.rs create mode 100644 src/libos/crates/keyable-arc/Cargo.toml create mode 100644 src/libos/crates/keyable-arc/src/lib.rs create mode 100644 src/libos/crates/object-id/Cargo.toml create mode 100644 src/libos/crates/object-id/src/lib.rs create mode 100644 src/libos/crates/sgx-untrusted-alloc/.gitignore create mode 100644 src/libos/crates/sgx-untrusted-alloc/Cargo.toml create mode 100644 src/libos/crates/sgx-untrusted-alloc/README.md create mode 100644 src/libos/crates/sgx-untrusted-alloc/src/box_.rs create mode 100644 src/libos/crates/sgx-untrusted-alloc/src/lib.rs create mode 100644 src/libos/crates/sgx-untrusted-alloc/src/maybe_untrusted.rs create mode 100644 src/libos/crates/sgx-untrusted-alloc/src/prelude.rs create mode 100644 src/libos/crates/sgx-untrusted-alloc/src/untrusted_allocator/free_space_manager.rs create mode 100644 src/libos/crates/sgx-untrusted-alloc/src/untrusted_allocator/mod.rs create mode 100644 src/libos/crates/sgx-untrusted-alloc/src/untrusted_allocator/vm_area.rs create mode 100644 src/libos/crates/sgx-untrusted-alloc/src/untrusted_allocator/vm_chunk_manager.rs create mode 100644 src/libos/crates/sgx-untrusted-alloc/src/untrusted_allocator/vm_range.rs create mode 100644 src/libos/crates/sgx-untrusted-alloc/src/untrusted_allocator/vm_util.rs diff --git a/src/libos/Cargo.toml b/src/libos/Cargo.toml index 9e4a22b1..4f964eeb 100644 --- a/src/libos/Cargo.toml +++ b/src/libos/Cargo.toml @@ -34,9 +34,16 @@ ctor = "0.1" regex = { git = "https://github.com/mesalock-linux/regex-sgx", default-features = false, features = ["std", "unicode", "mesalock_sgx"] } goblin = { version = "0.5.4", default-features = false, features = ["elf64", "elf32", "endian_fd"] } intrusive-collections = "0.9" -spin = "0.7" modular-bitfield = "0.11.2" +sgx-untrusted-alloc = { path = "./crates/sgx-untrusted-alloc", features = ["sgx"]} +io-uring-callback = { path = "./crates/io-uring-callback", features = ["sgx"]} +num_enum = { version = "0.5", default-features = false } +keyable-arc = { path = "./crates/keyable-arc" } +downcast-rs = { version = "1.2.0", default-features = false } +spin = "0.7" +byteorder = { version = "1.3.2", default-features = false } + [patch.'https://github.com/apache/teaclave-sgx-sdk.git'] sgx_tstd = { path = "../../deps/rust-sgx-sdk/sgx_tstd" } @@ -54,7 +61,7 @@ kernel_heap_monitor = []# Kernel heap usage tracking. With overhead. [target.'cfg(not(target_env = "sgx"))'.dependencies] sgx_types = { path = "../../deps/rust-sgx-sdk/sgx_types" } -sgx_tstd = { path = "../../deps/rust-sgx-sdk/sgx_tstd", features = ["backtrace"] } +sgx_tstd = { path = "../../deps/rust-sgx-sdk/sgx_tstd", features = ["backtrace", "thread"] } sgx_trts = { path = "../../deps/rust-sgx-sdk/sgx_trts" } sgx_tse = { path = "../../deps/rust-sgx-sdk/sgx_tse" } sgx_tcrypto = { path = "../../deps/rust-sgx-sdk/sgx_tcrypto" } diff --git a/src/libos/Makefile b/src/libos/Makefile index 74cf933d..c0e4e685 100644 --- a/src/libos/Makefile +++ b/src/libos/Makefile @@ -64,7 +64,8 @@ LIBOS_CORE_A := $(OBJ_DIR)/libos/lib/lib$(LIBOS_CORE_LIB_NAME).a LIBOS_CORE_RS_A := $(OBJ_DIR)/libos/lib/libocclum_libos_core_rs.a # All source code -RUST_SRCS := $(wildcard src/*.rs src/*/*.rs src/*/*/*.rs src/*/*/*/*.rs src/*/*/*/*/*.rs) +RUST_SRCS := $(wildcard src/*.rs src/*/*.rs src/*/*/*.rs src/*/*/*/*.rs src/*/*/*/*/*.rs \ + crates/*/src/*.rs crates/*/src/*/*.rs crates/*/src/*/*/*.rs crates/*/src/*/*/*/*.rs) RUST_TARGET_DIR := $(OBJ_DIR)/libos/cargo-target RUST_OUT_DIR := $(OBJ_DIR)/libos/lib EDL_C_SRCS := $(addprefix $(OBJ_DIR)/libos/,$(SRC_OBJ)/Enclave_t.c $(SRC_OBJ)/Enclave_t.h) @@ -166,7 +167,8 @@ $(OBJ_DIR)/libos/$(SRC_OBJ)/Enclave_t.c: $(SGX_EDGER8R) ../Enclave.edl $(SGX_EDGER8R) $(SGX_EDGER8R_MODE) --trusted $(CUR_DIR)/../Enclave.edl \ --search-path $(SGX_SDK)/include \ --search-path $(RUST_SGX_SDK_DIR)/edl \ - --search-path $(CRATES_DIR)/vdso-time/ocalls + --search-path $(CRATES_DIR)/vdso-time/ocalls \ + --search-path $(PROJECT_DIR)/deps/io-uring/ocalls @echo "GEN <= $@" $(C_OBJS):$(OBJ_DIR)/libos/$(SRC_OBJ)/%.o: src/%.c @@ -188,6 +190,7 @@ format-c: $(C_SRCS) $(CXX_SRCS) format-rust: $(RUST_SRCS) @$(call format-rust) + @cd crates && $(call format-rust) format-check: format-check-c format-check-rust @@ -196,6 +199,7 @@ format-check-c: $(C_SRCS) $(CXX_SRCS) format-check-rust: $(RUST_SRCS) @$(call format-check-rust) + @cd crates && $(call format-check-rust) COV_TARGET_DIR := $(RUST_TARGET_DIR)/debug/deps DEPS_DIR := $(shell pwd)/../../deps diff --git a/src/libos/crates/Cargo.toml b/src/libos/crates/Cargo.toml new file mode 100644 index 00000000..2f3e6bf0 --- /dev/null +++ b/src/libos/crates/Cargo.toml @@ -0,0 +1,24 @@ +[workspace] + +members = [ + "errno", + "io-uring-callback", + "keyable-arc", + "object-id", + "sgx-untrusted-alloc", + "vdso-time" +] + +# Default members can run on Linux; non-default members can only run inside SGX. +default-members = [ + "errno", + "io-uring-callback", + "keyable-arc", + "object-id", + "sgx-untrusted-alloc", + "vdso-time" +] + +exclude = [ + "test", +] diff --git a/src/libos/crates/common.mk b/src/libos/crates/common.mk new file mode 100644 index 00000000..c05d29fd --- /dev/null +++ b/src/libos/crates/common.mk @@ -0,0 +1,7 @@ +MAIN_MAKEFILE := $(firstword $(MAKEFILE_LIST)) +INCLUDE_MAKEFILE := $(lastword $(MAKEFILE_LIST)) +CURRENT_DIR := $(shell dirname $(realpath $(MAIN_MAKEFILE))) +ROOT_DIR := $(realpath $(shell dirname $(realpath $(INCLUDE_MAKEFILE)))/../../../) +RUST_SGX_SDK_DIR := $(ROOT_DIR)/deps/rust-sgx-sdk +LIBOS_DIR := $(ROOT_DIR)/src/libos +LIBOS_CRATES_DIR := $(LIBOS_DIR)/crates diff --git a/src/libos/crates/errno/src/lib.rs b/src/libos/crates/errno/src/lib.rs index 16f4ef9c..5ecfde10 100644 --- a/src/libos/crates/errno/src/lib.rs +++ b/src/libos/crates/errno/src/lib.rs @@ -116,8 +116,8 @@ mod result; mod to_errno; pub use self::backtrace::ErrorBacktrace; -pub use self::errno::*; pub use self::errno::Errno::*; +pub use self::errno::*; pub use self::error::{Error, ErrorLocation}; pub use self::result::{Result, ResultExt}; pub use self::to_errno::ToErrno; @@ -130,13 +130,18 @@ macro_rules! errno { let msg: &'static str = $error_msg; (errno, msg) }; - let error = - $crate::Error::embedded(inner_error, Some($crate::ErrorLocation::new(file!(), line!()))); + let error = $crate::Error::embedded( + inner_error, + Some($crate::ErrorLocation::new(file!(), line!())), + ); error }}; ($error_expr: expr) => {{ let inner_error = $error_expr; - let error = $crate::Error::boxed(inner_error, Some($crate::ErrorLocation::new(file!(), line!()))); + let error = $crate::Error::boxed( + inner_error, + Some($crate::ErrorLocation::new(file!(), line!())), + ); error }}; } diff --git a/src/libos/crates/io-uring-callback/Cargo.lock b/src/libos/crates/io-uring-callback/Cargo.lock new file mode 100644 index 00000000..71d1a67f --- /dev/null +++ b/src/libos/crates/io-uring-callback/Cargo.lock @@ -0,0 +1,400 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "atomic" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59bdb34bc650a32731b31bd8f0829cc15d24a708ee31559e0bb34f2bc320cba" + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" + +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "errno" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f258a7194e7f7c2a7837a8913aeab7fd8c383457034fa20ce4dd3dcb813e8eb8" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "fastrand" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" + +[[package]] +name = "futures" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0290714b38af9b4a7b094b8a37086d1b4e61f2df9122c3cad2577669145335" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" + +[[package]] +name = "futures-io" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa" + +[[package]] +name = "futures-sink" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817" + +[[package]] +name = "futures-task" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" + +[[package]] +name = "futures-util" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" +dependencies = [ + "futures-core", + "futures-sink", + "futures-task", + "pin-project-lite", + "pin-utils", +] + +[[package]] +name = "hashbrown_tstd" +version = "0.12.0" + +[[package]] +name = "io-uring" +version = "0.5.9" +dependencies = [ + "bitflags 1.3.2", + "libc", + "sgx_libc", + "sgx_trts", + "sgx_tstd", + "sgx_types", +] + +[[package]] +name = "io-uring-callback" +version = "0.1.0" +dependencies = [ + "atomic", + "cfg-if", + "futures", + "io-uring", + "lazy_static", + "libc", + "lock_api", + "log", + "sgx_libc", + "sgx_tstd", + "slab", + "spin 0.7.1", + "tempfile", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +dependencies = [ + "spin 0.5.2", +] + +[[package]] +name = "libc" +version = "0.2.150" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" + +[[package]] +name = "linux-raw-sys" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829" + +[[package]] +name = "lock_api" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd96ffd135b2fd7b973ac026d28085defbe8983df057ced3eb4f2130b0831312" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "rustix" +version = "0.38.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc99bc2d4f1fed22595588a013687477aedf3cdcfb26558c559edb67b4d9b22e" +dependencies = [ + "bitflags 2.4.1", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sgx_alloc" +version = "1.1.5" + +[[package]] +name = "sgx_backtrace_sys" +version = "1.1.5" +dependencies = [ + "cc", + "sgx_build_helper", + "sgx_libc", +] + +[[package]] +name = "sgx_build_helper" +version = "1.1.5" + +[[package]] +name = "sgx_demangle" +version = "1.1.5" + +[[package]] +name = "sgx_libc" +version = "1.1.5" +dependencies = [ + "sgx_types", +] + +[[package]] +name = "sgx_tprotected_fs" +version = "1.1.5" +dependencies = [ + "sgx_trts", + "sgx_types", +] + +[[package]] +name = "sgx_trts" +version = "1.1.5" +dependencies = [ + "sgx_libc", + "sgx_types", +] + +[[package]] +name = "sgx_tstd" +version = "1.1.5" +dependencies = [ + "hashbrown_tstd", + "sgx_alloc", + "sgx_backtrace_sys", + "sgx_demangle", + "sgx_libc", + "sgx_tprotected_fs", + "sgx_trts", + "sgx_types", + "sgx_unwind", +] + +[[package]] +name = "sgx_types" +version = "1.1.5" + +[[package]] +name = "sgx_unwind" +version = "1.1.5" +dependencies = [ + "sgx_build_helper", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "spin" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13287b4da9d1207a4f4929ac390916d64eacfe236a487e9a9f5b3be392be5162" + +[[package]] +name = "tempfile" +version = "3.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5" +dependencies = [ + "cfg-if", + "fastrand", + "redox_syscall", + "rustix", + "windows-sys", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" diff --git a/src/libos/crates/io-uring-callback/Cargo.toml b/src/libos/crates/io-uring-callback/Cargo.toml new file mode 100644 index 00000000..79972e58 --- /dev/null +++ b/src/libos/crates/io-uring-callback/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "io-uring-callback" +version = "0.1.0" +authors = ["Tate, Hongliang Tian "] +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[features] +default = ["libc"] +sgx = ["sgx_tstd", "sgx_libc", "io-uring/sgx"] + +[dependencies] +atomic = "0.5.0" +cfg-if = "1.0.0" +lock_api = "=0.4.2" +log = "0.4" +futures = { version = "0.3", default-features = false, features = ["alloc"] } +lazy_static = { version = "1.4.0", features = ["spin_no_std"] } +slab = { version = "0.4.5", default-features = false } +libc = { version = "0.2", optional = true } + +io-uring = { path = "../../../../deps/io-uring", features = ["unstable"] } +sgx_tstd = { path = "../../../../deps/rust-sgx-sdk/sgx_tstd", optional = true, features = ["backtrace"] } +sgx_libc = { path = "../../../../deps/rust-sgx-sdk/sgx_libc", optional = true } +spin = "0.7" + +[dev-dependencies] +tempfile = "3" diff --git a/src/libos/crates/io-uring-callback/README.md b/src/libos/crates/io-uring-callback/README.md new file mode 100644 index 00000000..fafbc588 --- /dev/null +++ b/src/libos/crates/io-uring-callback/README.md @@ -0,0 +1,15 @@ +# io-uring-callback + +io-uring with callback interface. + +## Usage +To use io-uring-callback, place the following line under the `[dependencies]` section in your `Cargo.toml`: + +``` +io-uring-callback = { path = "your_path/io-uring-callback" } +``` + +if use io-uring-callback in SGX (based on rust-sgx-sdk), place the following line under the `[dependencies]` section in your `Cargo.toml` and prepare incubator-teaclave-sgx-sdk envirenments according to io-uring-callback's `Cargo.toml`: +``` +io-uring-callback = { path = "your_path/io-uring-callback", features = ["sgx"] } +``` \ No newline at end of file diff --git a/src/libos/crates/io-uring-callback/examples/sgx/.gitignore b/src/libos/crates/io-uring-callback/examples/sgx/.gitignore new file mode 100644 index 00000000..3718176d --- /dev/null +++ b/src/libos/crates/io-uring-callback/examples/sgx/.gitignore @@ -0,0 +1,11 @@ +Cargo.lock +Enclave_u.c +Enclave_u.h +Enclave_t.c +Enclave_t.h +app/target +enclave/target +bin/app +*.o +*.a +*.so diff --git a/src/libos/crates/io-uring-callback/examples/sgx/Makefile b/src/libos/crates/io-uring-callback/examples/sgx/Makefile new file mode 100644 index 00000000..698f834e --- /dev/null +++ b/src/libos/crates/io-uring-callback/examples/sgx/Makefile @@ -0,0 +1,161 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +######## SGX SDK Settings ######## + +SGX_SDK ?= /opt/sgxsdk +SGX_MODE ?= HW +SGX_ARCH ?= x64 + +include ../../../common.mk + +include $(RUST_SGX_SDK_DIR)/buildenv.mk + +OCALLS_DIR := $(ROOT_DIR)/deps/io-uring/ocalls + +ifeq ($(shell getconf LONG_BIT), 32) + SGX_ARCH := x86 +else ifeq ($(findstring -m32, $(CXXFLAGS)), -m32) + SGX_ARCH := x86 +endif + +ifeq ($(SGX_ARCH), x86) + SGX_COMMON_CFLAGS := -m32 + SGX_LIBRARY_PATH := $(SGX_SDK)/lib + SGX_ENCLAVE_SIGNER := $(SGX_SDK)/bin/x86/sgx_sign + SGX_EDGER8R := $(SGX_SDK)/bin/x86/sgx_edger8r +else + SGX_COMMON_CFLAGS := -m64 + SGX_LIBRARY_PATH := $(SGX_SDK)/lib64 + SGX_ENCLAVE_SIGNER := $(SGX_SDK)/bin/x64/sgx_sign + SGX_EDGER8R := $(SGX_SDK)/bin/x64/sgx_edger8r +endif + +ifeq ($(SGX_DEBUG), 1) +ifeq ($(SGX_PRERELEASE), 1) +$(error Cannot set SGX_DEBUG and SGX_PRERELEASE at the same time!!) +endif +endif + +ifeq ($(SGX_DEBUG), 1) + SGX_COMMON_CFLAGS += -O0 -g +else + SGX_COMMON_CFLAGS += -O2 +endif + +SGX_COMMON_CFLAGS += -fstack-protector + +######## CUSTOM Settings ######## + +CUSTOM_LIBRARY_PATH := ./lib +CUSTOM_BIN_PATH := ./bin +CUSTOM_EDL_PATH := $(RUST_SGX_SDK_DIR)/edl +CUSTOM_COMMON_PATH := $(RUST_SGX_SDK_DIR)/common + +######## EDL Settings ######## + +Enclave_EDL_Files := enclave/Enclave_t.c enclave/Enclave_t.h app/Enclave_u.c app/Enclave_u.h + +######## APP Settings ######## + +App_Rust_Flags := --release +App_SRC_Files := $(shell find app/ -type f -name '*.rs') $(shell find app/ -type f -name 'Cargo.toml') +App_Include_Paths := -I ./app -I./include -I$(SGX_SDK)/include -I$(CUSTOM_EDL_PATH) +App_C_Flags := $(SGX_COMMON_CFLAGS) -fPIC -Wno-attributes $(App_Include_Paths) + +App_Rust_Path := ./app/target/release +App_Enclave_u_Object := lib/libEnclave_u.a +App_Name := bin/app + +######## Enclave Settings ######## + +ifneq ($(SGX_MODE), HW) + Trts_Library_Name := sgx_trts_sim + Service_Library_Name := sgx_tservice_sim +else + Trts_Library_Name := sgx_trts + Service_Library_Name := sgx_tservice +endif +Crypto_Library_Name := sgx_tcrypto +KeyExchange_Library_Name := sgx_tkey_exchange +ProtectedFs_Library_Name := sgx_tprotected_fs + +RustEnclave_C_Files := $(wildcard ./enclave/*.c) +RustEnclave_C_Objects := $(RustEnclave_C_Files:.c=.o) +RustEnclave_Include_Paths := -I$(CUSTOM_COMMON_PATH)/inc -I$(CUSTOM_EDL_PATH) -I$(SGX_SDK)/include -I$(SGX_SDK)/include/tlibc -I$(SGX_SDK)/include/stlport -I$(SGX_SDK)/include/epid -I ./enclave -I./include + +RustEnclave_Link_Libs := -L$(CUSTOM_LIBRARY_PATH) -lenclave +RustEnclave_Compile_Flags := $(SGX_COMMON_CFLAGS) $(ENCLAVE_CFLAGS) $(RustEnclave_Include_Paths) +RustEnclave_Link_Flags := -Wl,--no-undefined -nostdlib -nodefaultlibs -nostartfiles -L$(SGX_LIBRARY_PATH) \ + -Wl,--whole-archive -l$(Trts_Library_Name) -Wl,--no-whole-archive \ + -Wl,--start-group -lsgx_tstdc -l$(Service_Library_Name) -l$(Crypto_Library_Name) $(RustEnclave_Link_Libs) -Wl,--end-group \ + -Wl,--version-script=enclave/Enclave.lds \ + $(ENCLAVE_LDFLAGS) + +RustEnclave_Name := enclave/enclave.so +Signed_RustEnclave_Name := bin/enclave.signed.so + +.PHONY: all +all: $(App_Name) $(Signed_RustEnclave_Name) + +######## EDL Objects ######## + +$(Enclave_EDL_Files): $(SGX_EDGER8R) enclave/Enclave.edl + $(SGX_EDGER8R) --trusted enclave/Enclave.edl --search-path $(OCALLS_DIR) --search-path $(SGX_SDK)/include --search-path $(CUSTOM_EDL_PATH) --trusted-dir enclave + $(SGX_EDGER8R) --untrusted enclave/Enclave.edl --search-path $(OCALLS_DIR) --search-path $(SGX_SDK)/include --search-path $(CUSTOM_EDL_PATH) --untrusted-dir app + @echo "GEN => $(Enclave_EDL_Files)" + +######## App Objects ######## + +app/Enclave_u.o: $(Enclave_EDL_Files) + @$(CC) $(App_C_Flags) -c app/Enclave_u.c -o $@ + @echo "CC <= $<" + +$(App_Enclave_u_Object): app/Enclave_u.o + $(AR) rcsD $@ $^ + +$(App_Name): $(App_Enclave_u_Object) $(App_SRC_Files) + @cd app && SGX_SDK=$(SGX_SDK) cargo build $(App_Rust_Flags) + @echo "Cargo => $@" + mkdir -p bin + cp $(App_Rust_Path)/app ./bin + +######## Enclave Objects ######## + +enclave/Enclave_t.o: $(Enclave_EDL_Files) + @$(CC) $(RustEnclave_Compile_Flags) -c enclave/Enclave_t.c -o $@ + @echo "CC <= $<" + +$(RustEnclave_Name): enclave enclave/Enclave_t.o + @$(CXX) enclave/Enclave_t.o -o $@ $(RustEnclave_Link_Flags) + @echo "LINK => $@" + +$(Signed_RustEnclave_Name): $(RustEnclave_Name) + mkdir -p bin + @$(SGX_ENCLAVE_SIGNER) sign -key enclave/Enclave_private.pem -enclave $(RustEnclave_Name) -out $@ -config enclave/Enclave.config.xml + @echo "SIGN => $@" + +.PHONY: enclave +enclave: + $(MAKE) -C ./enclave/ + + +.PHONY: clean +clean: + @rm -f $(App_Name) $(RustEnclave_Name) $(Signed_RustEnclave_Name) enclave/*_t.* app/*_u.* lib/*.a + @cd enclave && cargo clean && rm -f Cargo.lock + @cd app && cargo clean && rm -f Cargo.lock diff --git a/src/libos/crates/io-uring-callback/examples/sgx/README.md b/src/libos/crates/io-uring-callback/examples/sgx/README.md new file mode 100644 index 00000000..6781a440 --- /dev/null +++ b/src/libos/crates/io-uring-callback/examples/sgx/README.md @@ -0,0 +1,14 @@ +## tcp_echo example for SGX +This is an example of using io-uring-callback in SGX. +This example combines tcp_echo example of io-uring-callback and hello-rust example of incubator-teaclave-sgx-sdk. +- ./app : untrusted code +- ./bin : executable program +- ./enclave : trusted code +- ./lib : library + +### run tcp_echo example in SGX +1. Prepare environments. + - clone incubator-teaclave-sgx-sdk repo to ```../../../third_parties/```. And checkout incubator-teaclave-sgx-sdk to ```d94996``` commit. + - prepare environments for incubator-teaclave-sgx-sdk. +2. ```make``` +3. ```cd bin && ./app``` diff --git a/src/libos/crates/io-uring-callback/examples/sgx/app/Cargo.toml b/src/libos/crates/io-uring-callback/examples/sgx/app/Cargo.toml new file mode 100644 index 00000000..3db6f41b --- /dev/null +++ b/src/libos/crates/io-uring-callback/examples/sgx/app/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "app" +version = "1.0.0" +build = "build.rs" + +[dependencies] +sgx-io-uring-ocalls = { path = "../deps/io-uring/ocalls" } +sgx_types = { path = "../deps/rust-sgx-sdk/sgx_types" } +sgx_urts = { path = "../deps/rust-sgx-sdk/sgx_urts" } + +[workspace] diff --git a/src/libos/crates/io-uring-callback/examples/sgx/app/build.rs b/src/libos/crates/io-uring-callback/examples/sgx/app/build.rs new file mode 100644 index 00000000..76c1d870 --- /dev/null +++ b/src/libos/crates/io-uring-callback/examples/sgx/app/build.rs @@ -0,0 +1,33 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License.. + +use std::env; + +fn main() { + let sdk_dir = env::var("SGX_SDK").unwrap_or_else(|_| "/opt/sgxsdk".to_string()); + let is_sim = env::var("SGX_MODE").unwrap_or_else(|_| "HW".to_string()); + + println!("cargo:rustc-link-search=native=../lib"); + println!("cargo:rustc-link-lib=static=Enclave_u"); + + println!("cargo:rustc-link-search=native={}/lib64", sdk_dir); + match is_sim.as_ref() { + "SW" => println!("cargo:rustc-link-lib=dylib=sgx_urts_sim"), + "HW" => println!("cargo:rustc-link-lib=dylib=sgx_urts"), + _ => println!("cargo:rustc-link-lib=dylib=sgx_urts"), // Treat undefined as HW + } +} diff --git a/src/libos/crates/io-uring-callback/examples/sgx/app/rust-toolchain b/src/libos/crates/io-uring-callback/examples/sgx/app/rust-toolchain new file mode 100644 index 00000000..148ed93d --- /dev/null +++ b/src/libos/crates/io-uring-callback/examples/sgx/app/rust-toolchain @@ -0,0 +1 @@ +nightly-2020-10-25 diff --git a/src/libos/crates/io-uring-callback/examples/sgx/app/src/main.rs b/src/libos/crates/io-uring-callback/examples/sgx/app/src/main.rs new file mode 100644 index 00000000..c663fa2a --- /dev/null +++ b/src/libos/crates/io-uring-callback/examples/sgx/app/src/main.rs @@ -0,0 +1,81 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License.. + +extern crate sgx_io_uring_ocalls; +extern crate sgx_types; +extern crate sgx_urts; +use sgx_types::*; +use sgx_urts::SgxEnclave; + +pub use sgx_io_uring_ocalls::*; + +static ENCLAVE_FILE: &'static str = "enclave.signed.so"; + +extern "C" { + fn run_sgx_example(eid: sgx_enclave_id_t, retval: *mut sgx_status_t) -> sgx_status_t; +} + +fn init_enclave() -> SgxResult { + let mut launch_token: sgx_launch_token_t = [0; 1024]; + let mut launch_token_updated: i32 = 0; + // call sgx_create_enclave to initialize an enclave instance + // Debug Support: set 2nd parameter to 1 + let debug = 1; + let mut misc_attr = sgx_misc_attribute_t { + secs_attr: sgx_attributes_t { flags: 0, xfrm: 0 }, + misc_select: 0, + }; + SgxEnclave::create( + ENCLAVE_FILE, + debug, + &mut launch_token, + &mut launch_token_updated, + &mut misc_attr, + ) +} + +fn main() { + let enclave = match init_enclave() { + Ok(r) => { + println!("[+] Init Enclave Successful {}!", r.geteid()); + r + } + Err(x) => { + println!("[-] Init Enclave Failed {}!", x.as_str()); + return; + } + }; + + let mut retval = sgx_status_t::SGX_SUCCESS; + let result = unsafe { run_sgx_example(enclave.geteid(), &mut retval) }; + match result { + sgx_status_t::SGX_SUCCESS => {} + _ => { + println!("[-] ECALL Enclave Failed {}!", result.as_str()); + return; + } + } + match retval { + sgx_status_t::SGX_SUCCESS => {} + _ => { + println!("[-] ECALL Returned Error {}!", retval.as_str()); + return; + } + } + println!("[+] run_sgx_example success..."); + enclave.destroy(); +} diff --git a/src/libos/crates/io-uring-callback/examples/sgx/bin/readme.txt b/src/libos/crates/io-uring-callback/examples/sgx/bin/readme.txt new file mode 100644 index 00000000..c5e82d74 --- /dev/null +++ b/src/libos/crates/io-uring-callback/examples/sgx/bin/readme.txt @@ -0,0 +1 @@ +bin \ No newline at end of file diff --git a/src/libos/crates/io-uring-callback/examples/sgx/enclave/Cargo.toml b/src/libos/crates/io-uring-callback/examples/sgx/enclave/Cargo.toml new file mode 100644 index 00000000..df04de2b --- /dev/null +++ b/src/libos/crates/io-uring-callback/examples/sgx/enclave/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "Helloworldsampleenclave" +version = "1.0.0" + +[lib] +name = "helloworldsampleenclave" +crate-type = ["staticlib"] + +[features] +default = [] + +[dependencies] +io-uring-callback = { path = "../../../../io-uring-callback", features = ["sgx"] } +io-uring = { path = "../../../../../../../deps/io-uring", features = ["sgx"] } +slab = { version = "0.4.5", default-features = false } +lazy_static = { version = "1.4.0", features = ["spin_no_std"] } + +[target.'cfg(not(target_env = "sgx"))'.dependencies] +sgx_types = { path = "../../../../../../../deps/rust-sgx-sdk/sgx_types" } +sgx_tstd = { path = "../../../../../../../deps/rust-sgx-sdk/sgx_tstd", features = ["backtrace", "thread"] } +sgx_trts = { path = "../../../../../../../deps/rust-sgx-sdk/sgx_trts" } + +[workspace] diff --git a/src/libos/crates/io-uring-callback/examples/sgx/enclave/Enclave.config.xml b/src/libos/crates/io-uring-callback/examples/sgx/enclave/Enclave.config.xml new file mode 100644 index 00000000..5293132b --- /dev/null +++ b/src/libos/crates/io-uring-callback/examples/sgx/enclave/Enclave.config.xml @@ -0,0 +1,13 @@ + + + 0 + 0 + 0x40000 + 0x400000 + 1 + 1 + 0 + 0 + 0 + 0xFFFFFFFF + diff --git a/src/libos/crates/io-uring-callback/examples/sgx/enclave/Enclave.edl b/src/libos/crates/io-uring-callback/examples/sgx/enclave/Enclave.edl new file mode 100644 index 00000000..144e0f73 --- /dev/null +++ b/src/libos/crates/io-uring-callback/examples/sgx/enclave/Enclave.edl @@ -0,0 +1,36 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +enclave { + from "sgx_tstd.edl" import *; + from "sgx_stdio.edl" import *; + from "sgx_backtrace.edl" import *; + from "sgx_tstdc.edl" import *; + from "sgx_net.edl" import *; + from "sgx_thread.edl" import *; + + from "sgx_io_uring_ocalls.edl" import *; + + trusted { + /* define ECALLs here. */ + public sgx_status_t run_sgx_example(); + }; + + untrusted { + /* define OCALLs here. */ + }; +}; diff --git a/src/libos/crates/io-uring-callback/examples/sgx/enclave/Enclave.lds b/src/libos/crates/io-uring-callback/examples/sgx/enclave/Enclave.lds new file mode 100644 index 00000000..e3d9d0ee --- /dev/null +++ b/src/libos/crates/io-uring-callback/examples/sgx/enclave/Enclave.lds @@ -0,0 +1,9 @@ +enclave.so +{ + global: + g_global_data_sim; + g_global_data; + enclave_entry; + local: + *; +}; diff --git a/src/libos/crates/io-uring-callback/examples/sgx/enclave/Enclave_private.pem b/src/libos/crates/io-uring-callback/examples/sgx/enclave/Enclave_private.pem new file mode 100644 index 00000000..529d07be --- /dev/null +++ b/src/libos/crates/io-uring-callback/examples/sgx/enclave/Enclave_private.pem @@ -0,0 +1,39 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIG4gIBAAKCAYEAroOogvsj/fZDZY8XFdkl6dJmky0lRvnWMmpeH41Bla6U1qLZ +AmZuyIF+mQC/cgojIsrBMzBxb1kKqzATF4+XwPwgKz7fmiddmHyYz2WDJfAjIveJ +ZjdMjM4+EytGlkkJ52T8V8ds0/L2qKexJ+NBLxkeQLfV8n1mIk7zX7jguwbCG1Pr +nEMdJ3Sew20vnje+RsngAzdPChoJpVsWi/K7cettX/tbnre1DL02GXc5qJoQYk7b +3zkmhz31TgFrd9VVtmUGyFXAysuSAb3EN+5VnHGr0xKkeg8utErea2FNtNIgua8H +ONfm9Eiyaav1SVKzPHlyqLtcdxH3I8Wg7yqMsaprZ1n5A1v/levxnL8+It02KseD +5HqV4rf/cImSlCt3lpRg8U5E1pyFQ2IVEC/XTDMiI3c+AR+w2jSRB3Bwn9zJtFlW +KHG3m1xGI4ck+Lci1JvWWLXQagQSPtZTsubxTQNx1gsgZhgv1JHVZMdbVlAbbRMC +1nSuJNl7KPAS/VfzAgEDAoIBgHRXxaynbVP5gkO0ug6Qw/E27wzIw4SmjsxG6Wpe +K7kfDeRskKxESdsA/xCrKkwGwhcx1iIgS5+Qscd1Yg+1D9X9asd/P7waPmWoZd+Z +AhlKwhdPsO7PiF3e1AzHhGQwsUTt/Y/aSI1MpHBvy2/s1h9mFCslOUxTmWw0oj/Q +ldIEgWeNR72CE2+jFIJIyml6ftnb6qzPiga8Bm48ubKh0kvySOqnkmnPzgh+JBD6 +JnBmtZbfPT97bwTT+N6rnPqOOApvfHPf15kWI8yDbprG1l4OCUaIUH1AszxLd826 +5IPM+8gINLRDP1MA6azECPjTyHXhtnSIBZCyWSVkc05vYmNXYUNiXWMajcxW9M02 +wKzFELO8NCEAkaTPxwo4SCyIjUxiK1LbQ9h8PSy4c1+gGP4LAMR8xqP4QKg6zdu9 +osUGG/xRe/uufgTBFkcjqBHtK5L5VI0jeNIUAgW/6iNbYXjBMJ0GfauLs+g1VsOm +WfdgXzsb9DYdMa0OXXHypmV4GwKBwQDUwQj8RKJ6c8cT4vcWCoJvJF00+RFL+P3i +Gx2DLERxRrDa8AVGfqaCjsR+3vLgG8V/py+z+dxZYSqeB80Qeo6PDITcRKoeAYh9 +xlT3LJOS+k1cJcEmlbbO2IjLkTmzSwa80fWexKu8/Xv6vv15gpqYl1ngYoqJM3pd +vzmTIOi7MKSZ0WmEQavrZj8zK4endE3v0eAEeQ55j1GImbypSf7Idh7wOXtjZ7WD +Dg6yWDrri+AP/L3gClMj8wsAxMV4ZR8CgcEA0fzDHkFa6raVOxWnObmRoDhAtE0a +cjUj976NM5yyfdf2MrKy4/RhdTiPZ6b08/lBC/+xRfV3xKVGzacm6QjqjZrUpgHC +0LKiZaMtccCJjLtPwQd0jGQEnKfMFaPsnhOc5y8qVkCzVOSthY5qhz0XNotHHFmJ +gffVgB0iqrMTvSL7IA2yqqpOqNRlhaYhNl8TiFP3gIeMtVa9rZy31JPgT2uJ+kfo +gV7sdTPEjPWZd7OshGxWpT6QfVDj/T9T7L6tAoHBAI3WBf2DFvxNL2KXT2QHAZ9t +k3imC4f7U+wSE6zILaDZyzygA4RUbwG0gv8/TJVn2P/Eynf76DuWHGlaiLWnCbSz +Az2DHBQBBaku409zDQym3j1ugMRjzzSQWzJg0SIyBH3hTmnYcn3+Uqcp/lEBvGW6 +O+rsXFt3pukqJmIV8HzLGGaLm62BHUeZf3dyWm+i3p/hQAL7Xvu04QW70xuGqdr5 +afV7p5eaeQIJXyGQJ0eylV/90+qxjMKiB1XYg6WYvwKBwQCL/ddpgOdHJGN8uRom +e7Zq0Csi3hGheMKlKbN3vcxT5U7MdyHtTZZOJbTvxKNNUNYH/8uD+PqDGNneb29G +BfGzvI3EASyLIcGZF3OhKwZd0jUrWk2y7Vhob91jwp2+t73vdMbkKyI4mHOuXvGv +fg95si9oO7EBT+Oqvhccd2J+F1IVXncccYnF4u5ZGWt5lLewN/pVr7MjjykeaHqN +t+rfnQam2psA6fL4zS2zTmZPzR2tnY8Y1GBTi0Ko1OKd1HMCgcAb5cB/7/AQlhP9 +yQa04PLH9ygQkKKptZp7dy5WcWRx0K/hAHRoi2aw1wZqfm7VBNu2SLcs90kCCCxp +6C5sfJi6b8NpNbIPC+sc9wsFr7pGo9SFzQ78UlcWYK2Gu2FxlMjonhka5hvo4zvg +WxlpXKEkaFt3gLd92m/dMqBrHfafH7VwOJY2zT3WIpjwuk0ZzmRg5p0pG/svVQEH +NZmwRwlopysbR69B/n1nefJ84UO50fLh5s5Zr3gBRwbWNZyzhXk= +-----END RSA PRIVATE KEY----- diff --git a/src/libos/crates/io-uring-callback/examples/sgx/enclave/Makefile b/src/libos/crates/io-uring-callback/examples/sgx/enclave/Makefile new file mode 100644 index 00000000..9f702623 --- /dev/null +++ b/src/libos/crates/io-uring-callback/examples/sgx/enclave/Makefile @@ -0,0 +1,38 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +Rust_Enclave_Name := libenclave.a +Rust_Enclave_Files := $(wildcard src/*.rs) +Rust_Target_Path := $(CURDIR)/../../../../incubator-teaclave-sgx-sdk/xargo + +ifeq ($(MITIGATION-CVE-2020-0551), LOAD) +export MITIGATION_CVE_2020_0551=LOAD +else ifeq ($(MITIGATION-CVE-2020-0551), CF) +export MITIGATION_CVE_2020_0551=CF +endif + +.PHONY: all + +all: $(Rust_Enclave_Name) + +$(Rust_Enclave_Name): $(Rust_Enclave_Files) +ifeq ($(XARGO_SGX), 1) + RUST_TARGET_PATH=$(Rust_Target_Path) xargo build --target x86_64-unknown-linux-sgx --release + cp ./target/x86_64-unknown-linux-sgx/release/libhelloworldsampleenclave.a ../lib/libenclave.a +else + cargo build --release + cp ./target/release/libhelloworldsampleenclave.a ../lib/libenclave.a +endif \ No newline at end of file diff --git a/src/libos/crates/io-uring-callback/examples/sgx/enclave/rust-toolchain b/src/libos/crates/io-uring-callback/examples/sgx/enclave/rust-toolchain new file mode 100644 index 00000000..148ed93d --- /dev/null +++ b/src/libos/crates/io-uring-callback/examples/sgx/enclave/rust-toolchain @@ -0,0 +1 @@ +nightly-2020-10-25 diff --git a/src/libos/crates/io-uring-callback/examples/sgx/enclave/src/lib.rs b/src/libos/crates/io-uring-callback/examples/sgx/enclave/src/lib.rs new file mode 100644 index 00000000..84df8718 --- /dev/null +++ b/src/libos/crates/io-uring-callback/examples/sgx/enclave/src/lib.rs @@ -0,0 +1,334 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License.. + +#![crate_name = "helloworldsampleenclave"] +#![crate_type = "staticlib"] +#![cfg_attr(not(target_env = "sgx"), no_std)] +#![cfg_attr(target_env = "sgx", feature(rustc_private))] + +extern crate sgx_trts; +extern crate sgx_types; +#[cfg(not(target_env = "sgx"))] +#[macro_use] +extern crate sgx_tstd as std; + +extern crate io_uring; +extern crate io_uring_callback; +extern crate lazy_static; +extern crate slab; + +use sgx_trts::libc; +use sgx_types::*; +use std::collections::VecDeque; +use std::os::unix::io::RawFd; +use std::prelude::v1::*; +use std::ptr; +use std::sync::SgxMutex as Mutex; + +use io_uring::opcode::types; +use io_uring_callback::{Builder, Handle, IoUring}; +use lazy_static::lazy_static; +use slab::Slab; + +lazy_static! { + static ref TOKEN_QUEUE: Mutex> = Mutex::new(VecDeque::new()); + static ref HANDLE_SLAB: Mutex> = Mutex::new(slab::Slab::new()); +} + +#[derive(Clone, Debug)] +enum Token { + Accept, + Poll { + fd: RawFd, + }, + Read { + fd: RawFd, + buf_index: usize, + }, + Write { + fd: RawFd, + buf_index: usize, + offset: usize, + len: usize, + }, +} + +pub struct AcceptCount { + fd: types::Fd, + count: usize, +} + +impl AcceptCount { + fn new(fd: RawFd, count: usize) -> AcceptCount { + AcceptCount { + fd: types::Fd(fd), + count: count, + } + } + + pub fn try_push_accept(&mut self, ring: &IoUring) { + while self.count > 0 { + let to_complete_token = Token::Accept; + let mut handle_slab = HANDLE_SLAB.lock().unwrap(); + let slab_entry = handle_slab.vacant_entry(); + let slab_key = slab_entry.key(); + + let complete_fn = move |retval: i32| { + let mut queue = TOKEN_QUEUE.lock().unwrap(); + queue.push_back((to_complete_token, retval)); + + HANDLE_SLAB.lock().unwrap().remove(slab_key); + }; + + let handle = + unsafe { ring.accept(self.fd, ptr::null_mut(), ptr::null_mut(), 0, complete_fn) }; + + slab_entry.insert(handle); + + self.count -= 1; + } + } +} + +#[no_mangle] +pub extern "C" fn run_sgx_example() -> sgx_status_t { + // std::backtrace::enable_backtrace("enclave.signed.so", std::backtrace::PrintFormat::Full); + println!("[ECALL] run_sgx_example"); + + let ring = Builder::new() + .setup_sqpoll(Some(500/* ms */)) + .build(256) + .unwrap(); + + let socket_fd = { + let socket_fd = unsafe { libc::ocall::socket(libc::AF_INET, libc::SOCK_STREAM, 0) }; + if socket_fd < 0 { + println!("[ECALL] create socket failed, ret: {}", socket_fd); + return sgx_status_t::SGX_ERROR_UNEXPECTED; + } + + let ret = unsafe { + let servaddr = libc::sockaddr_in { + sin_family: libc::AF_INET as u16, + sin_port: 3456_u16.to_be(), + sin_addr: libc::in_addr { s_addr: 0 }, + sin_zero: [0; 8], + }; + libc::ocall::bind( + socket_fd, + &servaddr as *const _ as *const libc::sockaddr, + core::mem::size_of::() as u32, + ) + }; + if ret < 0 { + println!("[ECALL] bind failed, ret: {}", ret); + unsafe { + libc::ocall::close(socket_fd); + } + return sgx_status_t::SGX_ERROR_UNEXPECTED; + } + + let ret = unsafe { libc::ocall::listen(socket_fd, 10) }; + if ret < 0 { + println!("[ECALL] listen failed, ret: {}", ret); + unsafe { + libc::ocall::close(socket_fd); + } + return sgx_status_t::SGX_ERROR_UNEXPECTED; + } + socket_fd + }; + + let mut bufpool = Vec::with_capacity(64); + let mut buf_alloc = Slab::with_capacity(64); + + println!("[ECALL] listen 127.0.0.1:3456"); + + let mut accept = AcceptCount::new(socket_fd, 3); + + loop { + accept.try_push_accept(&ring); + + ring.trigger_callbacks(); + + let mut queue = TOKEN_QUEUE.lock().unwrap(); + while !queue.is_empty() { + let (token, ret) = queue.pop_front().unwrap(); + match token { + Token::Accept => { + println!("[ECALL] accept"); + + accept.count += 1; + + let fd = ret; + + let to_complete_token = Token::Poll { fd }; + let mut handle_slab = HANDLE_SLAB.lock().unwrap(); + let slab_entry = handle_slab.vacant_entry(); + let slab_key = slab_entry.key(); + + let complete_fn = move |retval: i32| { + let mut queue = TOKEN_QUEUE.lock().unwrap(); + queue.push_back((to_complete_token, retval)); + + HANDLE_SLAB.lock().unwrap().remove(slab_key); + }; + + let handle = + unsafe { ring.poll(types::Fd(fd), libc::POLLIN as _, complete_fn) }; + + slab_entry.insert(handle); + } + Token::Poll { fd } => { + let (buf_index, buf) = match bufpool.pop() { + Some(buf_index) => (buf_index, &mut buf_alloc[buf_index]), + None => { + let buf = Box::new(unsafe { + std::slice::from_raw_parts_mut( + libc::ocall::malloc(2048) as *mut u8, + 2048, + ) + }); + let buf_entry = buf_alloc.vacant_entry(); + let buf_index = buf_entry.key(); + (buf_index, buf_entry.insert(buf)) + } + }; + + let to_complete_token = Token::Read { fd, buf_index }; + let mut handle_slab = HANDLE_SLAB.lock().unwrap(); + let slab_entry = handle_slab.vacant_entry(); + let slab_key = slab_entry.key(); + + let complete_fn = move |retval: i32| { + let mut queue = TOKEN_QUEUE.lock().unwrap(); + queue.push_back((to_complete_token, retval)); + + HANDLE_SLAB.lock().unwrap().remove(slab_key); + }; + + let handle = unsafe { + ring.read( + types::Fd(fd), + buf.as_mut_ptr(), + buf.len() as _, + 0, + 0, + complete_fn, + ) + }; + + slab_entry.insert(handle); + } + Token::Read { fd, buf_index } => { + if ret == 0 { + bufpool.push(buf_index); + + println!("[ECALL] shutdown"); + + unsafe { + libc::ocall::close(fd); + } + } else { + let len = ret as usize; + let buf = &buf_alloc[buf_index]; + + let to_complete_token = Token::Write { + fd, + buf_index, + len, + offset: 0, + }; + let mut handle_slab = HANDLE_SLAB.lock().unwrap(); + let slab_entry = handle_slab.vacant_entry(); + let slab_key = slab_entry.key(); + + let complete_fn = move |retval: i32| { + let mut queue = TOKEN_QUEUE.lock().unwrap(); + queue.push_back((to_complete_token, retval)); + + HANDLE_SLAB.lock().unwrap().remove(slab_key); + }; + + let handle = unsafe { + ring.write(types::Fd(fd), buf.as_ptr(), len as _, 0, 0, complete_fn) + }; + + slab_entry.insert(handle); + } + } + Token::Write { + fd, + buf_index, + offset, + len, + } => { + let write_len = ret as usize; + + if offset + write_len >= len { + bufpool.push(buf_index); + + let to_complete_token = Token::Poll { fd }; + let mut handle_slab = HANDLE_SLAB.lock().unwrap(); + let slab_entry = handle_slab.vacant_entry(); + let slab_key = slab_entry.key(); + + let complete_fn = move |retval: i32| { + let mut queue = TOKEN_QUEUE.lock().unwrap(); + queue.push_back((to_complete_token, retval)); + + HANDLE_SLAB.lock().unwrap().remove(slab_key); + }; + + let handle = + unsafe { ring.poll_add(types::Fd(fd), libc::POLLIN as _, complete_fn) }; + + slab_entry.insert(handle); + } else { + let offset = offset + write_len; + let len = len - offset; + + let buf = &buf_alloc[buf_index][offset..]; + + let to_complete_token = Token::Write { + fd, + buf_index, + offset, + len, + }; + let mut handle_slab = HANDLE_SLAB.lock().unwrap(); + let slab_entry = handle_slab.vacant_entry(); + let slab_key = slab_entry.key(); + + let complete_fn = move |retval: i32| { + let mut queue = TOKEN_QUEUE.lock().unwrap(); + queue.push_back((to_complete_token, retval)); + + HANDLE_SLAB.lock().unwrap().remove(slab_key); + }; + + let handle = unsafe { + ring.write(types::Fd(fd), buf.as_ptr(), len as _, 0, 0, complete_fn) + }; + + slab_entry.insert(handle); + }; + } + } + } + } +} diff --git a/src/libos/crates/io-uring-callback/examples/sgx/enclave/x86_64-unknown-linux-sgx.json b/src/libos/crates/io-uring-callback/examples/sgx/enclave/x86_64-unknown-linux-sgx.json new file mode 100644 index 00000000..10d37a74 --- /dev/null +++ b/src/libos/crates/io-uring-callback/examples/sgx/enclave/x86_64-unknown-linux-sgx.json @@ -0,0 +1,31 @@ +{ + "arch": "x86_64", + "cpu": "x86-64", + "data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128", + "dynamic-linking": true, + "env": "sgx", + "exe-allocation-crate": "alloc_system", + "executables": true, + "has-elf-tls": true, + "has-rpath": true, + "linker-flavor": "gcc", + "linker-is-gnu": true, + "llvm-target": "x86_64-unknown-linux-gnu", + "max-atomic-width": 64, + "os": "linux", + "position-independent-executables": true, + "pre-link-args": { + "gcc": [ + "-Wl,--as-needed", + "-Wl,-z,noexecstack", + "-m64" + ] + }, + "relro-level": "full", + "stack-probes": true, + "target-c-int-width": "32", + "target-endian": "little", + "target-family": "unix", + "target-pointer-width": "64", + "vendor": "mesalock" +} diff --git a/src/libos/crates/io-uring-callback/examples/sgx/lib/readme.txt b/src/libos/crates/io-uring-callback/examples/sgx/lib/readme.txt new file mode 100644 index 00000000..7951405f --- /dev/null +++ b/src/libos/crates/io-uring-callback/examples/sgx/lib/readme.txt @@ -0,0 +1 @@ +lib \ No newline at end of file diff --git a/src/libos/crates/io-uring-callback/examples/tcp_echo.rs b/src/libos/crates/io-uring-callback/examples/tcp_echo.rs new file mode 100644 index 00000000..8d3dec67 --- /dev/null +++ b/src/libos/crates/io-uring-callback/examples/tcp_echo.rs @@ -0,0 +1,251 @@ +use std::collections::VecDeque; +use std::net::TcpListener; +use std::os::unix::io::{AsRawFd, RawFd}; +use std::ptr; +use std::sync::Mutex; + +use io_uring::opcode::types; +use io_uring_callback::{Builder, IoHandle, IoUring}; +use lazy_static::lazy_static; + +lazy_static! { + static ref TOKEN_QUEUE: Mutex> = Mutex::new(VecDeque::new()); + static ref HANDLE_SLAB: Mutex> = Mutex::new(slab::Slab::new()); +} + +#[derive(Clone, Debug)] +enum Token { + Accept, + Poll { + fd: RawFd, + }, + Read { + fd: RawFd, + buf_index: usize, + }, + Write { + fd: RawFd, + buf_index: usize, + offset: usize, + len: usize, + }, +} + +pub struct AcceptCount { + fd: types::Fd, + count: usize, +} + +impl AcceptCount { + fn new(fd: RawFd, count: usize) -> AcceptCount { + AcceptCount { + fd: types::Fd(fd), + count: count, + } + } + + pub fn try_push_accept(&mut self, ring: &IoUring) { + while self.count > 0 { + let to_complete_token = Token::Accept; + let mut handle_slab = HANDLE_SLAB.lock().unwrap(); + let slab_entry = handle_slab.vacant_entry(); + let slab_key = slab_entry.key(); + + let complete_fn = move |retval: i32| { + let mut queue = TOKEN_QUEUE.lock().unwrap(); + queue.push_back((to_complete_token, retval)); + + HANDLE_SLAB.lock().unwrap().remove(slab_key); + }; + + let handle = + unsafe { ring.accept(self.fd, ptr::null_mut(), ptr::null_mut(), 0, complete_fn) }; + + slab_entry.insert(handle); + + self.count -= 1; + } + } +} + +fn main() { + let ring = Builder::new() + .setup_sqpoll(Some(500 /* ms */)) + .build(256) + .unwrap(); + let listener = TcpListener::bind(("127.0.0.1", 3456)).unwrap(); + + let mut bufpool = Vec::with_capacity(64); + let mut buf_alloc = slab::Slab::with_capacity(64); + + println!("listen {}", listener.local_addr().unwrap()); + + let mut accept = AcceptCount::new(listener.as_raw_fd(), 3); + + loop { + accept.try_push_accept(&ring); + + ring.poll_completions(0, 100); + + let mut queue = TOKEN_QUEUE.lock().unwrap(); + while !queue.is_empty() { + let (token, ret) = queue.pop_front().unwrap(); + match token { + Token::Accept => { + println!("accept"); + + accept.count += 1; + + let fd = ret; + + let to_complete_token = Token::Poll { fd }; + let mut handle_slab = HANDLE_SLAB.lock().unwrap(); + let slab_entry = handle_slab.vacant_entry(); + let slab_key = slab_entry.key(); + + let complete_fn = move |retval: i32| { + let mut queue = TOKEN_QUEUE.lock().unwrap(); + queue.push_back((to_complete_token, retval)); + + HANDLE_SLAB.lock().unwrap().remove(slab_key); + }; + + let handle = + unsafe { ring.poll(types::Fd(fd), libc::POLLIN as _, complete_fn) }; + + slab_entry.insert(handle); + } + Token::Poll { fd } => { + let (buf_index, buf) = match bufpool.pop() { + Some(buf_index) => (buf_index, &mut buf_alloc[buf_index]), + None => { + let buf = vec![0u8; 2048].into_boxed_slice(); + let buf_entry = buf_alloc.vacant_entry(); + let buf_index = buf_entry.key(); + (buf_index, buf_entry.insert(buf)) + } + }; + + let to_complete_token = Token::Read { fd, buf_index }; + let mut handle_slab = HANDLE_SLAB.lock().unwrap(); + let slab_entry = handle_slab.vacant_entry(); + let slab_key = slab_entry.key(); + + let complete_fn = move |retval: i32| { + let mut queue = TOKEN_QUEUE.lock().unwrap(); + queue.push_back((to_complete_token, retval)); + + HANDLE_SLAB.lock().unwrap().remove(slab_key); + }; + + let handle = unsafe { + ring.read( + types::Fd(fd), + buf.as_mut_ptr(), + buf.len() as _, + 0, + 0, + complete_fn, + ) + }; + + slab_entry.insert(handle); + } + Token::Read { fd, buf_index } => { + if ret == 0 { + bufpool.push(buf_index); + + println!("shutdown"); + + unsafe { + libc::close(fd); + } + } else { + let len = ret as usize; + let buf = &buf_alloc[buf_index]; + + let to_complete_token = Token::Write { + fd, + buf_index, + len, + offset: 0, + }; + let mut handle_slab = HANDLE_SLAB.lock().unwrap(); + let slab_entry = handle_slab.vacant_entry(); + let slab_key = slab_entry.key(); + + let complete_fn = move |retval: i32| { + let mut queue = TOKEN_QUEUE.lock().unwrap(); + queue.push_back((to_complete_token, retval)); + + HANDLE_SLAB.lock().unwrap().remove(slab_key); + }; + + let handle = unsafe { + ring.write(types::Fd(fd), buf.as_ptr(), len as _, 0, 0, complete_fn) + }; + + slab_entry.insert(handle); + } + } + Token::Write { + fd, + buf_index, + offset, + len, + } => { + let write_len = ret as usize; + + if offset + write_len >= len { + bufpool.push(buf_index); + + let to_complete_token = Token::Poll { fd }; + let mut handle_slab = HANDLE_SLAB.lock().unwrap(); + let slab_entry = handle_slab.vacant_entry(); + let slab_key = slab_entry.key(); + + let complete_fn = move |retval: i32| { + let mut queue = TOKEN_QUEUE.lock().unwrap(); + queue.push_back((to_complete_token, retval)); + + HANDLE_SLAB.lock().unwrap().remove(slab_key); + }; + + let handle = + unsafe { ring.poll(types::Fd(fd), libc::POLLIN as _, complete_fn) }; + + slab_entry.insert(handle); + } else { + let offset = offset + write_len; + let len = len - offset; + + let buf = &buf_alloc[buf_index][offset..]; + + let to_complete_token = Token::Write { + fd, + buf_index, + offset, + len, + }; + let mut handle_slab = HANDLE_SLAB.lock().unwrap(); + let slab_entry = handle_slab.vacant_entry(); + let slab_key = slab_entry.key(); + + let complete_fn = move |retval: i32| { + let mut queue = TOKEN_QUEUE.lock().unwrap(); + queue.push_back((to_complete_token, retval)); + + HANDLE_SLAB.lock().unwrap().remove(slab_key); + }; + + let handle = unsafe { + ring.write(types::Fd(fd), buf.as_ptr(), len as _, 0, 0, complete_fn) + }; + + slab_entry.insert(handle); + }; + } + } + } + } +} diff --git a/src/libos/crates/io-uring-callback/rust-toolchain b/src/libos/crates/io-uring-callback/rust-toolchain new file mode 100644 index 00000000..480e8aa7 --- /dev/null +++ b/src/libos/crates/io-uring-callback/rust-toolchain @@ -0,0 +1 @@ +nightly-2022-02-23 diff --git a/src/libos/crates/io-uring-callback/src/io_handle.rs b/src/libos/crates/io-uring-callback/src/io_handle.rs new file mode 100644 index 00000000..afb9d42d --- /dev/null +++ b/src/libos/crates/io-uring-callback/src/io_handle.rs @@ -0,0 +1,192 @@ +use std::sync::Arc; +cfg_if::cfg_if! { + if #[cfg(feature = "sgx")] { + use std::prelude::v1::*; + use spin::Mutex as Mutex; + } else { + use std::sync::Mutex; + } +} + +/// The handle to an I/O request pushed to the submission queue of an io_uring instance. +#[derive(Debug)] +#[repr(transparent)] +pub struct IoHandle(pub(crate) Arc); + +/// The state of an I/O request represented by an [`IoHandle`]. +/// If a request is in `Processed` or `Cancelled` state, means that the request is completed. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum IoState { + /// The I/O request has been submitted. + Submitted, + /// The I/O request has been processed by the kernel and returns a value. + Processed(i32), + /// The I/O request is being cancelled. + Cancelling, + /// The I/O request has been cancelled by the kernel. + Cancelled, +} + +const CANCEL_RETVAL: i32 = -libc::ECANCELED; + +impl IoHandle { + pub(crate) fn new(token: Arc) -> Self { + Self(token) + } + + /// Returns the state of the I/O request. + pub fn state(&self) -> IoState { + self.0.state() + } + + /// Returns the return value of the I/O request if it is completed. + pub fn retval(&self) -> Option { + self.0.retval() + } + + /// Release a handle. + /// + /// Normally, a handle is not alloed to be dropped before the I/O is completed. + /// This helps discover memory safety problems due to potential misuse by users. + /// + /// But sometimes keeping handles can be pointless. This is when the `release` + /// method can help. The release method explictly states that a handle is + /// useless and then drop it. + pub fn release(self) { + // Safety. The representation is transparent. + let token = unsafe { std::mem::transmute::>(self) }; + drop(token); + } +} + +impl Unpin for IoHandle {} + +impl Drop for IoHandle { + fn drop(&mut self) { + // The user cannot drop a handle if the request isn't completed. + assert!(matches!( + self.state(), + IoState::Processed(_) | IoState::Cancelled + )); + } +} + +/// A token representing an on-going I/O request. +/// +/// Tokens and handles are basically the same thing---an on-going I/O request. The main difference +/// is that handles are used by users, while tokens are used internally. +pub(crate) struct IoToken { + inner: Mutex, +} + +impl IoToken { + pub fn new(completion_callback: impl FnOnce(i32) + Send + 'static, token_key: u64) -> Self { + let inner = Mutex::new(Inner::new(completion_callback, token_key)); + Self { inner } + } + + pub fn state(&self) -> IoState { + let inner = self.inner.lock(); + inner.state() + } + + pub fn retval(&self) -> Option { + let inner = self.inner.lock(); + inner.retval() + } + + pub fn complete(&self, retval: i32) { + // let mut inner = self.inner.lock().unwrap(); + let mut inner = self.inner.lock(); + let callback = inner.complete(retval); + // Must release the lock before invoking the callback function. + // This avoids any deadlock if the IoHandle is accessed inside the callback by + // user. + drop(inner); + + (callback)(retval); + } + + /// Change the state from submited to cancelling. + /// If transition succeeds, return the token_key for following cancel operation. + pub fn transit_to_cancelling(&self) -> Result { + // let mut inner = self.inner.lock().unwrap(); + let mut inner = self.inner.lock(); + inner.transit_to_cancelling() + } +} + +impl std::fmt::Debug for IoToken { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("IoToken") + .field("state", &self.state()) + .finish() + } +} + +struct Inner { + state: IoState, + completion_callback: Option, + token_key: u64, +} + +type Callback = Box; + +impl Inner { + pub fn new(completion_callback: impl FnOnce(i32) + Send + 'static, token_key: u64) -> Self { + let state = IoState::Submitted; + let completion_callback = Some(Box::new(completion_callback) as _); + Self { + state, + completion_callback, + token_key, + } + } + + pub fn complete(&mut self, retval: i32) -> Callback { + match self.state { + IoState::Submitted => { + self.state = IoState::Processed(retval); + } + IoState::Cancelling => { + if retval == CANCEL_RETVAL { + // case 1: The request was cancelled successfully. + self.state = IoState::Cancelled; + } else { + // case 2.1: The request was cancelled with error. + // case 2.2: The request was not actually canceled. + self.state = IoState::Processed(retval); + } + } + _ => { + unreachable!("cannot do complete twice"); + } + } + + self.completion_callback.take().unwrap() + } + + pub fn transit_to_cancelling(&mut self) -> Result { + match self.state { + IoState::Submitted => { + self.state = IoState::Cancelling; + return Ok(self.token_key); + } + _ => { + return Err(()); + } + } + } + + pub fn retval(&self) -> Option { + match self.state { + IoState::Processed(retval) => Some(retval), + IoState::Cancelled => Some(CANCEL_RETVAL), + _ => None, + } + } + + pub fn state(&self) -> IoState { + self.state + } +} diff --git a/src/libos/crates/io-uring-callback/src/lib.rs b/src/libos/crates/io-uring-callback/src/lib.rs new file mode 100644 index 00000000..2df0627e --- /dev/null +++ b/src/libos/crates/io-uring-callback/src/lib.rs @@ -0,0 +1,812 @@ +//! A more user-friendly io_uring crate. +//! +//! # Overview +//! +//! While the original [io_uring crate](https://github.com/tokio-rs/io-uring) exposes io_uring's API in Rust, it has +//! one big shortcoming: users have to manually pop entries out of the completion queue and map those entries to +//! user requests. It makes the APIs cumbersome to use. +//! +//! This crate provides more user-friend APIs with the following features: +//! +//! * Callback-based. On the completion of an I/O request, the corresponding user-registered +//! callback will get invoked. No manual dispatching of I/O completions. +//! +//! * Async/await-ready. After submitting an I/O request, the user will get a handle that +//! represents the on-going I/O request. The user can await the handle (as it is a `Future`). +//! +//! * Polling-based I/O. Both I/O submissions and completions can be easily done in polling mode. +//! +//! # Usage +//! +//! Use [`Builder`] to create a new instance of [`IoUring`]. +//! +//! ``` +//! use io_uring_callback::{Builder, IoUring}; +//! +//! let io_uring: IoUring = Builder::new().build(256).unwrap(); +//! ``` +//! +//! A number of I/O operations are supported, e.g., `read`, `write`, `fsync`, `sendmsg`, +//! `recvmsg`, etc. Requests for such I/O operations can be pushed into the submission +//! queue of the io_uring with the corresponding methods. +//! +//! ``` +//! # use io_uring_callback::{Builder}; +//! use io_uring_callback::{Fd, RwFlags}; +//! +//! # let io_uring = Builder::new().build(256).unwrap(); +//! let fd = Fd(1); // use the stdout +//! let msg = "hello world\0"; +//! let completion_callback = move |retval: i32| { +//! assert!(retval > 0); +//! }; +//! let handle = unsafe { +//! io_uring.write(fd, msg.as_ptr(), msg.len() as u32, 0, RwFlags::default(), completion_callback) +//! }; +//! # while handle.retval().is_none() { +//! # io_uring.wait_completions(1); +//! # } +//! ``` +//! +//! You have to two ways to get notified about the completion of I/O requests. The first +//! is through the registered callback function and the second is by `await`ing the handle +//! (which is a `Future`) obtained as a result of pushing I/O requests. +//! +//! After completing the I/O requests, Linux will push I/O responses into the completion queue of +//! the io_uring instance. You need _periodically_ poll completions from the queue: +//! ```no_run +//! # use io_uring_callback::{Builder}; +//! # let io_uring = Builder::new().build(256).unwrap(); +//! let min_complete = 1; +//! let polling_retries = 5000; +//! io_uring.poll_completions(min_complete, polling_retries); +//! ``` +//! which will trigger registered callbacks and wake up handles. +//! +//! When the I/O request is completed (the request is processed or cancelled by the kernel), +//! `poll_completions` will trigger the user-registered callback. +//! +//! # I/O Handles +//! +//! After submitting an I/O request, the user will get as the return value +//! an instance of [`IoHandle`], which represents the submitted I/O requests. +//! +//! So why bother keeping I/O handles? The reasons are three-fold. +//! +//! - First, as a future, `IoHandle` allows you to await on it, which is quite +//! convenient if you happen to use io_uring with Rust's async/await. +//! - Second, `IoHandle` makes it possible to _cancel_ on-going I/O requests. +//! - Third, it makes the whole APIs less prone to memory safety issues. Recall that all I/O submitting +//! methods (e.g., `write`, `accept`, etc.) are _unsafe_ as there are no guarantee that +//! their arguments---like FDs or buffer pointers---are valid throughout the lifetime of +//! an I/O request. What if an user accidentally releases the in-use resources associated with +//! an on-going I/O request? I/O handles can detect such programming bugs as long as +//! the handles are also released along with other in-use I/O resources (which is most likely). +//! This is because when an `IoHandle` is dropped, we will panic if its state is neither +//! processed (`IoState::Processed`) or canceled (`IoState::Canceled`). That is, dropping +//! an `IoHandle` that is still in-use is forbidden. +//! +//! After pushing an I/O request into the submission queue, you will get an `IoHandle`. +//! With this handle, you can cancel the I/O request. +//! ``` +//! # use io_uring_callback::Builder; +//! use io_uring_callback::{Timespec, TimeoutFlags}; +//! +//! # let io_uring = Builder::new().build(256).unwrap(); +//! let tp = Timespec { tv_sec: 1, tv_nsec: 0, }; +//! let completion_callback = move |_retval: i32| {}; +//! let handle = unsafe { +//! io_uring.timeout(&tp as *const _, 0, TimeoutFlags::empty(), completion_callback) +//! }; +//! unsafe { io_uring.cancel(&handle); } +//! io_uring.wait_completions(1); +//! ``` + +#![feature(get_mut_unchecked)] +#![cfg_attr(feature = "sgx", no_std)] + +#[cfg(feature = "sgx")] +extern crate sgx_libc as libc; +#[cfg(feature = "sgx")] +extern crate sgx_tstd as std; + +use core::sync::atomic::AtomicUsize; +use std::sync::Arc; +use std::{collections::HashMap, io}; +cfg_if::cfg_if! { + if #[cfg(feature = "sgx")] { + use std::prelude::v1::*; + use spin::Mutex as Mutex; + } else { + use std::sync::Mutex; + } +} + +use atomic::Ordering; +use io_uring::opcode; +use io_uring::squeue::Entry as SqEntry; +use io_uring::types; +use slab::Slab; +use spin::RwLock; +use std::os::unix::prelude::RawFd; + +use crate::io_handle::IoToken; + +mod io_handle; + +pub use crate::io_handle::{IoHandle, IoState}; +pub use io_uring::types::{Fd, RwFlags, TimeoutFlags, Timespec}; +pub type IoUringRef = Arc; + +/// An io_uring instance. +/// +/// # Safety +/// +/// All I/O methods are based on the assumption that the resources (e.g., file descriptors, pointers, etc.) +/// given in their arguments are valid before the completion of the async I/O. +pub struct IoUring { + ring: io_uring::IoUring, + token_table: Mutex>>, + sq_lock: Mutex<()>, // For submission queue synchronization + fd_map: RwLock>, // (key: fd, value: op num) +} + +impl Drop for IoUring { + fn drop(&mut self) { + // By the end of the life of the io_uring instance, its token table should have been emptied. + // This emptyness check prevents handles created by this io_uring become "dangling". + // That is, no user will ever hold a handle whose associated io_uring instance has + // been destroyed. + // let token_table = self.token_table.lock().unwrap(); + let token_table = self.token_table.lock(); + assert!(token_table.len() == 0); + } +} + +impl IoUring { + /// The magic token_key for Cancel I/O request. + /// The magic token_key should be different from the token_table's keys. + const CANCEL_TOKEN_KEY: u64 = u64::MAX; + + /// Constructor for internal uses. + /// + /// Users should use `Builder` instead. + pub(crate) fn new(ring: io_uring::IoUring) -> Self { + let token_table = Mutex::new(Slab::new()); + let sq_lock = Mutex::new(()); + let fd_map = RwLock::new(HashMap::new()); + Self { + ring, + token_table, + sq_lock, + fd_map, + } + } + + /// Get the raw io_uring instance for advanced usage. + pub fn raw(&self) -> &io_uring::IoUring { + &self.ring + } + + /// Push an accept request into the submission queue of the io_uring. + /// + /// # Safety + /// + /// See the safety section of the `IoUring`. + pub unsafe fn accept( + &self, + fd: Fd, + addr: *mut libc::sockaddr, + addrlen: *mut libc::socklen_t, + flags: u32, + callback: impl FnOnce(i32) + Send + 'static, + ) -> IoHandle { + let entry = opcode::Accept::new(fd, addr, addrlen) + .flags(flags as i32) + .build(); + self.op_fetch_add(fd.0 as usize, 1); + self.push_entry(entry, callback) + } + + /// Push a connect request into the submission queue of the io_uring. + /// + /// # Safety + /// + /// See the safety section of the `IoUring`. + pub unsafe fn connect( + &self, + fd: Fd, + addr: *const libc::sockaddr, + addrlen: libc::socklen_t, + callback: impl FnOnce(i32) + Send + 'static, + ) -> IoHandle { + let entry = opcode::Connect::new(fd, addr, addrlen).build(); + self.op_fetch_add(fd.0 as usize, 1); + self.push_entry(entry, callback) + } + + /// Push a poll request into the submission queue of the io_uring. + /// + /// # Safety + /// + /// See the safety section of the `IoUring`. + pub unsafe fn poll( + &self, + fd: Fd, + flags: u32, + callback: impl FnOnce(i32) + Send + 'static, + ) -> IoHandle { + let entry = opcode::PollAdd::new(fd, flags).build(); + self.op_fetch_add(fd.0 as usize, 1); + self.push_entry(entry, callback) + } + + /// Push a read request into the submission queue of the io_uring. + /// + /// # Safety + /// + /// See the safety section of the `IoUring`. + pub unsafe fn read( + &self, + fd: Fd, + buf: *mut u8, + len: u32, + offset: libc::off_t, + flags: types::RwFlags, + callback: impl FnOnce(i32) + Send + 'static, + ) -> IoHandle { + let entry = opcode::Read::new(fd, buf, len) + .offset(offset) + .rw_flags(flags) + .build(); + self.op_fetch_add(fd.0 as usize, 1); + self.push_entry(entry, callback) + } + + /// Push a write request into the submission queue of the io_uring. + /// + /// # Safety + /// + /// See the safety section of the `IoUring`. + pub unsafe fn write( + &self, + fd: Fd, + buf: *const u8, + len: u32, + offset: libc::off_t, + flags: types::RwFlags, + callback: impl FnOnce(i32) + Send + 'static, + ) -> IoHandle { + let entry = opcode::Write::new(fd, buf, len) + .offset(offset) + .rw_flags(flags) + .build(); + self.op_fetch_add(fd.0 as usize, 1); + self.push_entry(entry, callback) + } + + /// Push a readv request into the submission queue of the io_uring. + /// + /// # Safety + /// + /// See the safety section of the `IoUring`. + pub unsafe fn readv( + &self, + fd: Fd, + iovec: *const libc::iovec, + len: u32, + offset: libc::off_t, + flags: types::RwFlags, + callback: impl FnOnce(i32) + Send + 'static, + ) -> IoHandle { + let entry = opcode::Readv::new(fd, iovec, len) + .offset(offset) + .rw_flags(flags) + .build(); + self.op_fetch_add(fd.0 as usize, 1); + self.push_entry(entry, callback) + } + + /// Push a writev request into the submission queue of the io_uring. + /// + /// # Safety + /// + /// See the safety section of the `IoUring`. + pub unsafe fn writev( + &self, + fd: Fd, + iovec: *const libc::iovec, + len: u32, + offset: libc::off_t, + flags: types::RwFlags, + callback: impl FnOnce(i32) + Send + 'static, + ) -> IoHandle { + let entry = opcode::Writev::new(fd, iovec, len) + .offset(offset) + .rw_flags(flags) + .build(); + self.op_fetch_add(fd.0 as usize, 1); + self.push_entry(entry, callback) + } + + /// Push a recvmsg request into the submission queue of the io_uring. + /// + /// # Safety + /// + /// See the safety section of the `IoUring`. + pub unsafe fn recvmsg( + &self, + fd: Fd, + msg: *mut libc::msghdr, + flags: u32, + callback: impl FnOnce(i32) + Send + 'static, + ) -> IoHandle { + let entry = opcode::RecvMsg::new(fd, msg).flags(flags).build(); + self.op_fetch_add(fd.0 as usize, 1); + self.push_entry(entry, callback) + } + + /// Push a sendmsg request into the submission queue of the io_uring. + /// + /// # Safety + /// + /// See the safety section of the `IoUring`. + pub unsafe fn sendmsg( + &self, + fd: Fd, + msg: *const libc::msghdr, + flags: u32, + callback: impl FnOnce(i32) + Send + 'static, + ) -> IoHandle { + let entry = opcode::SendMsg::new(fd, msg).flags(flags).build(); + self.op_fetch_add(fd.0 as usize, 1); + self.push_entry(entry, callback) + } + + /// Push a fsync request into the submission queue of the io_uring. + /// + /// # Safety + /// + /// See the safety section of the `IoUring`. + pub unsafe fn fsync( + &self, + fd: Fd, + datasync: bool, + callback: impl FnOnce(i32) + Send + 'static, + ) -> IoHandle { + let entry = if datasync { + opcode::Fsync::new(fd) + .flags(types::FsyncFlags::DATASYNC) + .build() + } else { + opcode::Fsync::new(fd).build() + }; + self.op_fetch_add(fd.0 as usize, 1); + self.push_entry(entry, callback) + } + + /// Push a timeout request into the submission queue of the io_uring. + /// + /// # Safety + /// + /// See the safety section of the `IoUring`. + pub unsafe fn timeout( + &self, + timespec: *const types::Timespec, + count: u32, + flags: types::TimeoutFlags, + callback: impl FnOnce(i32) + Send + 'static, + ) -> IoHandle { + let entry = opcode::Timeout::new(timespec) + .count(count) + .flags(flags) + .build(); + self.push_entry(entry, callback) + } + + /// Poll new I/O completions in the completions queue of io_uring + /// and return the number of I/O completions. + /// + /// Upon receiving completed I/O, the corresponding user-registered callback functions + /// will get invoked and the `IoHandle` (as a `Future`) will become ready. + /// + /// The method guarantees at least the specified number of entries are + /// popped from the completion queue. To do so, it starts by polling the + /// completion queue for at most the specified number of retries. + /// If the number of completion entries popped so far does not reach the + /// the specified minimum value, then the method shall block + /// until new completions arrive. After getting unblocked, the method + /// repeats polling. + /// + /// If the user does not want to the method to block, set `min_complete` + /// to 0. If the user does not want to the method to busy polling, set + /// `polling_retries` to 0. + pub fn poll_completions(&self, min_complete: usize, polling_retries: usize) -> usize { + let mut cq = unsafe { self.ring.completion_shared() }; // Safety: Only polling thread is using the completion queue + let mut nr_complete = 0; + loop { + // Polling for at most a specified number of times + let mut nr_retries = 0; + while nr_retries <= polling_retries { + // completetion queue must be synchoronized when loop for next entry. + cq.sync(); + if let Some(cqe) = cq.next() { + let retval = cqe.result(); + let token_key = cqe.user_data(); + + if token_key != IoUring::CANCEL_TOKEN_KEY { + let io_token = { + let token_idx = token_key as usize; + let mut token_table = self.token_table.lock(); + token_table.remove(token_idx) + }; + + io_token.complete(retval); + nr_complete += 1; + } + } else { + nr_retries += 1; + std::hint::spin_loop(); + } + } + + if nr_complete >= min_complete { + return nr_complete; + } + + // Wait until at least one new completion entry arrives + let _ = self.ring.submit_and_wait(1); + } + } + + /// Wait for at least the specified number of I/O completions. + pub fn wait_completions(&self, min_complete: usize) -> usize { + self.poll_completions(min_complete, 10) + } + + unsafe fn push(&self, entry: SqEntry) { + // Push the entry into the submission queue + // No other `SubmissionQueue`s may exist when calling submission_shared(). Thus must lock here. + // Since the loop below should be very quick, acquire lock here. + let sq_guard = self.sq_lock.lock(); + loop { + if self.ring.submission_shared().push(&entry).is_err() { + if self.ring.enter(1, 1, 0, None).is_err() { + panic!("sq broken"); + } + } else { + break; + } + } + drop(sq_guard); + + // Make sure Linux is aware of the new submission + if let Err(e) = self.ring.submit() { + panic!("submit failed, error: {}", e); + } + } + + // Push a submission entry to io_uring and return a corresponding handle. + // + // Safety. All resources referenced by the entry must be valid before its completion. + unsafe fn push_entry( + &self, + mut entry: SqEntry, + callback: impl FnOnce(i32) + Send + 'static, + ) -> IoHandle { + // Create the user-visible handle that is associated with the submission entry + let io_handle = { + // let mut token_table = self.token_table.lock().unwrap(); + let mut token_table = self.token_table.lock(); + let token_slot = token_table.vacant_entry(); + let token_key = token_slot.key() as u64; + assert!(token_key != IoUring::CANCEL_TOKEN_KEY); + + let token = Arc::new(IoToken::new(callback, token_key)); + token_slot.insert(token.clone()); + let handle = IoHandle::new(token); + + // Associated entry with token, the latter of which is pointed to by handle. + entry = entry.user_data(token_key); + + handle + }; + + self.push(entry); + + io_handle + } + + fn op_fetch_add(&self, fd: usize, val: usize) -> usize { + let fd_map = self.fd_map.upgradeable_read(); + match fd_map.get(&fd) { + Some(ops_num) => ops_num.fetch_add(val, Ordering::Relaxed), + None => { + let mut fd_map = fd_map.upgrade(); + fd_map.insert(fd, AtomicUsize::new(val)); + 0 + } + } + } + + pub fn disattach_fd(&self, fd: usize) -> Option { + let mut fd_map = self.fd_map.write(); + fd_map.remove(&fd) + } + + // Using the sum of the number of attached file descriptors (raw fd) as a measure of task load. + pub fn task_load(&self) -> usize { + let fd_map = self.fd_map.read(); + fd_map + .values() + .fold(0, |acc, val| acc + val.load(Ordering::Relaxed)) + } + + // The number of registered fd in this io_uring instance + pub fn registered_fds(&self) -> usize { + let fd_map = self.fd_map.read(); + fd_map.len() + } + + /// Cancel an ongoing I/O request. + /// + /// # safety + /// + /// The handle must be generated by this IoUring instance. + pub unsafe fn cancel(&self, handle: &IoHandle) { + let target_token_key = match handle.0.transit_to_cancelling() { + Ok(target_token_key) => target_token_key, + Err(_) => { + return; + } + }; + let mut entry = opcode::AsyncCancel::new(target_token_key).build(); + entry = entry.user_data(IoUring::CANCEL_TOKEN_KEY); + + self.push(entry); + } +} + +/// A builder for `IoUring`. +pub struct Builder { + inner: io_uring::Builder, +} + +impl Builder { + /// Creates a `IoUring` builder. + pub fn new() -> Self { + let inner = io_uring::IoUring::builder(); + Self { inner } + } + + /// When this flag is specified, a kernel thread is created to perform submission queue polling. + /// An io_uring instance configured in this way enables an application to issue I/O + /// without ever context switching into the kernel. + pub fn setup_sqpoll(&mut self, idle: u32) -> &mut Self { + self.inner.setup_sqpoll(idle); + self + } + + pub fn setup_attach_wq(&mut self, fd: RawFd) -> &mut Self { + self.inner.setup_attach_wq(fd); + self + } + + /// If this flag is specified, + /// then the poll thread will be bound to the cpu set in the value. + /// This flag is only meaningful when [Builder::setup_sqpoll] is enabled. + pub fn setup_sqpoll_cpu(&mut self, n: u32) -> &mut Self { + self.inner.setup_sqpoll_cpu(n); + self + } + + /// Create the completion queue with struct `io_uring_params.cq_entries` entries. + /// The value must be greater than entries, and may be rounded up to the next power-of-two. + pub fn setup_cqsize(&mut self, n: u32) -> &mut Self { + self.inner.setup_cqsize(n); + self + } + + /// Build a [IoUring]. + #[inline] + pub fn build(&self, entries: u32) -> io::Result { + let io_uring_inner = self.inner.build(entries)?; + let io_uring = IoUring::new(io_uring_inner); + Ok(io_uring) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::fs::File; + use std::io::{IoSlice, IoSliceMut, Write}; + use std::os::unix::io::{AsRawFd, FromRawFd}; + use std::thread; + use std::time::Duration; + use std::time::Instant; + + #[test] + fn test_builder() { + let _io_uring = Builder::new().setup_sqpoll(1000).build(256).unwrap(); + } + + #[test] + fn test_new() { + let _io_uring = IoUring::new(io_uring::IoUring::new(256).unwrap()); + } + + #[test] + fn test_writev_readv() { + let io_uring = IoUring::new(io_uring::IoUring::new(256).unwrap()); + + let fd = tempfile::tempfile().unwrap(); + let fd = Fd(fd.as_raw_fd()); + + let text = b"1234"; + let text2 = b"5678"; + let mut output = vec![0; text.len()]; + let mut output2 = vec![0; text2.len()]; + + let w_iovecs = vec![IoSlice::new(text), IoSlice::new(text2)]; + let r_iovecs = vec![IoSliceMut::new(&mut output), IoSliceMut::new(&mut output2)]; + + let complete_fn = move |_retval: i32| {}; + let handle = unsafe { + io_uring.writev( + fd, + w_iovecs.as_ptr().cast(), + w_iovecs.len() as _, + 0, + 0, + complete_fn, + ) + }; + + io_uring.wait_completions(1); + let retval = handle.retval().unwrap(); + assert_eq!(retval, (text.len() + text2.len()) as i32); + + let complete_fn = move |_retval: i32| {}; + let handle = unsafe { + io_uring.readv( + fd, + r_iovecs.as_ptr().cast(), + r_iovecs.len() as _, + 0, + 0, + complete_fn, + ) + }; + + io_uring.wait_completions(1); + let retval = handle.retval().unwrap(); + assert_eq!(retval, (text.len() + text2.len()) as i32); + assert_eq!(&output, text); + assert_eq!(&output2, text2); + } + + #[test] + fn test_poll() { + let mut fd = unsafe { + let fd = libc::eventfd(0, libc::EFD_CLOEXEC); + assert!(fd != -1); + File::from_raw_fd(fd) + }; + + let io_uring = IoUring::new(io_uring::IoUring::new(256).unwrap()); + + let complete_fn = move |_retval: i32| {}; + let handle = unsafe { io_uring.poll(Fd(fd.as_raw_fd()), libc::POLLIN as _, complete_fn) }; + + thread::sleep(Duration::from_millis(100)); + assert_eq!(io_uring.poll_completions(0, 10000), 0); + + fd.write(&0x1u64.to_ne_bytes()).unwrap(); + io_uring.wait_completions(1); + assert_eq!(handle.retval().unwrap(), 1); + } + + #[test] + fn test_cancel_poll() { + let mut fd = unsafe { + let fd = libc::eventfd(0, libc::EFD_CLOEXEC); + assert!(fd != -1); + File::from_raw_fd(fd) + }; + + let io_uring = IoUring::new(io_uring::IoUring::new(256).unwrap()); + + let complete_fn = move |_retval: i32| {}; + let poll_handle = + unsafe { io_uring.poll(Fd(fd.as_raw_fd()), libc::POLLIN as _, complete_fn) }; + + unsafe { + io_uring.cancel(&poll_handle); + } + + thread::sleep(Duration::from_millis(100)); + + fd.write(&0x1u64.to_ne_bytes()).unwrap(); + io_uring.wait_completions(1); + + assert_eq!(poll_handle.retval().unwrap(), -libc::ECANCELED); + } + + #[test] + fn test_cancel_poll_failed() { + let mut fd = unsafe { + let fd = libc::eventfd(0, libc::EFD_CLOEXEC); + assert!(fd != -1); + File::from_raw_fd(fd) + }; + + let io_uring = IoUring::new(io_uring::IoUring::new(256).unwrap()); + + let complete_fn = move |_retval: i32| {}; + let poll_handle = + unsafe { io_uring.poll(Fd(fd.as_raw_fd()), libc::POLLIN as _, complete_fn) }; + + fd.write(&0x1u64.to_ne_bytes()).unwrap(); + io_uring.wait_completions(1); + + unsafe { + io_uring.cancel(&poll_handle); + } + + thread::sleep(Duration::from_millis(100)); + assert_eq!(poll_handle.retval().unwrap(), 1); + } + + #[test] + fn test_timeout() { + let io_uring = IoUring::new(io_uring::IoUring::new(256).unwrap()); + + let start = Instant::now(); + let secs = 1; + let timespec = types::Timespec::new().sec(secs).nsec(0); + let complete_fn = move |_retval: i32| {}; + + let handle = unsafe { + io_uring.timeout( + ×pec as *const _, + 0, + types::TimeoutFlags::empty(), + complete_fn, + ) + }; + io_uring.wait_completions(1); + + assert_eq!(handle.retval().unwrap(), -libc::ETIME); + assert_eq!(start.elapsed().as_secs(), secs as u64); + } + + #[test] + fn test_cancel_timeout() { + let io_uring = IoUring::new(io_uring::IoUring::new(256).unwrap()); + + let start = Instant::now(); + let secs = 1; + let timespec = types::Timespec::new().sec(secs).nsec(0); + + let complete_fn = move |_retval: i32| {}; + + let handle = unsafe { + io_uring.timeout( + ×pec as *const _, + 0, + types::TimeoutFlags::empty(), + complete_fn, + ) + }; + + unsafe { + io_uring.cancel(&handle); + } + + io_uring.wait_completions(1); + + assert_eq!(handle.retval().unwrap(), -libc::ECANCELED); + assert_eq!(start.elapsed().as_secs(), 0); + } +} diff --git a/src/libos/crates/keyable-arc/Cargo.toml b/src/libos/crates/keyable-arc/Cargo.toml new file mode 100644 index 00000000..b574634d --- /dev/null +++ b/src/libos/crates/keyable-arc/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "keyable-arc" +version = "0.1.0" +authors = ["Tate, Hongliang Tian "] +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] + diff --git a/src/libos/crates/keyable-arc/src/lib.rs b/src/libos/crates/keyable-arc/src/lib.rs new file mode 100644 index 00000000..8d0c0ac2 --- /dev/null +++ b/src/libos/crates/keyable-arc/src/lib.rs @@ -0,0 +1,348 @@ +//! Same as the standard `Arc`, except that it can be used as the key type of a hash table. +//! +//! # Motivation +//! +//! A type `K` is _keyable_ if it can be used as the key type for a hash map. Specifically, +//! according to the document of `std::collections::HashMap`, the type `K` must satisfy +//! the following properties. +//! +//! 1. It implements the `Eq` and `Hash` traits. +//! 2. The two values of `k1` and `k2` of type `K` equal to each other, +//! if and only if their hash values equal to each other. +//! 3. The hashes of a value of `k` of type `K` cannot change while it +//! is in a map. +//! +//! Sometimes we want to use `Arc` as the key type for a hash map but cannot do so +//! since `T` does not satisfy the properties above. For example, a lot of types +//! do not or cannot implemennt the `Eq` trait. This is when `KeyableArc` can come +//! to your aid. +//! +//! # Overview +//! +//! For any type `T`, `KeyableArc` satisfies all the properties to be keyable. +//! This can be achieved easily and efficiently as we can simply use the address +//! of the data (of `T` type) of a `KeyableArc` object in the heap to determine the +//! equality and hash of the `KeyableArc` object. As the address won't change for +//! an immutable `KeyableArc` object, the hash and equality also stay the same. +//! +//! This crate is `#[no_std]` compatible, but requires the `alloc` crate. +//! +//! # Usage +//! +//! Here is a basic example to how that `KeyableArc` is keyable even when `T` +//! is not. +//! +//! ```rust +//! use std::collections::HashMap; +//! use std::sync::Arc; +//! use keyable_arc::KeyableArc; +//! +//! struct Dummy; // Does not implement Eq and Hash +//! +//! let map: HashMap, String> = HashMap::new(); +//! ``` +//! +//! `KeyableArc` is a reference counter-based smart pointer, just like `Arc`. +//! So you can use `KeyableArc` the same way you would use `Arc`. +//! +//! ```rust +//! use std::sync::atomic::{AtomicU64, Ordering::Relaxed}; +//! use keyable_arc::KeyableArc; +//! +//! let key_arc0 = KeyableArc::new(AtomicU64::new(0)); +//! let key_arc1 = key_arc0.clone(); +//! assert!(key_arc0.load(Relaxed) == 0 && key_arc1.load(Relaxed) == 0); +//! +//! key_arc0.fetch_add(1, Relaxed); +//! assert!(key_arc0.load(Relaxed) == 1 && key_arc1.load(Relaxed) == 1); +//! ``` +//! +//! # Differences from `Arc` +//! +//! Notice how `KeyableArc` differs from standard smart pointers in determining equality? +//! Two `KeyableArc` objects are considered different even when their data have the same +//! value. +//! +//! ```rust +//! use keyable_arc::KeyableArc; +//! +//! let key_arc0 = KeyableArc::new(0); +//! let key_arc1 = key_arc0.clone(); +//! assert!(key_arc0 == key_arc1); +//! assert!(*key_arc0 == *key_arc1); +//! +//! let key_arc1 = KeyableArc::new(0); +//! assert!(key_arc0 != key_arc1); +//! assert!(*key_arc0 == *key_arc1); +//! ``` +//! +//! `KeyableArc` is simply a wrapper of `Arc. So converting between them +//! through the `From` and `Into` traits is zero cost. +//! +//! ```rust +//! use std::sync::Arc; +//! use keyable_arc::KeyableArc; +//! +//! let key_arc: KeyableArc = Arc::new(0).into(); +//! let arc: Arc = KeyableArc::new(0).into(); +//! ``` +//! +//! # The weak version +//! +//! `KeyableWeak` is the weak version of `KeyableArc`, just like `Weak` is +//! that of `Arc`. And of course, `KeyableWeak` is also _keyable_ for any +//! type `T`. + +// TODO: Add `KeyableBox` or other keyable versions of smart pointers. +// If this is needed in the future, this crate should be renamed to `keyable`. + +// TODO: Add the missing methods offered by `Arc` or `Weak` but not their +// keyable counterparts. + +#![cfg_attr(not(test), no_std)] +#![feature(coerce_unsized)] +#![feature(unsize)] + +extern crate alloc; + +use alloc::sync::{Arc, Weak}; +use core::borrow::Borrow; +use core::convert::AsRef; +use core::fmt; +use core::hash::{Hash, Hasher}; +use core::marker::Unsize; +use core::ops::{CoerceUnsized, Deref}; + +/// Same as the standard `Arc`, except that it can be used as the key type of a hash table. +#[repr(transparent)] +pub struct KeyableArc(Arc); + +impl KeyableArc { + /// Constructs a new instance of `KeyableArc`. + #[inline] + pub fn new(data: T) -> Self { + Self(Arc::new(data)) + } +} + +impl KeyableArc { + /// Returns a raw pointer to the object `T` pointed to by this `KeyableArc`. + #[inline] + pub fn as_ptr(this: &Self) -> *const T { + Arc::as_ptr(&this.0) + } + + /// Creates a new `KeyableWeak` pointer to this allocation. + pub fn downgrade(this: &Self) -> KeyableWeak { + Arc::downgrade(&this.0).into() + } +} + +impl Deref for KeyableArc { + type Target = T; + + #[inline] + fn deref(&self) -> &T { + &*self.0 + } +} + +impl AsRef for KeyableArc { + #[inline] + fn as_ref(&self) -> &T { + &**self + } +} + +impl Borrow for KeyableArc { + #[inline] + fn borrow(&self) -> &T { + &**self + } +} + +impl From> for KeyableArc { + #[inline] + fn from(arc: Arc) -> Self { + Self(arc) + } +} + +impl Into> for KeyableArc { + #[inline] + fn into(self) -> Arc { + self.0 + } +} + +impl PartialEq for KeyableArc { + fn eq(&self, other: &Self) -> bool { + Arc::as_ptr(&self.0) == Arc::as_ptr(&other.0) + } +} + +impl Eq for KeyableArc {} + +impl Hash for KeyableArc { + fn hash(&self, s: &mut H) { + Arc::as_ptr(&self.0).hash(s) + } +} + +impl Clone for KeyableArc { + fn clone(&self) -> Self { + Self(self.0.clone()) + } +} + +impl fmt::Debug for KeyableArc { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(&**self, f) + } +} + +//========================================================= +// The weak version +//========================================================= + +/// The weak counterpart of `KeyableArc`, similar to `Weak`. +/// +/// `KeyableWeak` is also _keyable_ for any type `T` just like +/// `KeyableArc`. +#[repr(transparent)] +pub struct KeyableWeak(Weak); + +impl KeyableWeak { + /// Constructs a new `KeyableWeak`, without allocating any memory. + /// Calling `upgrade` on the return value always gives `None`. + #[inline] + pub fn new() -> Self { + Self(Weak::new()) + } + + /// Returns a raw pointer to the object `T` pointed to by this `KeyableWeak`. + /// + /// The pointer is valid only if there are some strong references. + /// The pointer may be dangling, unaligned or even null otherwise. + #[inline] + pub fn as_ptr(&self) -> *const T { + self.0.as_ptr() + } +} + +impl KeyableWeak { + /// Attempts to upgrade the Weak pointer to an Arc, + /// delaying dropping of the inner value if successful. + /// + /// Returns None if the inner value has since been dropped. + #[inline] + pub fn upgrade(&self) -> Option> { + self.0.upgrade().map(|arc| arc.into()) + } + + /// Gets the number of strong pointers pointing to this allocation. + #[inline] + pub fn strong_count(&self) -> usize { + self.0.strong_count() + } + + /// Gets the number of weak pointers pointing to this allocation. + #[inline] + pub fn weak_count(&self) -> usize { + self.0.weak_count() + } +} + +impl Hash for KeyableWeak { + fn hash(&self, s: &mut H) { + self.0.as_ptr().hash(s) + } +} + +impl From> for KeyableWeak { + #[inline] + fn from(weak: Weak) -> Self { + Self(weak) + } +} + +impl Into> for KeyableWeak { + #[inline] + fn into(self) -> Weak { + self.0 + } +} + +impl PartialEq for KeyableWeak { + fn eq(&self, other: &Self) -> bool { + self.0.as_ptr() == other.0.as_ptr() + } +} + +impl PartialOrd for KeyableWeak { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Eq for KeyableWeak {} + +impl Ord for KeyableWeak { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + self.0.as_ptr().cmp(&other.0.as_ptr()) + } +} + +impl fmt::Debug for KeyableWeak { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "(KeyableWeak)") + } +} + +// Enabling type coercing, e.g., converting from `KeyableArc` to `KeyableArc`, +// where `T` implements `S`. +impl, U: ?Sized> CoerceUnsized> for KeyableArc {} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn downgrade_and_upgrade() { + let arc = KeyableArc::new(1); + let weak = KeyableArc::downgrade(&arc); + assert!(arc.clone() == weak.upgrade().unwrap()); + assert!(weak == KeyableArc::downgrade(&arc)); + } + + #[test] + fn debug_format() { + println!("{:?}", KeyableArc::new(1u32)); + println!("{:?}", KeyableWeak::::new()); + } + + #[test] + fn use_as_key() { + use std::collections::HashMap; + + let mut map: HashMap, u32> = HashMap::new(); + let key = KeyableArc::new(1); + let val = 1; + map.insert(key.clone(), val); + assert!(map.get(&key) == Some(&val)); + assert!(map.remove(&key) == Some(val)); + assert!(map.keys().count() == 0); + } + + #[test] + fn as_trait_object() { + trait DummyTrait {} + struct DummyStruct; + impl DummyTrait for DummyStruct {} + + let arc_struct = KeyableArc::new(DummyStruct); + let arc_dyn0: KeyableArc = arc_struct.clone(); + let arc_dyn1: KeyableArc = arc_struct.clone(); + assert!(arc_dyn0 == arc_dyn1); + } +} diff --git a/src/libos/crates/object-id/Cargo.toml b/src/libos/crates/object-id/Cargo.toml new file mode 100644 index 00000000..53d9abb2 --- /dev/null +++ b/src/libos/crates/object-id/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "object-id" +version = "0.1.0" +authors = ["Tate, Hongliang Tian "] +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/src/libos/crates/object-id/src/lib.rs b/src/libos/crates/object-id/src/lib.rs new file mode 100644 index 00000000..87b84405 --- /dev/null +++ b/src/libos/crates/object-id/src/lib.rs @@ -0,0 +1,62 @@ +//! Assign a unique and immutable ID. +//! +//! Some types do not have a natural implementation for `PartialEq` or `Hash`. +//! In such cases, it can be convenient to assign an unique ID for each instance +//! of such types and use the ID to implement `PartialEq` or `Hash`. +//! +//! An ID have a length of 64-bit. + +#![cfg_attr(not(any(test, doctest)), no_std)] + +use core::sync::atomic::{AtomicU64, Ordering}; + +/// A unique id. +#[derive(PartialEq, Eq, Copy, Clone, Debug, Hash)] +#[repr(transparent)] +pub struct ObjectId(u64); + +impl ObjectId { + /// Create a new unique ID. + pub fn new() -> Self { + static NEXT_ID: AtomicU64 = AtomicU64::new(1); + let id = NEXT_ID.fetch_add(1, Ordering::Relaxed); + + // Make sure that we can detect the overflow of id even in face of + // (extremely) concurrent addition on NEXT_ID. + assert!(id <= u64::max_value() / 2); + + Self(id) + } + + /// Return a special "null" ID. + /// + /// Note that no ID created by `ObjectId::new()` will be equivalent to the + /// null ID. + pub const fn null() -> Self { + Self(0) + } + + /// Get the ID value as `u64`. + pub const fn get(&self) -> u64 { + self.0 + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn unique() { + let id0 = ObjectId::new(); + let id1 = ObjectId::new(); + assert!(id0 != id1); + assert!(id0.get() < id1.get()); + } + + #[test] + fn non_null() { + let id0 = ObjectId::new(); + assert!(id0 != ObjectId::null()); + } +} diff --git a/src/libos/crates/sgx-untrusted-alloc/.gitignore b/src/libos/crates/sgx-untrusted-alloc/.gitignore new file mode 100644 index 00000000..44709884 --- /dev/null +++ b/src/libos/crates/sgx-untrusted-alloc/.gitignore @@ -0,0 +1,2 @@ +target/ +Cargo.lock \ No newline at end of file diff --git a/src/libos/crates/sgx-untrusted-alloc/Cargo.toml b/src/libos/crates/sgx-untrusted-alloc/Cargo.toml new file mode 100644 index 00000000..be17452a --- /dev/null +++ b/src/libos/crates/sgx-untrusted-alloc/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "sgx-untrusted-alloc" +version = "0.1.0" +edition = "2021" + +[features] +default = ["libc"] +sgx = ["sgx_types", "sgx_tstd", "sgx_trts", "sgx_libc"] + +[dependencies] +cfg-if = "1.0.0" +libc = { version = "0.2", optional = true } +lazy_static = { version = "1.4.0", features = ["spin_no_std"] } +intrusive-collections = "0.9" +log = "0.4" +spin = "0.7" +errno = { path = "../errno" } + +sgx_types = { path = "../../../../deps/rust-sgx-sdk/sgx_types", optional = true } +sgx_tstd = { path = "../../../../deps/rust-sgx-sdk/sgx_tstd", optional = true, features = ["backtrace"] } +sgx_trts = { path = "../../../../deps/rust-sgx-sdk/sgx_trts", optional = true } +sgx_libc = { path = "../../../../deps/rust-sgx-sdk/sgx_libc", optional = true } diff --git a/src/libos/crates/sgx-untrusted-alloc/README.md b/src/libos/crates/sgx-untrusted-alloc/README.md new file mode 100644 index 00000000..afb54ce8 --- /dev/null +++ b/src/libos/crates/sgx-untrusted-alloc/README.md @@ -0,0 +1,10 @@ +# sgx-untrusted-alloc + +untrusted memory allocator for SGX. + +## Usage +To use sgx-untrusted-alloc, place the following line under the `[dependencies]` section in your `Cargo.toml`: + +``` +sgx-untrusted-alloc = { path = "your_path/sgx-untrusted-alloc" } +``` \ No newline at end of file diff --git a/src/libos/crates/sgx-untrusted-alloc/src/box_.rs b/src/libos/crates/sgx-untrusted-alloc/src/box_.rs new file mode 100644 index 00000000..fe8aeabd --- /dev/null +++ b/src/libos/crates/sgx-untrusted-alloc/src/box_.rs @@ -0,0 +1,190 @@ +use crate::untrusted_allocator::Allocator; +use std::fmt::Debug; +use std::mem::{align_of, size_of}; +use std::ops::{Deref, DerefMut}; +use std::ptr::NonNull; + +lazy_static! { + static ref UNTRUSTED_MEM_INSTANCE: Allocator = Allocator::new(); +} + +use crate::MaybeUntrusted; + +/// A memory location on the heap in untrusted memory. +/// +/// `UntrustedBox` Behaves similar to the standard `Box`, except that +/// it requires that the type bound of `T: MaybeUntrusted`. This is a safety +/// measure to avoid potential misuses. +pub struct UntrustedBox { + ptr: NonNull, +} + +impl Debug for UntrustedBox +where + T: ?Sized + Debug, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Untrusted T") + .field("T", unsafe { &self.ptr.as_ref() }) + .finish() + } +} + +impl UntrustedBox { + /// Creates a value of `T` on the heap in untrusted memory. + pub fn new(val: T) -> Self { + let mut new_self = Self::new_uninit(); + *new_self = val; + new_self + } + + /// Creates an _uninitialized_ value of `T` on the heap in untrusted memory. + pub fn new_uninit() -> Self { + let ptr = { + let raw_ptr = unsafe { + UNTRUSTED_MEM_INSTANCE + .alloc(size_of::(), None) + .expect("memory allocation failure") + } as *mut T; + assert!(raw_ptr != std::ptr::null_mut()); + assert!((raw_ptr as usize) % align_of::() == 0); + NonNull::new(raw_ptr).unwrap() + }; + Self { ptr } + } +} + +impl UntrustedBox<[T]> { + /// Creates a slice of `T` on the heap in untrusted memory. + /// + /// Note that the pointer and length of the slice is still kept in trusted memory; + /// only the pointer refers to untrusted memory. Thus, there is no risk of buffer + /// overflow. + pub fn new_slice(slice: &[T]) -> Self { + let mut uninit_slice = Self::new_uninit_slice(slice.len()); + uninit_slice.copy_from_slice(slice); + uninit_slice + } +} + +impl UntrustedBox<[T]> { + /// Creates an uninitialized slice of `T` on the heap in untrusted memory. + pub fn new_uninit_slice(len: usize) -> Self { + let ptr = { + let total_bytes = size_of::() * len; + let raw_slice_ptr = unsafe { + UNTRUSTED_MEM_INSTANCE + .alloc(total_bytes, None) + .expect("memory allocation failure") + } as *mut T; + assert!(raw_slice_ptr != std::ptr::null_mut()); + assert!((raw_slice_ptr as usize) % align_of::() == 0); + let untrusted_slice = unsafe { std::slice::from_raw_parts_mut(raw_slice_ptr, len) }; + // For DST types like slice, NonNull is now a fat pointer. + NonNull::new(untrusted_slice as _).unwrap() + }; + Self { ptr } + } +} + +impl UntrustedBox { + /// Gets an immutable pointer of the value on the untrusted memory. + pub fn as_ptr(&self) -> *const T { + self.ptr.as_ptr() + } + + /// Gets a mutable pointer of the value on the untrusted memory. + pub fn as_mut_ptr(&self) -> *mut T { + self.ptr.as_ptr() + } +} + +impl Drop for UntrustedBox { + fn drop(&mut self) { + unsafe { + UNTRUSTED_MEM_INSTANCE.free(self.as_mut_ptr() as *mut u8); + } + } +} + +impl Deref for UntrustedBox { + type Target = T; + + fn deref(&self) -> &T { + unsafe { self.ptr.as_ref() } + } +} + +impl DerefMut for UntrustedBox { + fn deref_mut(&mut self) -> &mut T { + unsafe { self.ptr.as_mut() } + } +} + +impl Default for UntrustedBox { + fn default() -> Self { + Self::new(T::default()) + } +} + +impl Clone for UntrustedBox { + fn clone(&self) -> Self { + Self::new(self.deref().clone()) + } +} + +unsafe impl Send for UntrustedBox {} +unsafe impl Sync for UntrustedBox {} + +#[cfg(test)] +mod tests { + use super::*; + + struct Point { + x: usize, + y: usize, + } + unsafe impl MaybeUntrusted for Point {} + + #[test] + fn with_i32() { + let mut untrusted_i32 = UntrustedBox::new(0i32); + assert!(*untrusted_i32 == 0); + *untrusted_i32 = 1; + assert!(*untrusted_i32 == 1); + drop(untrusted_i32); + } + + #[test] + fn with_point() { + let mut untrusted_point = UntrustedBox::new(Point { x: 0, y: 0 }); + assert!(untrusted_point.x == 0 && untrusted_point.y == 0); + untrusted_point.x += 10; + untrusted_point.y += 20; + assert!(untrusted_point.x == 10 && untrusted_point.y == 20); + drop(untrusted_point); + } + + #[test] + fn with_array() { + let mut untrusted_array = UntrustedBox::new([0u8, 1, 2, 3]); + untrusted_array + .iter() + .enumerate() + .for_each(|(pos, i)| assert!(pos as u8 == *i)); + + for i in untrusted_array.iter_mut() { + *i = 0; + } + untrusted_array.iter().for_each(|i| assert!(*i == 0)); + } + + #[test] + fn with_slice() { + let len = 4; + let mut untrusted_slice: UntrustedBox<[i32]> = UntrustedBox::new_uninit_slice(len); + assert!(untrusted_slice.len() == len); + untrusted_slice[1] = 123; + assert!(untrusted_slice[1] == 123); + } +} diff --git a/src/libos/crates/sgx-untrusted-alloc/src/lib.rs b/src/libos/crates/sgx-untrusted-alloc/src/lib.rs new file mode 100644 index 00000000..a93d922a --- /dev/null +++ b/src/libos/crates/sgx-untrusted-alloc/src/lib.rs @@ -0,0 +1,95 @@ +//! Allocation and access of _untrusted_ memory in a _safe_ way. +//! +//! # Usage +//! +//! ## Basics +//! +//! Suppose you have a data structure named `AcceptReq` +//! ```rust +//! struct AcceptReq { +//! addr: libc::sockaddr_storage, +//! addr_len: libc::socklen_t, +//! } +//! ``` +//! which is intended to be used as an untrusted buffer shared +//! with the host OS to pass arguments of the accept system call. +//! And we assume that this buffer must be present during the lifetime +//! of a listening socket. So it must be allocated on the heap in +//! untrusted memory. So how to do it? +//! +//! With this crate, it takes two steps. +//! +//! 1. Implement the [`MaybeUntrusted`] marker trait for the data structure. +//! +//! ```rust +//! use sgx_untrusted_alloc::MaybeUntrusted; +//! # struct AcceptReq; +//! +//! unsafe impl MaybeUntrusted for AcceptReq { } +//! ``` +//! +//! By implementing this trait, you are claiming: "I am fully aware of the +//! security risks in communicating with the host through _untrusted, +//! shared data structures_. I know that an attacker may peek or tamper with +//! the data structure at any possible timing or in an arbitrary way. +//! I will be very careful. And I am good to go." +//! +//! 2. You can now allocate the data structure in untrusted heap with +//! [`UntrustedBox`], which is similar to the standard `Box` albeit for the +//! untrusted memory. +//! +//! ```rust +//! # use sgx_untrusted_alloc::MaybeUntrusted; +//! # struct AcceptReq; +//! # unsafe impl MaybeUntrusted for AcceptReq { } +//! # +//! use sgx_untrusted_alloc::UntrustedBox; +//! +//! let accept_req: UntrustedBox = UntrustedBox::new_uninit(); +//! ``` +//! +//! Note that the convenient constructor method `UntrustedBox::::new_uninit` +//! creates an _uninitialized_ instance of `T` on untrusted heap. +//! Alternatively, you can create an _initialized_ instance with `UntrustedBox::new`. +//! +//! ## Arrays and slices +//! +//! You can also use `UntrustedBox` to allocate arrays (`[T; N]`) or +//! slices (`[T]`) on untrusted heap as long as the trait bound of `T: MaybeUntrusted` +//! is held. +//! +//! ```rust +//! use sgx_untrusted_alloc::{MaybeUntrusted, UntrustedBox}; +//! +//! let untrusted_array: UntrustedBox<[u8; 4]> = UntrustedBox::new_uninit(); +//! +//! let untrusted_slice: UntrustedBox<[u8]> = UntrustedBox::new_uninit_slice(4); +//! ``` +//! +//! Both `untrusted_array` and `untrusted_slice` above consist of four `u8` integers. + +#![cfg_attr(feature = "sgx", no_std)] +#![feature(linked_list_remove)] + +#[cfg(feature = "sgx")] +extern crate sgx_libc as libc; +#[cfg(feature = "sgx")] +extern crate sgx_tstd as std; + +#[macro_use] +extern crate alloc; +#[macro_use] +extern crate lazy_static; +extern crate intrusive_collections; +#[macro_use] +extern crate log; +extern crate spin; + +mod maybe_untrusted; +pub use maybe_untrusted::MaybeUntrusted; + +mod box_; +pub use box_::UntrustedBox; + +mod prelude; +mod untrusted_allocator; diff --git a/src/libos/crates/sgx-untrusted-alloc/src/maybe_untrusted.rs b/src/libos/crates/sgx-untrusted-alloc/src/maybe_untrusted.rs new file mode 100644 index 00000000..d4424096 --- /dev/null +++ b/src/libos/crates/sgx-untrusted-alloc/src/maybe_untrusted.rs @@ -0,0 +1,130 @@ +use std::cell::Cell; + +/// A marker trait for types that can be used in untrusted memory +/// in a relatively safe way. +/// +/// # Overview +/// +/// Not all types are created equal: some of them are difficult--- +/// if not impossible---to be used safely when residing in untrusted memory. +/// One obvious class is heap-backed Rust containers, like `Vec`. +/// If such types were put into untrusted memory, then an attacker could manipulate +/// the internal pointer. Enums, in general, cannot be used safely in untrusted +/// memory since an attacker could break Rust compiler's assumption on its memory +/// representation, thus causing undefined behaviors. Surprisingly, even a primitive +/// type like `bool` is dangerous to use in untrusted memory. This is also +/// due to Rust compiler's strong assumption on memory representation. +/// +/// Here is a list of core types that implements `MaybeUntrusted`: +/// * Primitive types: `u8`, `u16`, `u32`, `u64`, `usize` and their signed counterparts; +/// * Pointer types: `*const T` and `*mut T`, where `T: MaybeUntrusted`; +/// * Array types: `[T; N]`, where `T: MaybeUntrusted` and 1<= `N` <= 32; +/// * Core types: `Cell`, where `T: MaybeUntrusted`. +/// * Libc types: Most C-style structs defined in libc can have `MaybeUntrusted` implemented. +/// But since there are simply too many of them yet most of them are irrelevant for our usage, +/// it is more reasonable to implement `MaybeUntrusted` on demand. Currently, the list of libc +/// types that have implemented `MaybeUntrusted` is pretty short: +/// * `sockaddr_storage`. +/// +/// For user-defined types, the `MaybeUntrusted` trait should be implemented with discretion. +/// A good rule of thumb is only implementing `MaybeUntrusted` for C-style structs. +/// +/// # Must implement `Sized` +/// +/// The `MaybeUntrusted` trait requires `Sized`. In other words, it wouldn't be safe to +/// store any DST values in untrusted memory since the length part of DST may be tampered by +/// attacker, leading to buffer overflow. +/// +/// # Safe to be uninitialized +/// +/// Another implicit property of `MaybeUntrusted` types is that unlike most Rust types it is +/// perfectly fine to instantiate _uninitialized_ values for a `MaybeUntrusted` type. After all, +/// our thread model assumes an attacker that may tamper with the values of `MaybeUntrusted` +/// types in an arbitrary way. The code that handles `MaybeUntrusted` types must be robust +/// enough to deal with any possible values, including uninitialized ones. +/// +/// As a result, `MaybeUntrusted` comes with two convenient constructor methods that creates +/// an instance whose value is uninitialized or zeroed, respectively. +/// +/// # Good to have `Copy` +/// +/// As an effective defense against against TOCTOU attacks, a common practice is to---before +/// actually using a value of `MaybeUntrusted`---first copy the value into trusted memory and +/// validate its value. Thus, most types that implement `MaybeUntrusted` should also implement +/// `Copy`. +use std::mem::MaybeUninit; + +pub unsafe trait MaybeUntrusted: Sized { + fn uninit() -> Self { + unsafe { MaybeUninit::uninit().assume_init() } + } + + fn zeroed() -> Self { + unsafe { MaybeUninit::zeroed().assume_init() } + } +} + +macro_rules! impl_maybe_untrusted { + ($($type:ty),*) => { + $( + unsafe impl MaybeUntrusted for $type {} + )* + }; +} + +impl_maybe_untrusted! { + // Primitive types + u8, + u16, + u32, + u64, + usize, + i8, + i16, + i32, + i64, + isize, + + // Libc types + libc::sockaddr_storage, + libc::iovec +} + +unsafe impl MaybeUntrusted for *const T {} + +unsafe impl MaybeUntrusted for *mut T {} + +unsafe impl MaybeUntrusted for [T; 1] {} +unsafe impl MaybeUntrusted for [T; 2] {} +unsafe impl MaybeUntrusted for [T; 3] {} +unsafe impl MaybeUntrusted for [T; 4] {} +unsafe impl MaybeUntrusted for [T; 5] {} +unsafe impl MaybeUntrusted for [T; 6] {} +unsafe impl MaybeUntrusted for [T; 7] {} +unsafe impl MaybeUntrusted for [T; 8] {} +unsafe impl MaybeUntrusted for [T; 9] {} +unsafe impl MaybeUntrusted for [T; 10] {} +unsafe impl MaybeUntrusted for [T; 11] {} +unsafe impl MaybeUntrusted for [T; 12] {} +unsafe impl MaybeUntrusted for [T; 13] {} +unsafe impl MaybeUntrusted for [T; 14] {} +unsafe impl MaybeUntrusted for [T; 15] {} +unsafe impl MaybeUntrusted for [T; 16] {} +unsafe impl MaybeUntrusted for [T; 17] {} +unsafe impl MaybeUntrusted for [T; 18] {} +unsafe impl MaybeUntrusted for [T; 19] {} +unsafe impl MaybeUntrusted for [T; 20] {} +unsafe impl MaybeUntrusted for [T; 21] {} +unsafe impl MaybeUntrusted for [T; 22] {} +unsafe impl MaybeUntrusted for [T; 23] {} +unsafe impl MaybeUntrusted for [T; 24] {} +unsafe impl MaybeUntrusted for [T; 25] {} +unsafe impl MaybeUntrusted for [T; 26] {} +unsafe impl MaybeUntrusted for [T; 27] {} +unsafe impl MaybeUntrusted for [T; 28] {} +unsafe impl MaybeUntrusted for [T; 29] {} +unsafe impl MaybeUntrusted for [T; 30] {} +unsafe impl MaybeUntrusted for [T; 31] {} +unsafe impl MaybeUntrusted for [T; 32] {} + +unsafe impl MaybeUntrusted for Cell {} diff --git a/src/libos/crates/sgx-untrusted-alloc/src/prelude.rs b/src/libos/crates/sgx-untrusted-alloc/src/prelude.rs new file mode 100644 index 00000000..affdf260 --- /dev/null +++ b/src/libos/crates/sgx-untrusted-alloc/src/prelude.rs @@ -0,0 +1,5 @@ +// Convenient reexports for internal uses. +pub(crate) use errno::prelude::*; +pub(crate) use std::boxed::Box; +pub(crate) use std::fmt; +pub(crate) use std::vec::Vec; diff --git a/src/libos/crates/sgx-untrusted-alloc/src/untrusted_allocator/free_space_manager.rs b/src/libos/crates/sgx-untrusted-alloc/src/untrusted_allocator/free_space_manager.rs new file mode 100644 index 00000000..07857fca --- /dev/null +++ b/src/libos/crates/sgx-untrusted-alloc/src/untrusted_allocator/free_space_manager.rs @@ -0,0 +1,163 @@ +// Implements free space management for memory. +// Currently only use simple vector as the base structure. +// +// Basically use address-ordered first fit to find free ranges. +use super::*; +use std::cmp::Ordering; + +static INITIAL_SIZE: usize = 100; + +#[derive(Debug, Default)] +pub struct VMFreeSpaceManager { + free_manager: Vec, // Address-ordered first fit +} + +impl VMFreeSpaceManager { + pub fn new(initial_free_range: VMRange) -> Self { + let mut free_manager = Vec::with_capacity(INITIAL_SIZE); + free_manager.push(initial_free_range); + + VMFreeSpaceManager { + free_manager: free_manager, + } + } + + pub fn free_size(&self) -> usize { + self.free_manager + .iter() + .fold(0, |acc, free_range| acc + free_range.size()) + } + + pub fn find_free_range_internal(&mut self, size: usize, align: usize) -> Result { + // Record the minimal free range that satisfies the constraints + let mut result_free_range: Option = None; + let mut result_idx: Option = None; + let free_list = &self.free_manager; + + for (idx, free_range) in free_list.iter().enumerate() { + let free_range = { + if free_range.size() < size { + continue; + } + + // Check alignment + let start = align_up(free_range.start(), align); + let end = start + size; + if free_range.end() < end { + continue; + } + unsafe { VMRange::from_unchecked(start, end) } + }; + + result_free_range = Some(free_range); + result_idx = Some(idx); + break; + } + + if result_free_range.is_none() { + return_errno!(ENOMEM, "not enough memory"); + } + + let index = result_idx.unwrap(); + let result_free_range = result_free_range.unwrap(); + + self.free_list_update_range(index, result_free_range); + return Ok(result_free_range); + } + + fn free_list_update_range(&mut self, index: usize, range: VMRange) { + let free_list = &mut self.free_manager; + let ranges_after_subtraction = free_list[index].subtract(&range); + debug_assert!(ranges_after_subtraction.len() <= 2); + if ranges_after_subtraction.len() == 0 { + free_list.remove(index); + return; + } + free_list[index] = ranges_after_subtraction[0]; + if ranges_after_subtraction.len() == 2 { + free_list.insert(index + 1, ranges_after_subtraction[1]); + } + } + + pub fn add_range_back_to_free_manager(&mut self, dirty_range: &VMRange) -> Result<()> { + let free_list = &mut self.free_manager; + + // If the free list is empty, insert the dirty range and it's done. + if free_list.is_empty() { + free_list.push(*dirty_range); + return Ok(()); + } + + let dirty_range_start = dirty_range.start(); + let dirty_range_end = dirty_range.end(); + + // If the dirty range is before the first free range or after the last free range + let head_range = &mut free_list[0]; + match dirty_range_end.cmp(&head_range.start()) { + Ordering::Equal => { + head_range.set_start(dirty_range_start); + return Ok(()); + } + Ordering::Less => { + free_list.insert(0, *dirty_range); + return Ok(()); + } + _ => (), + } + + let tail_range = free_list.last_mut().unwrap(); + match dirty_range_start.cmp(&tail_range.end()) { + Ordering::Equal => { + tail_range.set_end(dirty_range_end); + return Ok(()); + } + Ordering::Greater => { + free_list.push(*dirty_range); + return Ok(()); + } + _ => (), + } + + // The dirty range must be between some two ranges. + debug_assert!(free_list.len() >= 2); + let mut idx = 0; + + while idx < free_list.len() - 1 { + let left_range = free_list[idx]; + let right_range = free_list[idx + 1]; + + if left_range.end() <= dirty_range_start && dirty_range_end <= right_range.start() { + match ( + dirty_range.is_contiguous_with(&left_range), + dirty_range.is_contiguous_with(&right_range), + ) { + (true, true) => { + let left_range = &mut free_list[idx]; + left_range.set_end(right_range.end()); + free_list.remove(idx + 1); + } + (true, false) => { + let left_range = &mut free_list[idx]; + left_range.set_end(dirty_range_end); + } + (false, true) => { + let right_range = &mut free_list[idx + 1]; + right_range.set_start(dirty_range_start); + } + (false, false) => { + free_list.insert(idx + 1, *dirty_range); + } + } + break; + } + idx += 1; + } + return Ok(()); + } + + pub fn is_free_range(&self, request_range: &VMRange) -> bool { + self.free_manager + .iter() + .any(|free_range| free_range.is_superset_of(request_range)) + } +} diff --git a/src/libos/crates/sgx-untrusted-alloc/src/untrusted_allocator/mod.rs b/src/libos/crates/sgx-untrusted-alloc/src/untrusted_allocator/mod.rs new file mode 100644 index 00000000..984f8467 --- /dev/null +++ b/src/libos/crates/sgx-untrusted-alloc/src/untrusted_allocator/mod.rs @@ -0,0 +1,111 @@ +use super::*; + +use crate::prelude::*; + +mod free_space_manager; +mod vm_area; +mod vm_chunk_manager; +mod vm_range; +mod vm_util; + +const PAGE_SIZE: usize = 4096; + +// The address of a block returned by malloc or realloc in GNU systems is always a multiple of eight (or sixteen on 64-bit systems). +const DEFAULT_ALIGNMENT: usize = 16; + +const DEFAULT_CHUNK_SIZE: usize = 16 * 1024 * 1024; // 16MB + +use vm_range::*; +use vm_util::*; + +use libc::{MAP_ANONYMOUS, MAP_PRIVATE, PROT_READ, PROT_WRITE}; +use spin::Mutex; +use std::collections::LinkedList; +use vm_chunk_manager::ChunkManager; + +pub struct Allocator { + inner: Mutex>, // Use linked list for fast insert/delete. +} + +impl Allocator { + // Initiate a untrusted memory allocator with default size. + pub fn new() -> Self { + let total_bytes = DEFAULT_CHUNK_SIZE; + + let inner = { + let mut new_list = LinkedList::new(); + let chunk_manager = ChunkManager::new(total_bytes) + .expect("Creating untrusted allocator instance failure"); + new_list.push_back(chunk_manager); + Mutex::new(new_list) + }; + + Self { inner } + } + + // Allocate a block of memory and return the start address in c style. + // Use exactly like malloc from libc. + pub unsafe fn alloc(&self, size: usize, align: Option) -> Result<*mut u8> { + let align = align.unwrap_or(DEFAULT_ALIGNMENT); + + // find a free range in the chunk list + for chunk_manager in self.inner.lock().iter_mut() { + if size > *chunk_manager.free_size() { + continue; + } + + if let Ok(addr) = chunk_manager.alloc(size, align) { + return Ok(addr as *mut u8); + } + } + + // if no free range found, create a new chunk and allocate from it. + let start_addr = self.alloc_from_new_chunk(size, align)?; + Ok(start_addr as *mut u8) + } + + // Free the memory block with specified start address + // For memory allocated with alloc, free must be called. + // Use exactly like free from libc. + pub unsafe fn free(&self, addr: *mut u8) { + let mut chunk_list = self.inner.lock(); + let (idx, chunk) = chunk_list + .iter_mut() + .enumerate() + .find(|(_, chunk)| chunk.contains(addr as usize)) + .expect("free failure"); + + chunk.free(addr as usize).expect("free failure"); + + // Keep at most one empty chunk in the list. And free other empty chunks: + // If the chunk is empty, and all other chunks are in use, push the chunk to the front of the list. + // If the chunk is empty and the front chunk is also empty, just remove this chunk. + // If this is the last chunk, just keep it. + if chunk.is_empty() && chunk_list.len() > 1 { + let empty_chunk = chunk_list.remove(idx); + if !chunk_list.front().unwrap().is_empty() { + chunk_list.push_front(empty_chunk); + } + } + } + + fn alloc_from_new_chunk(&self, size: usize, align: usize) -> Result { + let total_bytes = size.max(DEFAULT_CHUNK_SIZE); + + let mut new_chunk = ChunkManager::new(total_bytes)?; + let ret_addr = new_chunk.alloc(size, align)?; + + self.inner.lock().push_front(new_chunk); // Add chunk to the list head to be iterated earlier. + Ok(ret_addr) + } +} + +impl Drop for Allocator { + fn drop(&mut self) { + debug!("[untrusted alloc] Drop allocator"); + self.inner.lock().iter().for_each(|chunk| { + assert!(chunk.is_empty() == true); + assert!(chunk.is_free_range(chunk.range())); + }); + } +} diff --git a/src/libos/crates/sgx-untrusted-alloc/src/untrusted_allocator/vm_area.rs b/src/libos/crates/sgx-untrusted-alloc/src/untrusted_allocator/vm_area.rs new file mode 100644 index 00000000..c887b391 --- /dev/null +++ b/src/libos/crates/sgx-untrusted-alloc/src/untrusted_allocator/vm_area.rs @@ -0,0 +1,63 @@ +use super::vm_range::VMRange; +use super::*; +use std::ops::Deref; + +use intrusive_collections::rbtree::Link; +use intrusive_collections::{intrusive_adapter, KeyAdapter}; + +#[derive(Clone, Debug, Default)] +pub struct VMArea { + range: VMRange, +} + +impl VMArea { + pub fn new(range: VMRange) -> Self { + Self { range } + } + + pub fn range(&self) -> &VMRange { + &self.range + } +} + +impl Deref for VMArea { + type Target = VMRange; + + fn deref(&self) -> &Self::Target { + &self.range + } +} + +#[derive(Clone)] +pub struct VMAObj { + link: Link, + vma: VMArea, +} + +impl fmt::Debug for VMAObj { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", self.vma) + } +} + +// key adapter for RBTree which is sorted by the start of vma ranges +intrusive_adapter!(pub VMAAdapter = Box: VMAObj { link : Link }); +impl<'a> KeyAdapter<'a> for VMAAdapter { + type Key = usize; + fn get_key(&self, vma_obj: &'a VMAObj) -> usize { + vma_obj.vma.range().start() + } +} + +impl VMAObj { + pub fn new_vma_obj(vma: VMArea) -> Box { + Box::new(Self { + link: Link::new(), + vma, + }) + } + + pub fn vma(&self) -> &VMArea { + &self.vma + } +} diff --git a/src/libos/crates/sgx-untrusted-alloc/src/untrusted_allocator/vm_chunk_manager.rs b/src/libos/crates/sgx-untrusted-alloc/src/untrusted_allocator/vm_chunk_manager.rs new file mode 100644 index 00000000..36bb2ab8 --- /dev/null +++ b/src/libos/crates/sgx-untrusted-alloc/src/untrusted_allocator/vm_chunk_manager.rs @@ -0,0 +1,158 @@ +use super::*; + +use super::free_space_manager::VMFreeSpaceManager as FreeRangeManager; +use super::vm_area::*; + +use intrusive_collections::rbtree::RBTree; +use intrusive_collections::Bound; + +use libc::c_void; +cfg_if::cfg_if! { + if #[cfg(feature = "sgx")] { + use libc::ocall::{mmap, munmap}; + } else { + use libc::{mmap, munmap}; + } +} + +/// Memory chunk manager. +/// +/// Chunk is the memory unit for the allocator. +/// ChunkManager is implemented basically with two data structures: a red-black tree to track vmas in use and a FreeRangeManager to track +/// ranges which are free. + +#[derive(Debug, Default)] +pub struct ChunkManager { + range: VMRange, + free_size: usize, + vmas: RBTree, + free_manager: FreeRangeManager, +} + +#[allow(dead_code)] +impl ChunkManager { + pub fn new(total_size: usize) -> Result { + let start_address = { + let addr = unsafe { + mmap( + 0 as *mut _, + total_size, + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, + 0, + 0, + ) + }; + + if addr == libc::MAP_FAILED { + return_errno!(ENOMEM, "allocate new chunk failed"); + } + + let addr = addr as usize; + assert!(addr.checked_add(total_size).is_some()); + addr + }; + + let range = VMRange::new(start_address, start_address + total_size)?; + let vmas = RBTree::new(VMAAdapter::new()); + debug!( + "[untrusted alloc] create a new mem chunk, range = {:?}", + range + ); + Ok(ChunkManager { + range, + free_size: range.size(), + vmas, + free_manager: FreeRangeManager::new(range.clone()), + }) + } + + pub fn range(&self) -> &VMRange { + &self.range + } + + pub fn vmas(&self) -> &RBTree { + &self.vmas + } + + pub fn free_size(&self) -> &usize { + &self.free_size + } + + pub fn is_empty(&self) -> bool { + self.vmas.iter().count() == 0 + } + + pub fn alloc(&mut self, size: usize, align: usize) -> Result { + // Find and allocate a new range for this request + let new_range = self.free_manager.find_free_range_internal(size, align)?; + let new_addr = new_range.start(); + let new_vma = VMArea::new(new_range); + self.free_size -= new_vma.size(); + + debug!("[untrusted alloc] malloc range = {:?}", new_vma.range()); + self.vmas.insert(VMAObj::new_vma_obj(new_vma)); + Ok(new_addr) + } + + pub fn free(&mut self, addr: usize) -> Result<()> { + if addr == 0 { + return Ok(()); + } + + let mut vmas_cursor = self.vmas.find_mut(&addr); + if vmas_cursor.is_null() { + return_errno!(EINVAL, "no vma related was found"); + } + + let vma_obj = vmas_cursor.remove().unwrap(); + let vma = vma_obj.vma(); + debug!("[untrusted alloc] free range = {:?}", vma.range()); + self.free_manager + .add_range_back_to_free_manager(vma.range())?; + self.free_size += vma.size(); + Ok(()) + } + + pub fn find_used_mem_region(&self, addr: usize) -> Result { + let vma = self.vmas.upper_bound(Bound::Included(&addr)); + if vma.is_null() { + return_errno!(ESRCH, "no mmap regions that contains the address"); + } + let vma = vma.get().unwrap().vma(); + if !vma.contains(addr) { + return_errno!(ESRCH, "no mmap regions that contains the address"); + } + + return Ok(vma.range().clone()); + } + + pub fn usage_percentage(&self) -> f32 { + let total_size = self.range.size(); + let mut used_size = 0; + self.vmas + .iter() + .for_each(|vma_obj| used_size += vma_obj.vma().size()); + + return used_size as f32 / total_size as f32; + } + + // Returns whether the requested range is free + pub fn is_free_range(&self, request_range: &VMRange) -> bool { + self.free_manager.is_free_range(request_range) + } + + pub fn contains(&self, addr: usize) -> bool { + self.range.contains(addr) + } +} + +impl Drop for ChunkManager { + fn drop(&mut self) { + debug_assert!(self.is_empty() == true); + debug_assert!(self.free_size == self.range.size()); + debug_assert!(self.free_manager.free_size() == self.range.size()); + let ret = unsafe { munmap(self.range().start as *mut c_void, self.range().size()) }; + assert!(ret == 0); + } +} diff --git a/src/libos/crates/sgx-untrusted-alloc/src/untrusted_allocator/vm_range.rs b/src/libos/crates/sgx-untrusted-alloc/src/untrusted_allocator/vm_range.rs new file mode 100644 index 00000000..fbdb3df5 --- /dev/null +++ b/src/libos/crates/sgx-untrusted-alloc/src/untrusted_allocator/vm_range.rs @@ -0,0 +1,159 @@ +use super::*; + +#[derive(Clone, Copy, Default, Eq, PartialEq, Hash)] +pub struct VMRange { + pub(super) start: usize, + pub(super) end: usize, +} + +#[allow(dead_code)] +impl VMRange { + pub fn new(start: usize, end: usize) -> Result { + if start > end { + return_errno!(EINVAL, "invalid start end address"); + } + Ok(VMRange { + start: start, + end: end, + }) + } + + pub fn new_with_size(start: usize, size: usize) -> Result { + Self::new(start, start + size) + } + + pub fn new_empty(start: usize) -> Result { + if start % PAGE_SIZE != 0 { + return_errno!(EINVAL, "invalid start or end"); + } + Ok(VMRange { + start: start, + end: start, + }) + } + + pub unsafe fn from_unchecked(start: usize, end: usize) -> VMRange { + debug_assert!(start <= end); + VMRange { + start: start, + end: end, + } + } + + pub fn start(&self) -> usize { + self.start + } + + pub fn end(&self) -> usize { + self.end + } + + pub fn size(&self) -> usize { + self.end - self.start + } + + pub fn set_start(&mut self, new_start: usize) { + debug_assert!(new_start <= self.end); + self.start = new_start; + } + + pub fn set_end(&mut self, new_end: usize) { + debug_assert!(new_end >= self.start); + self.end = new_end; + } + + pub fn empty(&self) -> bool { + self.start == self.end + } + + pub fn is_superset_of(&self, other: &VMRange) -> bool { + self.start() <= other.start() && other.end() <= self.end() + } + + pub fn contains(&self, addr: usize) -> bool { + self.start() <= addr && addr < self.end() + } + + // Returns whether two ranges have non-empty interesection. + pub fn overlap_with(&self, other: &VMRange) -> bool { + let intersection_start = self.start().max(other.start()); + let intersection_end = self.end().min(other.end()); + intersection_start < intersection_end + } + + pub fn is_contiguous_with(&self, other: &VMRange) -> bool { + self.start == other.end || self.end == other.start + } + + // Returns a set of ranges by subtracting self with the other. + // + // Post-condition: the returned ranges have non-zero sizes. + pub fn subtract(&self, other: &VMRange) -> Vec { + if self.size() == 0 { + return vec![]; + } + + let intersection = match self.intersect(other) { + None => return vec![*self], + Some(intersection) => intersection, + }; + + let self_start = self.start(); + let self_end = self.end(); + let inter_start = intersection.start(); + let inter_end = intersection.end(); + debug_assert!(self_start <= inter_start); + debug_assert!(inter_end <= self_end); + + match (self_start < inter_start, inter_end < self_end) { + (false, false) => Vec::new(), + (false, true) => unsafe { vec![VMRange::from_unchecked(inter_end, self_end)] }, + (true, false) => unsafe { vec![VMRange::from_unchecked(self_start, inter_start)] }, + (true, true) => unsafe { + vec![ + VMRange::from_unchecked(self_start, inter_start), + VMRange::from_unchecked(inter_end, self_end), + ] + }, + } + } + + // Returns an non-empty intersection if where is any + pub fn intersect(&self, other: &VMRange) -> Option { + let intersection_start = self.start().max(other.start()); + let intersection_end = self.end().min(other.end()); + if intersection_start >= intersection_end { + return None; + } + unsafe { + Some(VMRange::from_unchecked( + intersection_start, + intersection_end, + )) + } + } + + pub unsafe fn as_slice(&self) -> &[u8] { + let buf_ptr = self.start() as *const u8; + let buf_size = self.size() as usize; + std::slice::from_raw_parts(buf_ptr, buf_size) + } + + pub unsafe fn as_slice_mut(&self) -> &mut [u8] { + let buf_ptr = self.start() as *mut u8; + let buf_size = self.size() as usize; + std::slice::from_raw_parts_mut(buf_ptr, buf_size) + } +} + +impl fmt::Debug for VMRange { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "VMRange {{ start: 0x{:x?}, end: 0x{:x?}, size: 0x{:x?} }}", + self.start, + self.end, + self.size() + ) + } +} diff --git a/src/libos/crates/sgx-untrusted-alloc/src/untrusted_allocator/vm_util.rs b/src/libos/crates/sgx-untrusted-alloc/src/untrusted_allocator/vm_util.rs new file mode 100644 index 00000000..774b1540 --- /dev/null +++ b/src/libos/crates/sgx-untrusted-alloc/src/untrusted_allocator/vm_util.rs @@ -0,0 +1,9 @@ +pub fn align_down(addr: usize, align: usize) -> usize { + debug_assert!(align.is_power_of_two()); + addr & !(align - 1) +} + +pub fn align_up(addr: usize, align: usize) -> usize { + debug_assert!(align.is_power_of_two()); + align_down(addr + (align - 1), align) +}