From bf6a8ddf78af4767bb5761f93096067efba1656b Mon Sep 17 00:00:00 2001 From: "Adam C. Foltzer" Date: Mon, 15 Apr 2019 13:20:05 -0700 Subject: [PATCH 01/21] [packaging] use the new cargo-deb feature to rename the packages --- lucet-runtime/Cargo.toml | 2 +- lucet-wasi/Cargo.toml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/lucet-runtime/Cargo.toml b/lucet-runtime/Cargo.toml index 0197eeebc..b4c4b7cdf 100644 --- a/lucet-runtime/Cargo.toml +++ b/lucet-runtime/Cargo.toml @@ -26,6 +26,7 @@ name = "lucet_runtime" crate-type = ["rlib", "staticlib", "cdylib"] [package.metadata.deb] +name = "fst-lucet-runtime" maintainer = "Adam C. Foltzer " depends = "$auto" priority = "optional" @@ -34,5 +35,4 @@ assets = [ ["target/release/liblucet_runtime.rlib", "/opt/fst-lucet-runtime/lib/", "644"], ["target/release/liblucet_runtime.so", "/opt/fst-lucet-runtime/lib/", "755"], ["include/*.h", "/opt/fst-lucet-runtime/include/", "644"], - ["README.rst", "/opt/fst-lucet-runtime/share/doc/lucet-runtime/", "644"], ] diff --git a/lucet-wasi/Cargo.toml b/lucet-wasi/Cargo.toml index b20379ee2..8d9ba2025 100644 --- a/lucet-wasi/Cargo.toml +++ b/lucet-wasi/Cargo.toml @@ -29,6 +29,7 @@ name = "lucet_wasi" crate-type = ["rlib", "staticlib", "cdylib"] [package.metadata.deb] +name = "fst-lucet-wasi" maintainer = "Adam C. Foltzer " depends = "$auto" priority = "optional" From 3c12474d5003dd6896de5c11bb9f4cab8c47bd3b Mon Sep 17 00:00:00 2001 From: "Adam C. Foltzer" Date: Tue, 16 Apr 2019 10:39:32 -0700 Subject: [PATCH 02/21] [lucet-wasi] use /dev/null by default for stdio handles When we clean up these interfaces, it'd be nice to make these pseudo files rather than opening an actual file decriptor, so there are no system calls involved in creating a new WASI context. --- lucet-wasi/src/ctx.rs | 20 ++++++++++++++++---- lucet-wasi/src/main.rs | 5 ++++- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/lucet-wasi/src/ctx.rs b/lucet-wasi/src/ctx.rs index fcb9bed5b..889ecfc99 100644 --- a/lucet-wasi/src/ctx.rs +++ b/lucet-wasi/src/ctx.rs @@ -19,15 +19,16 @@ pub struct WasiCtxBuilder { impl WasiCtxBuilder { /// Builder for a new `WasiCtx`. pub fn new() -> Self { + let null = dev_null(); WasiCtxBuilder { fds: HashMap::new(), preopens: HashMap::new(), args: vec![], env: HashMap::new(), } - .fd_dup(0, stdin()) - .fd_dup(1, stdout()) - .fd_dup(2, stderr()) + .fd_dup(0, &null) + .fd_dup(1, &null) + .fd_dup(2, &null) } pub fn args(mut self, args: &[&str]) -> Self { @@ -57,6 +58,12 @@ impl WasiCtxBuilder { self } + pub fn inherit_stdio(self) -> Self { + self.fd_dup(0, &stdin()) + .fd_dup(1, &stdout()) + .fd_dup(2, &stderr()) + } + pub fn inherit_env(mut self) -> Self { self.env = std::env::vars() .map(|(k, v)| { @@ -104,7 +111,7 @@ impl WasiCtxBuilder { /// context, so it will not be closed when the `WasiCtx` is dropped. /// /// TODO: handle `dup` errors - pub fn fd_dup(self, wasm_fd: host::__wasi_fd_t, fd: F) -> Self { + pub fn fd_dup(self, wasm_fd: host::__wasi_fd_t, fd: &F) -> Self { // safe because we're getting a valid RawFd from the F directly unsafe { self.raw_fd(wasm_fd, dup(fd.as_raw_fd()).unwrap()) } } @@ -189,6 +196,7 @@ impl WasiCtx { pub fn new(args: &[&str]) -> WasiCtx { WasiCtxBuilder::new() .args(args) + .inherit_stdio() .inherit_env() .build() .expect("default options don't fail") @@ -230,3 +238,7 @@ impl WasiCtx { Ok(fd) } } + +fn dev_null() -> File { + File::open("/dev/null").expect("failed to open /dev/null") +} diff --git a/lucet-wasi/src/main.rs b/lucet-wasi/src/main.rs index eb9837c6b..67ee5b90c 100644 --- a/lucet-wasi/src/main.rs +++ b/lucet-wasi/src/main.rs @@ -175,7 +175,10 @@ fn run(config: Config) { let args = std::iter::once(config.lucet_module) .chain(config.guest_args.into_iter()) .collect::>(); - let mut ctx = WasiCtxBuilder::new().args(&args).inherit_env(); + let mut ctx = WasiCtxBuilder::new() + .args(&args) + .inherit_stdio() + .inherit_env(); for (dir, guest_path) in config.preopen_dirs { ctx = ctx.preopened_dir(dir, guest_path); } From a77f2a1be87ad89728249bc5ae84df55026eacc1 Mon Sep 17 00:00:00 2001 From: "Adam C. Foltzer" Date: Tue, 16 Apr 2019 11:00:43 -0700 Subject: [PATCH 03/21] [lucetc] add exact reserved size setting --- lucetc/src/lib.rs | 19 +++++++++++++++++++ lucetc/src/main.rs | 5 +++++ lucetc/src/options.rs | 15 +++++++++++++++ 3 files changed, 39 insertions(+) diff --git a/lucetc/src/lib.rs b/lucetc/src/lib.rs index 5ff59bef2..d69d7a06c 100644 --- a/lucetc/src/lib.rs +++ b/lucetc/src/lib.rs @@ -61,6 +61,15 @@ pub trait LucetcOpts { fn max_reserved_size(&mut self, max_reserved_size: u64); fn with_max_reserved_size(self, max_reserved_size: u64) -> Self; + /// Set the reserved size exactly. + /// + /// Equivalent to setting the minimum and maximum reserved sizes to the same value. + fn reserved_size(&mut self, reserved_size: u64); + /// Set the reserved size exactly. + /// + /// Equivalent to setting the minimum and maximum reserved sizes to the same value. + fn with_reserved_size(self, reserved_size: u64) -> Self; + fn guard_size(&mut self, guard_size: u64); fn with_guard_size(self, guard_size: u64) -> Self; } @@ -113,6 +122,16 @@ impl LucetcOpts for T { self } + fn reserved_size(&mut self, reserved_size: u64) { + self.as_lucetc().heap.min_reserved_size = reserved_size; + self.as_lucetc().heap.max_reserved_size = reserved_size; + } + + fn with_reserved_size(mut self, reserved_size: u64) -> Self { + self.reserved_size(reserved_size); + self + } + fn guard_size(&mut self, guard_size: u64) { self.as_lucetc().heap.guard_size = guard_size; } diff --git a/lucetc/src/main.rs b/lucetc/src/main.rs index 88539167a..c8f0fe82d 100644 --- a/lucetc/src/main.rs +++ b/lucetc/src/main.rs @@ -58,6 +58,11 @@ pub fn run(opts: &Options) -> Result<(), Error> { c.max_reserved_size(max_reserved_size); } + // this comes after min and max, so it overrides them if present + if let Some(reserved_size) = opts.reserved_size { + c.reserved_size(reserved_size); + } + if let Some(guard_size) = opts.guard_size { c.guard_size(guard_size); } diff --git a/lucetc/src/options.rs b/lucetc/src/options.rs index 46ade8c18..427b0a45b 100644 --- a/lucetc/src/options.rs +++ b/lucetc/src/options.rs @@ -38,6 +38,7 @@ pub struct Options { pub builtins_path: Option, pub min_reserved_size: Option, pub max_reserved_size: Option, + pub reserved_size: Option, pub guard_size: Option, pub opt_level: OptLevel, } @@ -80,6 +81,12 @@ impl Options { None }; + let reserved_size = if let Some(reserved_str) = m.value_of("reserved_size") { + Some(parse_humansized(reserved_str)?) + } else { + None + }; + let guard_size = if let Some(guard_str) = m.value_of("guard_size") { Some(parse_humansized(guard_str)?) } else { @@ -102,6 +109,7 @@ impl Options { builtins_path, min_reserved_size, max_reserved_size, + reserved_size, guard_size, opt_level, }) @@ -154,6 +162,13 @@ impl Options { .multiple(false) .help("maximum size of usable linear memory region. must be multiple of 4k. default: 4 GiB"), ) + .arg( + Arg::with_name("reserved_size") + .long("--reserved-size") + .takes_value(true) + .multiple(false) + .help("exact size of usable linear memory region, overriding --{min,max}-reserved-size. must be multiple of 4k"), + ) .arg( Arg::with_name("guard_size") .long("--guard-size") From 608ca14af15c8f41d7b6738fbdf41f42b150a83d Mon Sep 17 00:00:00 2001 From: "Adam C. Foltzer" Date: Tue, 16 Apr 2019 16:01:03 -0700 Subject: [PATCH 04/21] [lucet-wasi-sdk] refine API and be more verbose with commands --- lucet-wasi-sdk/src/lib.rs | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/lucet-wasi-sdk/src/lib.rs b/lucet-wasi-sdk/src/lib.rs index cc77a5caf..218e714dc 100644 --- a/lucet-wasi-sdk/src/lib.rs +++ b/lucet-wasi-sdk/src/lib.rs @@ -63,8 +63,8 @@ pub trait CompileOpts { fn cflag>(&mut self, cflag: S); fn with_cflag>(self, cflag: S) -> Self; - fn include>(&mut self, include: S); - fn with_include>(self, include: S) -> Self; + fn include>(&mut self, include: S); + fn with_include>(self, include: S) -> Self; } impl CompileOpts for Compile { @@ -77,11 +77,12 @@ impl CompileOpts for Compile { self } - fn include>(&mut self, include: S) { - self.cflags.push(format!("-I{}", include.as_ref())); + fn include>(&mut self, include: S) { + self.cflags + .push(format!("-I{}", include.as_ref().display())); } - fn with_include>(mut self, include: S) -> Self { + fn with_include>(mut self, include: S) -> Self { self.include(include); self } @@ -125,6 +126,9 @@ impl Compile { for cflag in self.cflags.iter() { cmd.arg(cflag); } + if self.print_output { + println!("running: {:?}", cmd); + } let run = cmd.output().expect("clang executable exists"); CompileError::check(run, self.print_output) } @@ -181,6 +185,9 @@ impl Link { for ldflag in self.ldflags.iter() { cmd.arg(format!("-Wl,{}", ldflag)); } + if self.print_output { + println!("running: {:?}", cmd); + } let run = cmd.output().expect("clang executable exists"); CompileError::check(run, self.print_output) } @@ -236,13 +243,13 @@ impl CompileOpts for T { self } - fn include>(&mut self, include: S) { + fn include>(&mut self, include: S) { self.as_link() .cflags - .push(format!("-I{}", include.as_ref())); + .push(format!("-I{}", include.as_ref().display())); } - fn with_include>(mut self, include: S) -> Self { + fn with_include>(mut self, include: S) -> Self { self.include(include); self } @@ -269,8 +276,12 @@ impl Lucetc { } } - pub fn print_output(mut self, print: bool) -> Self { + pub fn print_output(&mut self, print: bool) { self.link.print_output = print; + } + + pub fn with_print_output(mut self, print: bool) -> Self { + self.print_output(print); self } From 577a541353485db42e5c80d7abc502dea70fc33d Mon Sep 17 00:00:00 2001 From: "Adam C. Foltzer" Date: Wed, 24 Apr 2019 11:54:22 -0700 Subject: [PATCH 05/21] [lucet-runtime] add macros for hostcalls and instance termination This makes it possible to properly unwind within hostcalls, though panics that travel across the host/guest boundary will still not behave properly due to stack layout. --- benchmarks/lucet-benchmarks/src/modules.rs | 2 +- .../src/hostcall_macros.rs | 91 + .../lucet-runtime-internals/src/lib.rs | 2 + .../lucet-runtime-internals/src/vmctx.rs | 97 +- .../guests/host/bindings.json | 3 +- .../lucet-runtime-tests/src/entrypoint.rs | 34 +- .../lucet-runtime-tests/src/guest_fault.rs | 89 +- lucet-runtime/lucet-runtime-tests/src/host.rs | 79 +- .../lucet-runtime-tests/src/strcmp.rs | 14 +- lucet-runtime/src/c_api.rs | 147 +- lucet-runtime/src/lib.rs | 29 +- lucet-wasi/src/hostcalls.rs | 1697 ++++++++--------- 12 files changed, 1220 insertions(+), 1064 deletions(-) create mode 100644 lucet-runtime/lucet-runtime-internals/src/hostcall_macros.rs diff --git a/benchmarks/lucet-benchmarks/src/modules.rs b/benchmarks/lucet-benchmarks/src/modules.rs index 7e655a795..2972f0085 100644 --- a/benchmarks/lucet-benchmarks/src/modules.rs +++ b/benchmarks/lucet-benchmarks/src/modules.rs @@ -11,7 +11,7 @@ fn wasi_bindings() -> Bindings { pub fn compile_hello>(so_file: P) { let wasm_build = Lucetc::new(&["guests/hello.c"]) - .print_output(true) + .with_print_output(true) .with_cflag("-Wall") .with_cflag("-Werror") .with_bindings(wasi_bindings()); diff --git a/lucet-runtime/lucet-runtime-internals/src/hostcall_macros.rs b/lucet-runtime/lucet-runtime-internals/src/hostcall_macros.rs new file mode 100644 index 000000000..1c3ac37d1 --- /dev/null +++ b/lucet-runtime/lucet-runtime-internals/src/hostcall_macros.rs @@ -0,0 +1,91 @@ +/// The macro that surrounds definitions of Lucet hostcalls in Rust. +/// +/// It is important to use this macro for hostcalls, rather than exporting them directly, as it +/// installs unwind protection that prevents panics from unwinding into the guest stack. +/// +/// Since this is not yet a proc macro, the syntax is unfortunately fairly brittle. The functions it +/// encloses must be of the form: +/// +/// ```ignore +/// #[$attr1] +/// #[$attr2] +/// ... // any number of attributes are supported; in most cases you will want `#[no_mangle]` +/// pub unsafe extern "C" fn $name( // must be `pub unsafe extern "C"` +/// &mut $vmctx, +/// $arg1: $arg1_ty, +/// $arg2: $arg2_ty, +/// ... , // trailing comma must always be present +/// ) -> $ret_ty { // return type must always be present even if it is `()` +/// // body +/// } +/// ``` +#[macro_export] +macro_rules! lucet_hostcalls { + { + $( + $(#[$attr:meta])* + pub unsafe extern "C" fn $name:ident( + &mut $vmctx:ident + $(, $arg:ident : $arg_ty:ty )*, + ) -> $ret_ty:ty { + $($body:tt)* + } + )* + } => { + $( + $(#[$attr])* + pub unsafe extern "C" fn $name( + vmctx_raw: *mut $crate::vmctx::lucet_vmctx, + $( $arg: $arg_ty ),* + ) -> $ret_ty { + unsafe fn hostcall_impl( + $vmctx: &mut $crate::vmctx::Vmctx, + $( $arg : $arg_ty ),* + ) -> $ret_ty { + $($body)* + } + let res = std::panic::catch_unwind(|| { + hostcall_impl(&mut $crate::vmctx::Vmctx::from_raw(vmctx_raw), $( $arg ),*) + }); + match res { + Ok(res) => res, + Err(e) => { + if let Some(details) = e.downcast_ref::<$crate::instance::TerminationDetails>() { + let mut vmctx = $crate::vmctx::Vmctx::from_raw(vmctx_raw); + vmctx.terminate_no_unwind(details.clone()); + } else { + std::panic::resume_unwind(e); + } + } + } + } + )* + } +} + +/// Terminate an instance from within a hostcall, returning an optional value as an error. +/// +/// Use this instead of `panic!` when you want the instance to terminate, but not the entire host +/// program. Like `panic!`, you can pass a format string with arguments, a value that implements +/// `Any`, or nothing to return a default message. +/// +/// Upon termination, the call to `Instance::run()` will return with an +/// `Err(Error::RuntimeTerminated)` value containing the value you pass to this macro. +/// +/// This macro safely unwinds the hostcall stack out to the entrypoint of the hostcall, so any +/// resources that may have been acquired will be properly dropped. +#[macro_export] +macro_rules! lucet_hostcall_terminate { + () => { + lucet_hostcall_terminate!("lucet_hostcall_terminate") + }; + ( $payload:expr ) => { + panic!($crate::instance::TerminationDetails::provide($payload)) + }; + ( $payload:expr, ) => { + lucet_hostcall_terminate!($payload) + }; + ( $fmt:expr, $($arg:tt)+ ) => { + lucet_hostcall_terminate!(format!($fmt, $($arg),+)) + }; +} diff --git a/lucet-runtime/lucet-runtime-internals/src/lib.rs b/lucet-runtime/lucet-runtime-internals/src/lib.rs index 40480ad58..7777b0e0c 100644 --- a/lucet-runtime/lucet-runtime-internals/src/lib.rs +++ b/lucet-runtime/lucet-runtime-internals/src/lib.rs @@ -6,6 +6,8 @@ #[macro_use] pub mod error; +#[macro_use] +pub mod hostcall_macros; #[macro_use] #[cfg(test)] diff --git a/lucet-runtime/lucet-runtime-internals/src/vmctx.rs b/lucet-runtime/lucet-runtime-internals/src/vmctx.rs index 1c25e8a91..b1c08f3a3 100644 --- a/lucet-runtime/lucet-runtime-internals/src/vmctx.rs +++ b/lucet-runtime/lucet-runtime-internals/src/vmctx.rs @@ -85,21 +85,34 @@ impl Vmctx { /// Get a reference to a context value of a particular type. If it does not exist, /// the context will terminate. pub fn get_embed_ctx(&self) -> &T { - unsafe { self.instance_mut().get_embed_ctx_or_term() } + match self.instance().embed_ctx.get::() { + Some(t) => t, + None => { + // this value will be caught by the wrapper in `lucet_hostcalls!` + panic!(TerminationDetails::GetEmbedCtx) + } + } } /// Get a mutable reference to a context value of a particular type> If it does not exist, /// the context will terminate. pub fn get_embed_ctx_mut(&mut self) -> &mut T { - unsafe { self.instance_mut().get_embed_ctx_mut_or_term() } + match unsafe { self.instance_mut().embed_ctx.get_mut::() } { + Some(t) => t, + None => { + // this value will be caught by the wrapper in `lucet_hostcalls!` + panic!(TerminationDetails::GetEmbedCtx) + } + } } - /// Terminate this guest and return to the host context. + /// Terminate this guest and return to the host context without unwinding. /// - /// This will return an `Error::RuntimeTerminated` value to the caller of `Instance::run()`. - pub fn terminate(&mut self, info: I) -> ! { - let details = TerminationDetails::provide(info); - unsafe { self.instance_mut().terminate(details) } + /// This is almost certainly not what you want to use to terminate an instance from a hostcall, + /// as any resources currently in scope will not be dropped. Instead, use + /// `lucet_hostcall_terminate!` which unwinds to the enclosing hostcall body. + pub unsafe fn terminate_no_unwind(&mut self, details: TerminationDetails) -> ! { + self.instance_mut().terminate(details) } /// Grow the guest memory by the given number of WebAssembly pages. @@ -130,21 +143,24 @@ impl Vmctx { /// from its own context. /// /// ```no_run + /// use lucet_runtime_internals::{lucet_hostcalls, lucet_hostcall_terminate}; /// use lucet_runtime_internals::vmctx::{lucet_vmctx, Vmctx}; - /// #[no_mangle] - /// extern "C" fn hostcall_call_binop( - /// vmctx: *mut lucet_vmctx, - /// binop_table_idx: u32, - /// binop_func_idx: u32, - /// operand1: u32, - /// operand2: u32, - /// ) -> u32 { - /// let mut ctx = unsafe { Vmctx::from_raw(vmctx) }; - /// if let Ok(binop) = ctx.get_func_from_idx(binop_table_idx, binop_func_idx) { - /// let typed_binop = binop as *const extern "C" fn(*mut lucet_vmctx, u32, u32) -> u32; - /// unsafe { (*typed_binop)(vmctx, operand1, operand2) } - /// } else { - /// ctx.terminate("invalid function index") + /// + /// lucet_hostcalls! { + /// #[no_mangle] + /// pub unsafe extern "C" fn hostcall_call_binop( + /// &mut vmctx, + /// binop_table_idx: u32, + /// binop_func_idx: u32, + /// operand1: u32, + /// operand2: u32, + /// ) -> u32 { + /// if let Ok(binop) = vmctx.get_func_from_idx(binop_table_idx, binop_func_idx) { + /// let typed_binop = binop as *const extern "C" fn(*mut lucet_vmctx, u32, u32) -> u32; + /// unsafe { (*typed_binop)(vmctx.as_raw(), operand1, operand2) } + /// } else { + /// lucet_hostcall_terminate!("invalid function index") + /// } /// } /// } pub fn get_func_from_idx( @@ -158,18 +174,6 @@ impl Vmctx { } } -/// Terminating an instance requires mutating the state field, and then jumping back to the -/// host context. The mutable borrow may conflict with a mutable borrow of the embed_ctx if -/// this is performed via a method call. We use a macro so we can convince the borrow checker that -/// this is safe at each use site. -macro_rules! inst_terminate { - ($self:ident, $details:expr) => {{ - $self.state = State::Terminated { details: $details }; - #[allow(unused_unsafe)] // The following unsafe will be incorrectly warned as unused - HOST_CTX.with(|host_ctx| unsafe { Context::set(&*host_ctx.get()) }) - }}; -} - /// Get an `Instance` from the `vmctx` pointer. /// /// Only safe to call from within the guest context. @@ -202,29 +206,14 @@ pub unsafe fn instance_from_vmctx<'a>(vmctx: *mut lucet_vmctx) -> &'a mut Instan } impl Instance { - /// Helper function specific to Vmctx::get_embed_ctx. From the vmctx interface, - /// there is no way to recover if the expected embedder ctx is not set, so we terminate - /// the instance. - fn get_embed_ctx_or_term(&mut self) -> &T { - match self.embed_ctx.get::() { - Some(t) => t, - None => inst_terminate!(self, TerminationDetails::GetEmbedCtx), - } - } - - /// Helper function specific to Vmctx::get_embed_ctx_mut. See above. - fn get_embed_ctx_mut_or_term(&mut self) -> &mut T { - match self.embed_ctx.get_mut::() { - Some(t) => t, - None => inst_terminate!(self, TerminationDetails::GetEmbedCtx), - } - } - - /// Terminate the guest and swap back to the host context. + /// Terminate the guest and swap back to the host context without unwinding. /// - /// Only safe to call from within the guest context. + /// This is almost certainly not what you want to use to terminate from a hostcall; use panics + /// with `TerminationDetails` instead. unsafe fn terminate(&mut self, details: TerminationDetails) -> ! { - inst_terminate!(self, details) + self.state = State::Terminated { details }; + #[allow(unused_unsafe)] // The following unsafe will be incorrectly warned as unused + HOST_CTX.with(|host_ctx| unsafe { Context::set(&*host_ctx.get()) }) } } diff --git a/lucet-runtime/lucet-runtime-tests/guests/host/bindings.json b/lucet-runtime/lucet-runtime-tests/guests/host/bindings.json index 26b5c8b44..383bcf316 100644 --- a/lucet-runtime/lucet-runtime-tests/guests/host/bindings.json +++ b/lucet-runtime/lucet-runtime-tests/guests/host/bindings.json @@ -1,6 +1,7 @@ { "env": { "hostcall_test_func_hello": "hostcall_test_func_hello", - "hostcall_test_func_hostcall_error": "hostcall_test_func_hostcall_error" + "hostcall_test_func_hostcall_error": "hostcall_test_func_hostcall_error", + "hostcall_test_func_hostcall_error_unwind": "hostcall_test_func_hostcall_error_unwind" } } diff --git a/lucet-runtime/lucet-runtime-tests/src/entrypoint.rs b/lucet-runtime/lucet-runtime-tests/src/entrypoint.rs index 3a1820513..480534329 100644 --- a/lucet-runtime/lucet-runtime-tests/src/entrypoint.rs +++ b/lucet-runtime/lucet-runtime-tests/src/entrypoint.rs @@ -135,13 +135,20 @@ macro_rules! entrypoint_tests { ( $TestRegion:path ) => { use libc::c_void; use lucet_runtime::vmctx::{lucet_vmctx, Vmctx}; - use lucet_runtime::{DlModule, Error, Limits, Module, Region, Val, WASM_PAGE_SIZE}; + use lucet_runtime::{ + lucet_hostcalls, DlModule, Error, Limits, Module, Region, Val, WASM_PAGE_SIZE, + }; use std::sync::Arc; use $TestRegion as TestRegion; use $crate::entrypoint::{mock_calculator_module, wat_calculator_module}; - #[no_mangle] - extern "C" fn black_box(_vmctx: *mut lucet_vmctx, _val: *mut c_void) {} + lucet_hostcalls! { + #[no_mangle] + pub unsafe extern "C" fn black_box( + &mut _vmctx, + _val: *mut c_void, + ) -> () {} + } #[test] fn mock_calc_add_2() { @@ -711,14 +718,19 @@ macro_rules! entrypoint_tests { .expect("instance runs"); } - #[no_mangle] - extern "C" fn callback_hostcall(vmctx: *mut lucet_vmctx, cb_idx: u32, x: u64) -> u64 { - let vmctx = unsafe { Vmctx::from_raw(vmctx) }; - let func = vmctx - .get_func_from_idx(0, cb_idx) - .expect("can get function by index"); - let func = func as *const extern "C" fn(*mut lucet_vmctx, u64) -> u64; - unsafe { (*func)(vmctx.as_raw(), x) + 1 } + lucet_hostcalls! { + #[no_mangle] + pub unsafe extern "C" fn callback_hostcall( + &mut vmctx, + cb_idx: u32, + x: u64, + ) -> u64 { + let func = vmctx + .get_func_from_idx(0, cb_idx) + .expect("can get function by index"); + let func = func as *const extern "C" fn(*mut lucet_vmctx, u64) -> u64; + (*func)(vmctx.as_raw(), x) + 1 + } } #[test] diff --git a/lucet-runtime/lucet-runtime-tests/src/guest_fault.rs b/lucet-runtime/lucet-runtime-tests/src/guest_fault.rs index 6e55084bc..3aacf3cfa 100644 --- a/lucet-runtime/lucet-runtime-tests/src/guest_fault.rs +++ b/lucet-runtime/lucet-runtime-tests/src/guest_fault.rs @@ -1,48 +1,54 @@ use crate::helpers::MockModuleBuilder; +use lucet_runtime_internals::lucet_hostcalls; use lucet_runtime_internals::module::{Module, TrapManifestRecord, TrapSite}; -use lucet_runtime_internals::vmctx::{lucet_vmctx, Vmctx}; +use lucet_runtime_internals::vmctx::lucet_vmctx; use std::sync::Arc; pub fn mock_traps_module() -> Arc { - extern "C" fn onetwothree(_vmctx: *mut lucet_vmctx) -> std::os::raw::c_int { - 123 - } - - extern "C" fn hostcall_main(vmctx: *mut lucet_vmctx) { - extern "C" { - // actually is defined in this file - fn hostcall_test(vmctx: *mut lucet_vmctx); + lucet_hostcalls! { + pub unsafe extern "C" fn onetwothree( + &mut _vmctx, + ) -> std::os::raw::c_int { + 123 } - unsafe { - hostcall_test(vmctx); + + pub unsafe extern "C" fn hostcall_main( + &mut vmctx, + ) -> () { + extern "C" { + // actually is defined in this file + fn hostcall_test(vmctx: *mut lucet_vmctx); + } + hostcall_test(vmctx.as_raw()); std::hint::unreachable_unchecked(); } - } - extern "C" fn infinite_loop(_vmctx: *mut lucet_vmctx) { - loop {} - } + pub unsafe extern "C" fn infinite_loop( + &mut _vmctx, + ) -> () { + loop {} + } - extern "C" fn fatal(vmctx: *mut lucet_vmctx) { - let mut vmctx = unsafe { Vmctx::from_raw(vmctx) }; - let heap_base = vmctx.heap_mut().as_mut_ptr(); + pub unsafe extern "C" fn fatal( + &mut vmctx, + ) -> () { + let heap_base = vmctx.heap_mut().as_mut_ptr(); - // Using the default limits, each instance as of this writing takes up 0x200026000 bytes - // worth of virtual address space. We want to access a point beyond all the instances, so - // that memory is unmapped. We assume no more than 16 instances are mapped - // concurrently. This may change as the library, test configuration, linker, phase of moon, - // etc change, but for now it works. - unsafe { + // Using the default limits, each instance as of this writing takes up 0x200026000 bytes + // worth of virtual address space. We want to access a point beyond all the instances, so + // that memory is unmapped. We assume no more than 16 instances are mapped + // concurrently. This may change as the library, test configuration, linker, phase of moon, + // etc change, but for now it works. *heap_base.offset(0x200026000 * 16) = 0; } - } - extern "C" fn recoverable_fatal(_vmctx: *mut lucet_vmctx) { - use std::os::raw::c_char; - extern "C" { - fn guest_recoverable_get_ptr() -> *mut c_char; - } - unsafe { + pub unsafe extern "C" fn recoverable_fatal( + &mut _vmctx, + ) -> () { + use std::os::raw::c_char; + extern "C" { + fn guest_recoverable_get_ptr() -> *mut c_char; + } *guest_recoverable_get_ptr() = '\0' as c_char; } } @@ -118,7 +124,8 @@ macro_rules! guest_fault_tests { use libc::{c_void, siginfo_t, SIGSEGV}; use lucet_runtime::vmctx::{lucet_vmctx, Vmctx}; use lucet_runtime::{ - DlModule, Error, FaultDetails, Instance, Limits, Region, SignalBehavior, TrapCode, + lucet_hostcall_terminate, lucet_hostcalls, DlModule, Error, FaultDetails, Instance, + Limits, Region, SignalBehavior, TrapCode, }; use nix::sys::mman::{mmap, MapFlags, ProtFlags}; use nix::sys::signal::{sigaction, SaFlags, SigAction, SigHandler, SigSet, Signal}; @@ -173,9 +180,13 @@ macro_rules! guest_fault_tests { static HOSTCALL_TEST_ERROR: &'static str = "hostcall_test threw an error!"; - #[no_mangle] - unsafe extern "C" fn hostcall_test(vmctx: *mut lucet_vmctx) { - Vmctx::from_raw(vmctx).terminate(HOSTCALL_TEST_ERROR); + lucet_hostcalls! { + #[no_mangle] + pub unsafe extern "C" fn hostcall_test( + &mut _vmctx, + ) -> () { + lucet_hostcall_terminate!(HOSTCALL_TEST_ERROR); + } } fn run_onetwothree(inst: &mut Instance) { @@ -504,8 +515,12 @@ macro_rules! guest_fault_tests { *HOST_SIGSEGV_TRIGGERED.lock().unwrap() = true; } - extern "C" fn sleepy_guest(_vmctx: *const lucet_vmctx) { - std::thread::sleep(std::time::Duration::from_millis(20)); + lucet_hostcalls! { + pub unsafe extern "C" fn sleepy_guest( + &mut _vmctx, + ) -> () { + std::thread::sleep(std::time::Duration::from_millis(20)); + } } test_ex(|| { diff --git a/lucet-runtime/lucet-runtime-tests/src/host.rs b/lucet-runtime/lucet-runtime-tests/src/host.rs index 8f833b377..e99c66ec3 100644 --- a/lucet-runtime/lucet-runtime-tests/src/host.rs +++ b/lucet-runtime/lucet-runtime-tests/src/host.rs @@ -1,10 +1,13 @@ #[macro_export] macro_rules! host_tests { ( $TestRegion:path ) => { + use lazy_static::lazy_static; use libc::c_void; use lucet_runtime::vmctx::{lucet_vmctx, Vmctx}; - use lucet_runtime::{DlModule, Error, Limits, Region, TrapCode}; - use std::sync::Arc; + use lucet_runtime::{ + lucet_hostcall_terminate, lucet_hostcalls, DlModule, Error, Limits, Region, TrapCode, + }; + use std::sync::{Arc, Mutex}; use $TestRegion as TestRegion; use $crate::build::test_module_c; #[test] @@ -18,30 +21,48 @@ macro_rules! host_tests { assert!(module.is_err()); } - #[no_mangle] - extern "C" fn hostcall_test_func_hello( - vmctx: *mut lucet_vmctx, - hello_ptr: u32, - hello_len: u32, - ) { - unsafe { - let mut vmctx = Vmctx::from_raw(vmctx); + const ERROR_MESSAGE: &'static str = "hostcall_test_func_hostcall_error"; + + lazy_static! { + static ref HOSTCALL_MUTEX: Mutex<()> = Mutex::new(()); + } + + lucet_hostcalls! { + #[no_mangle] + pub unsafe extern "C" fn hostcall_test_func_hello( + &mut vmctx, + hello_ptr: u32, + hello_len: u32, + ) -> () { let heap = vmctx.heap(); let hello = heap.as_ptr() as usize + hello_ptr as usize; if !vmctx.check_heap(hello as *const c_void, hello_len as usize) { - vmctx.terminate("heap access"); + lucet_hostcall_terminate!("heap access"); } let hello = std::slice::from_raw_parts(hello as *const u8, hello_len as usize); if hello.starts_with(b"hello") { *vmctx.get_embed_ctx_mut::() = true; } } - } - const ERROR_MESSAGE: &'static str = "hostcall_test_func_hostcall_error"; - #[no_mangle] - extern "C" fn hostcall_test_func_hostcall_error(vmctx: *mut lucet_vmctx) { - unsafe { Vmctx::from_raw(vmctx).terminate(ERROR_MESSAGE) } + #[no_mangle] + pub unsafe extern "C" fn hostcall_test_func_hostcall_error( + &mut _vmctx, + ) -> () { + lucet_hostcall_terminate!(ERROR_MESSAGE); + } + + #[allow(unreachable_code)] + #[no_mangle] + pub unsafe extern "C" fn hostcall_test_func_hostcall_error_unwind( + &mut vmctx, + ) -> () { + let lock = HOSTCALL_MUTEX.lock().unwrap(); + unsafe { + lucet_hostcall_terminate!(ERROR_MESSAGE); + } + drop(lock); + } } #[test] @@ -102,6 +123,32 @@ macro_rules! host_tests { } } + #[test] + fn run_hostcall_error_unwind() { + let module = + test_module_c("host", "hostcall_error_unwind.c").expect("build and load module"); + let region = TestRegion::create(1, &Limits::default()).expect("region can be created"); + let mut inst = region + .new_instance(module) + .expect("instance can be created"); + + match inst.run(b"main", &[]) { + Err(Error::RuntimeTerminated(term)) => { + assert_eq!( + *term + .provided_details() + .expect("user provided termination reason") + .downcast_ref::<&'static str>() + .expect("error was static str"), + ERROR_MESSAGE + ); + } + res => panic!("unexpected result: {:?}", res), + } + + assert!(HOSTCALL_MUTEX.is_poisoned()); + } + #[test] fn run_fpe() { let module = test_module_c("host", "fpe.c").expect("build and load module"); diff --git a/lucet-runtime/lucet-runtime-tests/src/strcmp.rs b/lucet-runtime/lucet-runtime-tests/src/strcmp.rs index 2c6b9e260..80bbe415b 100644 --- a/lucet-runtime/lucet-runtime-tests/src/strcmp.rs +++ b/lucet-runtime/lucet-runtime-tests/src/strcmp.rs @@ -3,16 +3,20 @@ macro_rules! strcmp_tests { ( $TestRegion:path ) => { use libc::{c_char, c_int, c_void, strcmp, uint64_t}; use lucet_runtime::vmctx::lucet_vmctx; - use lucet_runtime::{Error, Limits, Region, Val, WASM_PAGE_SIZE}; + use lucet_runtime::{lucet_hostcalls, Error, Limits, Region, Val, WASM_PAGE_SIZE}; use std::ffi::CString; use std::sync::Arc; use $TestRegion as TestRegion; use $crate::build::test_module_c; - #[no_mangle] - unsafe extern "C" fn hostcall_host_fault(_vmctx: *const lucet_vmctx) { - let oob = (-1isize) as *mut c_char; - *oob = 'x' as c_char; + lucet_hostcalls! { + #[no_mangle] + pub unsafe extern "C" fn hostcall_host_fault( + &mut _vmctx, + ) -> () { + let oob = (-1isize) as *mut c_char; + *oob = 'x' as c_char; + } } fn strcmp_compare(s1: &str, s2: &str) { diff --git a/lucet-runtime/src/c_api.rs b/lucet-runtime/src/c_api.rs index 17e943943..e4bd29c3f 100644 --- a/lucet-runtime/src/c_api.rs +++ b/lucet-runtime/src/c_api.rs @@ -1,14 +1,14 @@ -extern crate lucet_runtime_internals; - use crate::{DlModule, Instance, Limits, MmapRegion, Module, Region, TrapCode}; use libc::{c_char, c_int, c_void}; use lucet_runtime_internals::c_api::*; use lucet_runtime_internals::instance::{ instance_handle_from_raw, instance_handle_to_raw, InstanceInternal, }; -use lucet_runtime_internals::vmctx::{instance_from_vmctx, lucet_vmctx, Vmctx, VmctxInternal}; +use lucet_runtime_internals::vmctx::VmctxInternal; use lucet_runtime_internals::WASM_PAGE_SIZE; -use lucet_runtime_internals::{assert_nonnull, with_ffi_arcs}; +use lucet_runtime_internals::{ + assert_nonnull, lucet_hostcall_terminate, lucet_hostcalls, with_ffi_arcs, +}; use num_traits::FromPrimitive; use std::ffi::CStr; use std::ptr; @@ -362,76 +362,87 @@ pub fn ensure_linked() { }); } -#[no_mangle] -pub unsafe extern "C" fn lucet_vmctx_get_heap(vmctx: *mut lucet_vmctx) -> *mut u8 { - Vmctx::from_raw(vmctx).instance().alloc().slot().heap as *mut u8 -} +lucet_hostcalls! { + #[no_mangle] + pub unsafe extern "C" fn lucet_vmctx_get_heap( + &mut vmctx, + ) -> *mut u8 { + vmctx.instance().alloc().slot().heap as *mut u8 + } -#[no_mangle] -pub unsafe extern "C" fn lucet_vmctx_get_globals(vmctx: *mut lucet_vmctx) -> *mut i64 { - Vmctx::from_raw(vmctx).instance().alloc().slot().globals as *mut i64 -} + #[no_mangle] + pub unsafe extern "C" fn lucet_vmctx_get_globals( + &mut vmctx, + ) -> *mut i64 { + vmctx.instance().alloc().slot().globals as *mut i64 + } -/// Get the number of WebAssembly pages currently in the heap. -#[no_mangle] -pub unsafe extern "C" fn lucet_vmctx_current_memory(vmctx: *mut lucet_vmctx) -> libc::uint32_t { - Vmctx::from_raw(vmctx).instance().alloc().heap_len() as u32 / WASM_PAGE_SIZE -} + #[no_mangle] + /// Get the number of WebAssembly pages currently in the heap. + pub unsafe extern "C" fn lucet_vmctx_current_memory( + &mut vmctx, + ) -> libc::uint32_t { + vmctx.instance().alloc().heap_len() as u32 / WASM_PAGE_SIZE + } -#[no_mangle] -/// Grows the guest heap by the given number of WebAssembly pages. -/// -/// On success, returns the number of pages that existed before the call. On failure, returns `-1`. -pub unsafe extern "C" fn lucet_vmctx_grow_memory( - vmctx: *mut lucet_vmctx, - additional_pages: libc::uint32_t, -) -> libc::int32_t { - let inst = instance_from_vmctx(vmctx); - if let Ok(old_pages) = inst.grow_memory(additional_pages) { - old_pages as libc::int32_t - } else { - -1 + #[no_mangle] + /// Grows the guest heap by the given number of WebAssembly pages. + /// + /// On success, returns the number of pages that existed before the call. On failure, returns `-1`. + pub unsafe extern "C" fn lucet_vmctx_grow_memory( + &mut vmctx, + additional_pages: libc::uint32_t, + ) -> libc::int32_t { + if let Ok(old_pages) = vmctx.instance_mut().grow_memory(additional_pages) { + old_pages as libc::int32_t + } else { + -1 + } } -} -#[no_mangle] -/// Check if a memory region is inside the instance heap. -pub unsafe extern "C" fn lucet_vmctx_check_heap( - vmctx: *mut lucet_vmctx, - ptr: *mut c_void, - len: libc::size_t, -) -> bool { - let inst = instance_from_vmctx(vmctx); - inst.check_heap(ptr, len) -} + #[no_mangle] + /// Check if a memory region is inside the instance heap. + pub unsafe extern "C" fn lucet_vmctx_check_heap( + &mut vmctx, + ptr: *mut c_void, + len: libc::size_t, + ) -> bool { + vmctx.instance().check_heap(ptr, len) + } -#[no_mangle] -pub unsafe extern "C" fn lucet_vmctx_get_func_from_idx( - vmctx: *mut lucet_vmctx, - table_idx: u32, - func_idx: u32, -) -> *const c_void { - let inst = instance_from_vmctx(vmctx); - inst.module() - .get_func_from_idx(table_idx, func_idx) - // the Rust API actually returns a pointer to a function pointer, so we want to dereference - // one layer of that to make it nicer in C - .map(|fptr| *(fptr as *const *const c_void)) - .unwrap_or(std::ptr::null()) -} + #[no_mangle] + pub unsafe extern "C" fn lucet_vmctx_get_func_from_idx( + &mut vmctx, + table_idx: u32, + func_idx: u32, + ) -> *const c_void { + vmctx.instance() + .module() + .get_func_from_idx(table_idx, func_idx) + // the Rust API actually returns a pointer to a function pointer, so we want to dereference + // one layer of that to make it nicer in C + .map(|fptr| *(fptr as *const *const c_void)) + .unwrap_or(std::ptr::null()) + } -#[no_mangle] -pub unsafe extern "C" fn lucet_vmctx_terminate(vmctx: *mut lucet_vmctx, info: *mut c_void) { - Vmctx::from_raw(vmctx).terminate(info); -} + #[no_mangle] + pub unsafe extern "C" fn lucet_vmctx_terminate( + &mut _vmctx, + info: *mut c_void, + ) -> () { + lucet_hostcall_terminate!(info); + } -#[no_mangle] -/// Get the delegate object for the current instance. -/// -/// TODO: rename -pub unsafe extern "C" fn lucet_vmctx_get_delegate(vmctx: *mut lucet_vmctx) -> *mut c_void { - let inst = instance_from_vmctx(vmctx); - inst.get_embed_ctx::<*mut c_void>() - .map(|p| *p) - .unwrap_or(std::ptr::null_mut()) + #[no_mangle] + /// Get the delegate object for the current instance. + /// + /// TODO: rename + pub unsafe extern "C" fn lucet_vmctx_get_delegate( + &mut vmctx, + ) -> *mut c_void { + vmctx.instance() + .get_embed_ctx::<*mut c_void>() + .map(|p| *p) + .unwrap_or(std::ptr::null_mut()) + } } diff --git a/lucet-runtime/src/lib.rs b/lucet-runtime/src/lib.rs index 4ffb79505..af85c489f 100644 --- a/lucet-runtime/src/lib.rs +++ b/lucet-runtime/src/lib.rs @@ -65,23 +65,26 @@ //! demo](https://wasm.fastly-labs.com/), hostcalls are provided for manipulating HTTP requests, //! accessing a key/value store, etc. //! -//! Some simple hostcalls can be implemented simply as an exported C function that takes an opaque -//! pointer argument (usually called `vmctx`). Hostcalls that require access to some underlying -//! state, such as the key/value store in Terrarium, can access a custom embedder context through -//! `vmctx`. For example, to make a `u32` available to hostcalls: +//! Some simple hostcalls can be implemented by wrapping an externed C function with the +//! [`lucet_hostcalls!`](macro.lucet_hostcalls.html] macro. The function must take a special `&mut +//! vmctx` argument for the guest context, similar to `&mut self` on methods. Hostcalls that require +//! access to some underlying state, such as the key/value store in Terrarium, can access a custom +//! embedder context through `vmctx`. For example, to make a `u32` available to hostcalls: //! //! ```no_run -//! use lucet_runtime::{DlModule, Limits, MmapRegion, Region}; +//! use lucet_runtime::{DlModule, Limits, MmapRegion, Region, lucet_hostcalls}; //! use lucet_runtime::vmctx::{Vmctx, lucet_vmctx}; //! //! struct MyContext { x: u32 } //! -//! #[no_mangle] -//! unsafe extern "C" fn foo(vmctx: *mut lucet_vmctx) { -//! let mut vmctx = Vmctx::from_raw(vmctx); -//! let hostcall_context = vmctx -//! .get_embed_ctx_mut::(); -//! hostcall_context.x = 42; +//! lucet_hostcalls! { +//! #[no_mangle] +//! pub unsafe extern "C" fn foo( +//! &mut vmctx, +//! ) -> () { +//! let hostcall_context = vmctx.get_embed_ctx_mut::(); +//! hostcall_context.x = 42; +//! } //! } //! //! let module = DlModule::load("/my/lucet/module.so").unwrap(); @@ -195,7 +198,7 @@ //! that, for example, a `SIGSEGV` on a non-Lucet thread of a host program will still likely abort //! the entire process. -mod c_api; +pub mod c_api; pub use lucet_runtime_internals::alloc::Limits; pub use lucet_runtime_internals::error::Error; @@ -207,7 +210,7 @@ pub use lucet_runtime_internals::region::mmap::MmapRegion; pub use lucet_runtime_internals::region::{InstanceBuilder, Region, RegionCreate}; pub use lucet_runtime_internals::trapcode::TrapCode; pub use lucet_runtime_internals::val::{UntypedRetVal, Val}; -pub use lucet_runtime_internals::WASM_PAGE_SIZE; +pub use lucet_runtime_internals::{lucet_hostcall_terminate, lucet_hostcalls, WASM_PAGE_SIZE}; pub mod vmctx { //! Functions for manipulating instances from hostcalls. diff --git a/lucet-wasi/src/hostcalls.rs b/lucet-wasi/src/hostcalls.rs index fab47b37c..9a6971f25 100644 --- a/lucet-wasi/src/hostcalls.rs +++ b/lucet-wasi/src/hostcalls.rs @@ -9,13 +9,15 @@ //! something nice. #![allow(non_camel_case_types)] +#![allow(unused_unsafe)] use crate::ctx::WasiCtx; use crate::fdentry::{determine_type_rights, FdEntry}; use crate::memory::*; use crate::{host, wasm32}; use cast::From as _0; -use lucet_runtime::vmctx::{lucet_vmctx, Vmctx}; +use lucet_runtime::vmctx::Vmctx; +use lucet_runtime::{lucet_hostcall_terminate, lucet_hostcalls}; use nix::convert_ioctl_res; use nix::libc::c_int; @@ -30,676 +32,913 @@ const O_RSYNC: nix::fcntl::OFlag = nix::fcntl::OFlag::O_RSYNC; #[cfg(not(target_os = "linux"))] const O_RSYNC: nix::fcntl::OFlag = nix::fcntl::OFlag::O_SYNC; -#[no_mangle] -pub extern "C" fn __wasi_proc_exit(vmctx: *mut lucet_vmctx, rval: wasm32::__wasi_exitcode_t) -> ! { - let mut vmctx = unsafe { Vmctx::from_raw(vmctx) }; - vmctx.terminate(dec_exitcode(rval)) -} +lucet_hostcalls! { + #[no_mangle] + pub unsafe extern "C" fn __wasi_proc_exit( + &mut _vmctx, + rval: wasm32::__wasi_exitcode_t, + ) -> ! { + lucet_hostcall_terminate!(dec_exitcode(rval)); + } -#[no_mangle] -pub extern "C" fn __wasi_args_get( - vmctx_raw: *mut lucet_vmctx, - argv_ptr: wasm32::uintptr_t, - argv_buf: wasm32::uintptr_t, -) -> wasm32::__wasi_errno_t { - let mut vmctx = unsafe { Vmctx::from_raw(vmctx_raw) }; - let ctx: &WasiCtx = vmctx.get_embed_ctx(); + #[no_mangle] + pub unsafe extern "C" fn __wasi_args_get( + &mut vmctx, + argv_ptr: wasm32::uintptr_t, + argv_buf: wasm32::uintptr_t, + ) -> wasm32::__wasi_errno_t { + let ctx: &WasiCtx = vmctx.get_embed_ctx(); + + let mut argv_buf_offset = 0; + let mut argv = vec![]; + + for arg in ctx.args.iter() { + let arg_bytes = arg.as_bytes_with_nul(); + let arg_ptr = argv_buf + argv_buf_offset; + + // nasty aliasing here, but we aren't interfering with the borrow for `ctx` + // TODO: rework vmctx interface to avoid this + let mut vmctx = unsafe { Vmctx::from_raw(vmctx.as_raw()) }; + if let Err(e) = unsafe { enc_slice_of(&mut vmctx, arg_bytes, arg_ptr) } { + return enc_errno(e); + } - let mut argv_buf_offset = 0; - let mut argv = vec![]; + argv.push(arg_ptr); - for arg in ctx.args.iter() { - let arg_bytes = arg.as_bytes_with_nul(); - let arg_ptr = argv_buf + argv_buf_offset; + argv_buf_offset = if let Some(new_offset) = argv_buf_offset.checked_add( + wasm32::uintptr_t::cast(arg_bytes.len()) + .expect("cast overflow would have been caught by `enc_slice_of` above"), + ) { + new_offset + } else { + return wasm32::__WASI_EOVERFLOW; + } + } - // nasty aliasing here, but we aren't interfering with the borrow for `ctx` - // TODO: rework vmctx interface to avoid this - let mut vmctx = unsafe { Vmctx::from_raw(vmctx_raw) }; - if let Err(e) = unsafe { enc_slice_of(&mut vmctx, arg_bytes, arg_ptr) } { - return enc_errno(e); + unsafe { + enc_slice_of(vmctx, argv.as_slice(), argv_ptr) + .map(|_| wasm32::__WASI_ESUCCESS) + .unwrap_or_else(|e| e) } + } - argv.push(arg_ptr); + #[no_mangle] + pub unsafe extern "C" fn __wasi_args_sizes_get( + &mut vmctx, + argc_ptr: wasm32::uintptr_t, + argv_buf_size_ptr: wasm32::uintptr_t, + ) -> wasm32::__wasi_errno_t { + let ctx: &WasiCtx = vmctx.get_embed_ctx(); + + let argc = ctx.args.len(); + let argv_size = ctx + .args + .iter() + .map(|arg| arg.as_bytes_with_nul().len()) + .sum(); - argv_buf_offset = if let Some(new_offset) = argv_buf_offset.checked_add( - wasm32::uintptr_t::cast(arg_bytes.len()) - .expect("cast overflow would have been caught by `enc_slice_of` above"), - ) { - new_offset - } else { - return wasm32::__WASI_EOVERFLOW; + unsafe { + if let Err(e) = enc_usize_byref(vmctx, argc_ptr, argc) { + return enc_errno(e); + } + if let Err(e) = enc_usize_byref(vmctx, argv_buf_size_ptr, argv_size) { + return enc_errno(e); + } } + wasm32::__WASI_ESUCCESS } - unsafe { - enc_slice_of(&mut vmctx, argv.as_slice(), argv_ptr) - .map(|_| wasm32::__WASI_ESUCCESS) - .unwrap_or_else(|e| e) - } -} + #[no_mangle] + pub unsafe extern "C" fn __wasi_clock_res_get( + &mut vmctx, + clock_id: wasm32::__wasi_clockid_t, + resolution_ptr: wasm32::uintptr_t, + ) -> wasm32::__wasi_errno_t { + // convert the supported clocks to the libc types, or return EINVAL + let clock_id = match dec_clockid(clock_id) { + host::__WASI_CLOCK_REALTIME => libc::CLOCK_REALTIME, + host::__WASI_CLOCK_MONOTONIC => libc::CLOCK_MONOTONIC, + host::__WASI_CLOCK_PROCESS_CPUTIME_ID => libc::CLOCK_PROCESS_CPUTIME_ID, + host::__WASI_CLOCK_THREAD_CPUTIME_ID => libc::CLOCK_THREAD_CPUTIME_ID, + _ => return wasm32::__WASI_EINVAL, + }; -#[no_mangle] -pub extern "C" fn __wasi_args_sizes_get( - vmctx: *mut lucet_vmctx, - argc_ptr: wasm32::uintptr_t, - argv_buf_size_ptr: wasm32::uintptr_t, -) -> wasm32::__wasi_errno_t { - let mut vmctx = unsafe { Vmctx::from_raw(vmctx) }; + // no `nix` wrapper for clock_getres, so we do it ourselves + let mut timespec = unsafe { std::mem::uninitialized::() }; + let res = unsafe { libc::clock_getres(clock_id, &mut timespec as *mut libc::timespec) }; + if res != 0 { + return wasm32::errno_from_nix(nix::errno::Errno::last()); + } - let ctx: &WasiCtx = vmctx.get_embed_ctx(); + // convert to nanoseconds, returning EOVERFLOW in case of overflow; this is freelancing a bit + // from the spec but seems like it'll be an unusual situation to hit + (timespec.tv_sec as host::__wasi_timestamp_t) + .checked_mul(1_000_000_000) + .and_then(|sec_ns| sec_ns.checked_add(timespec.tv_nsec as host::__wasi_timestamp_t)) + .map(|resolution| { + // a supported clock can never return zero; this case will probably never get hit, but + // make sure we follow the spec + if resolution == 0 { + wasm32::__WASI_EINVAL + } else { + unsafe { + enc_timestamp_byref(vmctx, resolution_ptr, resolution) + .map(|_| wasm32::__WASI_ESUCCESS) + .unwrap_or_else(|e| e) + } + } + }) + .unwrap_or(wasm32::__WASI_EOVERFLOW) + } - let argc = ctx.args.len(); - let argv_size = ctx - .args - .iter() - .map(|arg| arg.as_bytes_with_nul().len()) - .sum(); + #[no_mangle] + pub unsafe extern "C" fn __wasi_clock_time_get( + &mut vmctx, + clock_id: wasm32::__wasi_clockid_t, + // ignored for now, but will be useful once we put optional limits on precision to reduce side + // channels + _precision: wasm32::__wasi_timestamp_t, + time_ptr: wasm32::uintptr_t, + ) -> wasm32::__wasi_errno_t { + // convert the supported clocks to the libc types, or return EINVAL + let clock_id = match dec_clockid(clock_id) { + host::__WASI_CLOCK_REALTIME => libc::CLOCK_REALTIME, + host::__WASI_CLOCK_MONOTONIC => libc::CLOCK_MONOTONIC, + host::__WASI_CLOCK_PROCESS_CPUTIME_ID => libc::CLOCK_PROCESS_CPUTIME_ID, + host::__WASI_CLOCK_THREAD_CPUTIME_ID => libc::CLOCK_THREAD_CPUTIME_ID, + _ => return wasm32::__WASI_EINVAL, + }; - unsafe { - if let Err(e) = enc_usize_byref(&mut vmctx, argc_ptr, argc) { - return enc_errno(e); + // no `nix` wrapper for clock_getres, so we do it ourselves + let mut timespec = unsafe { std::mem::uninitialized::() }; + let res = unsafe { libc::clock_gettime(clock_id, &mut timespec as *mut libc::timespec) }; + if res != 0 { + return wasm32::errno_from_nix(nix::errno::Errno::last()); } - if let Err(e) = enc_usize_byref(&mut vmctx, argv_buf_size_ptr, argv_size) { - return enc_errno(e); - } - } - wasm32::__WASI_ESUCCESS -} -#[no_mangle] -pub extern "C" fn __wasi_clock_res_get( - vmctx: *mut lucet_vmctx, - clock_id: wasm32::__wasi_clockid_t, - resolution_ptr: wasm32::uintptr_t, -) -> wasm32::__wasi_errno_t { - let mut vmctx = unsafe { Vmctx::from_raw(vmctx) }; - - // convert the supported clocks to the libc types, or return EINVAL - let clock_id = match dec_clockid(clock_id) { - host::__WASI_CLOCK_REALTIME => libc::CLOCK_REALTIME, - host::__WASI_CLOCK_MONOTONIC => libc::CLOCK_MONOTONIC, - host::__WASI_CLOCK_PROCESS_CPUTIME_ID => libc::CLOCK_PROCESS_CPUTIME_ID, - host::__WASI_CLOCK_THREAD_CPUTIME_ID => libc::CLOCK_THREAD_CPUTIME_ID, - _ => return wasm32::__WASI_EINVAL, - }; - - // no `nix` wrapper for clock_getres, so we do it ourselves - let mut timespec = unsafe { std::mem::uninitialized::() }; - let res = unsafe { libc::clock_getres(clock_id, &mut timespec as *mut libc::timespec) }; - if res != 0 { - return wasm32::errno_from_nix(nix::errno::Errno::last()); + // convert to nanoseconds, returning EOVERFLOW in case of overflow; this is freelancing a bit + // from the spec but seems like it'll be an unusual situation to hit + (timespec.tv_sec as host::__wasi_timestamp_t) + .checked_mul(1_000_000_000) + .and_then(|sec_ns| sec_ns.checked_add(timespec.tv_nsec as host::__wasi_timestamp_t)) + .map(|time| unsafe { + enc_timestamp_byref(vmctx, time_ptr, time) + .map(|_| wasm32::__WASI_ESUCCESS) + .unwrap_or_else(|e| e) + }) + .unwrap_or(wasm32::__WASI_EOVERFLOW) } - // convert to nanoseconds, returning EOVERFLOW in case of overflow; this is freelancing a bit - // from the spec but seems like it'll be an unusual situation to hit - (timespec.tv_sec as host::__wasi_timestamp_t) - .checked_mul(1_000_000_000) - .and_then(|sec_ns| sec_ns.checked_add(timespec.tv_nsec as host::__wasi_timestamp_t)) - .map(|resolution| { - // a supported clock can never return zero; this case will probably never get hit, but - // make sure we follow the spec - if resolution == 0 { - wasm32::__WASI_EINVAL - } else { - unsafe { - enc_timestamp_byref(&mut vmctx, resolution_ptr, resolution) - .map(|_| wasm32::__WASI_ESUCCESS) - .unwrap_or_else(|e| e) - } + #[no_mangle] + pub unsafe extern "C" fn __wasi_environ_get( + &mut vmctx, + environ_ptr: wasm32::uintptr_t, + environ_buf: wasm32::uintptr_t, + ) -> wasm32::__wasi_errno_t { + let ctx: &WasiCtx = vmctx.get_embed_ctx(); + + let mut environ_buf_offset = 0; + let mut environ = vec![]; + + for pair in ctx.env.iter() { + let env_bytes = pair.as_bytes_with_nul(); + let env_ptr = environ_buf + environ_buf_offset; + + // nasty aliasing here, but we aren't interfering with the borrow for `ctx` + // TODO: rework vmctx interface to avoid this + let mut vmctx = unsafe { Vmctx::from_raw(vmctx.as_raw()) }; + if let Err(e) = unsafe { enc_slice_of(&mut vmctx, env_bytes, env_ptr) } { + return enc_errno(e); } - }) - .unwrap_or(wasm32::__WASI_EOVERFLOW) -} -#[no_mangle] -pub extern "C" fn __wasi_clock_time_get( - vmctx: *mut lucet_vmctx, - clock_id: wasm32::__wasi_clockid_t, - // ignored for now, but will be useful once we put optional limits on precision to reduce side - // channels - _precision: wasm32::__wasi_timestamp_t, - time_ptr: wasm32::uintptr_t, -) -> wasm32::__wasi_errno_t { - let mut vmctx = unsafe { Vmctx::from_raw(vmctx) }; - - // convert the supported clocks to the libc types, or return EINVAL - let clock_id = match dec_clockid(clock_id) { - host::__WASI_CLOCK_REALTIME => libc::CLOCK_REALTIME, - host::__WASI_CLOCK_MONOTONIC => libc::CLOCK_MONOTONIC, - host::__WASI_CLOCK_PROCESS_CPUTIME_ID => libc::CLOCK_PROCESS_CPUTIME_ID, - host::__WASI_CLOCK_THREAD_CPUTIME_ID => libc::CLOCK_THREAD_CPUTIME_ID, - _ => return wasm32::__WASI_EINVAL, - }; - - // no `nix` wrapper for clock_getres, so we do it ourselves - let mut timespec = unsafe { std::mem::uninitialized::() }; - let res = unsafe { libc::clock_gettime(clock_id, &mut timespec as *mut libc::timespec) }; - if res != 0 { - return wasm32::errno_from_nix(nix::errno::Errno::last()); - } + environ.push(env_ptr); - // convert to nanoseconds, returning EOVERFLOW in case of overflow; this is freelancing a bit - // from the spec but seems like it'll be an unusual situation to hit - (timespec.tv_sec as host::__wasi_timestamp_t) - .checked_mul(1_000_000_000) - .and_then(|sec_ns| sec_ns.checked_add(timespec.tv_nsec as host::__wasi_timestamp_t)) - .map(|time| unsafe { - enc_timestamp_byref(&mut vmctx, time_ptr, time) + environ_buf_offset = if let Some(new_offset) = environ_buf_offset.checked_add( + wasm32::uintptr_t::cast(env_bytes.len()) + .expect("cast overflow would have been caught by `enc_slice_of` above"), + ) { + new_offset + } else { + return wasm32::__WASI_EOVERFLOW; + } + } + + unsafe { + enc_slice_of(vmctx, environ.as_slice(), environ_ptr) .map(|_| wasm32::__WASI_ESUCCESS) .unwrap_or_else(|e| e) - }) - .unwrap_or(wasm32::__WASI_EOVERFLOW) -} - -#[no_mangle] -pub extern "C" fn __wasi_environ_get( - vmctx_raw: *mut lucet_vmctx, - environ_ptr: wasm32::uintptr_t, - environ_buf: wasm32::uintptr_t, -) -> wasm32::__wasi_errno_t { - let mut vmctx = unsafe { Vmctx::from_raw(vmctx_raw) }; - let ctx: &WasiCtx = vmctx.get_embed_ctx(); - - let mut environ_buf_offset = 0; - let mut environ = vec![]; + } + } - for pair in ctx.env.iter() { - let env_bytes = pair.as_bytes_with_nul(); - let env_ptr = environ_buf + environ_buf_offset; + #[no_mangle] + pub unsafe extern "C" fn __wasi_environ_sizes_get( + &mut vmctx, + environ_count_ptr: wasm32::uintptr_t, + environ_size_ptr: wasm32::uintptr_t, + ) -> wasm32::__wasi_errno_t { + let ctx: &WasiCtx = vmctx.get_embed_ctx(); + + let environ_count = ctx.env.len(); + if let Some(environ_size) = ctx.env.iter().try_fold(0, |acc: u32, pair| { + acc.checked_add(pair.as_bytes_with_nul().len() as u32) + }) { + unsafe { + if let Err(e) = enc_usize_byref(vmctx, environ_count_ptr, environ_count) { + return enc_errno(e); + } + if let Err(e) = enc_usize_byref(vmctx, environ_size_ptr, environ_size as usize) { + return enc_errno(e); + } + } + wasm32::__WASI_ESUCCESS + } else { + wasm32::__WASI_EOVERFLOW + } + } - // nasty aliasing here, but we aren't interfering with the borrow for `ctx` - // TODO: rework vmctx interface to avoid this - let mut vmctx = unsafe { Vmctx::from_raw(vmctx_raw) }; - if let Err(e) = unsafe { enc_slice_of(&mut vmctx, env_bytes, env_ptr) } { - return enc_errno(e); + #[no_mangle] + pub unsafe extern "C" fn __wasi_fd_close( + &mut vmctx, + fd: wasm32::__wasi_fd_t, + ) -> wasm32::__wasi_errno_t { + let ctx: &mut WasiCtx = vmctx.get_embed_ctx_mut(); + let fd = dec_fd(fd); + if let Some(fdent) = ctx.fds.get(&fd) { + // can't close preopened files + if fdent.preopen_path.is_some() { + return wasm32::__WASI_ENOTSUP; + } } + if let Some(mut fdent) = ctx.fds.remove(&fd) { + fdent.fd_object.needs_close = false; + match nix::unistd::close(fdent.fd_object.rawfd) { + Ok(_) => wasm32::__WASI_ESUCCESS, + Err(e) => wasm32::errno_from_nix(e.as_errno().unwrap()), + } + } else { + wasm32::__WASI_EBADF + } + } - environ.push(env_ptr); + #[no_mangle] + pub unsafe extern "C" fn __wasi_fd_fdstat_get( + &mut vmctx, + fd: wasm32::__wasi_fd_t, + fdstat_ptr: wasm32::uintptr_t, // *mut wasm32::__wasi_fdstat_t + ) -> wasm32::__wasi_errno_t { + let host_fd = dec_fd(fd); + let mut host_fdstat = match unsafe { dec_fdstat_byref(vmctx, fdstat_ptr) } { + Ok(host_fdstat) => host_fdstat, + Err(e) => return enc_errno(e), + }; - environ_buf_offset = if let Some(new_offset) = environ_buf_offset.checked_add( - wasm32::uintptr_t::cast(env_bytes.len()) - .expect("cast overflow would have been caught by `enc_slice_of` above"), - ) { - new_offset + let ctx: &mut WasiCtx = vmctx.get_embed_ctx_mut(); + let errno = if let Some(fe) = ctx.fds.get(&host_fd) { + host_fdstat.fs_filetype = fe.fd_object.ty; + host_fdstat.fs_rights_base = fe.rights_base; + host_fdstat.fs_rights_inheriting = fe.rights_inheriting; + use nix::fcntl::{fcntl, OFlag, F_GETFL}; + match fcntl(fe.fd_object.rawfd, F_GETFL).map(OFlag::from_bits_truncate) { + Ok(flags) => { + host_fdstat.fs_flags = host::fdflags_from_nix(flags); + wasm32::__WASI_ESUCCESS + } + Err(e) => wasm32::errno_from_nix(e.as_errno().unwrap()), + } } else { - return wasm32::__WASI_EOVERFLOW; + wasm32::__WASI_EBADF + }; + + unsafe { + enc_fdstat_byref(vmctx, fdstat_ptr, host_fdstat) + .expect("can write back into the pointer we read from"); } + + errno } - unsafe { - enc_slice_of(&mut vmctx, environ.as_slice(), environ_ptr) - .map(|_| wasm32::__WASI_ESUCCESS) - .unwrap_or_else(|e| e) + #[no_mangle] + pub unsafe extern "C" fn __wasi_fd_fdstat_set_flags( + &mut vmctx, + fd: wasm32::__wasi_fd_t, + fdflags: wasm32::__wasi_fdflags_t, + ) -> wasm32::__wasi_errno_t { + let host_fd = dec_fd(fd); + let host_fdflags = dec_fdflags(fdflags); + let nix_flags = host::nix_from_fdflags(host_fdflags); + + let ctx: &mut WasiCtx = vmctx.get_embed_ctx_mut(); + + if let Some(fe) = ctx.fds.get(&host_fd) { + match nix::fcntl::fcntl(fe.fd_object.rawfd, nix::fcntl::F_SETFL(nix_flags)) { + Ok(_) => wasm32::__WASI_ESUCCESS, + Err(e) => wasm32::errno_from_nix(e.as_errno().unwrap()), + } + } else { + wasm32::__WASI_EBADF + } } -} -#[no_mangle] -pub extern "C" fn __wasi_environ_sizes_get( - vmctx: *mut lucet_vmctx, - environ_count_ptr: wasm32::uintptr_t, - environ_size_ptr: wasm32::uintptr_t, -) -> wasm32::__wasi_errno_t { - let mut vmctx = unsafe { Vmctx::from_raw(vmctx) }; + #[no_mangle] + pub unsafe extern "C" fn __wasi_fd_seek( + &mut vmctx, + fd: wasm32::__wasi_fd_t, + offset: wasm32::__wasi_filedelta_t, + whence: wasm32::__wasi_whence_t, + newoffset: wasm32::uintptr_t, + ) -> wasm32::__wasi_errno_t { + let ctx: &mut WasiCtx = vmctx.get_embed_ctx_mut(); + let fd = dec_fd(fd); + let offset = dec_filedelta(offset); + let whence = dec_whence(whence); + + let host_newoffset = { + use nix::unistd::{lseek, Whence}; + let nwhence = match whence as u32 { + host::__WASI_WHENCE_CUR => Whence::SeekCur, + host::__WASI_WHENCE_END => Whence::SeekEnd, + host::__WASI_WHENCE_SET => Whence::SeekSet, + _ => return wasm32::__WASI_EINVAL, + }; - let ctx: &WasiCtx = vmctx.get_embed_ctx(); + let rights = if offset == 0 && whence as u32 == host::__WASI_WHENCE_CUR { + host::__WASI_RIGHT_FD_TELL + } else { + host::__WASI_RIGHT_FD_SEEK | host::__WASI_RIGHT_FD_TELL + }; + match ctx.get_fd_entry(fd, rights.into(), 0) { + Ok(fe) => match lseek(fe.fd_object.rawfd, offset, nwhence) { + Ok(newoffset) => newoffset, + Err(e) => return wasm32::errno_from_nix(e.as_errno().unwrap()), + }, + Err(e) => return enc_errno(e), + } + }; - let environ_count = ctx.env.len(); - if let Some(environ_size) = ctx.env.iter().try_fold(0, |acc: u32, pair| { - acc.checked_add(pair.as_bytes_with_nul().len() as u32) - }) { unsafe { - if let Err(e) = enc_usize_byref(&mut vmctx, environ_count_ptr, environ_count) { - return enc_errno(e); - } - if let Err(e) = enc_usize_byref(&mut vmctx, environ_size_ptr, environ_size as usize) { - return enc_errno(e); - } + enc_filesize_byref(vmctx, newoffset, host_newoffset as u64) + .map(|_| wasm32::__WASI_ESUCCESS) + .unwrap_or_else(|e| e) } - wasm32::__WASI_ESUCCESS - } else { - wasm32::__WASI_EOVERFLOW } -} -#[no_mangle] -pub extern "C" fn __wasi_fd_close( - vmctx: *mut lucet_vmctx, - fd: wasm32::__wasi_fd_t, -) -> wasm32::__wasi_errno_t { - let mut vmctx = unsafe { Vmctx::from_raw(vmctx) }; - let ctx: &mut WasiCtx = vmctx.get_embed_ctx_mut(); - let fd = dec_fd(fd); - if let Some(fdent) = ctx.fds.get(&fd) { - // can't close preopened files - if fdent.preopen_path.is_some() { - return wasm32::__WASI_ENOTSUP; + #[no_mangle] + pub unsafe extern "C" fn __wasi_fd_prestat_get( + &mut vmctx, + fd: wasm32::__wasi_fd_t, + prestat_ptr: wasm32::uintptr_t, + ) -> wasm32::__wasi_errno_t { + let ctx: &WasiCtx = vmctx.get_embed_ctx(); + let fd = dec_fd(fd); + // TODO: is this the correct right for this? + match ctx.get_fd_entry(fd, host::__WASI_RIGHT_PATH_OPEN.into(), 0) { + Ok(fe) => { + if let Some(po_path) = &fe.preopen_path { + if fe.fd_object.ty != host::__WASI_FILETYPE_DIRECTORY as host::__wasi_filetype_t { + return wasm32::__WASI_ENOTDIR; + } + // nasty aliasing here, but we aren't interfering with the borrow for `ctx` + // TODO: rework vmctx interface to avoid this + unsafe { + enc_prestat_byref( + &mut Vmctx::from_raw(vmctx.as_raw()), + prestat_ptr, + host::__wasi_prestat_t { + pr_type: host::__WASI_PREOPENTYPE_DIR as host::__wasi_preopentype_t, + u: host::__wasi_prestat_t___wasi_prestat_u { + dir: + host::__wasi_prestat_t___wasi_prestat_u___wasi_prestat_u_dir_t { + pr_name_len: po_path.as_os_str().as_bytes().len(), + }, + }, + }, + ) + .map(|_| wasm32::__WASI_ESUCCESS) + .unwrap_or_else(|e| e) + } + } else { + wasm32::__WASI_ENOTSUP + } + } + Err(e) => enc_errno(e), } } - if let Some(mut fdent) = ctx.fds.remove(&fd) { - fdent.fd_object.needs_close = false; - match nix::unistd::close(fdent.fd_object.rawfd) { - Ok(_) => wasm32::__WASI_ESUCCESS, - Err(e) => wasm32::errno_from_nix(e.as_errno().unwrap()), + + #[no_mangle] + pub unsafe extern "C" fn __wasi_fd_prestat_dir_name( + &mut vmctx, + fd: wasm32::__wasi_fd_t, + path_ptr: wasm32::uintptr_t, + path_len: wasm32::size_t, + ) -> wasm32::__wasi_errno_t { + let ctx: &WasiCtx = vmctx.get_embed_ctx(); + let fd = dec_fd(fd); + match ctx.get_fd_entry(fd, host::__WASI_RIGHT_PATH_OPEN.into(), 0) { + Ok(fe) => { + if let Some(po_path) = &fe.preopen_path { + if fe.fd_object.ty != host::__WASI_FILETYPE_DIRECTORY as host::__wasi_filetype_t { + return wasm32::__WASI_ENOTDIR; + } + let path_bytes = po_path.as_os_str().as_bytes(); + if path_bytes.len() > dec_usize(path_len) { + return wasm32::__WASI_ENAMETOOLONG; + } + // nasty aliasing here, but we aren't interfering with the borrow for `ctx` + // TODO: rework vmctx interface to avoid this + unsafe { + enc_slice_of(&mut Vmctx::from_raw(vmctx.as_raw()), path_bytes, path_ptr) + .map(|_| wasm32::__WASI_ESUCCESS) + .unwrap_or_else(|e| e) + } + } else { + wasm32::__WASI_ENOTSUP + } + } + Err(e) => enc_errno(e), } - } else { - wasm32::__WASI_EBADF } -} -#[no_mangle] -pub extern "C" fn __wasi_fd_fdstat_get( - vmctx: *mut lucet_vmctx, - fd: wasm32::__wasi_fd_t, - fdstat_ptr: wasm32::uintptr_t, // *mut wasm32::__wasi_fdstat_t -) -> wasm32::__wasi_errno_t { - let mut vmctx = unsafe { Vmctx::from_raw(vmctx) }; - - let host_fd = dec_fd(fd); - let mut host_fdstat = match unsafe { dec_fdstat_byref(&mut vmctx, fdstat_ptr) } { - Ok(host_fdstat) => host_fdstat, - Err(e) => return enc_errno(e), - }; - - let ctx: &mut WasiCtx = vmctx.get_embed_ctx_mut(); - let errno = if let Some(fe) = ctx.fds.get(&host_fd) { - host_fdstat.fs_filetype = fe.fd_object.ty; - host_fdstat.fs_rights_base = fe.rights_base; - host_fdstat.fs_rights_inheriting = fe.rights_inheriting; - use nix::fcntl::{fcntl, OFlag, F_GETFL}; - match fcntl(fe.fd_object.rawfd, F_GETFL).map(OFlag::from_bits_truncate) { - Ok(flags) => { - host_fdstat.fs_flags = host::fdflags_from_nix(flags); - wasm32::__WASI_ESUCCESS - } - Err(e) => wasm32::errno_from_nix(e.as_errno().unwrap()), + #[no_mangle] + pub unsafe extern "C" fn __wasi_fd_read( + &mut vmctx, + fd: wasm32::__wasi_fd_t, + iovs_ptr: wasm32::uintptr_t, + iovs_len: wasm32::size_t, + nread: wasm32::uintptr_t, + ) -> wasm32::__wasi_errno_t { + use nix::sys::uio::{readv, IoVec}; + + let fd = dec_fd(fd); + let mut iovs = match unsafe { dec_ciovec_slice(vmctx, iovs_ptr, iovs_len) } { + Ok(iovs) => iovs, + Err(e) => return enc_errno(e), + }; + + let ctx: &mut WasiCtx = vmctx.get_embed_ctx_mut(); + let fe = match ctx.get_fd_entry(fd, host::__WASI_RIGHT_FD_READ.into(), 0) { + Ok(fe) => fe, + Err(e) => return enc_errno(e), + }; + + let mut iovs: Vec> = iovs + .iter_mut() + .map(|iov| unsafe { host::ciovec_to_nix_mut(iov) }) + .collect(); + + let full_nread = iovs.iter().map(|iov| iov.as_slice().len()).sum(); + + let host_nread = match readv(fe.fd_object.rawfd, &mut iovs) { + Ok(len) => len, + Err(e) => return wasm32::errno_from_nix(e.as_errno().unwrap()), + }; + + if host_nread < full_nread { + // we hit eof, so remove the fdentry from the context + let mut fe = ctx.fds.remove(&fd).expect("file entry is still there"); + fe.fd_object.needs_close = false; } - } else { - wasm32::__WASI_EBADF - }; - unsafe { - enc_fdstat_byref(&mut vmctx, fdstat_ptr, host_fdstat) - .expect("can write back into the pointer we read from"); + unsafe { + enc_usize_byref(vmctx, nread, host_nread) + .map(|_| wasm32::__WASI_ESUCCESS) + .unwrap_or_else(|e| e) + } } - errno -} + #[no_mangle] + pub unsafe extern "C" fn __wasi_fd_write( + &mut vmctx, + fd: wasm32::__wasi_fd_t, + iovs_ptr: wasm32::uintptr_t, + iovs_len: wasm32::size_t, + nwritten: wasm32::uintptr_t, + ) -> wasm32::__wasi_errno_t { + use nix::sys::uio::{writev, IoVec}; + + let fd = dec_fd(fd); + let iovs = match unsafe { dec_ciovec_slice(vmctx, iovs_ptr, iovs_len) } { + Ok(iovs) => iovs, + Err(e) => return enc_errno(e), + }; -#[no_mangle] -pub extern "C" fn __wasi_fd_fdstat_set_flags( - vmctx: *mut lucet_vmctx, - fd: wasm32::__wasi_fd_t, - fdflags: wasm32::__wasi_fdflags_t, -) -> wasm32::__wasi_errno_t { - let mut vmctx = unsafe { Vmctx::from_raw(vmctx) }; + let ctx: &mut WasiCtx = vmctx.get_embed_ctx_mut(); + let fe = match ctx.get_fd_entry(fd, host::__WASI_RIGHT_FD_WRITE.into(), 0) { + Ok(fe) => fe, + Err(e) => return enc_errno(e), + }; - let host_fd = dec_fd(fd); - let host_fdflags = dec_fdflags(fdflags); - let nix_flags = host::nix_from_fdflags(host_fdflags); + let iovs: Vec> = iovs + .iter() + .map(|iov| unsafe { host::ciovec_to_nix(iov) }) + .collect(); - let ctx: &mut WasiCtx = vmctx.get_embed_ctx_mut(); + let host_nwritten = match writev(fe.fd_object.rawfd, &iovs) { + Ok(len) => len, + Err(e) => return wasm32::errno_from_nix(e.as_errno().unwrap()), + }; - if let Some(fe) = ctx.fds.get(&host_fd) { - match nix::fcntl::fcntl(fe.fd_object.rawfd, nix::fcntl::F_SETFL(nix_flags)) { - Ok(_) => wasm32::__WASI_ESUCCESS, - Err(e) => wasm32::errno_from_nix(e.as_errno().unwrap()), + unsafe { + enc_usize_byref(vmctx, nwritten, host_nwritten) + .map(|_| wasm32::__WASI_ESUCCESS) + .unwrap_or_else(|e| e) } - } else { - wasm32::__WASI_EBADF } -} -#[no_mangle] -pub extern "C" fn __wasi_fd_seek( - vmctx: *mut lucet_vmctx, - fd: wasm32::__wasi_fd_t, - offset: wasm32::__wasi_filedelta_t, - whence: wasm32::__wasi_whence_t, - newoffset: wasm32::uintptr_t, -) -> wasm32::__wasi_errno_t { - let mut vmctx = unsafe { Vmctx::from_raw(vmctx) }; - let ctx: &mut WasiCtx = vmctx.get_embed_ctx_mut(); - let fd = dec_fd(fd); - let offset = dec_filedelta(offset); - let whence = dec_whence(whence); - - let host_newoffset = { - use nix::unistd::{lseek, Whence}; - let nwhence = match whence as u32 { - host::__WASI_WHENCE_CUR => Whence::SeekCur, - host::__WASI_WHENCE_END => Whence::SeekEnd, - host::__WASI_WHENCE_SET => Whence::SeekSet, - _ => return wasm32::__WASI_EINVAL, + #[no_mangle] + pub unsafe extern "C" fn __wasi_path_open( + &mut vmctx, + dirfd: wasm32::__wasi_fd_t, + dirflags: wasm32::__wasi_lookupflags_t, + path_ptr: wasm32::uintptr_t, + path_len: wasm32::size_t, + oflags: wasm32::__wasi_oflags_t, + fs_rights_base: wasm32::__wasi_rights_t, + fs_rights_inheriting: wasm32::__wasi_rights_t, + fs_flags: wasm32::__wasi_fdflags_t, + fd_out_ptr: wasm32::uintptr_t, + ) -> wasm32::__wasi_errno_t { + use nix::errno::Errno; + use nix::fcntl::{openat, AtFlags, OFlag}; + use nix::sys::stat::{fstatat, Mode, SFlag}; + + let dirfd = dec_fd(dirfd); + let dirflags = dec_lookupflags(dirflags); + let oflags = dec_oflags(oflags); + let fs_rights_base = dec_rights(fs_rights_base); + let fs_rights_inheriting = dec_rights(fs_rights_inheriting); + let fs_flags = dec_fdflags(fs_flags); + + // which open mode do we need? + let read = fs_rights_base + & ((host::__WASI_RIGHT_FD_READ | host::__WASI_RIGHT_FD_READDIR) as host::__wasi_rights_t) + != 0; + let write = fs_rights_base + & ((host::__WASI_RIGHT_FD_DATASYNC + | host::__WASI_RIGHT_FD_WRITE + | host::__WASI_RIGHT_FD_ALLOCATE + | host::__WASI_RIGHT_PATH_FILESTAT_SET_SIZE) as host::__wasi_rights_t) + != 0; + + let mut nix_all_oflags = if read && write { + OFlag::O_RDWR + } else if read { + OFlag::O_RDONLY + } else { + OFlag::O_WRONLY }; - let rights = if offset == 0 && whence as u32 == host::__WASI_WHENCE_CUR { - host::__WASI_RIGHT_FD_TELL - } else { - host::__WASI_RIGHT_FD_SEEK | host::__WASI_RIGHT_FD_TELL + // on non-Capsicum systems, we always want nofollow + nix_all_oflags.insert(OFlag::O_NOFOLLOW); + + // which rights are needed on the dirfd? + let mut needed_base = host::__WASI_RIGHT_PATH_OPEN as host::__wasi_rights_t; + let mut needed_inheriting = fs_rights_base | fs_rights_inheriting; + + // convert open flags + let nix_oflags = host::nix_from_oflags(oflags); + nix_all_oflags.insert(nix_oflags); + if nix_all_oflags.contains(OFlag::O_CREAT) { + needed_base |= host::__WASI_RIGHT_PATH_CREATE_FILE as host::__wasi_rights_t; + } + if nix_all_oflags.contains(OFlag::O_TRUNC) { + needed_inheriting |= host::__WASI_RIGHT_PATH_FILESTAT_SET_SIZE as host::__wasi_rights_t; + } + + // convert file descriptor flags + nix_all_oflags.insert(host::nix_from_fdflags(fs_flags)); + if nix_all_oflags.contains(OFlag::O_DSYNC) { + needed_inheriting |= host::__WASI_RIGHT_FD_DATASYNC as host::__wasi_rights_t; + } + if nix_all_oflags.intersects(O_RSYNC | OFlag::O_SYNC) { + needed_inheriting |= host::__WASI_RIGHT_FD_SYNC as host::__wasi_rights_t; + } + + let path = match unsafe { dec_slice_of::(vmctx, path_ptr, path_len) } { + Ok((ptr, len)) => OsStr::from_bytes(unsafe { std::slice::from_raw_parts(ptr, len) }), + Err(e) => return enc_errno(e), }; - match ctx.get_fd_entry(fd, rights.into(), 0) { - Ok(fe) => match lseek(fe.fd_object.rawfd, offset, nwhence) { - Ok(newoffset) => newoffset, - Err(e) => return wasm32::errno_from_nix(e.as_errno().unwrap()), - }, + + let (dir, path) = match path_get( + &vmctx, + dirfd, + dirflags, + path, + needed_base, + needed_inheriting, + nix_oflags.contains(OFlag::O_CREAT), + ) { + Ok((dir, path)) => (dir, path), Err(e) => return enc_errno(e), + }; + + let new_fd = match openat( + dir, + path.as_os_str(), + nix_all_oflags, + Mode::from_bits_truncate(0o777), + ) { + Ok(fd) => fd, + Err(e) => { + match e.as_errno() { + // Linux returns ENXIO instead of EOPNOTSUPP when opening a socket + Some(Errno::ENXIO) => { + if let Ok(stat) = fstatat(dir, path.as_os_str(), AtFlags::AT_SYMLINK_NOFOLLOW) { + if SFlag::from_bits_truncate(stat.st_mode).contains(SFlag::S_IFSOCK) { + return wasm32::__WASI_ENOTSUP; + } else { + return wasm32::__WASI_ENXIO; + } + } else { + return wasm32::__WASI_ENXIO; + } + } + Some(e) => return wasm32::errno_from_nix(e), + None => return wasm32::__WASI_ENOSYS, + } + } + }; + + // Determine the type of the new file descriptor and which rights contradict with this type + let guest_fd = match unsafe { determine_type_rights(new_fd) } { + Err(e) => { + // if `close` fails, note it but do not override the underlying errno + nix::unistd::close(new_fd).unwrap_or_else(|e| { + dbg!(e); + }); + return enc_errno(e); + } + Ok((_ty, max_base, max_inheriting)) => { + let mut fe = unsafe { FdEntry::from_raw_fd(new_fd) }; + fe.rights_base &= max_base; + fe.rights_inheriting &= max_inheriting; + match vmctx.get_embed_ctx_mut::().insert_fd_entry(fe) { + Ok(fd) => fd, + Err(e) => return enc_errno(e), + } + } + }; + + unsafe { + enc_fd_byref(vmctx, fd_out_ptr, guest_fd) + .map(|_| wasm32::__WASI_ESUCCESS) + .unwrap_or_else(|e| e) } - }; + } - unsafe { - enc_filesize_byref(&mut vmctx, newoffset, host_newoffset as u64) - .map(|_| wasm32::__WASI_ESUCCESS) - .unwrap_or_else(|e| e) + #[no_mangle] + pub unsafe extern "C" fn __wasi_random_get( + &mut vmctx, + buf_ptr: wasm32::uintptr_t, + buf_len: wasm32::size_t, + ) -> wasm32::__wasi_errno_t { + use rand::{thread_rng, RngCore}; + + let buf_len = dec_usize(buf_len); + let buf_ptr = match unsafe { dec_ptr(vmctx, buf_ptr, buf_len) } { + Ok(ptr) => ptr, + Err(e) => return enc_errno(e), + }; + + let buf = unsafe { std::slice::from_raw_parts_mut(buf_ptr, buf_len) }; + + thread_rng().fill_bytes(buf); + + return wasm32::__WASI_ESUCCESS; + } + + #[no_mangle] + pub unsafe extern "C" fn __wasi_poll_oneoff( + &mut vmctx, + input: wasm32::uintptr_t, + output: wasm32::uintptr_t, + nsubscriptions: wasm32::size_t, + nevents: wasm32::uintptr_t, + ) -> wasm32::__wasi_errno_t { + if nsubscriptions as u64 > wasm32::__wasi_filesize_t::max_value() { + return wasm32::__WASI_EINVAL; + } + unsafe { enc_pointee(vmctx, nevents, 0) }.unwrap(); + let input_slice_ = + unsafe { dec_slice_of::(vmctx, input, nsubscriptions) } + .unwrap(); + let input_slice = unsafe { slice::from_raw_parts(input_slice_.0, input_slice_.1) }; + + let output_slice_ = + unsafe { dec_slice_of::(vmctx, output, nsubscriptions) } + .unwrap(); + let output_slice = unsafe { slice::from_raw_parts_mut(output_slice_.0, output_slice_.1) }; + + let input: Vec<_> = input_slice.iter().map(|x| dec_subscription(x)).collect(); + + let timeout = input + .iter() + .filter_map(|event| match event { + Ok(event) if event.type_ == wasm32::__WASI_EVENTTYPE_CLOCK => Some(ClockEventData { + delay: wasi_clock_to_relative_ns_delay(unsafe { event.u.clock }) / 1_000_000, + userdata: event.userdata, + }), + _ => None, + }) + .min_by_key(|event| event.delay); + let fd_events: Vec<_> = input + .iter() + .filter_map(|event| match event { + Ok(event) + if event.type_ == wasm32::__WASI_EVENTTYPE_FD_READ + || event.type_ == wasm32::__WASI_EVENTTYPE_FD_WRITE => + { + Some(FdEventData { + fd: unsafe { event.u.fd_readwrite.fd } as c_int, + type_: event.type_, + userdata: event.userdata, + }) + } + _ => None, + }) + .collect(); + if fd_events.is_empty() && timeout.is_none() { + return wasm32::__WASI_ESUCCESS; + } + let mut poll_fds: Vec<_> = fd_events + .iter() + .map(|event| { + let mut flags = nix::poll::EventFlags::empty(); + match event.type_ { + wasm32::__WASI_EVENTTYPE_FD_READ => flags.insert(nix::poll::EventFlags::POLLIN), + wasm32::__WASI_EVENTTYPE_FD_WRITE => flags.insert(nix::poll::EventFlags::POLLOUT), + // An event on a file descriptor can currently only be of type FD_READ or FD_WRITE + // Nothing else has been defined in the specification, and these are also the only two + // events we filtered before. If we get something else here, the code has a serious bug. + _ => unreachable!(), + }; + nix::poll::PollFd::new(event.fd, flags) + }) + .collect(); + let timeout = timeout.map(|ClockEventData { delay, userdata }| ClockEventData { + delay: cmp::min(delay, c_int::max_value() as u128), + userdata, + }); + let poll_timeout = timeout.map(|timeout| timeout.delay as c_int).unwrap_or(-1); + let ready = loop { + match nix::poll::poll(&mut poll_fds, poll_timeout) { + Err(_) => { + if nix::errno::Errno::last() == nix::errno::Errno::EINTR { + continue; + } + return wasm32::errno_from_nix(nix::errno::Errno::last()); + } + Ok(ready) => break ready as usize, + } + }; + if ready == 0 { + return __wasi_poll_oneoff_handle_timeout_event(vmctx, output_slice, nevents, timeout); + } + let events = fd_events.iter().zip(poll_fds.iter()).take(ready); + __wasi_poll_oneoff_handle_fd_event(vmctx, output_slice, nevents, events) + } +} + +// define the `fionread()` function, equivalent to `ioctl(fd, FIONREAD, *bytes)` +nix::ioctl_read_bad!(fionread, nix::libc::FIONREAD, c_int); + +fn wasi_clock_to_relative_ns_delay( + wasi_clock: host::__wasi_subscription_t___wasi_subscription_u___wasi_subscription_u_clock_t, +) -> u128 { + if wasi_clock.flags != wasm32::__WASI_SUBSCRIPTION_CLOCK_ABSTIME { + return wasi_clock.timeout as u128; + } + let now: u128 = SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .expect("Current date is before the epoch") + .as_nanos(); + let deadline = wasi_clock.timeout as u128; + deadline.saturating_sub(now) +} + +#[derive(Debug, Copy, Clone)] +struct ClockEventData { + delay: u128, + userdata: host::__wasi_userdata_t, +} +#[derive(Debug, Copy, Clone)] +struct FdEventData { + fd: c_int, + type_: host::__wasi_eventtype_t, + userdata: host::__wasi_userdata_t, +} + +fn __wasi_poll_oneoff_handle_timeout_event( + vmctx: &mut Vmctx, + output_slice: &mut [wasm32::__wasi_event_t], + nevents: wasm32::uintptr_t, + timeout: Option, +) -> wasm32::__wasi_errno_t { + if let Some(ClockEventData { userdata, .. }) = timeout { + let output_event = host::__wasi_event_t { + userdata, + type_: wasm32::__WASI_EVENTTYPE_CLOCK, + error: wasm32::__WASI_ESUCCESS, + u: host::__wasi_event_t___wasi_event_u { + fd_readwrite: host::__wasi_event_t___wasi_event_u___wasi_event_u_fd_readwrite_t { + nbytes: 0, + flags: 0, + }, + }, + }; + output_slice[0] = enc_event(output_event); + if let Err(e) = unsafe { enc_pointee(vmctx, nevents, 1) } { + return enc_errno(e); + } + } else { + // shouldn't happen + if let Err(e) = unsafe { enc_pointee(vmctx, nevents, 0) } { + return enc_errno(e); + } } + wasm32::__WASI_ESUCCESS } -#[no_mangle] -pub extern "C" fn __wasi_fd_prestat_get( - vmctx_raw: *mut lucet_vmctx, - fd: wasm32::__wasi_fd_t, - prestat_ptr: wasm32::uintptr_t, +fn __wasi_poll_oneoff_handle_fd_event<'t>( + vmctx: &mut Vmctx, + output_slice: &mut [wasm32::__wasi_event_t], + nevents: wasm32::uintptr_t, + events: impl Iterator, ) -> wasm32::__wasi_errno_t { - let vmctx = unsafe { Vmctx::from_raw(vmctx_raw) }; - let ctx: &WasiCtx = vmctx.get_embed_ctx(); - let fd = dec_fd(fd); - // TODO: is this the correct right for this? - match ctx.get_fd_entry(fd, host::__WASI_RIGHT_PATH_OPEN.into(), 0) { - Ok(fe) => { - if let Some(po_path) = &fe.preopen_path { - if fe.fd_object.ty != host::__WASI_FILETYPE_DIRECTORY as host::__wasi_filetype_t { - return wasm32::__WASI_ENOTDIR; - } - // nasty aliasing here, but we aren't interfering with the borrow for `ctx` - // TODO: rework vmctx interface to avoid this - unsafe { - enc_prestat_byref( - &mut Vmctx::from_raw(vmctx_raw), - prestat_ptr, - host::__wasi_prestat_t { - pr_type: host::__WASI_PREOPENTYPE_DIR as host::__wasi_preopentype_t, - u: host::__wasi_prestat_t___wasi_prestat_u { - dir: - host::__wasi_prestat_t___wasi_prestat_u___wasi_prestat_u_dir_t { - pr_name_len: po_path.as_os_str().as_bytes().len(), - }, - }, + let mut output_slice_cur = output_slice.iter_mut(); + let mut revents_count = 0; + for (fd_event, poll_fd) in events { + let revents = match poll_fd.revents() { + Some(revents) => revents, + None => continue, + }; + let mut nbytes = 0; + if fd_event.type_ == wasm32::__WASI_EVENTTYPE_FD_READ { + let _ = unsafe { fionread(fd_event.fd, &mut nbytes) }; + } + let output_event = if revents.contains(nix::poll::EventFlags::POLLNVAL) { + host::__wasi_event_t { + userdata: fd_event.userdata, + type_: fd_event.type_, + error: wasm32::__WASI_EBADF, + u: host::__wasi_event_t___wasi_event_u { + fd_readwrite: + host::__wasi_event_t___wasi_event_u___wasi_event_u_fd_readwrite_t { + nbytes: 0, + flags: wasm32::__WASI_EVENT_FD_READWRITE_HANGUP, + }, + }, + } + } else if revents.contains(nix::poll::EventFlags::POLLERR) { + host::__wasi_event_t { + userdata: fd_event.userdata, + type_: fd_event.type_, + error: wasm32::__WASI_EIO, + u: host::__wasi_event_t___wasi_event_u { + fd_readwrite: + host::__wasi_event_t___wasi_event_u___wasi_event_u_fd_readwrite_t { + nbytes: 0, + flags: wasm32::__WASI_EVENT_FD_READWRITE_HANGUP, + }, + }, + } + } else if revents.contains(nix::poll::EventFlags::POLLHUP) { + host::__wasi_event_t { + userdata: fd_event.userdata, + type_: fd_event.type_, + error: wasm32::__WASI_ESUCCESS, + u: host::__wasi_event_t___wasi_event_u { + fd_readwrite: + host::__wasi_event_t___wasi_event_u___wasi_event_u_fd_readwrite_t { + nbytes: 0, + flags: wasm32::__WASI_EVENT_FD_READWRITE_HANGUP, }, - ) - .map(|_| wasm32::__WASI_ESUCCESS) - .unwrap_or_else(|e| e) - } - } else { - wasm32::__WASI_ENOTSUP + }, } - } - Err(e) => enc_errno(e), - } -} - -#[no_mangle] -pub extern "C" fn __wasi_fd_prestat_dir_name( - vmctx_raw: *mut lucet_vmctx, - fd: wasm32::__wasi_fd_t, - path_ptr: wasm32::uintptr_t, - path_len: wasm32::size_t, -) -> wasm32::__wasi_errno_t { - let vmctx = unsafe { Vmctx::from_raw(vmctx_raw) }; - let ctx: &WasiCtx = vmctx.get_embed_ctx(); - let fd = dec_fd(fd); - match ctx.get_fd_entry(fd, host::__WASI_RIGHT_PATH_OPEN.into(), 0) { - Ok(fe) => { - if let Some(po_path) = &fe.preopen_path { - if fe.fd_object.ty != host::__WASI_FILETYPE_DIRECTORY as host::__wasi_filetype_t { - return wasm32::__WASI_ENOTDIR; - } - let path_bytes = po_path.as_os_str().as_bytes(); - if path_bytes.len() > dec_usize(path_len) { - return wasm32::__WASI_ENAMETOOLONG; - } - // nasty aliasing here, but we aren't interfering with the borrow for `ctx` - // TODO: rework vmctx interface to avoid this - unsafe { - enc_slice_of(&mut Vmctx::from_raw(vmctx_raw), path_bytes, path_ptr) - .map(|_| wasm32::__WASI_ESUCCESS) - .unwrap_or_else(|e| e) - } - } else { - wasm32::__WASI_ENOTSUP + } else if revents.contains(nix::poll::EventFlags::POLLIN) + | revents.contains(nix::poll::EventFlags::POLLOUT) + { + host::__wasi_event_t { + userdata: fd_event.userdata, + type_: fd_event.type_, + error: wasm32::__WASI_ESUCCESS, + u: host::__wasi_event_t___wasi_event_u { + fd_readwrite: + host::__wasi_event_t___wasi_event_u___wasi_event_u_fd_readwrite_t { + nbytes: nbytes as host::__wasi_filesize_t, + flags: 0, + }, + }, } - } - Err(e) => enc_errno(e), - } -} - -#[no_mangle] -pub extern "C" fn __wasi_fd_read( - vmctx: *mut lucet_vmctx, - fd: wasm32::__wasi_fd_t, - iovs_ptr: wasm32::uintptr_t, - iovs_len: wasm32::size_t, - nread: wasm32::uintptr_t, -) -> wasm32::__wasi_errno_t { - use nix::sys::uio::{readv, IoVec}; - - let mut vmctx = unsafe { Vmctx::from_raw(vmctx) }; - let fd = dec_fd(fd); - let mut iovs = match unsafe { dec_ciovec_slice(&mut vmctx, iovs_ptr, iovs_len) } { - Ok(iovs) => iovs, - Err(e) => return enc_errno(e), - }; - - let ctx: &mut WasiCtx = vmctx.get_embed_ctx_mut(); - let fe = match ctx.get_fd_entry(fd, host::__WASI_RIGHT_FD_READ.into(), 0) { - Ok(fe) => fe, - Err(e) => return enc_errno(e), - }; - - let mut iovs: Vec> = iovs - .iter_mut() - .map(|iov| unsafe { host::ciovec_to_nix_mut(iov) }) - .collect(); - - let full_nread = iovs.iter().map(|iov| iov.as_slice().len()).sum(); - - let host_nread = match readv(fe.fd_object.rawfd, &mut iovs) { - Ok(len) => len, - Err(e) => return wasm32::errno_from_nix(e.as_errno().unwrap()), - }; - - if host_nread < full_nread { - // we hit eof, so remove the fdentry from the context - let mut fe = ctx.fds.remove(&fd).expect("file entry is still there"); - fe.fd_object.needs_close = false; - } - - unsafe { - enc_usize_byref(&mut vmctx, nread, host_nread) - .map(|_| wasm32::__WASI_ESUCCESS) - .unwrap_or_else(|e| e) - } -} - -#[no_mangle] -pub extern "C" fn __wasi_fd_write( - vmctx: *mut lucet_vmctx, - fd: wasm32::__wasi_fd_t, - iovs_ptr: wasm32::uintptr_t, - iovs_len: wasm32::size_t, - nwritten: wasm32::uintptr_t, -) -> wasm32::__wasi_errno_t { - use nix::sys::uio::{writev, IoVec}; - - let mut vmctx = unsafe { Vmctx::from_raw(vmctx) }; - let fd = dec_fd(fd); - let iovs = match unsafe { dec_ciovec_slice(&mut vmctx, iovs_ptr, iovs_len) } { - Ok(iovs) => iovs, - Err(e) => return enc_errno(e), - }; - - let ctx: &mut WasiCtx = vmctx.get_embed_ctx_mut(); - let fe = match ctx.get_fd_entry(fd, host::__WASI_RIGHT_FD_WRITE.into(), 0) { - Ok(fe) => fe, - Err(e) => return enc_errno(e), - }; - - let iovs: Vec> = iovs - .iter() - .map(|iov| unsafe { host::ciovec_to_nix(iov) }) - .collect(); - - let host_nwritten = match writev(fe.fd_object.rawfd, &iovs) { - Ok(len) => len, - Err(e) => return wasm32::errno_from_nix(e.as_errno().unwrap()), - }; - - unsafe { - enc_usize_byref(&mut vmctx, nwritten, host_nwritten) - .map(|_| wasm32::__WASI_ESUCCESS) - .unwrap_or_else(|e| e) - } -} - -#[no_mangle] -pub extern "C" fn __wasi_path_open( - vmctx: *mut lucet_vmctx, - dirfd: wasm32::__wasi_fd_t, - dirflags: wasm32::__wasi_lookupflags_t, - path_ptr: wasm32::uintptr_t, - path_len: wasm32::size_t, - oflags: wasm32::__wasi_oflags_t, - fs_rights_base: wasm32::__wasi_rights_t, - fs_rights_inheriting: wasm32::__wasi_rights_t, - fs_flags: wasm32::__wasi_fdflags_t, - fd_out_ptr: wasm32::uintptr_t, -) -> wasm32::__wasi_errno_t { - use nix::errno::Errno; - use nix::fcntl::{openat, AtFlags, OFlag}; - use nix::sys::stat::{fstatat, Mode, SFlag}; - - let dirfd = dec_fd(dirfd); - let dirflags = dec_lookupflags(dirflags); - let oflags = dec_oflags(oflags); - let fs_rights_base = dec_rights(fs_rights_base); - let fs_rights_inheriting = dec_rights(fs_rights_inheriting); - let fs_flags = dec_fdflags(fs_flags); - - // which open mode do we need? - let read = fs_rights_base - & ((host::__WASI_RIGHT_FD_READ | host::__WASI_RIGHT_FD_READDIR) as host::__wasi_rights_t) - != 0; - let write = fs_rights_base - & ((host::__WASI_RIGHT_FD_DATASYNC - | host::__WASI_RIGHT_FD_WRITE - | host::__WASI_RIGHT_FD_ALLOCATE - | host::__WASI_RIGHT_PATH_FILESTAT_SET_SIZE) as host::__wasi_rights_t) - != 0; - - let mut nix_all_oflags = if read && write { - OFlag::O_RDWR - } else if read { - OFlag::O_RDONLY - } else { - OFlag::O_WRONLY - }; - - // on non-Capsicum systems, we always want nofollow - nix_all_oflags.insert(OFlag::O_NOFOLLOW); - - // which rights are needed on the dirfd? - let mut needed_base = host::__WASI_RIGHT_PATH_OPEN as host::__wasi_rights_t; - let mut needed_inheriting = fs_rights_base | fs_rights_inheriting; - - // convert open flags - let nix_oflags = host::nix_from_oflags(oflags); - nix_all_oflags.insert(nix_oflags); - if nix_all_oflags.contains(OFlag::O_CREAT) { - needed_base |= host::__WASI_RIGHT_PATH_CREATE_FILE as host::__wasi_rights_t; - } - if nix_all_oflags.contains(OFlag::O_TRUNC) { - needed_inheriting |= host::__WASI_RIGHT_PATH_FILESTAT_SET_SIZE as host::__wasi_rights_t; - } - - // convert file descriptor flags - nix_all_oflags.insert(host::nix_from_fdflags(fs_flags)); - if nix_all_oflags.contains(OFlag::O_DSYNC) { - needed_inheriting |= host::__WASI_RIGHT_FD_DATASYNC as host::__wasi_rights_t; - } - if nix_all_oflags.intersects(O_RSYNC | OFlag::O_SYNC) { - needed_inheriting |= host::__WASI_RIGHT_FD_SYNC as host::__wasi_rights_t; + } else { + continue; + }; + *output_slice_cur.next().unwrap() = enc_event(output_event); + revents_count += 1; } - - let mut vmctx = unsafe { Vmctx::from_raw(vmctx) }; - - let path = match unsafe { dec_slice_of::(&mut vmctx, path_ptr, path_len) } { - Ok((ptr, len)) => OsStr::from_bytes(unsafe { std::slice::from_raw_parts(ptr, len) }), - Err(e) => return enc_errno(e), - }; - - let (dir, path) = match path_get( - &vmctx, - dirfd, - dirflags, - path, - needed_base, - needed_inheriting, - nix_oflags.contains(OFlag::O_CREAT), - ) { - Ok((dir, path)) => (dir, path), - Err(e) => return enc_errno(e), - }; - - let new_fd = match openat( - dir, - path.as_os_str(), - nix_all_oflags, - Mode::from_bits_truncate(0o777), - ) { - Ok(fd) => fd, - Err(e) => { - match e.as_errno() { - // Linux returns ENXIO instead of EOPNOTSUPP when opening a socket - Some(Errno::ENXIO) => { - if let Ok(stat) = fstatat(dir, path.as_os_str(), AtFlags::AT_SYMLINK_NOFOLLOW) { - if SFlag::from_bits_truncate(stat.st_mode).contains(SFlag::S_IFSOCK) { - return wasm32::__WASI_ENOTSUP; - } else { - return wasm32::__WASI_ENXIO; - } - } else { - return wasm32::__WASI_ENXIO; - } - } - Some(e) => return wasm32::errno_from_nix(e), - None => return wasm32::__WASI_ENOSYS, - } - } - }; - - // Determine the type of the new file descriptor and which rights contradict with this type - let guest_fd = match unsafe { determine_type_rights(new_fd) } { - Err(e) => { - // if `close` fails, note it but do not override the underlying errno - nix::unistd::close(new_fd).unwrap_or_else(|e| { - dbg!(e); - }); - return enc_errno(e); - } - Ok((_ty, max_base, max_inheriting)) => { - let mut fe = unsafe { FdEntry::from_raw_fd(new_fd) }; - fe.rights_base &= max_base; - fe.rights_inheriting &= max_inheriting; - match vmctx.get_embed_ctx_mut::().insert_fd_entry(fe) { - Ok(fd) => fd, - Err(e) => return enc_errno(e), - } - } - }; - - unsafe { - enc_fd_byref(&mut vmctx, fd_out_ptr, guest_fd) - .map(|_| wasm32::__WASI_ESUCCESS) - .unwrap_or_else(|e| e) + if let Err(e) = unsafe { enc_pointee(vmctx, nevents, revents_count) } { + return enc_errno(e); } + wasm32::__WASI_ESUCCESS } /// Normalizes a path to ensure that the target path is located under the directory provided. @@ -969,264 +1208,6 @@ pub fn path_get>( } } -#[no_mangle] -pub extern "C" fn __wasi_random_get( - vmctx: *mut lucet_vmctx, - buf_ptr: wasm32::uintptr_t, - buf_len: wasm32::size_t, -) -> wasm32::__wasi_errno_t { - use rand::{thread_rng, RngCore}; - - let mut vmctx = unsafe { Vmctx::from_raw(vmctx) }; - - let buf_len = dec_usize(buf_len); - let buf_ptr = match unsafe { dec_ptr(&mut vmctx, buf_ptr, buf_len) } { - Ok(ptr) => ptr, - Err(e) => return enc_errno(e), - }; - - let buf = unsafe { std::slice::from_raw_parts_mut(buf_ptr, buf_len) }; - - thread_rng().fill_bytes(buf); - - return wasm32::__WASI_ESUCCESS; -} - -// define the `fionread()` function, equivalent to `ioctl(fd, FIONREAD, *bytes)` -nix::ioctl_read_bad!(fionread, nix::libc::FIONREAD, c_int); - -fn wasi_clock_to_relative_ns_delay( - wasi_clock: host::__wasi_subscription_t___wasi_subscription_u___wasi_subscription_u_clock_t, -) -> u128 { - if wasi_clock.flags != wasm32::__WASI_SUBSCRIPTION_CLOCK_ABSTIME { - return wasi_clock.timeout as u128; - } - let now: u128 = SystemTime::now() - .duration_since(SystemTime::UNIX_EPOCH) - .expect("Current date is before the epoch") - .as_nanos(); - let deadline = wasi_clock.timeout as u128; - deadline.saturating_sub(now) -} - -#[derive(Debug, Copy, Clone)] -struct ClockEventData { - delay: u128, - userdata: host::__wasi_userdata_t, -} -#[derive(Debug, Copy, Clone)] -struct FdEventData { - fd: c_int, - type_: host::__wasi_eventtype_t, - userdata: host::__wasi_userdata_t, -} - -fn __wasi_poll_oneoff_handle_timeout_event( - vmctx: &mut Vmctx, - output_slice: &mut [wasm32::__wasi_event_t], - nevents: wasm32::uintptr_t, - timeout: Option, -) -> wasm32::__wasi_errno_t { - if let Some(ClockEventData { userdata, .. }) = timeout { - let output_event = host::__wasi_event_t { - userdata, - type_: wasm32::__WASI_EVENTTYPE_CLOCK, - error: wasm32::__WASI_ESUCCESS, - u: host::__wasi_event_t___wasi_event_u { - fd_readwrite: host::__wasi_event_t___wasi_event_u___wasi_event_u_fd_readwrite_t { - nbytes: 0, - flags: 0, - }, - }, - }; - output_slice[0] = enc_event(output_event); - if let Err(e) = unsafe { enc_pointee(vmctx, nevents, 1) } { - return enc_errno(e); - } - } else { - // shouldn't happen - if let Err(e) = unsafe { enc_pointee(vmctx, nevents, 0) } { - return enc_errno(e); - } - } - wasm32::__WASI_ESUCCESS -} - -fn __wasi_poll_oneoff_handle_fd_event<'t>( - vmctx: &mut Vmctx, - output_slice: &mut [wasm32::__wasi_event_t], - nevents: wasm32::uintptr_t, - events: impl Iterator, -) -> wasm32::__wasi_errno_t { - let mut output_slice_cur = output_slice.iter_mut(); - let mut revents_count = 0; - for (fd_event, poll_fd) in events { - let revents = match poll_fd.revents() { - Some(revents) => revents, - None => continue, - }; - let mut nbytes = 0; - if fd_event.type_ == wasm32::__WASI_EVENTTYPE_FD_READ { - let _ = unsafe { fionread(fd_event.fd, &mut nbytes) }; - } - let output_event = if revents.contains(nix::poll::EventFlags::POLLNVAL) { - host::__wasi_event_t { - userdata: fd_event.userdata, - type_: fd_event.type_, - error: wasm32::__WASI_EBADF, - u: host::__wasi_event_t___wasi_event_u { - fd_readwrite: - host::__wasi_event_t___wasi_event_u___wasi_event_u_fd_readwrite_t { - nbytes: 0, - flags: wasm32::__WASI_EVENT_FD_READWRITE_HANGUP, - }, - }, - } - } else if revents.contains(nix::poll::EventFlags::POLLERR) { - host::__wasi_event_t { - userdata: fd_event.userdata, - type_: fd_event.type_, - error: wasm32::__WASI_EIO, - u: host::__wasi_event_t___wasi_event_u { - fd_readwrite: - host::__wasi_event_t___wasi_event_u___wasi_event_u_fd_readwrite_t { - nbytes: 0, - flags: wasm32::__WASI_EVENT_FD_READWRITE_HANGUP, - }, - }, - } - } else if revents.contains(nix::poll::EventFlags::POLLHUP) { - host::__wasi_event_t { - userdata: fd_event.userdata, - type_: fd_event.type_, - error: wasm32::__WASI_ESUCCESS, - u: host::__wasi_event_t___wasi_event_u { - fd_readwrite: - host::__wasi_event_t___wasi_event_u___wasi_event_u_fd_readwrite_t { - nbytes: 0, - flags: wasm32::__WASI_EVENT_FD_READWRITE_HANGUP, - }, - }, - } - } else if revents.contains(nix::poll::EventFlags::POLLIN) - | revents.contains(nix::poll::EventFlags::POLLOUT) - { - host::__wasi_event_t { - userdata: fd_event.userdata, - type_: fd_event.type_, - error: wasm32::__WASI_ESUCCESS, - u: host::__wasi_event_t___wasi_event_u { - fd_readwrite: - host::__wasi_event_t___wasi_event_u___wasi_event_u_fd_readwrite_t { - nbytes: nbytes as host::__wasi_filesize_t, - flags: 0, - }, - }, - } - } else { - continue; - }; - *output_slice_cur.next().unwrap() = enc_event(output_event); - revents_count += 1; - } - if let Err(e) = unsafe { enc_pointee(vmctx, nevents, revents_count) } { - return enc_errno(e); - } - wasm32::__WASI_ESUCCESS -} - -#[no_mangle] -pub extern "C" fn __wasi_poll_oneoff( - vmctx: *mut lucet_vmctx, - input: wasm32::uintptr_t, - output: wasm32::uintptr_t, - nsubscriptions: wasm32::size_t, - nevents: wasm32::uintptr_t, -) -> wasm32::__wasi_errno_t { - if nsubscriptions as u64 > wasm32::__wasi_filesize_t::max_value() { - return wasm32::__WASI_EINVAL; - } - let mut vmctx = unsafe { Vmctx::from_raw(vmctx) }; - unsafe { enc_pointee(&mut vmctx, nevents, 0) }.unwrap(); - let input_slice_ = - unsafe { dec_slice_of::(&mut vmctx, input, nsubscriptions) } - .unwrap(); - let input_slice = unsafe { slice::from_raw_parts(input_slice_.0, input_slice_.1) }; - - let output_slice_ = - unsafe { dec_slice_of::(&mut vmctx, output, nsubscriptions) } - .unwrap(); - let output_slice = unsafe { slice::from_raw_parts_mut(output_slice_.0, output_slice_.1) }; - - let input: Vec<_> = input_slice.iter().map(|x| dec_subscription(x)).collect(); - - let timeout = input - .iter() - .filter_map(|event| match event { - Ok(event) if event.type_ == wasm32::__WASI_EVENTTYPE_CLOCK => Some(ClockEventData { - delay: wasi_clock_to_relative_ns_delay(unsafe { event.u.clock }) / 1_000_000, - userdata: event.userdata, - }), - _ => None, - }) - .min_by_key(|event| event.delay); - let fd_events: Vec<_> = input - .iter() - .filter_map(|event| match event { - Ok(event) - if event.type_ == wasm32::__WASI_EVENTTYPE_FD_READ - || event.type_ == wasm32::__WASI_EVENTTYPE_FD_WRITE => - { - Some(FdEventData { - fd: unsafe { event.u.fd_readwrite.fd } as c_int, - type_: event.type_, - userdata: event.userdata, - }) - } - _ => None, - }) - .collect(); - if fd_events.is_empty() && timeout.is_none() { - return wasm32::__WASI_ESUCCESS; - } - let mut poll_fds: Vec<_> = fd_events - .iter() - .map(|event| { - let mut flags = nix::poll::EventFlags::empty(); - match event.type_ { - wasm32::__WASI_EVENTTYPE_FD_READ => flags.insert(nix::poll::EventFlags::POLLIN), - wasm32::__WASI_EVENTTYPE_FD_WRITE => flags.insert(nix::poll::EventFlags::POLLOUT), - // An event on a file descriptor can currently only be of type FD_READ or FD_WRITE - // Nothing else has been defined in the specification, and these are also the only two - // events we filtered before. If we get something else here, the code has a serious bug. - _ => unreachable!(), - }; - nix::poll::PollFd::new(event.fd, flags) - }) - .collect(); - let timeout = timeout.map(|ClockEventData { delay, userdata }| ClockEventData { - delay: cmp::min(delay, c_int::max_value() as u128), - userdata, - }); - let poll_timeout = timeout.map(|timeout| timeout.delay as c_int).unwrap_or(-1); - let ready = loop { - match nix::poll::poll(&mut poll_fds, poll_timeout) { - Err(_) => { - if nix::errno::Errno::last() == nix::errno::Errno::EINTR { - continue; - } - return wasm32::errno_from_nix(nix::errno::Errno::last()); - } - Ok(ready) => break ready as usize, - } - }; - if ready == 0 { - return __wasi_poll_oneoff_handle_timeout_event(&mut vmctx, output_slice, nevents, timeout); - } - let events = fd_events.iter().zip(poll_fds.iter()).take(ready); - __wasi_poll_oneoff_handle_fd_event(&mut vmctx, output_slice, nevents, events) -} - #[doc(hidden)] pub fn ensure_linked() { unsafe { From 2ae55a14b6f5f66f788a1adb8485d8367965e695 Mon Sep 17 00:00:00 2001 From: "Adam C. Foltzer" Date: Thu, 25 Apr 2019 17:59:26 -0700 Subject: [PATCH 06/21] [lucet-runtime] RefCell-based `Vmctx` interface This makes almost all of the `Vmctx` methods into `&self`, so hostcalls won't have to go through as many contortions when using multiple parts of the context. To report dynamic borrowing errors, there is a new `TerminationDetails` variant. --- .../lucet-runtime-internals/src/c_api.rs | 14 +- .../lucet-runtime-internals/src/embed_ctx.rs | 30 +++-- .../lucet-runtime-internals/src/instance.rs | 48 ++++--- .../lucet-runtime-internals/src/module.rs | 1 - .../src/module/globals.rs | 113 ---------------- .../lucet-runtime-internals/src/vmctx.rs | 114 +++++++++++----- .../lucet-runtime-tests/src/globals.rs | 111 +++++++++++++++- .../lucet-runtime-tests/src/guest_fault.rs | 66 +++++----- lucet-runtime/lucet-runtime-tests/src/host.rs | 122 +++++++++++++++++- lucet-runtime/src/c_api.rs | 4 +- lucet-runtime/src/lib.rs | 4 +- lucet-wasi/src/hostcalls.rs | 44 +++---- lucet-wasi/src/memory.rs | 32 ++--- 13 files changed, 441 insertions(+), 262 deletions(-) delete mode 100644 lucet-runtime/lucet-runtime-internals/src/module/globals.rs diff --git a/lucet-runtime/lucet-runtime-internals/src/c_api.rs b/lucet-runtime/lucet-runtime-internals/src/c_api.rs index 716ca8dfe..54d6c3050 100644 --- a/lucet-runtime/lucet-runtime-internals/src/c_api.rs +++ b/lucet-runtime/lucet-runtime-internals/src/c_api.rs @@ -55,7 +55,8 @@ macro_rules! with_ffi_arcs { /// Marker type for the `vmctx` pointer argument. /// -/// This type should only be used with [`Vmctx::from_raw()`](struct.Vmctx.html#method.from_raw). +/// This type should only be used with [`Vmctx::from_raw()`](struct.Vmctx.html#method.from_raw) or +/// the C API. #[repr(C)] pub struct lucet_vmctx { _unused: [u8; 0], @@ -243,8 +244,12 @@ pub mod lucet_state { reason: lucet_terminated_reason::Signal, provided: std::ptr::null_mut(), }, - TerminationDetails::GetEmbedCtx => lucet_terminated { - reason: lucet_terminated_reason::GetEmbedCtx, + TerminationDetails::CtxNotFound => lucet_terminated { + reason: lucet_terminated_reason::CtxNotFound, + provided: std::ptr::null_mut(), + }, + TerminationDetails::BorrowError(_) => lucet_terminated { + reason: lucet_terminated_reason::BorrowError, provided: std::ptr::null_mut(), }, TerminationDetails::Provided(p) => lucet_terminated { @@ -298,7 +303,8 @@ pub mod lucet_state { #[derive(Clone, Copy)] pub enum lucet_terminated_reason { Signal, - GetEmbedCtx, + CtxNotFound, + BorrowError, Provided, } diff --git a/lucet-runtime/lucet-runtime-internals/src/embed_ctx.rs b/lucet-runtime/lucet-runtime-internals/src/embed_ctx.rs index 7b9f98f0e..3affe2217 100644 --- a/lucet-runtime/lucet-runtime-internals/src/embed_ctx.rs +++ b/lucet-runtime/lucet-runtime-internals/src/embed_ctx.rs @@ -1,4 +1,5 @@ use std::any::{Any, TypeId}; +use std::cell::{BorrowError, BorrowMutError, Ref, RefCell, RefMut}; use std::collections::HashMap; /// A map that holds at most one value of any type. @@ -6,7 +7,7 @@ use std::collections::HashMap; /// This is similar to the type provided by the `anymap` crate, but we can get away with simpler /// types on the methods due to our more specialized use case. pub struct CtxMap { - map: HashMap>, + map: HashMap>>, } impl CtxMap { @@ -18,25 +19,33 @@ impl CtxMap { self.map.contains_key(&TypeId::of::()) } - pub fn get(&self) -> Option<&T> { + pub fn try_get(&self) -> Option, BorrowError>> { self.map.get(&TypeId::of::()).map(|x| { - x.downcast_ref::() - .expect("value stored with TypeId::of:: is always type T") + x.try_borrow().map(|r| { + Ref::map(r, |b| { + b.downcast_ref::() + .expect("value stored with TypeId::of:: is always type T") + }) + }) }) } - pub fn get_mut(&mut self) -> Option<&mut T> { + pub fn try_get_mut(&mut self) -> Option, BorrowMutError>> { self.map.get_mut(&TypeId::of::()).map(|x| { - x.downcast_mut::() - .expect("value stored with TypeId::of:: is always type T") + x.try_borrow_mut().map(|r| { + RefMut::map(r, |b| { + b.downcast_mut::() + .expect("value stored with TypeId::of:: is always type T") + }) + }) }) } pub fn insert(&mut self, x: T) -> Option { self.map - .insert(TypeId::of::(), Box::new(x) as Box) + .insert(TypeId::of::(), RefCell::new(Box::new(x) as Box)) .map(|x_prev| { - *x_prev + *(x_prev.into_inner()) .downcast::() .expect("value stored with TypeId::of:: is always type T") }) @@ -50,7 +59,8 @@ impl CtxMap { pub fn remove(&mut self) -> Option { self.map.remove(&TypeId::of::()).map(|x| { - *x.downcast::() + *(x.into_inner()) + .downcast::() .expect("value stored with TypeId::of:: is always type T") }) } diff --git a/lucet-runtime/lucet-runtime-internals/src/instance.rs b/lucet-runtime/lucet-runtime-internals/src/instance.rs index b679ee17c..684278568 100644 --- a/lucet-runtime/lucet-runtime-internals/src/instance.rs +++ b/lucet-runtime/lucet-runtime-internals/src/instance.rs @@ -16,7 +16,7 @@ use crate::WASM_PAGE_SIZE; use libc::{c_void, siginfo_t, uintptr_t, SIGBUS, SIGSEGV}; use memoffset::offset_of; use std::any::Any; -use std::cell::{RefCell, UnsafeCell}; +use std::cell::{BorrowError, BorrowMutError, Ref, RefCell, RefMut, UnsafeCell}; use std::ffi::{CStr, CString}; use std::mem; use std::ops::{Deref, DerefMut}; @@ -387,13 +387,13 @@ impl Instance { } /// Get a reference to a context value of a particular type, if it exists. - pub fn get_embed_ctx(&self) -> Option<&T> { - self.embed_ctx.get::() + pub fn get_embed_ctx(&self) -> Option, BorrowError>> { + self.embed_ctx.try_get::() } /// Get a mutable reference to a context value of a particular type, if it exists. - pub fn get_embed_ctx_mut(&mut self) -> Option<&mut T> { - self.embed_ctx.get_mut::() + pub fn get_embed_ctx_mut(&mut self) -> Option, BorrowMutError>> { + self.embed_ctx.try_get_mut::() } /// Insert a context value. @@ -715,10 +715,13 @@ impl std::fmt::Display for FaultDetails { /// error has occurred in a hostcall, rather than in WebAssembly code. #[derive(Clone)] pub enum TerminationDetails { + /// Returned when a signal handler terminates the instance. Signal, - GetEmbedCtx, - /// Calls to `Vmctx::terminate()` may attach an arbitrary pointer for extra debugging - /// information. + /// Returned when `get_embed_ctx` or `get_embed_ctx_mut` are used with a type that is not present. + CtxNotFound, + /// Returned when dynamic borrowing rules of methods like `Vmctx::heap()` are violated. + BorrowError(&'static str), + /// Calls to `lucet_hostcall_terminate` provide a payload for use by the embedder. Provided(Arc), } @@ -747,17 +750,28 @@ fn termination_details_any_typing() { ); } +impl PartialEq for TerminationDetails { + fn eq(&self, rhs: &TerminationDetails) -> bool { + use TerminationDetails::*; + match (self, rhs) { + (Signal, Signal) => true, + (BorrowError(msg1), BorrowError(msg2)) => msg1 == msg2, + (CtxNotFound, CtxNotFound) => true, + // can't compare `Any` + _ => false, + } + } +} + impl std::fmt::Debug for TerminationDetails { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!( - f, - "TerminationDetails::{}", - match self { - TerminationDetails::Signal => "Signal", - TerminationDetails::GetEmbedCtx => "GetEmbedCtx", - TerminationDetails::Provided(_) => "Provided(Any)", - } - ) + write!(f, "TerminationDetails::")?; + match self { + TerminationDetails::Signal => write!(f, "Signal"), + TerminationDetails::BorrowError(msg) => write!(f, "BorrowError({})", msg), + TerminationDetails::CtxNotFound => write!(f, "CtxNotFound"), + TerminationDetails::Provided(_) => write!(f, "Provided(Any)"), + } } } diff --git a/lucet-runtime/lucet-runtime-internals/src/module.rs b/lucet-runtime/lucet-runtime-internals/src/module.rs index 3d9251e25..a99e76f34 100644 --- a/lucet-runtime/lucet-runtime-internals/src/module.rs +++ b/lucet-runtime/lucet-runtime-internals/src/module.rs @@ -1,5 +1,4 @@ mod dl; -mod globals; mod mock; mod sparse_page_data; diff --git a/lucet-runtime/lucet-runtime-internals/src/module/globals.rs b/lucet-runtime/lucet-runtime-internals/src/module/globals.rs deleted file mode 100644 index 8616eb1bc..000000000 --- a/lucet-runtime/lucet-runtime-internals/src/module/globals.rs +++ /dev/null @@ -1,113 +0,0 @@ -#[macro_export] -macro_rules! globals_tests { - ( $TestRegion:path ) => { - use std::sync::Arc; - use $TestRegion as TestRegion; - use $crate::alloc::Limits; - use $crate::error::Error; - use $crate::module::{MockModuleBuilder, Module}; - use $crate::region::Region; - use $crate::vmctx::{lucet_vmctx, Vmctx}; - - fn mock_import_module() -> Arc { - MockModuleBuilder::new() - .with_import(0, "something", "else") - .build() - } - - #[test] - fn reject_import() { - let module = mock_import_module(); - let region = TestRegion::create(1, &Limits::default()).expect("region can be created"); - match region.new_instance(module) { - Ok(_) => panic!("instance creation should not succeed"), - Err(Error::Unsupported(_)) => (), - Err(e) => panic!("unexpected error: {}", e), - } - } - - fn mock_globals_module() -> Arc { - extern "C" fn get_global0(vmctx: *mut lucet_vmctx) -> i64 { - unsafe { Vmctx::from_raw(vmctx) }.globals()[0] - } - - extern "C" fn set_global0(vmctx: *mut lucet_vmctx, val: i64) { - unsafe { Vmctx::from_raw(vmctx) }.globals_mut()[0] = val; - } - - extern "C" fn get_global1(vmctx: *mut lucet_vmctx) -> i64 { - unsafe { Vmctx::from_raw(vmctx) }.globals()[1] - } - - MockModuleBuilder::new() - .with_global(0, -1) - .with_global(1, 420) - .with_export_func(b"get_global0", get_global0 as *const extern "C" fn()) - .with_export_func(b"set_global0", set_global0 as *const extern "C" fn()) - .with_export_func(b"get_global1", get_global1 as *const extern "C" fn()) - .build() - } - - /* replace with use of instance public api to make sure defined globals are initialized - * correctly - */ - - #[test] - fn globals_initialized() { - let module = mock_globals_module(); - let region = TestRegion::create(1, &Limits::default()).expect("region can be created"); - let inst = region - .new_instance(module) - .expect("instance can be created"); - assert_eq!(inst.globals()[0], -1); - assert_eq!(inst.globals()[1], 420); - } - - #[test] - fn get_global0() { - let module = mock_globals_module(); - let region = TestRegion::create(1, &Limits::default()).expect("region can be created"); - let mut inst = region - .new_instance(module) - .expect("instance can be created"); - - let retval = inst.run(b"get_global0", &[]).expect("instance runs"); - assert_eq!(i64::from(retval), -1); - } - - #[test] - fn get_both_globals() { - let module = mock_globals_module(); - let region = TestRegion::create(1, &Limits::default()).expect("region can be created"); - let mut inst = region - .new_instance(module) - .expect("instance can be created"); - - let retval = inst.run(b"get_global0", &[]).expect("instance runs"); - assert_eq!(i64::from(retval), -1); - - let retval = inst.run(b"get_global1", &[]).expect("instance runs"); - assert_eq!(i64::from(retval), 420); - } - - #[test] - fn mutate_global0() { - let module = mock_globals_module(); - let region = TestRegion::create(1, &Limits::default()).expect("region can be created"); - let mut inst = region - .new_instance(module) - .expect("instance can be created"); - - inst.run(b"set_global0", &[666i64.into()]) - .expect("instance runs"); - - let retval = inst.run(b"get_global0", &[]).expect("instance runs"); - assert_eq!(i64::from(retval), 666); - } - }; -} - -#[cfg(test)] -mod tests { - globals_tests!(crate::region::mmap::MmapRegion); -} diff --git a/lucet-runtime/lucet-runtime-internals/src/vmctx.rs b/lucet-runtime/lucet-runtime-internals/src/vmctx.rs index b1c08f3a3..e3cde401c 100644 --- a/lucet-runtime/lucet-runtime-internals/src/vmctx.rs +++ b/lucet-runtime/lucet-runtime-internals/src/vmctx.rs @@ -13,11 +13,24 @@ use crate::instance::{ HOST_CTX, }; use std::any::Any; +use std::borrow::{Borrow, BorrowMut}; +use std::cell::{Ref, RefCell, RefMut}; /// An opaque handle to a running instance's context. #[derive(Debug)] pub struct Vmctx { vmctx: *mut lucet_vmctx, + heap: RefCell>, + globals: RefCell>, +} + +impl Drop for Vmctx { + fn drop(&mut self) { + let heap = self.heap.replace(Box::new([])); + let globals = self.globals.replace(Box::new([])); + Box::leak(heap); + Box::leak(globals); + } } pub trait VmctxInternal { @@ -48,11 +61,18 @@ impl VmctxInternal for Vmctx { impl Vmctx { /// Create a `Vmctx` from the compiler-inserted `vmctx` argument in a guest /// function. + /// + /// This is almost certainly not what you want to use to get a `Vmctx`; instead use the `&mut + /// Vmctx` argument to a `lucet_hostcalls!`-wrapped function. pub unsafe fn from_raw(vmctx: *mut lucet_vmctx) -> Vmctx { - let res = Vmctx { vmctx }; - // we don't actually need the instance for this call, but asking for it here causes an - // earlier failure if the pointer isn't valid - assert!(res.instance().valid_magic()); + let inst = instance_from_vmctx(vmctx); + assert!(inst.valid_magic()); + + let res = Vmctx { + vmctx, + heap: RefCell::new(Box::<[u8]>::from_raw(inst.heap_mut())), + globals: RefCell::new(Box::<[i64]>::from_raw(inst.globals_mut())), + }; res } @@ -62,13 +82,27 @@ impl Vmctx { } /// Return the WebAssembly heap as a slice of bytes. - pub fn heap(&self) -> &[u8] { - self.instance().heap() + /// + /// If the heap is already mutably borrowed by `heap_mut()`, the instance will + /// terminate with `TerminationDetails::BorrowError`. + pub fn heap(&self) -> Ref<[u8]> { + let r = self + .heap + .try_borrow() + .unwrap_or_else(|_| panic!(TerminationDetails::BorrowError("heap"))); + Ref::map(r, |b| b.borrow()) } /// Return the WebAssembly heap as a mutable slice of bytes. - pub fn heap_mut(&mut self) -> &mut [u8] { - unsafe { self.instance_mut().heap_mut() } + /// + /// If the heap is already borrowed by `heap()` or `heap_mut()`, the instance will terminate + /// with `TerminationDetails::BorrowError`. + pub fn heap_mut(&self) -> RefMut<[u8]> { + let r = self + .heap + .try_borrow_mut() + .unwrap_or_else(|_| panic!(TerminationDetails::BorrowError("heap_mut"))); + RefMut::map(r, |b| b.borrow_mut()) } /// Check whether a given range in the host address space overlaps with the memory that backs @@ -82,27 +116,33 @@ impl Vmctx { self.instance().contains_embed_ctx::() } - /// Get a reference to a context value of a particular type. If it does not exist, - /// the context will terminate. - pub fn get_embed_ctx(&self) -> &T { - match self.instance().embed_ctx.get::() { - Some(t) => t, - None => { - // this value will be caught by the wrapper in `lucet_hostcalls!` - panic!(TerminationDetails::GetEmbedCtx) - } + /// Get a reference to a context value of a particular type. + /// + /// If a context of that type does not exist, the instance will terminate with + /// `TerminationDetails::CtxNotFound`. + /// + /// If the context is already mutably borrowed by `get_embed_ctx_mut`, the instance will + /// terminate with `TerminationDetails::BorrowError`. + pub fn get_embed_ctx(&self) -> Ref { + match self.instance().embed_ctx.try_get::() { + Some(Ok(t)) => t, + Some(Err(_)) => panic!(TerminationDetails::BorrowError("get_embed_ctx")), + None => panic!(TerminationDetails::CtxNotFound), } } - /// Get a mutable reference to a context value of a particular type> If it does not exist, - /// the context will terminate. - pub fn get_embed_ctx_mut(&mut self) -> &mut T { - match unsafe { self.instance_mut().embed_ctx.get_mut::() } { - Some(t) => t, - None => { - // this value will be caught by the wrapper in `lucet_hostcalls!` - panic!(TerminationDetails::GetEmbedCtx) - } + /// Get a mutable reference to a context value of a particular type. + /// + /// If a context of that type does not exist, the instance will terminate with + /// `TerminationDetails::CtxNotFound`. + /// + /// If the context is already borrowed by some other use of `get_embed_ctx` or + /// `get_embed_ctx_mut`, the instance will terminate with `TerminationDetails::BorrowError`. + pub fn get_embed_ctx_mut(&self) -> RefMut { + match unsafe { self.instance_mut().embed_ctx.try_get_mut::() } { + Some(Ok(t)) => t, + Some(Err(_)) => panic!(TerminationDetails::BorrowError("get_embed_ctx_mut")), + None => panic!(TerminationDetails::CtxNotFound), } } @@ -123,13 +163,27 @@ impl Vmctx { } /// Return the WebAssembly globals as a slice of `i64`s. - pub fn globals(&self) -> &[i64] { - self.instance().globals() + /// + /// If the globals are already mutably borrowed by `globals_mut()`, the instance will terminate + /// with `TerminationDetails::BorrowError`. + pub fn globals(&self) -> Ref<[i64]> { + let r = self + .globals + .try_borrow() + .unwrap_or_else(|_| panic!(TerminationDetails::BorrowError("globals"))); + Ref::map(r, |b| b.borrow()) } /// Return the WebAssembly globals as a mutable slice of `i64`s. - pub fn globals_mut(&mut self) -> &mut [i64] { - unsafe { self.instance_mut().globals_mut() } + /// + /// If the globals are already borrowed by `globals()` or `globals_mut()`, the instance will + /// terminate with `TerminationDetails::BorrowError`. + pub fn globals_mut(&self) -> RefMut<[i64]> { + let r = self + .globals + .try_borrow_mut() + .unwrap_or_else(|_| panic!(TerminationDetails::BorrowError("globals_mut"))); + RefMut::map(r, |b| b.borrow_mut()) } /// Get a function pointer by WebAssembly table and function index. diff --git a/lucet-runtime/lucet-runtime-tests/src/globals.rs b/lucet-runtime/lucet-runtime-tests/src/globals.rs index c6da6d35b..74a7a1a54 100644 --- a/lucet-runtime/lucet-runtime-tests/src/globals.rs +++ b/lucet-runtime/lucet-runtime-tests/src/globals.rs @@ -2,11 +2,11 @@ macro_rules! globals_tests { ( $TestRegion:path ) => { use lucet_runtime::vmctx::{lucet_vmctx, Vmctx}; - use lucet_runtime::{Limits, Region}; - use lucet_runtime_internals::instance::InstanceInternal; + use lucet_runtime::{Error, Limits, Module, Region}; use std::sync::Arc; use $TestRegion as TestRegion; use $crate::build::test_module_wasm; + use $crate::helpers::MockModuleBuilder; #[test] fn defined_globals() { @@ -28,7 +28,7 @@ macro_rules! globals_tests { // [4] = 5 // [8] = 6 - let heap_u32 = unsafe { inst.alloc().heap_u32() }; + let heap_u32 = unsafe { inst.heap_u32() }; assert_eq!(heap_u32[0..=2], [4, 5, 6]); inst.run(b"main", &[]).expect("instance runs"); @@ -38,8 +38,111 @@ macro_rules! globals_tests { // [4] = 2 // [8] = 6 - let heap_u32 = unsafe { inst.alloc().heap_u32() }; + let heap_u32 = unsafe { inst.heap_u32() }; assert_eq!(heap_u32[0..=2], [3, 2, 6]); } + + fn mock_import_module() -> Arc { + MockModuleBuilder::new() + .with_import(0, "something", "else") + .build() + } + + #[test] + fn reject_import() { + let module = mock_import_module(); + let region = TestRegion::create(1, &Limits::default()).expect("region can be created"); + match region.new_instance(module) { + Ok(_) => panic!("instance creation should not succeed"), + Err(Error::Unsupported(_)) => (), + Err(e) => panic!("unexpected error: {}", e), + } + } + + fn mock_globals_module() -> Arc { + extern "C" { + fn lucet_vmctx_get_globals(vmctx: *mut lucet_vmctx) -> *mut i64; + } + + unsafe extern "C" fn get_global0(vmctx: *mut lucet_vmctx) -> i64 { + let globals = std::slice::from_raw_parts(lucet_vmctx_get_globals(vmctx), 2); + globals[0] + } + + unsafe extern "C" fn set_global0(vmctx: *mut lucet_vmctx, val: i64) { + let globals = std::slice::from_raw_parts_mut(lucet_vmctx_get_globals(vmctx), 2); + globals[0] = val; + } + + unsafe extern "C" fn get_global1(vmctx: *mut lucet_vmctx) -> i64 { + let globals = std::slice::from_raw_parts(lucet_vmctx_get_globals(vmctx), 2); + globals[1] + } + + MockModuleBuilder::new() + .with_global(0, -1) + .with_global(1, 420) + .with_export_func(b"get_global0", get_global0 as *const extern "C" fn()) + .with_export_func(b"set_global0", set_global0 as *const extern "C" fn()) + .with_export_func(b"get_global1", get_global1 as *const extern "C" fn()) + .build() + } + + /* replace with use of instance public api to make sure defined globals are initialized + * correctly + */ + + #[test] + fn globals_initialized() { + let module = mock_globals_module(); + let region = TestRegion::create(1, &Limits::default()).expect("region can be created"); + let inst = region + .new_instance(module) + .expect("instance can be created"); + assert_eq!(inst.globals()[0], -1); + assert_eq!(inst.globals()[1], 420); + } + + #[test] + fn get_global0() { + let module = mock_globals_module(); + let region = TestRegion::create(1, &Limits::default()).expect("region can be created"); + let mut inst = region + .new_instance(module) + .expect("instance can be created"); + + let retval = inst.run(b"get_global0", &[]).expect("instance runs"); + assert_eq!(i64::from(retval), -1); + } + + #[test] + fn get_both_globals() { + let module = mock_globals_module(); + let region = TestRegion::create(1, &Limits::default()).expect("region can be created"); + let mut inst = region + .new_instance(module) + .expect("instance can be created"); + + let retval = inst.run(b"get_global0", &[]).expect("instance runs"); + assert_eq!(i64::from(retval), -1); + + let retval = inst.run(b"get_global1", &[]).expect("instance runs"); + assert_eq!(i64::from(retval), 420); + } + + #[test] + fn mutate_global0() { + let module = mock_globals_module(); + let region = TestRegion::create(1, &Limits::default()).expect("region can be created"); + let mut inst = region + .new_instance(module) + .expect("instance can be created"); + + inst.run(b"set_global0", &[666i64.into()]) + .expect("instance runs"); + + let retval = inst.run(b"get_global0", &[]).expect("instance runs"); + assert_eq!(i64::from(retval), 666); + } }; } diff --git a/lucet-runtime/lucet-runtime-tests/src/guest_fault.rs b/lucet-runtime/lucet-runtime-tests/src/guest_fault.rs index 3aacf3cfa..6f2667481 100644 --- a/lucet-runtime/lucet-runtime-tests/src/guest_fault.rs +++ b/lucet-runtime/lucet-runtime-tests/src/guest_fault.rs @@ -1,54 +1,52 @@ use crate::helpers::MockModuleBuilder; -use lucet_runtime_internals::lucet_hostcalls; use lucet_runtime_internals::module::{Module, TrapManifestRecord, TrapSite}; use lucet_runtime_internals::vmctx::lucet_vmctx; use std::sync::Arc; pub fn mock_traps_module() -> Arc { - lucet_hostcalls! { - pub unsafe extern "C" fn onetwothree( - &mut _vmctx, - ) -> std::os::raw::c_int { - 123 - } + extern "C" fn onetwothree(_vmctx: *mut lucet_vmctx) -> std::os::raw::c_int { + 123 + } - pub unsafe extern "C" fn hostcall_main( - &mut vmctx, - ) -> () { - extern "C" { - // actually is defined in this file - fn hostcall_test(vmctx: *mut lucet_vmctx); - } - hostcall_test(vmctx.as_raw()); + extern "C" fn hostcall_main(vmctx: *mut lucet_vmctx) -> () { + extern "C" { + // actually is defined in this file + fn hostcall_test(vmctx: *mut lucet_vmctx); + } + unsafe { + hostcall_test(vmctx); std::hint::unreachable_unchecked(); } + } - pub unsafe extern "C" fn infinite_loop( - &mut _vmctx, - ) -> () { - loop {} + extern "C" fn infinite_loop(_vmctx: *mut lucet_vmctx) -> () { + loop {} + } + + extern "C" fn fatal(vmctx: *mut lucet_vmctx) -> () { + extern "C" { + fn lucet_vmctx_get_heap(vmctx: *mut lucet_vmctx) -> *mut u8; } - pub unsafe extern "C" fn fatal( - &mut vmctx, - ) -> () { - let heap_base = vmctx.heap_mut().as_mut_ptr(); + unsafe { + let heap_base = lucet_vmctx_get_heap(vmctx); // Using the default limits, each instance as of this writing takes up 0x200026000 bytes - // worth of virtual address space. We want to access a point beyond all the instances, so - // that memory is unmapped. We assume no more than 16 instances are mapped - // concurrently. This may change as the library, test configuration, linker, phase of moon, - // etc change, but for now it works. + // worth of virtual address space. We want to access a point beyond all the instances, + // so that memory is unmapped. We assume no more than 16 instances are mapped + // concurrently. This may change as the library, test configuration, linker, phase of + // moon, etc change, but for now it works. *heap_base.offset(0x200026000 * 16) = 0; } + } - pub unsafe extern "C" fn recoverable_fatal( - &mut _vmctx, - ) -> () { - use std::os::raw::c_char; - extern "C" { - fn guest_recoverable_get_ptr() -> *mut c_char; - } + extern "C" fn recoverable_fatal(_vmctx: *mut lucet_vmctx) -> () { + use std::os::raw::c_char; + extern "C" { + fn guest_recoverable_get_ptr() -> *mut c_char; + } + + unsafe { *guest_recoverable_get_ptr() = '\0' as c_char; } } diff --git a/lucet-runtime/lucet-runtime-tests/src/host.rs b/lucet-runtime/lucet-runtime-tests/src/host.rs index e99c66ec3..e34a12aa6 100644 --- a/lucet-runtime/lucet-runtime-tests/src/host.rs +++ b/lucet-runtime/lucet-runtime-tests/src/host.rs @@ -5,11 +5,13 @@ macro_rules! host_tests { use libc::c_void; use lucet_runtime::vmctx::{lucet_vmctx, Vmctx}; use lucet_runtime::{ - lucet_hostcall_terminate, lucet_hostcalls, DlModule, Error, Limits, Region, TrapCode, + lucet_hostcall_terminate, lucet_hostcalls, DlModule, Error, Limits, Region, + TerminationDetails, TrapCode, }; use std::sync::{Arc, Mutex}; use $TestRegion as TestRegion; use $crate::build::test_module_c; + use $crate::helpers::MockModuleBuilder; #[test] fn load_module() { let _module = test_module_c("host", "trivial.c").expect("build and load module"); @@ -63,6 +65,41 @@ macro_rules! host_tests { } drop(lock); } + + #[no_mangle] + pub unsafe extern "C" fn hostcall_bad_borrow( + &mut vmctx, + ) -> bool { + let heap = vmctx.heap(); + let mut other_heap = vmctx.heap_mut(); + heap[0] == other_heap[0] + } + + #[no_mangle] + pub unsafe extern "C" fn hostcall_missing_embed_ctx( + &mut vmctx, + ) -> bool { + struct S { + x: bool + } + let ctx = vmctx.get_embed_ctx::(); + ctx.x + } + + #[no_mangle] + pub unsafe extern "C" fn hostcall_multiple_vmctx( + &mut vmctx, + ) -> bool { + let mut vmctx1 = Vmctx::from_raw(vmctx.as_raw()); + vmctx1.heap_mut()[0] = 0xAF; + drop(vmctx1); + + let mut vmctx2 = Vmctx::from_raw(vmctx.as_raw()); + let res = vmctx2.heap()[0] == 0xAF; + drop(vmctx2); + + res + } } #[test] @@ -97,7 +134,7 @@ macro_rules! host_tests { inst.run(b"main", &[]).expect("instance runs"); - assert!(inst.get_embed_ctx::().unwrap()); + assert!(*inst.get_embed_ctx::().unwrap().unwrap()); } #[test] @@ -166,5 +203,86 @@ macro_rules! host_tests { } } } + + #[test] + fn run_hostcall_bad_borrow() { + extern "C" { + fn hostcall_bad_borrow(vmctx: *mut lucet_vmctx) -> bool; + } + + unsafe extern "C" fn f(vmctx: *mut lucet_vmctx) { + hostcall_bad_borrow(vmctx); + } + + let module = MockModuleBuilder::new() + .with_export_func(b"f", f as *const extern "C" fn()) + .build(); + + let region = TestRegion::create(1, &Limits::default()).expect("region can be created"); + let mut inst = region + .new_instance(module) + .expect("instance can be created"); + + match inst.run(b"f", &[]) { + Err(Error::RuntimeTerminated(details)) => { + assert_eq!(details, TerminationDetails::BorrowError("heap_mut")); + } + res => { + panic!("unexpected result: {:?}", res); + } + } + } + + #[test] + fn run_hostcall_missing_embed_ctx() { + extern "C" { + fn hostcall_missing_embed_ctx(vmctx: *mut lucet_vmctx) -> bool; + } + + unsafe extern "C" fn f(vmctx: *mut lucet_vmctx) { + hostcall_missing_embed_ctx(vmctx); + } + + let module = MockModuleBuilder::new() + .with_export_func(b"f", f as *const extern "C" fn()) + .build(); + + let region = TestRegion::create(1, &Limits::default()).expect("region can be created"); + let mut inst = region + .new_instance(module) + .expect("instance can be created"); + + match inst.run(b"f", &[]) { + Err(Error::RuntimeTerminated(details)) => { + assert_eq!(details, TerminationDetails::CtxNotFound); + } + res => { + panic!("unexpected result: {:?}", res); + } + } + } + + #[test] + fn run_hostcall_multiple_vmctx() { + extern "C" { + fn hostcall_multiple_vmctx(vmctx: *mut lucet_vmctx) -> bool; + } + + unsafe extern "C" fn f(vmctx: *mut lucet_vmctx) { + hostcall_multiple_vmctx(vmctx); + } + + let module = MockModuleBuilder::new() + .with_export_func(b"f", f as *const extern "C" fn()) + .build(); + + let region = TestRegion::create(1, &Limits::default()).expect("region can be created"); + let mut inst = region + .new_instance(module) + .expect("instance can be created"); + + let retval = inst.run(b"f", &[]).expect("instance runs"); + assert_eq!(bool::from(retval), true); + } }; } diff --git a/lucet-runtime/src/c_api.rs b/lucet-runtime/src/c_api.rs index e4bd29c3f..51090bbdf 100644 --- a/lucet-runtime/src/c_api.rs +++ b/lucet-runtime/src/c_api.rs @@ -283,7 +283,7 @@ pub unsafe extern "C" fn lucet_instance_grow_heap( pub unsafe extern "C" fn lucet_instance_embed_ctx(inst: *mut lucet_instance) -> *mut c_void { with_instance_ptr_unchecked!(inst, { inst.get_embed_ctx::<*mut c_void>() - .map(|p| *p) + .map(|r| r.map(|p| *p).unwrap_or(ptr::null_mut())) .unwrap_or(ptr::null_mut()) }) } @@ -442,7 +442,7 @@ lucet_hostcalls! { ) -> *mut c_void { vmctx.instance() .get_embed_ctx::<*mut c_void>() - .map(|p| *p) + .map(|r| r.map(|p| *p).unwrap_or(ptr::null_mut())) .unwrap_or(std::ptr::null_mut()) } } diff --git a/lucet-runtime/src/lib.rs b/lucet-runtime/src/lib.rs index af85c489f..fdc22be94 100644 --- a/lucet-runtime/src/lib.rs +++ b/lucet-runtime/src/lib.rs @@ -82,7 +82,7 @@ //! pub unsafe extern "C" fn foo( //! &mut vmctx, //! ) -> () { -//! let hostcall_context = vmctx.get_embed_ctx_mut::(); +//! let mut hostcall_context = vmctx.get_embed_ctx_mut::(); //! hostcall_context.x = 42; //! } //! } @@ -97,7 +97,7 @@ //! //! inst.run(b"call_foo", &[]).unwrap(); //! -//! let context_after = inst.get_embed_ctx::().unwrap(); +//! let context_after = inst.get_embed_ctx::().unwrap().unwrap(); //! assert_eq!(context_after.x, 42); //! ``` //! diff --git a/lucet-wasi/src/hostcalls.rs b/lucet-wasi/src/hostcalls.rs index 9a6971f25..603c1364c 100644 --- a/lucet-wasi/src/hostcalls.rs +++ b/lucet-wasi/src/hostcalls.rs @@ -47,7 +47,7 @@ lucet_hostcalls! { argv_ptr: wasm32::uintptr_t, argv_buf: wasm32::uintptr_t, ) -> wasm32::__wasi_errno_t { - let ctx: &WasiCtx = vmctx.get_embed_ctx(); + let ctx = vmctx.get_embed_ctx::(); let mut argv_buf_offset = 0; let mut argv = vec![]; @@ -56,10 +56,7 @@ lucet_hostcalls! { let arg_bytes = arg.as_bytes_with_nul(); let arg_ptr = argv_buf + argv_buf_offset; - // nasty aliasing here, but we aren't interfering with the borrow for `ctx` - // TODO: rework vmctx interface to avoid this - let mut vmctx = unsafe { Vmctx::from_raw(vmctx.as_raw()) }; - if let Err(e) = unsafe { enc_slice_of(&mut vmctx, arg_bytes, arg_ptr) } { + if let Err(e) = unsafe { enc_slice_of(vmctx, arg_bytes, arg_ptr) } { return enc_errno(e); } @@ -88,7 +85,7 @@ lucet_hostcalls! { argc_ptr: wasm32::uintptr_t, argv_buf_size_ptr: wasm32::uintptr_t, ) -> wasm32::__wasi_errno_t { - let ctx: &WasiCtx = vmctx.get_embed_ctx(); + let ctx = vmctx.get_embed_ctx::(); let argc = ctx.args.len(); let argv_size = ctx @@ -195,7 +192,7 @@ lucet_hostcalls! { environ_ptr: wasm32::uintptr_t, environ_buf: wasm32::uintptr_t, ) -> wasm32::__wasi_errno_t { - let ctx: &WasiCtx = vmctx.get_embed_ctx(); + let ctx = vmctx.get_embed_ctx::(); let mut environ_buf_offset = 0; let mut environ = vec![]; @@ -204,10 +201,7 @@ lucet_hostcalls! { let env_bytes = pair.as_bytes_with_nul(); let env_ptr = environ_buf + environ_buf_offset; - // nasty aliasing here, but we aren't interfering with the borrow for `ctx` - // TODO: rework vmctx interface to avoid this - let mut vmctx = unsafe { Vmctx::from_raw(vmctx.as_raw()) }; - if let Err(e) = unsafe { enc_slice_of(&mut vmctx, env_bytes, env_ptr) } { + if let Err(e) = unsafe { enc_slice_of(vmctx, env_bytes, env_ptr) } { return enc_errno(e); } @@ -236,7 +230,7 @@ lucet_hostcalls! { environ_count_ptr: wasm32::uintptr_t, environ_size_ptr: wasm32::uintptr_t, ) -> wasm32::__wasi_errno_t { - let ctx: &WasiCtx = vmctx.get_embed_ctx(); + let ctx = vmctx.get_embed_ctx::(); let environ_count = ctx.env.len(); if let Some(environ_size) = ctx.env.iter().try_fold(0, |acc: u32, pair| { @@ -261,7 +255,7 @@ lucet_hostcalls! { &mut vmctx, fd: wasm32::__wasi_fd_t, ) -> wasm32::__wasi_errno_t { - let ctx: &mut WasiCtx = vmctx.get_embed_ctx_mut(); + let mut ctx = vmctx.get_embed_ctx_mut::(); let fd = dec_fd(fd); if let Some(fdent) = ctx.fds.get(&fd) { // can't close preopened files @@ -292,7 +286,7 @@ lucet_hostcalls! { Err(e) => return enc_errno(e), }; - let ctx: &mut WasiCtx = vmctx.get_embed_ctx_mut(); + let ctx = vmctx.get_embed_ctx_mut::(); let errno = if let Some(fe) = ctx.fds.get(&host_fd) { host_fdstat.fs_filetype = fe.fd_object.ty; host_fdstat.fs_rights_base = fe.rights_base; @@ -327,7 +321,7 @@ lucet_hostcalls! { let host_fdflags = dec_fdflags(fdflags); let nix_flags = host::nix_from_fdflags(host_fdflags); - let ctx: &mut WasiCtx = vmctx.get_embed_ctx_mut(); + let ctx = vmctx.get_embed_ctx_mut::(); if let Some(fe) = ctx.fds.get(&host_fd) { match nix::fcntl::fcntl(fe.fd_object.rawfd, nix::fcntl::F_SETFL(nix_flags)) { @@ -347,7 +341,7 @@ lucet_hostcalls! { whence: wasm32::__wasi_whence_t, newoffset: wasm32::uintptr_t, ) -> wasm32::__wasi_errno_t { - let ctx: &mut WasiCtx = vmctx.get_embed_ctx_mut(); + let ctx = vmctx.get_embed_ctx_mut::(); let fd = dec_fd(fd); let offset = dec_filedelta(offset); let whence = dec_whence(whence); @@ -388,7 +382,7 @@ lucet_hostcalls! { fd: wasm32::__wasi_fd_t, prestat_ptr: wasm32::uintptr_t, ) -> wasm32::__wasi_errno_t { - let ctx: &WasiCtx = vmctx.get_embed_ctx(); + let ctx = vmctx.get_embed_ctx::(); let fd = dec_fd(fd); // TODO: is this the correct right for this? match ctx.get_fd_entry(fd, host::__WASI_RIGHT_PATH_OPEN.into(), 0) { @@ -397,11 +391,9 @@ lucet_hostcalls! { if fe.fd_object.ty != host::__WASI_FILETYPE_DIRECTORY as host::__wasi_filetype_t { return wasm32::__WASI_ENOTDIR; } - // nasty aliasing here, but we aren't interfering with the borrow for `ctx` - // TODO: rework vmctx interface to avoid this unsafe { enc_prestat_byref( - &mut Vmctx::from_raw(vmctx.as_raw()), + vmctx, prestat_ptr, host::__wasi_prestat_t { pr_type: host::__WASI_PREOPENTYPE_DIR as host::__wasi_preopentype_t, @@ -431,7 +423,7 @@ lucet_hostcalls! { path_ptr: wasm32::uintptr_t, path_len: wasm32::size_t, ) -> wasm32::__wasi_errno_t { - let ctx: &WasiCtx = vmctx.get_embed_ctx(); + let ctx = vmctx.get_embed_ctx::(); let fd = dec_fd(fd); match ctx.get_fd_entry(fd, host::__WASI_RIGHT_PATH_OPEN.into(), 0) { Ok(fe) => { @@ -443,10 +435,8 @@ lucet_hostcalls! { if path_bytes.len() > dec_usize(path_len) { return wasm32::__WASI_ENAMETOOLONG; } - // nasty aliasing here, but we aren't interfering with the borrow for `ctx` - // TODO: rework vmctx interface to avoid this unsafe { - enc_slice_of(&mut Vmctx::from_raw(vmctx.as_raw()), path_bytes, path_ptr) + enc_slice_of(vmctx, path_bytes, path_ptr) .map(|_| wasm32::__WASI_ESUCCESS) .unwrap_or_else(|e| e) } @@ -474,7 +464,7 @@ lucet_hostcalls! { Err(e) => return enc_errno(e), }; - let ctx: &mut WasiCtx = vmctx.get_embed_ctx_mut(); + let mut ctx = vmctx.get_embed_ctx_mut::(); let fe = match ctx.get_fd_entry(fd, host::__WASI_RIGHT_FD_READ.into(), 0) { Ok(fe) => fe, Err(e) => return enc_errno(e), @@ -521,7 +511,7 @@ lucet_hostcalls! { Err(e) => return enc_errno(e), }; - let ctx: &mut WasiCtx = vmctx.get_embed_ctx_mut(); + let ctx = vmctx.get_embed_ctx_mut::(); let fe = match ctx.get_fd_entry(fd, host::__WASI_RIGHT_FD_WRITE.into(), 0) { Ok(fe) => fe, Err(e) => return enc_errno(e), @@ -989,7 +979,7 @@ pub fn path_get>( Err(errno) } - let ctx: &WasiCtx = vmctx.get_embed_ctx(); + let ctx = vmctx.get_embed_ctx::(); let dirfe = ctx.get_fd_entry(dirfd, needed_base, needed_inheriting)?; diff --git a/lucet-wasi/src/memory.rs b/lucet-wasi/src/memory.rs index 89cf9cfed..eb1da092f 100644 --- a/lucet-wasi/src/memory.rs +++ b/lucet-wasi/src/memory.rs @@ -23,11 +23,11 @@ macro_rules! bail_errno { } pub unsafe fn dec_ptr( - vmctx: &mut Vmctx, + vmctx: &Vmctx, ptr: wasm32::uintptr_t, len: usize, ) -> Result<*mut u8, host::__wasi_errno_t> { - let heap = vmctx.heap_mut(); + let mut heap = vmctx.heap_mut(); // check that `len` fits in the wasm32 address space if len > wasm32::UINTPTR_MAX as usize { @@ -44,7 +44,7 @@ pub unsafe fn dec_ptr( } pub unsafe fn dec_ptr_to( - vmctx: &mut Vmctx, + vmctx: &Vmctx, ptr: wasm32::uintptr_t, ) -> Result<*mut T, host::__wasi_errno_t> { // check that the ptr is aligned @@ -55,14 +55,14 @@ pub unsafe fn dec_ptr_to( } pub unsafe fn dec_pointee( - vmctx: &mut Vmctx, + vmctx: &Vmctx, ptr: wasm32::uintptr_t, ) -> Result { dec_ptr_to::(vmctx, ptr).map(|p| p.read()) } pub unsafe fn enc_pointee( - vmctx: &mut Vmctx, + vmctx: &Vmctx, ptr: wasm32::uintptr_t, t: T, ) -> Result<(), host::__wasi_errno_t> { @@ -70,7 +70,7 @@ pub unsafe fn enc_pointee( } pub unsafe fn dec_slice_of( - vmctx: &mut Vmctx, + vmctx: &Vmctx, ptr: wasm32::uintptr_t, len: wasm32::size_t, ) -> Result<(*mut T, usize), host::__wasi_errno_t> { @@ -91,7 +91,7 @@ pub unsafe fn dec_slice_of( } pub unsafe fn enc_slice_of( - vmctx: &mut Vmctx, + vmctx: &Vmctx, slice: &[T], ptr: wasm32::uintptr_t, ) -> Result<(), host::__wasi_errno_t> { @@ -120,7 +120,7 @@ macro_rules! dec_enc_scalar { } pub unsafe fn $dec_byref( - vmctx: &mut Vmctx, + vmctx: &Vmctx, ptr: wasm32::uintptr_t, ) -> Result { dec_pointee::(vmctx, ptr).map($dec) @@ -131,7 +131,7 @@ macro_rules! dec_enc_scalar { } pub unsafe fn $enc_byref( - vmctx: &mut Vmctx, + vmctx: &Vmctx, ptr: wasm32::uintptr_t, x: host::$ty, ) -> Result<(), host::__wasi_errno_t> { @@ -141,7 +141,7 @@ macro_rules! dec_enc_scalar { } pub unsafe fn dec_ciovec( - vmctx: &mut Vmctx, + vmctx: &Vmctx, ciovec: &wasm32::__wasi_ciovec_t, ) -> Result { let len = dec_usize(ciovec.buf_len); @@ -152,7 +152,7 @@ pub unsafe fn dec_ciovec( } pub unsafe fn dec_ciovec_slice( - vmctx: &mut Vmctx, + vmctx: &Vmctx, ptr: wasm32::uintptr_t, len: wasm32::size_t, ) -> Result, host::__wasi_errno_t> { @@ -201,7 +201,7 @@ pub fn dec_fdstat(fdstat: wasm32::__wasi_fdstat_t) -> host::__wasi_fdstat_t { } pub unsafe fn dec_fdstat_byref( - vmctx: &mut Vmctx, + vmctx: &Vmctx, fdstat_ptr: wasm32::uintptr_t, ) -> Result { dec_pointee::(vmctx, fdstat_ptr).map(dec_fdstat) @@ -218,7 +218,7 @@ pub fn enc_fdstat(fdstat: host::__wasi_fdstat_t) -> wasm32::__wasi_fdstat_t { } pub unsafe fn enc_fdstat_byref( - vmctx: &mut Vmctx, + vmctx: &Vmctx, fdstat_ptr: wasm32::uintptr_t, host_fdstat: host::__wasi_fdstat_t, ) -> Result<(), host::__wasi_errno_t> { @@ -285,7 +285,7 @@ pub fn dec_prestat( } pub unsafe fn dec_prestat_byref( - vmctx: &mut Vmctx, + vmctx: &Vmctx, prestat_ptr: wasm32::uintptr_t, ) -> Result { dec_pointee::(vmctx, prestat_ptr).and_then(dec_prestat) @@ -311,7 +311,7 @@ pub fn enc_prestat( } pub unsafe fn enc_prestat_byref( - vmctx: &mut Vmctx, + vmctx: &Vmctx, prestat_ptr: wasm32::uintptr_t, host_prestat: host::__wasi_prestat_t, ) -> Result<(), host::__wasi_errno_t> { @@ -343,7 +343,7 @@ pub fn enc_usize(size: usize) -> wasm32::size_t { } pub unsafe fn enc_usize_byref( - vmctx: &mut Vmctx, + vmctx: &Vmctx, usize_ptr: wasm32::uintptr_t, host_usize: usize, ) -> Result<(), host::__wasi_errno_t> { From 153fe213edc80943cd81c91e694fc992c5e22bbe Mon Sep 17 00:00:00 2001 From: "Adam C. Foltzer" Date: Fri, 26 Apr 2019 09:56:08 -0700 Subject: [PATCH 07/21] [lucet-runtime] silence panic output when terminating instances --- .../src/instance/signals.rs | 33 ++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/lucet-runtime/lucet-runtime-internals/src/instance/signals.rs b/lucet-runtime/lucet-runtime-internals/src/instance/signals.rs index c9f92b452..a33cc813d 100644 --- a/lucet-runtime/lucet-runtime-internals/src/instance/signals.rs +++ b/lucet-runtime/lucet-runtime-internals/src/instance/signals.rs @@ -11,7 +11,8 @@ use lucet_module_data::TrapCode; use nix::sys::signal::{ pthread_sigmask, raise, sigaction, SaFlags, SigAction, SigHandler, SigSet, SigmaskHow, Signal, }; -use std::sync::Mutex; +use std::panic; +use std::sync::{Arc, Mutex}; lazy_static! { // TODO: work out an alternative to this that is signal-safe for `reraise_host_signal_in_handler` @@ -206,6 +207,7 @@ struct SignalState { saved_sigfpe: SigAction, saved_sigill: SigAction, saved_sigsegv: SigAction, + saved_panic_hook: Option>>, } // raw pointers in the saved types @@ -229,21 +231,50 @@ unsafe fn setup_guest_signal_state(ostate: &mut Option) { let saved_sigill = sigaction(Signal::SIGILL, &sa).expect("sigaction succeeds"); let saved_sigsegv = sigaction(Signal::SIGSEGV, &sa).expect("sigaction succeeds"); + let saved_panic_hook = Some(setup_guest_panic_hook()); + *ostate = Some(SignalState { counter: 1, saved_sigbus, saved_sigfpe, saved_sigill, saved_sigsegv, + saved_panic_hook, }); } +fn setup_guest_panic_hook() -> Arc> { + let saved_panic_hook = Arc::new(panic::take_hook()); + let closure_saved_panic_hook = saved_panic_hook.clone(); + std::panic::set_hook(Box::new(move |panic_info| { + if panic_info + .payload() + .downcast_ref::() + .is_none() + { + closure_saved_panic_hook(panic_info); + } else { + // this is a panic used to implement instance termination (such as + // `lucet_hostcall_terminate!`), so we don't want to print a backtrace; instead, we do + // nothing + } + })); + saved_panic_hook +} + unsafe fn restore_host_signal_state(state: &mut SignalState) { // restore signal handlers sigaction(Signal::SIGBUS, &state.saved_sigbus).expect("sigaction succeeds"); sigaction(Signal::SIGFPE, &state.saved_sigfpe).expect("sigaction succeeds"); sigaction(Signal::SIGILL, &state.saved_sigill).expect("sigaction succeeds"); sigaction(Signal::SIGSEGV, &state.saved_sigsegv).expect("sigaction succeeds"); + + // restore panic hook + drop(panic::take_hook()); + state + .saved_panic_hook + .take() + .map(|hook| Arc::try_unwrap(hook).map(|hook| panic::set_hook(hook))); } unsafe fn reraise_host_signal_in_handler( From d1ef54a4f4a4b8635ae6dc13a623bc8191cd751d Mon Sep 17 00:00:00 2001 From: "Adam C. Foltzer" Date: Fri, 26 Apr 2019 11:12:27 -0700 Subject: [PATCH 08/21] [lucet-wasi] expose `inherit_stdio` C API --- lucet-wasi/include/lucet_wasi.h | 2 ++ lucet-wasi/src/c_api.rs | 11 +++++++++++ 2 files changed, 13 insertions(+) diff --git a/lucet-wasi/include/lucet_wasi.h b/lucet-wasi/include/lucet_wasi.h index 92d4d05d8..b0c5d6edc 100644 --- a/lucet-wasi/include/lucet_wasi.h +++ b/lucet-wasi/include/lucet_wasi.h @@ -11,6 +11,8 @@ enum lucet_error lucet_wasi_ctx_args(struct lucet_wasi_ctx *wasi_ctx, size_t arg enum lucet_error lucet_wasi_ctx_inherit_env(struct lucet_wasi_ctx *wasi_ctx); +enum lucet_error lucet_wasi_ctx_inherit_stdio(struct lucet_wasi_ctx *wasi_ctx); + void lucet_wasi_ctx_destroy(struct lucet_wasi_ctx *wasi_ctx); enum lucet_error lucet_region_new_instance_with_wasi_ctx(const struct lucet_region * region, diff --git a/lucet-wasi/src/c_api.rs b/lucet-wasi/src/c_api.rs index 484b6c090..be8dc9416 100644 --- a/lucet-wasi/src/c_api.rs +++ b/lucet-wasi/src/c_api.rs @@ -45,6 +45,17 @@ pub unsafe extern "C" fn lucet_wasi_ctx_inherit_env(wasi_ctx: *mut lucet_wasi_ct lucet_error::Ok } +#[no_mangle] +pub unsafe extern "C" fn lucet_wasi_ctx_inherit_stdio( + wasi_ctx: *mut lucet_wasi_ctx, +) -> lucet_error { + assert_nonnull!(wasi_ctx); + let mut b = Box::from_raw(wasi_ctx as *mut WasiCtxBuilder); + *b = b.inherit_stdio(); + Box::into_raw(b); + lucet_error::Ok +} + #[no_mangle] pub unsafe extern "C" fn lucet_wasi_ctx_destroy(wasi_ctx: *mut lucet_wasi_ctx) { Box::from_raw(wasi_ctx as *mut WasiCtxBuilder); From 64f896428a9994cd4947da9f6a7ea066922914cc Mon Sep 17 00:00:00 2001 From: "Adam C. Foltzer" Date: Fri, 26 Apr 2019 11:53:21 -0700 Subject: [PATCH 09/21] [lucet-benchmarks] add benchmarks for hostcall wrapper --- benchmarks/lucet-benchmarks/src/modules.rs | 35 ++++++++++++++++++++- benchmarks/lucet-benchmarks/src/seq.rs | 36 ++++++++++++++++++++++ 2 files changed, 70 insertions(+), 1 deletion(-) diff --git a/benchmarks/lucet-benchmarks/src/modules.rs b/benchmarks/lucet-benchmarks/src/modules.rs index 2972f0085..d08a5f662 100644 --- a/benchmarks/lucet-benchmarks/src/modules.rs +++ b/benchmarks/lucet-benchmarks/src/modules.rs @@ -1,4 +1,5 @@ -use lucet_runtime::vmctx::lucet_vmctx; +use lucet_runtime::vmctx::{Vmctx, lucet_vmctx}; +use lucet_runtime::lucet_hostcalls; use lucet_runtime_internals::module::{HeapSpec, MockModuleBuilder, Module}; use lucet_wasi_sdk::{CompileOpts, Lucetc}; use lucetc::{Bindings, LucetcOpts}; @@ -175,3 +176,35 @@ pub fn many_args_mock() -> Arc { .with_export_func(b"f", f as *const extern "C" fn()) .build() } + +pub fn hostcalls_mock() -> Arc { + lucet_hostcalls! { + pub unsafe extern "C" fn hostcall_wrapped( + &mut vmctx, + ) -> () { + assert_eq!(vmctx.heap()[0], 0); + } + } + + unsafe extern "C" fn hostcall_raw(vmctx: *mut lucet_vmctx) { + let vmctx = Vmctx::from_raw(vmctx); + assert_eq!(vmctx.heap()[0], 0); + } + + unsafe extern "C" fn wrapped(vmctx: *mut lucet_vmctx) { + for _ in 0..1000 { + hostcall_wrapped(vmctx); + } + } + + unsafe extern "C" fn raw(vmctx: *mut lucet_vmctx) { + for _ in 0..1000 { + hostcall_raw(vmctx); + } + } + + MockModuleBuilder::new() + .with_export_func(b"wrapped", wrapped as *const extern "C" fn()) + .with_export_func(b"raw", raw as *const extern "C" fn()) + .build() +} diff --git a/benchmarks/lucet-benchmarks/src/seq.rs b/benchmarks/lucet-benchmarks/src/seq.rs index baa3c484b..c090693b6 100644 --- a/benchmarks/lucet-benchmarks/src/seq.rs +++ b/benchmarks/lucet-benchmarks/src/seq.rs @@ -350,6 +350,40 @@ fn run_many_args(c: &mut Criterion) { }); } +fn run_hostcall_wrapped(c: &mut Criterion) { + fn body(inst: &mut InstanceHandle) { + inst.run(b"wrapped", &[]).unwrap(); + } + + let module = hostcalls_mock(); + let region = R::create(1, &Limits::default()).unwrap(); + + c.bench_function(&format!("run_hostcall_wrapped ({})", R::TYPE_NAME), move |b| { + b.iter_batched_ref( + || region.new_instance(module.clone()).unwrap(), + |inst| body(inst), + criterion::BatchSize::PerIteration, + ) + }); +} + +fn run_hostcall_raw(c: &mut Criterion) { + fn body(inst: &mut InstanceHandle) { + inst.run(b"raw", &[]).unwrap(); + } + + let module = hostcalls_mock(); + let region = R::create(1, &Limits::default()).unwrap(); + + c.bench_function(&format!("run_hostcall_raw ({})", R::TYPE_NAME), move |b| { + b.iter_batched_ref( + || region.new_instance(module.clone()).unwrap(), + |inst| body(inst), + criterion::BatchSize::PerIteration, + ) + }); +} + pub fn seq_benches(c: &mut Criterion) { load_mkregion_and_instantiate::(c); instantiate::(c); @@ -362,4 +396,6 @@ pub fn seq_benches(c: &mut Criterion) { run_fib::(c); run_hello::(c); run_many_args::(c); + run_hostcall_wrapped::(c); + run_hostcall_raw::(c); } From 7561b9897368d2fd993bde21b9b4e38fad5ff955 Mon Sep 17 00:00:00 2001 From: "Adam C. Foltzer" Date: Fri, 26 Apr 2019 12:02:45 -0700 Subject: [PATCH 10/21] make `/host` ignore more specific, and check in missing test --- .gitignore | 5 ++++- .../guests/host/hostcall_error_unwind.c | 9 +++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 lucet-runtime/lucet-runtime-tests/guests/host/hostcall_error_unwind.c diff --git a/.gitignore b/.gitignore index 669bd5bf4..d0ba2d0ec 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,8 @@ target/ *.rs.bk *.pyc -host + +# devenv-installed directory +/host + core.* diff --git a/lucet-runtime/lucet-runtime-tests/guests/host/hostcall_error_unwind.c b/lucet-runtime/lucet-runtime-tests/guests/host/hostcall_error_unwind.c new file mode 100644 index 000000000..6b48ce840 --- /dev/null +++ b/lucet-runtime/lucet-runtime-tests/guests/host/hostcall_error_unwind.c @@ -0,0 +1,9 @@ +#include + +extern void hostcall_test_func_hostcall_error_unwind(void); + +int main(void) +{ + hostcall_test_func_hostcall_error_unwind(); + return 0; +} From 0eb7b00f72c0593dcd8f241de6a4ce1f204f4ff5 Mon Sep 17 00:00:00 2001 From: "Adam C. Foltzer" Date: Fri, 26 Apr 2019 13:43:01 -0700 Subject: [PATCH 11/21] [lucet-benchmarks] hostcall overhead benches now take more arguments --- benchmarks/lucet-benchmarks/src/modules.rs | 56 +++++++++++++++++++--- 1 file changed, 50 insertions(+), 6 deletions(-) diff --git a/benchmarks/lucet-benchmarks/src/modules.rs b/benchmarks/lucet-benchmarks/src/modules.rs index d08a5f662..426364bb4 100644 --- a/benchmarks/lucet-benchmarks/src/modules.rs +++ b/benchmarks/lucet-benchmarks/src/modules.rs @@ -1,5 +1,5 @@ -use lucet_runtime::vmctx::{Vmctx, lucet_vmctx}; use lucet_runtime::lucet_hostcalls; +use lucet_runtime::vmctx::{lucet_vmctx, Vmctx}; use lucet_runtime_internals::module::{HeapSpec, MockModuleBuilder, Module}; use lucet_wasi_sdk::{CompileOpts, Lucetc}; use lucetc::{Bindings, LucetcOpts}; @@ -179,27 +179,71 @@ pub fn many_args_mock() -> Arc { pub fn hostcalls_mock() -> Arc { lucet_hostcalls! { + #[inline(never)] + #[no_mangle] pub unsafe extern "C" fn hostcall_wrapped( &mut vmctx, + x1: u64, + x2: u64, + x3: u64, + x4: u64, + x5: u64, + x6: u64, + x7: u64, + x8: u64, + x9: u64, + x10: u64, + x11: u64, + x12: u64, + x13: u64, + x14: u64, + x15: u64, + x16: u64, ) -> () { - assert_eq!(vmctx.heap()[0], 0); + vmctx.heap_mut()[0] = + (x1 + x2 + x3 + x4 + x5 + x6 + x7 + x8 + x9 + x10 + x11 + x12 + x13 + x14 + x15 + x16) + as u8; + assert_eq!(vmctx.heap()[0], 136); } } - unsafe extern "C" fn hostcall_raw(vmctx: *mut lucet_vmctx) { + #[inline(never)] + #[no_mangle] + pub unsafe extern "C" fn hostcall_raw( + vmctx: *mut lucet_vmctx, + x1: u64, + x2: u64, + x3: u64, + x4: u64, + x5: u64, + x6: u64, + x7: u64, + x8: u64, + x9: u64, + x10: u64, + x11: u64, + x12: u64, + x13: u64, + x14: u64, + x15: u64, + x16: u64, + ) { let vmctx = Vmctx::from_raw(vmctx); - assert_eq!(vmctx.heap()[0], 0); + vmctx.heap_mut()[0] = + (x1 + x2 + x3 + x4 + x5 + x6 + x7 + x8 + x9 + x10 + x11 + x12 + x13 + x14 + x15 + x16) + as u8; + assert_eq!(vmctx.heap()[0], 136); } unsafe extern "C" fn wrapped(vmctx: *mut lucet_vmctx) { for _ in 0..1000 { - hostcall_wrapped(vmctx); + hostcall_wrapped(vmctx, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16); } } unsafe extern "C" fn raw(vmctx: *mut lucet_vmctx) { for _ in 0..1000 { - hostcall_raw(vmctx); + hostcall_raw(vmctx, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16); } } From c207a24dd887fd5f0518e99d0a17d5bb46ae0234 Mon Sep 17 00:00:00 2001 From: "Adam C. Foltzer" Date: Fri, 26 Apr 2019 14:11:08 -0700 Subject: [PATCH 12/21] [lucet-runtime] always inline hostcall implementation; use `move` This doesn't seem to make much of a dent in the benchmarking time, but it's the right thing to do --- lucet-runtime/lucet-runtime-internals/src/hostcall_macros.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lucet-runtime/lucet-runtime-internals/src/hostcall_macros.rs b/lucet-runtime/lucet-runtime-internals/src/hostcall_macros.rs index 1c3ac37d1..1ff975fc4 100644 --- a/lucet-runtime/lucet-runtime-internals/src/hostcall_macros.rs +++ b/lucet-runtime/lucet-runtime-internals/src/hostcall_macros.rs @@ -38,13 +38,14 @@ macro_rules! lucet_hostcalls { vmctx_raw: *mut $crate::vmctx::lucet_vmctx, $( $arg: $arg_ty ),* ) -> $ret_ty { + #[inline(always)] unsafe fn hostcall_impl( $vmctx: &mut $crate::vmctx::Vmctx, $( $arg : $arg_ty ),* ) -> $ret_ty { $($body)* } - let res = std::panic::catch_unwind(|| { + let res = std::panic::catch_unwind(move || { hostcall_impl(&mut $crate::vmctx::Vmctx::from_raw(vmctx_raw), $( $arg ),*) }); match res { From e56975c18ff87c16640dfe3ead4a9bb8a4df291f Mon Sep 17 00:00:00 2001 From: "Adam C. Foltzer" Date: Fri, 26 Apr 2019 15:59:35 -0700 Subject: [PATCH 13/21] [lucet-runtime] tweak the terrarium-only vmctx testing interface --- .../lucet-runtime-internals/src/vmctx.rs | 82 ++++++++++++------- 1 file changed, 51 insertions(+), 31 deletions(-) diff --git a/lucet-runtime/lucet-runtime-internals/src/vmctx.rs b/lucet-runtime/lucet-runtime-internals/src/vmctx.rs index e3cde401c..883ecb6f5 100644 --- a/lucet-runtime/lucet-runtime-internals/src/vmctx.rs +++ b/lucet-runtime/lucet-runtime-internals/src/vmctx.rs @@ -22,6 +22,9 @@ pub struct Vmctx { vmctx: *mut lucet_vmctx, heap: RefCell>, globals: RefCell>, + + // only used for the `vmctx_from_mock_instance` fake vmctxs + check_instance: bool, } impl Drop for Vmctx { @@ -50,28 +53,28 @@ pub trait VmctxInternal { impl VmctxInternal for Vmctx { fn instance(&self) -> &Instance { - unsafe { instance_from_vmctx(self.vmctx) } + unsafe { instance_from_vmctx(self.vmctx, self.check_instance) } } unsafe fn instance_mut(&self) -> &mut Instance { - instance_from_vmctx(self.vmctx) + instance_from_vmctx(self.vmctx, self.check_instance) } } impl Vmctx { - /// Create a `Vmctx` from the compiler-inserted `vmctx` argument in a guest - /// function. + /// Create a `Vmctx` from the compiler-inserted `vmctx` argument in a guest function. /// /// This is almost certainly not what you want to use to get a `Vmctx`; instead use the `&mut /// Vmctx` argument to a `lucet_hostcalls!`-wrapped function. pub unsafe fn from_raw(vmctx: *mut lucet_vmctx) -> Vmctx { - let inst = instance_from_vmctx(vmctx); + let inst = instance_from_vmctx(vmctx, true); assert!(inst.valid_magic()); let res = Vmctx { vmctx, heap: RefCell::new(Box::<[u8]>::from_raw(inst.heap_mut())), globals: RefCell::new(Box::<[i64]>::from_raw(inst.globals_mut())), + check_instance: true, }; res } @@ -231,28 +234,33 @@ impl Vmctx { /// Get an `Instance` from the `vmctx` pointer. /// /// Only safe to call from within the guest context. -pub unsafe fn instance_from_vmctx<'a>(vmctx: *mut lucet_vmctx) -> &'a mut Instance { +pub unsafe fn instance_from_vmctx<'a>( + vmctx: *mut lucet_vmctx, + check_instance: bool, +) -> &'a mut Instance { assert!(!vmctx.is_null(), "vmctx is not null"); let inst_ptr = (vmctx as usize - instance_heap_offset()) as *mut Instance; - // We shouldn't actually need to access the thread local, only the exception handler should - // need to. But, as long as the thread local exists, we should make sure that the guest - // hasn't pulled any shenanigans and passed a bad vmctx. (Codegen should ensure the guest - // cant pull any shenanigans but there have been bugs before.) - CURRENT_INSTANCE.with(|current_instance| { - if let Some(current_inst_ptr) = current_instance.borrow().map(|nn| nn.as_ptr()) { - assert_eq!( - inst_ptr, current_inst_ptr, - "vmctx corresponds to current instance" - ); - } else { - panic!( - "current instance is not set; thread local storage failure can indicate \ - dynamic linking issues" - ); - } - }); + if check_instance { + // We shouldn't actually need to access the thread local, only the exception handler should + // need to. But, as long as the thread local exists, we should make sure that the guest + // hasn't pulled any shenanigans and passed a bad vmctx. (Codegen should ensure the guest + // cant pull any shenanigans but there have been bugs before.) + CURRENT_INSTANCE.with(|current_instance| { + if let Some(current_inst_ptr) = current_instance.borrow().map(|nn| nn.as_ptr()) { + assert_eq!( + inst_ptr, current_inst_ptr, + "vmctx corresponds to current instance" + ); + } else { + panic!( + "current instance is not set; thread local storage failure can indicate \ + dynamic linking issues" + ); + } + }); + } let inst = inst_ptr.as_mut().unwrap(); assert!(inst.valid_magic()); @@ -271,17 +279,29 @@ impl Instance { } } -/// Unsafely get a `Vmctx` from an `InstanceHandle`, and fake a current instance TLS variable. +/// Unsafely get a `Vmctx` from an `InstanceHandle` without checking the current instance TLS +/// variable. /// /// This is provided for compatibility with the Terrarium memory management test suite, but should /// absolutely not be used in newer code. #[deprecated] +#[allow(deprecated)] pub unsafe fn vmctx_from_mock_instance(inst: &InstanceHandle) -> Vmctx { - CURRENT_INSTANCE.with(|current_instance| { - let mut current_instance = current_instance.borrow_mut(); - *current_instance = Some(std::ptr::NonNull::new_unchecked( - inst.alloc().slot().start as *mut Instance, - )); - }); - Vmctx::from_raw(inst.alloc().slot().heap as *mut lucet_vmctx) + Vmctx::from_raw_unchecked(inst.alloc().slot().heap as *mut lucet_vmctx) +} + +impl Vmctx { + #[deprecated] + unsafe fn from_raw_unchecked(vmctx: *mut lucet_vmctx) -> Vmctx { + let inst = instance_from_vmctx(vmctx, false); + assert!(inst.valid_magic()); + + let res = Vmctx { + vmctx, + heap: RefCell::new(Box::<[u8]>::from_raw(inst.heap_mut())), + globals: RefCell::new(Box::<[i64]>::from_raw(inst.globals_mut())), + check_instance: false, + }; + res + } } From 394494c2ca3c803e654630eec9a60a66b691886f Mon Sep 17 00:00:00 2001 From: "Adam C. Foltzer" Date: Fri, 26 Apr 2019 17:31:30 -0700 Subject: [PATCH 14/21] [lucet-runtime] remove obsolete testing functions --- .../lucet-runtime-internals/src/vmctx.rs | 81 +++++-------------- .../lucet-runtime-tests/src/helpers.rs | 2 - 2 files changed, 22 insertions(+), 61 deletions(-) diff --git a/lucet-runtime/lucet-runtime-internals/src/vmctx.rs b/lucet-runtime/lucet-runtime-internals/src/vmctx.rs index 883ecb6f5..f35a9d4a8 100644 --- a/lucet-runtime/lucet-runtime-internals/src/vmctx.rs +++ b/lucet-runtime/lucet-runtime-internals/src/vmctx.rs @@ -9,8 +9,7 @@ use crate::alloc::instance_heap_offset; use crate::context::Context; use crate::error::Error; use crate::instance::{ - Instance, InstanceHandle, InstanceInternal, State, TerminationDetails, CURRENT_INSTANCE, - HOST_CTX, + Instance, InstanceInternal, State, TerminationDetails, CURRENT_INSTANCE, HOST_CTX, }; use std::any::Any; use std::borrow::{Borrow, BorrowMut}; @@ -22,9 +21,6 @@ pub struct Vmctx { vmctx: *mut lucet_vmctx, heap: RefCell>, globals: RefCell>, - - // only used for the `vmctx_from_mock_instance` fake vmctxs - check_instance: bool, } impl Drop for Vmctx { @@ -53,11 +49,11 @@ pub trait VmctxInternal { impl VmctxInternal for Vmctx { fn instance(&self) -> &Instance { - unsafe { instance_from_vmctx(self.vmctx, self.check_instance) } + unsafe { instance_from_vmctx(self.vmctx) } } unsafe fn instance_mut(&self) -> &mut Instance { - instance_from_vmctx(self.vmctx, self.check_instance) + instance_from_vmctx(self.vmctx) } } @@ -67,14 +63,13 @@ impl Vmctx { /// This is almost certainly not what you want to use to get a `Vmctx`; instead use the `&mut /// Vmctx` argument to a `lucet_hostcalls!`-wrapped function. pub unsafe fn from_raw(vmctx: *mut lucet_vmctx) -> Vmctx { - let inst = instance_from_vmctx(vmctx, true); + let inst = instance_from_vmctx(vmctx); assert!(inst.valid_magic()); let res = Vmctx { vmctx, heap: RefCell::new(Box::<[u8]>::from_raw(inst.heap_mut())), globals: RefCell::new(Box::<[i64]>::from_raw(inst.globals_mut())), - check_instance: true, }; res } @@ -234,33 +229,28 @@ impl Vmctx { /// Get an `Instance` from the `vmctx` pointer. /// /// Only safe to call from within the guest context. -pub unsafe fn instance_from_vmctx<'a>( - vmctx: *mut lucet_vmctx, - check_instance: bool, -) -> &'a mut Instance { +pub unsafe fn instance_from_vmctx<'a>(vmctx: *mut lucet_vmctx) -> &'a mut Instance { assert!(!vmctx.is_null(), "vmctx is not null"); let inst_ptr = (vmctx as usize - instance_heap_offset()) as *mut Instance; - if check_instance { - // We shouldn't actually need to access the thread local, only the exception handler should - // need to. But, as long as the thread local exists, we should make sure that the guest - // hasn't pulled any shenanigans and passed a bad vmctx. (Codegen should ensure the guest - // cant pull any shenanigans but there have been bugs before.) - CURRENT_INSTANCE.with(|current_instance| { - if let Some(current_inst_ptr) = current_instance.borrow().map(|nn| nn.as_ptr()) { - assert_eq!( - inst_ptr, current_inst_ptr, - "vmctx corresponds to current instance" - ); - } else { - panic!( - "current instance is not set; thread local storage failure can indicate \ - dynamic linking issues" - ); - } - }); - } + // We shouldn't actually need to access the thread local, only the exception handler should + // need to. But, as long as the thread local exists, we should make sure that the guest + // hasn't pulled any shenanigans and passed a bad vmctx. (Codegen should ensure the guest + // cant pull any shenanigans but there have been bugs before.) + CURRENT_INSTANCE.with(|current_instance| { + if let Some(current_inst_ptr) = current_instance.borrow().map(|nn| nn.as_ptr()) { + assert_eq!( + inst_ptr, current_inst_ptr, + "vmctx corresponds to current instance" + ); + } else { + panic!( + "current instance is not set; thread local storage failure can indicate \ + dynamic linking issues" + ); + } + }); let inst = inst_ptr.as_mut().unwrap(); assert!(inst.valid_magic()); @@ -278,30 +268,3 @@ impl Instance { HOST_CTX.with(|host_ctx| unsafe { Context::set(&*host_ctx.get()) }) } } - -/// Unsafely get a `Vmctx` from an `InstanceHandle` without checking the current instance TLS -/// variable. -/// -/// This is provided for compatibility with the Terrarium memory management test suite, but should -/// absolutely not be used in newer code. -#[deprecated] -#[allow(deprecated)] -pub unsafe fn vmctx_from_mock_instance(inst: &InstanceHandle) -> Vmctx { - Vmctx::from_raw_unchecked(inst.alloc().slot().heap as *mut lucet_vmctx) -} - -impl Vmctx { - #[deprecated] - unsafe fn from_raw_unchecked(vmctx: *mut lucet_vmctx) -> Vmctx { - let inst = instance_from_vmctx(vmctx, false); - assert!(inst.valid_magic()); - - let res = Vmctx { - vmctx, - heap: RefCell::new(Box::<[u8]>::from_raw(inst.heap_mut())), - globals: RefCell::new(Box::<[i64]>::from_raw(inst.globals_mut())), - check_instance: false, - }; - res - } -} diff --git a/lucet-runtime/lucet-runtime-tests/src/helpers.rs b/lucet-runtime/lucet-runtime-tests/src/helpers.rs index 967810c9c..f91f8fa79 100644 --- a/lucet-runtime/lucet-runtime-tests/src/helpers.rs +++ b/lucet-runtime/lucet-runtime-tests/src/helpers.rs @@ -1,7 +1,5 @@ // re-export types that should only be used for testing pub use lucet_runtime_internals::module::{HeapSpec, MockModuleBuilder}; -#[allow(deprecated)] -pub use lucet_runtime_internals::vmctx::vmctx_from_mock_instance; use lazy_static::lazy_static; use std::sync::RwLock; From d65d75a5d23f7b748ec77ffcd95c1030b21d558c Mon Sep 17 00:00:00 2001 From: "Adam C. Foltzer" Date: Mon, 29 Apr 2019 10:46:44 -0700 Subject: [PATCH 15/21] =?UTF-8?q?[lucet-runtime]=20=F0=9F=90=9B=20fix=20so?= =?UTF-8?q?undness=20of=20vmctx=20heap=20methods?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Also adds a privileged type for terminating from C --- lucet-runtime/lucet-runtime-internals/src/c_api.rs | 11 +++++++++-- .../lucet-runtime-internals/src/instance.rs | 4 ++-- lucet-runtime/lucet-runtime-internals/src/vmctx.rs | 14 ++++++++++++++ lucet-runtime/src/c_api.rs | 4 ++-- 4 files changed, 27 insertions(+), 6 deletions(-) diff --git a/lucet-runtime/lucet-runtime-internals/src/c_api.rs b/lucet-runtime/lucet-runtime-internals/src/c_api.rs index be6a242b6..5ce4c6502 100644 --- a/lucet-runtime/lucet-runtime-internals/src/c_api.rs +++ b/lucet-runtime/lucet-runtime-internals/src/c_api.rs @@ -195,8 +195,15 @@ pub type lucet_signal_handler = unsafe extern "C" fn( pub type lucet_fatal_handler = unsafe extern "C" fn(inst: *mut lucet_instance); +pub struct CTerminationDetails { + pub details: *mut c_void, +} + +unsafe impl Send for CTerminationDetails {} +unsafe impl Sync for CTerminationDetails {} + pub mod lucet_state { - use crate::c_api::lucet_val; + use crate::c_api::{lucet_val, CTerminationDetails}; use crate::instance::{State, TerminationDetails}; use crate::module::AddrDetails; use crate::sysdeps::UContext; @@ -256,7 +263,7 @@ pub mod lucet_state { reason: lucet_terminated_reason::Provided, provided: p .downcast_ref() - .map(|v| *v) + .map(|CTerminationDetails { details }| *details) .unwrap_or(std::ptr::null_mut()), }, }, diff --git a/lucet-runtime/lucet-runtime-internals/src/instance.rs b/lucet-runtime/lucet-runtime-internals/src/instance.rs index 765c1e5e2..7eec7f57f 100644 --- a/lucet-runtime/lucet-runtime-internals/src/instance.rs +++ b/lucet-runtime/lucet-runtime-internals/src/instance.rs @@ -723,11 +723,11 @@ pub enum TerminationDetails { /// Returned when dynamic borrowing rules of methods like `Vmctx::heap()` are violated. BorrowError(&'static str), /// Calls to `lucet_hostcall_terminate` provide a payload for use by the embedder. - Provided(Arc), + Provided(Arc), } impl TerminationDetails { - pub fn provide(details: A) -> Self { + pub fn provide(details: A) -> Self { TerminationDetails::Provided(Arc::new(details)) } pub fn provided_details(&self) -> Option<&dyn Any> { diff --git a/lucet-runtime/lucet-runtime-internals/src/vmctx.rs b/lucet-runtime/lucet-runtime-internals/src/vmctx.rs index f35a9d4a8..802c8c9f0 100644 --- a/lucet-runtime/lucet-runtime-internals/src/vmctx.rs +++ b/lucet-runtime/lucet-runtime-internals/src/vmctx.rs @@ -84,6 +84,9 @@ impl Vmctx { /// If the heap is already mutably borrowed by `heap_mut()`, the instance will /// terminate with `TerminationDetails::BorrowError`. pub fn heap(&self) -> Ref<[u8]> { + unsafe { + self.reconstitute_heap_if_needed(); + } let r = self .heap .try_borrow() @@ -96,6 +99,9 @@ impl Vmctx { /// If the heap is already borrowed by `heap()` or `heap_mut()`, the instance will terminate /// with `TerminationDetails::BorrowError`. pub fn heap_mut(&self) -> RefMut<[u8]> { + unsafe { + self.reconstitute_heap_if_needed(); + } let r = self .heap .try_borrow_mut() @@ -103,6 +109,14 @@ impl Vmctx { RefMut::map(r, |b| b.borrow_mut()) } + unsafe fn reconstitute_heap_if_needed(&self) { + let inst = self.instance_mut(); + if inst.heap_mut().len() != self.heap.borrow().len() { + let old_heap = self.heap.replace(Box::<[u8]>::from_raw(inst.heap_mut())); + Box::leak(old_heap); + } + } + /// Check whether a given range in the host address space overlaps with the memory that backs /// the instance heap. pub fn check_heap(&self, ptr: *const T, len: usize) -> bool { diff --git a/lucet-runtime/src/c_api.rs b/lucet-runtime/src/c_api.rs index 39a306972..cb3bec40d 100644 --- a/lucet-runtime/src/c_api.rs +++ b/lucet-runtime/src/c_api.rs @@ -429,9 +429,9 @@ lucet_hostcalls! { #[no_mangle] pub unsafe extern "C" fn lucet_vmctx_terminate( &mut _vmctx, - info: *mut c_void, + details: *mut c_void, ) -> () { - lucet_hostcall_terminate!(info); + lucet_hostcall_terminate!(CTerminationDetails { details}); } #[no_mangle] From 031089ef439bbc555ef88d1e8422ccfa974687a7 Mon Sep 17 00:00:00 2001 From: "Adam C. Foltzer" Date: Mon, 29 Apr 2019 11:10:51 -0700 Subject: [PATCH 16/21] [lucet-runtime] document the assumptions around the Vmctx heap view --- lucet-runtime/lucet-runtime-internals/src/vmctx.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lucet-runtime/lucet-runtime-internals/src/vmctx.rs b/lucet-runtime/lucet-runtime-internals/src/vmctx.rs index 802c8c9f0..fcbc99e26 100644 --- a/lucet-runtime/lucet-runtime-internals/src/vmctx.rs +++ b/lucet-runtime/lucet-runtime-internals/src/vmctx.rs @@ -109,6 +109,16 @@ impl Vmctx { RefMut::map(r, |b| b.borrow_mut()) } + /// Check whether the heap has grown, and replace the heap view if it has. + /// + /// This handles the case where `Vmctx::grow_memory()` and `Vmctx::heap()` are called in + /// sequence. Since `Vmctx::grow_memory()` takes `&mut self`, heap references cannot live across + /// it. + /// + /// TODO: There is still an unsound case, though, when a heap reference is held across a call + /// back into the guest via `Vmctx::get_func_from_idx()`. That guest code may grow the heap as + /// well, causing any outstanding heap references to become invalid. We will address this when + /// we rework the interface for calling back into the guest. unsafe fn reconstitute_heap_if_needed(&self) { let inst = self.instance_mut(); if inst.heap_mut().len() != self.heap.borrow().len() { From 476b4e24cf67eac9a944f1300945535b658469a5 Mon Sep 17 00:00:00 2001 From: Frank Denis Date: Sat, 27 Apr 2019 00:15:08 +0200 Subject: [PATCH 17/21] Add another explicit page zeroing when MADV_DONTNEED is not enough --- lucet-runtime/lucet-runtime-internals/src/region/mmap.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lucet-runtime/lucet-runtime-internals/src/region/mmap.rs b/lucet-runtime/lucet-runtime-internals/src/region/mmap.rs index c97fd5e14..cdaee6e04 100644 --- a/lucet-runtime/lucet-runtime-internals/src/region/mmap.rs +++ b/lucet-runtime/lucet-runtime-internals/src/region/mmap.rs @@ -100,6 +100,13 @@ impl RegionInternal for MmapRegion { { // eprintln!("setting none {:p}[{:x}]", *ptr, len); unsafe { + // MADV_DONTNEED is not guaranteed to clear pages on non-Linux systems + #[cfg(not(target_os = "linux"))] + { + mprotect(*ptr, *len, ProtFlags::PROT_READ | ProtFlags::PROT_WRITE) + .expect("mprotect succeeds during drop"); + memset(*ptr, 0, *len); + } mprotect(*ptr, *len, ProtFlags::PROT_NONE).expect("mprotect succeeds during drop"); madvise(*ptr, *len, MmapAdvise::MADV_DONTNEED) .expect("madvise succeeds during drop"); From c20ca5b7c2fab345f615dca3595366c28d5cdaac Mon Sep 17 00:00:00 2001 From: Frank Denis Date: Sat, 27 Apr 2019 01:10:19 +0200 Subject: [PATCH 18/21] Zero the heap only until the accessible size --- lucet-runtime/lucet-runtime-internals/src/region/mmap.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lucet-runtime/lucet-runtime-internals/src/region/mmap.rs b/lucet-runtime/lucet-runtime-internals/src/region/mmap.rs index cdaee6e04..525364792 100644 --- a/lucet-runtime/lucet-runtime-internals/src/region/mmap.rs +++ b/lucet-runtime/lucet-runtime-internals/src/region/mmap.rs @@ -91,7 +91,8 @@ impl RegionInternal for MmapRegion { // clear and disable access to the heap, stack, globals, and sigstack for (ptr, len) in [ - (slot.heap, slot.limits.heap_address_space_size), + // We don't ever shrink the heap, so we only need to zero up until the accessible size + (slot.heap, alloc.heap_accessible_size), (slot.stack, slot.limits.stack_size), (slot.globals, slot.limits.globals_size), (slot.sigstack, SIGSTKSZ), From 0ffcccb670de29131bd2d1b6bf24da703623e169 Mon Sep 17 00:00:00 2001 From: Frank Denis Date: Mon, 29 Apr 2019 15:07:03 +0200 Subject: [PATCH 19/21] Update faerie to version 0.10 + support for extended ELF section Also update cranelift, that already moved to faerie 0.10. --- Cargo.lock | 99 +++++++++++++++++++++++++++-------------------- Cargo.toml | 2 +- cranelift | 2 +- faerie | 2 +- lucetc/Cargo.toml | 2 +- 5 files changed, 62 insertions(+), 45 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a59350a30..f728afede 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -203,87 +203,87 @@ dependencies = [ [[package]] name = "cranelift-bforest" -version = "0.29.0" +version = "0.30.0" dependencies = [ - "cranelift-entity 0.29.0", + "cranelift-entity 0.30.0", ] [[package]] name = "cranelift-codegen" -version = "0.29.0" +version = "0.30.0" dependencies = [ - "cranelift-bforest 0.29.0", - "cranelift-codegen-meta 0.29.0", - "cranelift-entity 0.29.0", + "cranelift-bforest 0.30.0", + "cranelift-codegen-meta 0.30.0", + "cranelift-entity 0.30.0", "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", - "target-lexicon 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "target-lexicon 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "cranelift-codegen-meta" -version = "0.29.0" +version = "0.30.0" dependencies = [ - "cranelift-entity 0.29.0", + "cranelift-entity 0.30.0", ] [[package]] name = "cranelift-entity" -version = "0.29.0" +version = "0.30.0" [[package]] name = "cranelift-faerie" -version = "0.29.0" +version = "0.30.0" dependencies = [ - "cranelift-codegen 0.29.0", - "cranelift-module 0.29.0", - "faerie 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", + "cranelift-codegen 0.30.0", + "cranelift-module 0.30.0", + "faerie 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "goblin 0.0.21 (registry+https://github.com/rust-lang/crates.io-index)", - "target-lexicon 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "target-lexicon 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "cranelift-frontend" -version = "0.29.0" +version = "0.30.0" dependencies = [ - "cranelift-codegen 0.29.0", + "cranelift-codegen 0.30.0", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", - "target-lexicon 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "target-lexicon 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "cranelift-module" -version = "0.29.0" +version = "0.30.0" dependencies = [ - "cranelift-codegen 0.29.0", - "cranelift-entity 0.29.0", + "cranelift-codegen 0.30.0", + "cranelift-entity 0.30.0", "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "cranelift-native" -version = "0.29.0" +version = "0.30.0" dependencies = [ - "cranelift-codegen 0.29.0", + "cranelift-codegen 0.30.0", "raw-cpuid 6.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "target-lexicon 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "target-lexicon 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "cranelift-wasm" -version = "0.29.0" +version = "0.30.0" dependencies = [ "cast 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "cranelift-codegen 0.29.0", - "cranelift-entity 0.29.0", - "cranelift-frontend 0.29.0", + "cranelift-codegen 0.30.0", + "cranelift-entity 0.30.0", + "cranelift-frontend 0.30.0", "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", - "wasmparser 0.23.0 (registry+https://github.com/rust-lang/crates.io-index)", + "wasmparser 0.29.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -410,7 +410,7 @@ dependencies = [ [[package]] name = "faerie" -version = "0.9.1" +version = "0.10.0" dependencies = [ "env_logger 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", @@ -421,14 +421,14 @@ dependencies = [ "string-interner 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)", "structopt 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)", "structopt-derive 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)", - "target-lexicon 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "target-lexicon 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "faerie" -version = "0.9.1" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -replace = "faerie 0.9.1" +replace = "faerie 0.10.0" [[package]] name = "failure" @@ -751,14 +751,14 @@ dependencies = [ "bincode 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", - "cranelift-codegen 0.29.0", - "cranelift-faerie 0.29.0", - "cranelift-frontend 0.29.0", - "cranelift-module 0.29.0", - "cranelift-native 0.29.0", - "cranelift-wasm 0.29.0", + "cranelift-codegen 0.30.0", + "cranelift-faerie 0.30.0", + "cranelift-frontend 0.30.0", + "cranelift-module 0.30.0", + "cranelift-native 0.30.0", + "cranelift-wasm 0.30.0", "env_logger 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", - "faerie 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", + "faerie 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "human-size 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1296,6 +1296,16 @@ dependencies = [ "serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "target-lexicon" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "tempfile" version = "3.0.7" @@ -1469,6 +1479,11 @@ name = "wasmparser" version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "wasmparser" +version = "0.29.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "which" version = "2.0.1" @@ -1565,7 +1580,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum env_logger 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b61fa891024a945da30a9581546e8cfaf5602c7b3f4c137a2805cf388f92075a" "checksum errno 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "c2a071601ed01b988f896ab14b95e67335d1eeb50190932a1320f7fe3cadc84e" "checksum errno-dragonfly 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "14ca354e36190500e1e1fb267c647932382b54053c50b14970856c0b00a35067" -"checksum faerie 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f48412f92b56015a240e249847295b38b0a731435806c21a199403b2c317272c" +"checksum faerie 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3c6d75e6376216d6228fbab8025087523666623d9302ff17dd023d024bf98302" "checksum failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "795bd83d3abeb9220f257e597aa0080a508b27533824adf336529648f6abf7e2" "checksum failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ea1063915fd7ef4309e222a5a07cf9c319fb9c7836b1f89b85458672dbb127e1" "checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" @@ -1647,6 +1662,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum syn 0.15.31 (registry+https://github.com/rust-lang/crates.io-index)" = "d2b4cfac95805274c6afdb12d8f770fa2d27c045953e7b630a81801953699a9a" "checksum synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "73687139bf99285483c96ac0add482c3776528beac1d97d444f6e91f203a2015" "checksum target-lexicon 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d6923974ce4eb5bd28814756256d8ab71c28dd6e7483313fe7ab6614306bf633" +"checksum target-lexicon 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1b0ab4982b8945c35cc1c46a83a9094c414f6828a099ce5dcaa8ee2b04642dcb" "checksum tempfile 3.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "b86c784c88d98c801132806dadd3819ed29d8600836c4088e855cdf3e178ed8a" "checksum termcolor 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "4096add70612622289f2fdcdbd5086dc81c1e2675e6ae58d6c4f62a16c6d7f2f" "checksum terminal_size 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "023345d35850b69849741bd9a5432aa35290e3d8eb76af8717026f270d1cf133" @@ -1668,6 +1684,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum wait-timeout 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" "checksum walkdir 2.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "9d9d7ed3431229a144296213105a390676cc49c9b6a72bd19f3176c98e129fa1" "checksum wasmparser 0.23.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b5e01c420bc7d36e778bd242e1167b079562ba8b34087122cc9057187026d060" +"checksum wasmparser 0.29.2 (registry+https://github.com/rust-lang/crates.io-index)" = "981a8797cf89762e0233ec45fae731cb79a4dfaee12d9f0fe6cee01e4ac58d00" "checksum which 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b57acb10231b9493c8472b20cb57317d0679a49e0bdbee44b3b803a6473af164" "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" "checksum winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "f10e386af2b13e47c89e7236a7a14a086791a2b88ebad6df9bf42040195cf770" diff --git a/Cargo.toml b/Cargo.toml index 34eed17b3..f1ff22eb8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,4 +20,4 @@ exclude = ["cranelift"] rpath = true [replace] -"faerie:0.9.1" = { path = "faerie" } +"faerie:0.10.0" = { path = "faerie" } diff --git a/cranelift b/cranelift index 894cecc78..c0ee30f9a 160000 --- a/cranelift +++ b/cranelift @@ -1 +1 @@ -Subproject commit 894cecc78fa3169c7efd74bafb5165315f0f7f36 +Subproject commit c0ee30f9a95bd80bd6485c21bad7e8e0b8c5caf0 diff --git a/faerie b/faerie index ebe9edff9..dcb671a99 160000 --- a/faerie +++ b/faerie @@ -1 +1 @@ -Subproject commit ebe9edff906749bd52718b8552d560b232a608ff +Subproject commit dcb671a99faa32b72fac88d2406e952a41844636 diff --git a/lucetc/Cargo.toml b/lucetc/Cargo.toml index 108e8543e..11e3fabda 100644 --- a/lucetc/Cargo.toml +++ b/lucetc/Cargo.toml @@ -28,7 +28,7 @@ wasmparser = "0.23" clap="2.32" log = "0.4" env_logger = "0.6" -faerie = "0.9.1" +faerie = "0.10.0" failure = "0.1" serde = "1.0" serde_json = "1.0" From 5ee39812417a47d795da6a9827f04b3e0d2c2e84 Mon Sep 17 00:00:00 2001 From: "Adam C. Foltzer" Date: Tue, 30 Apr 2019 11:05:27 -0700 Subject: [PATCH 20/21] [lucet-runtime] make sigaltstack restore on a per-thread basis This was previously causing the Lucet sigstack to stay installed after an instance returns, as long as another instance was running. --- .../src/instance/signals.rs | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/lucet-runtime/lucet-runtime-internals/src/instance/signals.rs b/lucet-runtime/lucet-runtime-internals/src/instance/signals.rs index 67581e2db..68dc76ca6 100644 --- a/lucet-runtime/lucet-runtime-internals/src/instance/signals.rs +++ b/lucet-runtime/lucet-runtime-internals/src/instance/signals.rs @@ -54,7 +54,9 @@ impl Instance { where F: FnOnce(&mut Instance) -> Result, { - // setup signal stack for this thread + // Set up the signal stack for this thread. Note that because signal stacks are per-thread, + // rather than per-process, we do this for every run, while the signal handler is installed + // only once per process. let guest_sigstack = SigStack::new( self.alloc.slot().sigstack, SigStackFlags::empty(), @@ -88,13 +90,6 @@ impl Instance { state.counter -= 1; if state.counter == 0 { unsafe { - // restore the host signal stack - if !altstack_flags() - .expect("the current stack flags could be retrieved") - .contains(SigStackFlags::SS_ONSTACK) - { - sigaltstack(previous_sigstack).expect("sigaltstack restoration succeeds"); - } restore_host_signal_state(state); } true @@ -108,6 +103,16 @@ impl Instance { *ostate = None; } + unsafe { + // restore the host signal stack for this thread + if !altstack_flags() + .expect("the current stack flags could be retrieved") + .contains(SigStackFlags::SS_ONSTACK) + { + sigaltstack(previous_sigstack).expect("sigaltstack restoration succeeds"); + } + } + res } } From 1f259308125b337a37043d532d55c94fccdeed4c Mon Sep 17 00:00:00 2001 From: "Adam C. Foltzer" Date: Tue, 30 Apr 2019 13:41:07 -0700 Subject: [PATCH 21/21] [lucet-runtime] improve clarity of Vmctx heap/globals views This patch renames these fields to emphasize that they are views into memory managed elsewhere, rather than a normal Rust-allocated structure. Also adds comments whenever we use `Box::leak()` to avoid normal Rust deallocation. --- .../lucet-runtime-internals/src/vmctx.rs | 52 ++++++++++++------- 1 file changed, 34 insertions(+), 18 deletions(-) diff --git a/lucet-runtime/lucet-runtime-internals/src/vmctx.rs b/lucet-runtime/lucet-runtime-internals/src/vmctx.rs index fcbc99e26..a1700b87f 100644 --- a/lucet-runtime/lucet-runtime-internals/src/vmctx.rs +++ b/lucet-runtime/lucet-runtime-internals/src/vmctx.rs @@ -19,16 +19,28 @@ use std::cell::{Ref, RefCell, RefMut}; #[derive(Debug)] pub struct Vmctx { vmctx: *mut lucet_vmctx, - heap: RefCell>, - globals: RefCell>, + /// A view of the underlying instance's heap. + /// + /// This must never be dropped automatically, as the view does not own the heap. Rather, this is + /// a value used to implement dynamic borrowing of the heap contents that are owned and managed + /// by the instance and its `Alloc`. + heap_view: RefCell>, + /// A view of the underlying instance's globals. + /// + /// This must never be dropped automatically, as the view does not own the globals. Rather, this + /// is a value used to implement dynamic borrowing of the globals that are owned and managed by + /// the instance and its `Alloc`. + globals_view: RefCell>, } impl Drop for Vmctx { fn drop(&mut self) { - let heap = self.heap.replace(Box::new([])); - let globals = self.globals.replace(Box::new([])); - Box::leak(heap); - Box::leak(globals); + let heap_view = self.heap_view.replace(Box::new([])); + let globals_view = self.globals_view.replace(Box::new([])); + // as described in the definition of `Vmctx`, we cannot allow the boxed views of the heap + // and globals to be dropped + Box::leak(heap_view); + Box::leak(globals_view); } } @@ -68,8 +80,8 @@ impl Vmctx { let res = Vmctx { vmctx, - heap: RefCell::new(Box::<[u8]>::from_raw(inst.heap_mut())), - globals: RefCell::new(Box::<[i64]>::from_raw(inst.globals_mut())), + heap_view: RefCell::new(Box::<[u8]>::from_raw(inst.heap_mut())), + globals_view: RefCell::new(Box::<[i64]>::from_raw(inst.globals_mut())), }; res } @@ -85,10 +97,10 @@ impl Vmctx { /// terminate with `TerminationDetails::BorrowError`. pub fn heap(&self) -> Ref<[u8]> { unsafe { - self.reconstitute_heap_if_needed(); + self.reconstitute_heap_view_if_needed(); } let r = self - .heap + .heap_view .try_borrow() .unwrap_or_else(|_| panic!(TerminationDetails::BorrowError("heap"))); Ref::map(r, |b| b.borrow()) @@ -100,10 +112,10 @@ impl Vmctx { /// with `TerminationDetails::BorrowError`. pub fn heap_mut(&self) -> RefMut<[u8]> { unsafe { - self.reconstitute_heap_if_needed(); + self.reconstitute_heap_view_if_needed(); } let r = self - .heap + .heap_view .try_borrow_mut() .unwrap_or_else(|_| panic!(TerminationDetails::BorrowError("heap_mut"))); RefMut::map(r, |b| b.borrow_mut()) @@ -119,11 +131,15 @@ impl Vmctx { /// back into the guest via `Vmctx::get_func_from_idx()`. That guest code may grow the heap as /// well, causing any outstanding heap references to become invalid. We will address this when /// we rework the interface for calling back into the guest. - unsafe fn reconstitute_heap_if_needed(&self) { + unsafe fn reconstitute_heap_view_if_needed(&self) { let inst = self.instance_mut(); - if inst.heap_mut().len() != self.heap.borrow().len() { - let old_heap = self.heap.replace(Box::<[u8]>::from_raw(inst.heap_mut())); - Box::leak(old_heap); + if inst.heap_mut().len() != self.heap_view.borrow().len() { + let old_heap_view = self + .heap_view + .replace(Box::<[u8]>::from_raw(inst.heap_mut())); + // as described in the definition of `Vmctx`, we cannot allow the boxed view of the heap + // to be dropped + Box::leak(old_heap_view); } } @@ -190,7 +206,7 @@ impl Vmctx { /// with `TerminationDetails::BorrowError`. pub fn globals(&self) -> Ref<[i64]> { let r = self - .globals + .globals_view .try_borrow() .unwrap_or_else(|_| panic!(TerminationDetails::BorrowError("globals"))); Ref::map(r, |b| b.borrow()) @@ -202,7 +218,7 @@ impl Vmctx { /// terminate with `TerminationDetails::BorrowError`. pub fn globals_mut(&self) -> RefMut<[i64]> { let r = self - .globals + .globals_view .try_borrow_mut() .unwrap_or_else(|_| panic!(TerminationDetails::BorrowError("globals_mut"))); RefMut::map(r, |b| b.borrow_mut())