From b382cc8f9d367779b54a9aa034c4287fb8241dde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Wed, 8 Sep 2021 12:28:23 +0200 Subject: [PATCH] Improve sandbox internal api (#9709) * Improve sandbox internal api This improves the internal sandbox api for the executor implementations. The main point is to hide the tls in the internal api and not having it exposed to the outside. This is especially needed for wasmtime 0.29.0 * Fmt * Make it nicer --- substrate/Cargo.lock | 1 + substrate/client/executor/common/Cargo.toml | 1 + .../client/executor/common/src/sandbox.rs | 544 ++++++++---------- substrate/client/executor/wasmi/src/lib.rs | 185 +++--- .../client/executor/wasmtime/src/host.rs | 539 ++++++++--------- .../executor/wasmtime/src/state_holder.rs | 6 +- 6 files changed, 579 insertions(+), 697 deletions(-) diff --git a/substrate/Cargo.lock b/substrate/Cargo.lock index d5205f0dd4..698060c70d 100644 --- a/substrate/Cargo.lock +++ b/substrate/Cargo.lock @@ -7679,6 +7679,7 @@ name = "sc-executor-common" version = "0.10.0-dev" dependencies = [ "derive_more", + "environmental", "parity-scale-codec", "pwasm-utils", "sc-allocator", diff --git a/substrate/client/executor/common/Cargo.toml b/substrate/client/executor/common/Cargo.toml index 402df438f6..c4fc8c27f7 100644 --- a/substrate/client/executor/common/Cargo.toml +++ b/substrate/client/executor/common/Cargo.toml @@ -24,6 +24,7 @@ sp-wasm-interface = { version = "4.0.0-dev", path = "../../../primitives/wasm-in sp-maybe-compressed-blob = { version = "4.0.0-dev", path = "../../../primitives/maybe-compressed-blob" } sp-serializer = { version = "3.0.0", path = "../../../primitives/serializer" } thiserror = "1.0.21" +environmental = "1.1.3" wasmer = { version = "1.0", optional = true } wasmer-compiler-singlepass = { version = "1.0", optional = true } diff --git a/substrate/client/executor/common/src/sandbox.rs b/substrate/client/executor/common/src/sandbox.rs index 7a92e8e2bd..b627294241 100644 --- a/substrate/client/executor/common/src/sandbox.rs +++ b/substrate/client/executor/common/src/sandbox.rs @@ -37,6 +37,8 @@ use wasmi::{ use crate::util::wasmer::MemoryWrapper as WasmerMemoryWrapper; use crate::util::wasmi::MemoryWrapper as WasmiMemoryWrapper; +environmental::environmental!(SandboxContextStore: trait SandboxContext); + /// Index of a function inside the supervisor. /// /// This is a typically an index in the default table of the supervisor, however @@ -164,17 +166,11 @@ impl ImportResolver for Imports { } } -/// This trait encapsulates sandboxing capabilities. -/// -/// Note that this functions are only called in the `supervisor` context. -pub trait SandboxCapabilities: FunctionContext { - /// Represents a function reference into the supervisor environment. - /// Provides an abstraction over execution environment. - type SupervisorFuncRef; - +/// The sandbox context used to execute sandboxed functions. +pub trait SandboxContext { /// Invoke a function in the supervisor environment. /// - /// This first invokes the dispatch_thunk function, passing in the function index of the + /// This first invokes the dispatch thunk function, passing in the function index of the /// desired function to call and serialized arguments. The thunk calls the desired function /// with the deserialized arguments, then serializes the result into memory and returns /// reference. The pointer to and length of the result in linear memory is encoded into an @@ -187,24 +183,23 @@ pub trait SandboxCapabilities: FunctionContext { /// execution. fn invoke( &mut self, - dispatch_thunk: &Self::SupervisorFuncRef, invoke_args_ptr: Pointer, invoke_args_len: WordSize, state: u32, func_idx: SupervisorFuncIndex, ) -> Result; + + /// Returns the supervisor context. + fn supervisor_context(&mut self) -> &mut dyn FunctionContext; } /// Implementation of [`Externals`] that allows execution of guest module with /// [externals][`Externals`] that might refer functions defined by supervisor. /// /// [`Externals`]: ../wasmi/trait.Externals.html -pub struct GuestExternals<'a, FE: SandboxCapabilities + 'a> { - /// Supervisor function environment - supervisor_externals: &'a mut FE, - +pub struct GuestExternals<'a> { /// Instance of sandboxed module to be dispatched - sandbox_instance: &'a SandboxInstance, + sandbox_instance: &'a SandboxInstance, /// External state passed to guest environment, see the `instantiate` function state: u32, @@ -232,114 +227,108 @@ fn deserialize_result( } } -impl<'a, FE: SandboxCapabilities + 'a> Externals for GuestExternals<'a, FE> { +impl<'a> Externals for GuestExternals<'a> { fn invoke_index( &mut self, index: usize, args: RuntimeArgs, ) -> std::result::Result, Trap> { - // Make `index` typesafe again. - let index = GuestFuncIndex(index); + SandboxContextStore::with(|sandbox_context| { + // Make `index` typesafe again. + let index = GuestFuncIndex(index); - // Convert function index from guest to supervisor space - let func_idx = self.sandbox_instance - .guest_to_supervisor_mapping - .func_by_guest_index(index) - .expect( - "`invoke_index` is called with indexes registered via `FuncInstance::alloc_host`; - `FuncInstance::alloc_host` is called with indexes that were obtained from `guest_to_supervisor_mapping`; - `func_by_guest_index` called with `index` can't return `None`; - qed" + // Convert function index from guest to supervisor space + let func_idx = self.sandbox_instance + .guest_to_supervisor_mapping + .func_by_guest_index(index) + .expect( + "`invoke_index` is called with indexes registered via `FuncInstance::alloc_host`; + `FuncInstance::alloc_host` is called with indexes that were obtained from `guest_to_supervisor_mapping`; + `func_by_guest_index` called with `index` can't return `None`; + qed" + ); + + // Serialize arguments into a byte vector. + let invoke_args_data: Vec = args + .as_ref() + .iter() + .cloned() + .map(sp_wasm_interface::Value::from) + .collect::>() + .encode(); + + let state = self.state; + + // Move serialized arguments inside the memory, invoke dispatch thunk and + // then free allocated memory. + let invoke_args_len = invoke_args_data.len() as WordSize; + let invoke_args_ptr = sandbox_context + .supervisor_context() + .allocate_memory(invoke_args_len) + .map_err(|_| trap("Can't allocate memory in supervisor for the arguments"))?; + + let deallocate = |supervisor_context: &mut dyn FunctionContext, ptr, fail_msg| { + supervisor_context.deallocate_memory(ptr).map_err(|_| trap(fail_msg)) + }; + + if sandbox_context + .supervisor_context() + .write_memory(invoke_args_ptr, &invoke_args_data) + .is_err() + { + deallocate( + sandbox_context.supervisor_context(), + invoke_args_ptr, + "Failed dealloction after failed write of invoke arguments", + )?; + return Err(trap("Can't write invoke args into memory")) + } + + let result = sandbox_context.invoke( + invoke_args_ptr, + invoke_args_len, + state, + func_idx, ); - // Serialize arguments into a byte vector. - let invoke_args_data: Vec = args - .as_ref() - .iter() - .cloned() - .map(sp_wasm_interface::Value::from) - .collect::>() - .encode(); - - let state = self.state; - - // Move serialized arguments inside the memory, invoke dispatch thunk and - // then free allocated memory. - let invoke_args_len = invoke_args_data.len() as WordSize; - let invoke_args_ptr = self - .supervisor_externals - .allocate_memory(invoke_args_len) - .map_err(|_| trap("Can't allocate memory in supervisor for the arguments"))?; - - let deallocate = |this: &mut GuestExternals, ptr, fail_msg| { - this.supervisor_externals.deallocate_memory(ptr).map_err(|_| trap(fail_msg)) - }; - - if self - .supervisor_externals - .write_memory(invoke_args_ptr, &invoke_args_data) - .is_err() - { deallocate( - self, + sandbox_context.supervisor_context(), invoke_args_ptr, - "Failed dealloction after failed write of invoke arguments", + "Can't deallocate memory for dispatch thunk's invoke arguments", )?; - return Err(trap("Can't write invoke args into memory")) - } + let result = result?; - let result = self.supervisor_externals.invoke( - &self.sandbox_instance.dispatch_thunk, - invoke_args_ptr, - invoke_args_len, - state, - func_idx, - ); + // dispatch_thunk returns pointer to serialized arguments. + // Unpack pointer and len of the serialized result data. + let (serialized_result_val_ptr, serialized_result_val_len) = { + // Cast to u64 to use zero-extension. + let v = result as u64; + let ptr = (v as u64 >> 32) as u32; + let len = (v & 0xFFFFFFFF) as u32; + (Pointer::new(ptr), len) + }; - deallocate( - self, - invoke_args_ptr, - "Can't deallocate memory for dispatch thunk's invoke arguments", - )?; - let result = result?; + let serialized_result_val = sandbox_context + .supervisor_context() + .read_memory(serialized_result_val_ptr, serialized_result_val_len) + .map_err(|_| trap("Can't read the serialized result from dispatch thunk")); - // dispatch_thunk returns pointer to serialized arguments. - // Unpack pointer and len of the serialized result data. - let (serialized_result_val_ptr, serialized_result_val_len) = { - // Cast to u64 to use zero-extension. - let v = result as u64; - let ptr = (v as u64 >> 32) as u32; - let len = (v & 0xFFFFFFFF) as u32; - (Pointer::new(ptr), len) - }; - - let serialized_result_val = self - .supervisor_externals - .read_memory(serialized_result_val_ptr, serialized_result_val_len) - .map_err(|_| trap("Can't read the serialized result from dispatch thunk")); - - deallocate( - self, - serialized_result_val_ptr, - "Can't deallocate memory for dispatch thunk's result", - ) - .and_then(|_| serialized_result_val) - .and_then(|serialized_result_val| deserialize_result(&serialized_result_val)) + deallocate( + sandbox_context.supervisor_context(), + serialized_result_val_ptr, + "Can't deallocate memory for dispatch thunk's result", + ) + .and_then(|_| serialized_result_val) + .and_then(|serialized_result_val| deserialize_result(&serialized_result_val)) + }).expect("SandboxContextStore is set when invoking sandboxed functions; qed") } } -fn with_guest_externals( - supervisor_externals: &mut FE, - sandbox_instance: &SandboxInstance, - state: u32, - f: F, -) -> R +fn with_guest_externals(sandbox_instance: &SandboxInstance, state: u32, f: F) -> R where - FE: SandboxCapabilities, - F: FnOnce(&mut GuestExternals) -> R, + F: FnOnce(&mut GuestExternals) -> R, { - let mut guest_externals = GuestExternals { supervisor_externals, sandbox_instance, state }; - f(&mut guest_externals) + f(&mut GuestExternals { sandbox_instance, state }) } /// Module instance in terms of selected backend @@ -366,13 +355,12 @@ enum BackendInstance { /// This is generic over a supervisor function reference type. /// /// [`invoke`]: #method.invoke -pub struct SandboxInstance { +pub struct SandboxInstance { backend_instance: BackendInstance, - dispatch_thunk: FR, guest_to_supervisor_mapping: GuestToSupervisorFunctionMapping, } -impl SandboxInstance { +impl SandboxInstance { /// Invoke an exported function by a name. /// /// `supervisor_externals` is required to execute the implementations @@ -380,7 +368,7 @@ impl SandboxInstance { /// /// The `state` parameter can be used to provide custom data for /// these syscall implementations. - pub fn invoke<'a, FE, SCH, DTH>( + pub fn invoke( &self, // function to call that is exported from the module @@ -391,68 +379,66 @@ impl SandboxInstance { // arbitraty context data of the call state: u32, - ) -> std::result::Result, wasmi::Error> - where - FE: SandboxCapabilities + 'a, - SCH: SandboxCapabilitiesHolder, - DTH: DispatchThunkHolder, - { - SCH::with_sandbox_capabilities(|supervisor_externals| { - with_guest_externals(supervisor_externals, self, state, |guest_externals| { - match &self.backend_instance { - BackendInstance::Wasmi(wasmi_instance) => { - let wasmi_result = - wasmi_instance.invoke_export(export_name, args, guest_externals)?; - Ok(wasmi_result) - }, + sandbox_context: &mut dyn SandboxContext, + ) -> std::result::Result, wasmi::Error> { + match &self.backend_instance { + BackendInstance::Wasmi(wasmi_instance) => + with_guest_externals(self, state, |guest_externals| { + let wasmi_result = SandboxContextStore::using(sandbox_context, || { + wasmi_instance.invoke_export(export_name, args, guest_externals) + })?; - #[cfg(feature = "wasmer-sandbox")] - BackendInstance::Wasmer(wasmer_instance) => { - let function = wasmer_instance - .exports - .get_function(export_name) - .map_err(|error| wasmi::Error::Function(error.to_string()))?; + Ok(wasmi_result) + }), - let args: Vec = args - .iter() - .map(|v| match *v { - RuntimeValue::I32(val) => wasmer::Val::I32(val), - RuntimeValue::I64(val) => wasmer::Val::I64(val), - RuntimeValue::F32(val) => wasmer::Val::F32(val.into()), - RuntimeValue::F64(val) => wasmer::Val::F64(val.into()), - }) - .collect(); + #[cfg(feature = "wasmer-sandbox")] + BackendInstance::Wasmer(wasmer_instance) => { + let function = wasmer_instance + .exports + .get_function(export_name) + .map_err(|error| wasmi::Error::Function(error.to_string()))?; - let wasmer_result = - DTH::initialize_thunk(&self.dispatch_thunk, || function.call(&args)) - .map_err(|error| wasmi::Error::Function(error.to_string()))?; + let args: Vec = args + .iter() + .map(|v| match *v { + RuntimeValue::I32(val) => wasmer::Val::I32(val), + RuntimeValue::I64(val) => wasmer::Val::I64(val), + RuntimeValue::F32(val) => wasmer::Val::F32(val.into()), + RuntimeValue::F64(val) => wasmer::Val::F64(val.into()), + }) + .collect(); - if wasmer_result.len() > 1 { - return Err(wasmi::Error::Function( - "multiple return types are not supported yet".to_owned(), - )) - } + let wasmer_result = SandboxContextStore::using(sandbox_context, || { + function.call(&args).map_err(|error| wasmi::Error::Function(error.to_string())) + })?; - let wasmer_result = if let Some(wasmer_value) = wasmer_result.first() { - let wasmer_value = match *wasmer_value { - wasmer::Val::I32(val) => RuntimeValue::I32(val), - wasmer::Val::I64(val) => RuntimeValue::I64(val), - wasmer::Val::F32(val) => RuntimeValue::F32(val.into()), - wasmer::Val::F64(val) => RuntimeValue::F64(val.into()), - _ => unreachable!(), - }; + if wasmer_result.len() > 1 { + return Err(wasmi::Error::Function( + "multiple return types are not supported yet".into(), + )) + } - Some(wasmer_value) - } else { - None + wasmer_result + .first() + .map(|wasm_value| { + let wasmer_value = match *wasm_value { + wasmer::Val::I32(val) => RuntimeValue::I32(val), + wasmer::Val::I64(val) => RuntimeValue::I64(val), + wasmer::Val::F32(val) => RuntimeValue::F32(val.into()), + wasmer::Val::F64(val) => RuntimeValue::F64(val.into()), + _ => + return Err(wasmi::Error::Function(format!( + "Unsupported return value: {:?}", + wasm_value, + ))), }; - Ok(wasmer_result) - }, - } - }) - }) + Ok(wasmer_value) + }) + .transpose() + }, + } } /// Get the value from a global with the given `name`. @@ -548,8 +534,8 @@ impl GuestEnvironment { /// Decodes an environment definition from the given raw bytes. /// /// Returns `Err` if the definition cannot be decoded. - pub fn decode( - store: &Store, + pub fn decode
( + store: &Store
, raw_env_def: &[u8], ) -> std::result::Result { let (imports, guest_to_supervisor_mapping) = @@ -562,47 +548,18 @@ impl GuestEnvironment { /// /// To finish off the instantiation the user must call `register`. #[must_use] -pub struct UnregisteredInstance { - sandbox_instance: Rc>, +pub struct UnregisteredInstance { + sandbox_instance: Rc, } -impl UnregisteredInstance { +impl UnregisteredInstance { /// Finalizes instantiation of this module. - pub fn register(self, store: &mut Store) -> u32 { + pub fn register
(self, store: &mut Store
, dispatch_thunk: DT) -> u32 { // At last, register the instance. - let instance_idx = store.register_sandbox_instance(self.sandbox_instance); - instance_idx + store.register_sandbox_instance(self.sandbox_instance, dispatch_thunk) } } -/// Helper type to provide sandbox capabilities to the inner context -pub trait SandboxCapabilitiesHolder { - /// Supervisor function reference - type SupervisorFuncRef; - - /// Capabilities trait - type SC: SandboxCapabilities; - - /// Wrapper that provides sandbox capabilities in a limited context - fn with_sandbox_capabilities R>(f: F) -> R; -} - -/// Helper type to provide dispatch thunk to the inner context -pub trait DispatchThunkHolder { - /// Dispatch thunk for this particular context - type DispatchThunk; - - /// Provide `DispatchThunk` for the runtime method call and execute the given function `f`. - /// - /// During the execution of the provided function `dispatch_thunk` will be callable. - fn initialize_thunk(s: &Self::DispatchThunk, f: F) -> R - where - F: FnOnce() -> R; - - /// Wrapper that provides dispatch thunk in a limited context - fn with_dispatch_thunk R>(f: F) -> R; -} - /// Sandbox backend to use pub enum SandboxBackend { /// Wasm interpreter @@ -716,14 +673,17 @@ impl BackendContext { /// This struct keeps track of all sandboxed components. /// /// This is generic over a supervisor function reference type. -pub struct Store { - // Memories and instances are `Some` until torn down. - instances: Vec>>>, +pub struct Store
{ + /// Stores the instance and the dispatch thunk associated to per instance. + /// + /// Instances are `Some` until torn down. + instances: Vec, DT)>>, + /// Memories are `Some` until torn down. memories: Vec>, backend_context: BackendContext, } -impl Store { +impl Store
{ /// Create a new empty sandbox store. pub fn new(backend: SandboxBackend) -> Self { Store { @@ -776,11 +736,28 @@ impl Store { /// /// Returns `Err` If `instance_idx` isn't a valid index of an instance or /// instance is already torndown. - pub fn instance(&self, instance_idx: u32) -> Result>> { + pub fn instance(&self, instance_idx: u32) -> Result> { self.instances .get(instance_idx as usize) - .cloned() .ok_or_else(|| "Trying to access a non-existent instance")? + .as_ref() + .map(|v| v.0.clone()) + .ok_or_else(|| "Trying to access a torndown instance".into()) + } + + /// Returns dispatch thunk by `instance_idx`. + /// + /// # Errors + /// + /// Returns `Err` If `instance_idx` isn't a valid index of an instance or + /// instance is already torndown. + pub fn dispatch_thunk(&self, instance_idx: u32) -> Result
{ + self.instances + .get(instance_idx as usize) + .as_ref() + .ok_or_else(|| "Trying to access a non-existent instance")? + .as_ref() + .map(|v| v.1.clone()) .ok_or_else(|| "Trying to access a torndown instance".into()) } @@ -842,27 +819,20 @@ impl Store { /// Note: Due to borrowing constraints dispatch thunk is now propagated using DTH /// /// Returns uninitialized sandboxed module instance or an instantiation error. - pub fn instantiate<'a, FE, SCH, DTH>( + pub fn instantiate( &mut self, wasm: &[u8], guest_env: GuestEnvironment, state: u32, - ) -> std::result::Result, InstantiationError> - where - FR: Clone + 'static, - FE: SandboxCapabilities + 'a, - SCH: SandboxCapabilitiesHolder, - DTH: DispatchThunkHolder, - { - let backend_context = &self.backend_context; - - let sandbox_instance = match backend_context { + sandbox_context: &mut dyn SandboxContext, + ) -> std::result::Result { + let sandbox_instance = match self.backend_context { BackendContext::Wasmi => - Self::instantiate_wasmi::(wasm, guest_env, state)?, + Self::instantiate_wasmi(wasm, guest_env, state, sandbox_context)?, #[cfg(feature = "wasmer-sandbox")] - BackendContext::Wasmer(context) => - Self::instantiate_wasmer::(context, wasm, guest_env, state)?, + BackendContext::Wasmer(ref context) => + Self::instantiate_wasmer(&context, wasm, guest_env, state, sandbox_context)?, }; Ok(UnregisteredInstance { sandbox_instance }) @@ -870,74 +840,58 @@ impl Store { } // Private routines -impl Store { - fn register_sandbox_instance(&mut self, sandbox_instance: Rc>) -> u32 { +impl
Store
{ + fn register_sandbox_instance( + &mut self, + sandbox_instance: Rc, + dispatch_thunk: DT, + ) -> u32 { let instance_idx = self.instances.len(); - self.instances.push(Some(sandbox_instance)); + self.instances.push(Some((sandbox_instance, dispatch_thunk))); instance_idx as u32 } - fn instantiate_wasmi<'a, FE, SCH, DTH>( + fn instantiate_wasmi( wasm: &[u8], guest_env: GuestEnvironment, state: u32, - ) -> std::result::Result>, InstantiationError> - where - FR: Clone + 'static, - FE: SandboxCapabilities + 'a, - SCH: SandboxCapabilitiesHolder, - DTH: DispatchThunkHolder, - { + sandbox_context: &mut dyn SandboxContext, + ) -> std::result::Result, InstantiationError> { let wasmi_module = Module::from_buffer(wasm).map_err(|_| InstantiationError::ModuleDecoding)?; let wasmi_instance = ModuleInstance::new(&wasmi_module, &guest_env.imports) .map_err(|_| InstantiationError::Instantiation)?; - let sandbox_instance = DTH::with_dispatch_thunk(|dispatch_thunk| { - Rc::new(SandboxInstance { - // In general, it's not a very good idea to use `.not_started_instance()` for - // anything but for extracting memory and tables. But in this particular case, we - // are extracting for the purpose of running `start` function which should be ok. - backend_instance: BackendInstance::Wasmi( - wasmi_instance.not_started_instance().clone(), - ), - dispatch_thunk: dispatch_thunk.clone(), - guest_to_supervisor_mapping: guest_env.guest_to_supervisor_mapping, - }) + let sandbox_instance = Rc::new(SandboxInstance { + // In general, it's not a very good idea to use `.not_started_instance()` for + // anything but for extracting memory and tables. But in this particular case, we + // are extracting for the purpose of running `start` function which should be ok. + backend_instance: BackendInstance::Wasmi(wasmi_instance.not_started_instance().clone()), + guest_to_supervisor_mapping: guest_env.guest_to_supervisor_mapping, }); - SCH::with_sandbox_capabilities(|supervisor_externals| { - with_guest_externals( - supervisor_externals, - &sandbox_instance, - state, - |guest_externals| { - wasmi_instance - .run_start(guest_externals) - .map_err(|_| InstantiationError::StartTrapped) + with_guest_externals(&sandbox_instance, state, |guest_externals| { + SandboxContextStore::using(sandbox_context, || { + wasmi_instance + .run_start(guest_externals) + .map_err(|_| InstantiationError::StartTrapped) + }) - // Note: no need to run start on wasmtime instance, since it's done - // automatically - }, - ) + // Note: no need to run start on wasmtime instance, since it's done + // automatically })?; Ok(sandbox_instance) } #[cfg(feature = "wasmer-sandbox")] - fn instantiate_wasmer<'a, FE, SCH, DTH>( + fn instantiate_wasmer( context: &WasmerBackend, wasm: &[u8], guest_env: GuestEnvironment, state: u32, - ) -> std::result::Result>, InstantiationError> - where - FR: Clone + 'static, - FE: SandboxCapabilities + 'a, - SCH: SandboxCapabilitiesHolder, - DTH: DispatchThunkHolder, - { + sandbox_context: &mut dyn SandboxContext, + ) -> std::result::Result, InstantiationError> { let module = wasmer::Module::new(&context.store, wasm) .map_err(|_| InstantiationError::ModuleDecoding)?; @@ -991,7 +945,7 @@ impl Store { .func_by_guest_index(guest_func_index) .ok_or(InstantiationError::ModuleDecoding)?; - let function = Self::wasmer_dispatch_function::( + let function = Self::wasmer_dispatch_function( supervisor_func_index, &context.store, func_ty, @@ -1012,68 +966,71 @@ impl Store { import_object.register(module_name, exports); } - let instance = + let instance = SandboxContextStore::using(sandbox_context, || { wasmer::Instance::new(&module, &import_object).map_err(|error| match error { wasmer::InstantiationError::Link(_) => InstantiationError::Instantiation, wasmer::InstantiationError::Start(_) => InstantiationError::StartTrapped, wasmer::InstantiationError::HostEnvInitialization(_) => InstantiationError::EnvironmentDefinitionCorrupted, - })?; + }) + })?; Ok(Rc::new(SandboxInstance { backend_instance: BackendInstance::Wasmer(instance), - dispatch_thunk: DTH::with_dispatch_thunk(|dispatch_thunk| dispatch_thunk.clone()), guest_to_supervisor_mapping: guest_env.guest_to_supervisor_mapping, })) } #[cfg(feature = "wasmer-sandbox")] - fn wasmer_dispatch_function<'a, FE, SCH, DTH>( + fn wasmer_dispatch_function( supervisor_func_index: SupervisorFuncIndex, store: &wasmer::Store, func_ty: &wasmer::FunctionType, state: u32, - ) -> wasmer::Function - where - FR: Clone + 'static, - FE: SandboxCapabilities + 'a, - SCH: SandboxCapabilitiesHolder, - DTH: DispatchThunkHolder, - { + ) -> wasmer::Function { wasmer::Function::new(store, func_ty, move |params| { - SCH::with_sandbox_capabilities(|supervisor_externals| { + SandboxContextStore::with(|sandbox_context| { use sp_wasm_interface::Value; // Serialize arguments into a byte vector. let invoke_args_data = params .iter() .map(|val| match val { - wasmer::Val::I32(val) => Value::I32(*val), - wasmer::Val::I64(val) => Value::I64(*val), - wasmer::Val::F32(val) => Value::F32(f32::to_bits(*val)), - wasmer::Val::F64(val) => Value::F64(f64::to_bits(*val)), - _ => unimplemented!(), + wasmer::Val::I32(val) => Ok(Value::I32(*val)), + wasmer::Val::I64(val) => Ok(Value::I64(*val)), + wasmer::Val::F32(val) => Ok(Value::F32(f32::to_bits(*val))), + wasmer::Val::F64(val) => Ok(Value::F64(f64::to_bits(*val))), + _ => Err(wasmer::RuntimeError::new(format!( + "Unsupported function argument: {:?}", + val + ))), }) - .collect::>() + .collect::, _>>()? .encode(); // Move serialized arguments inside the memory, invoke dispatch thunk and // then free allocated memory. let invoke_args_len = invoke_args_data.len() as WordSize; - let invoke_args_ptr = - supervisor_externals.allocate_memory(invoke_args_len).map_err(|_| { + let invoke_args_ptr = sandbox_context + .supervisor_context() + .allocate_memory(invoke_args_len) + .map_err(|_| { wasmer::RuntimeError::new( "Can't allocate memory in supervisor for the arguments", ) })?; - let deallocate = |fe: &mut FE, ptr, fail_msg| { + let deallocate = |fe: &mut dyn FunctionContext, ptr, fail_msg| { fe.deallocate_memory(ptr).map_err(|_| wasmer::RuntimeError::new(fail_msg)) }; - if supervisor_externals.write_memory(invoke_args_ptr, &invoke_args_data).is_err() { + if sandbox_context + .supervisor_context() + .write_memory(invoke_args_ptr, &invoke_args_data) + .is_err() + { deallocate( - supervisor_externals, + sandbox_context.supervisor_context(), invoke_args_ptr, "Failed dealloction after failed write of invoke arguments", )?; @@ -1082,16 +1039,9 @@ impl Store { } // Perform the actuall call - let serialized_result = DTH::with_dispatch_thunk(|dispatch_thunk| { - supervisor_externals.invoke( - &dispatch_thunk, - invoke_args_ptr, - invoke_args_len, - state, - supervisor_func_index, - ) - }) - .map_err(|e| wasmer::RuntimeError::new(e.to_string()))?; + let serialized_result = sandbox_context + .invoke(invoke_args_ptr, invoke_args_len, state, supervisor_func_index) + .map_err(|e| wasmer::RuntimeError::new(e.to_string()))?; // dispatch_thunk returns pointer to serialized arguments. // Unpack pointer and len of the serialized result data. @@ -1103,7 +1053,8 @@ impl Store { (Pointer::new(ptr), len) }; - let serialized_result_val = supervisor_externals + let serialized_result_val = sandbox_context + .supervisor_context() .read_memory(serialized_result_val_ptr, serialized_result_val_len) .map_err(|_| { wasmer::RuntimeError::new( @@ -1112,7 +1063,7 @@ impl Store { }); let deserialized_result = deallocate( - supervisor_externals, + sandbox_context.supervisor_context(), serialized_result_val_ptr, "Can't deallocate memory for dispatch thunk's result", ) @@ -1133,6 +1084,7 @@ impl Store { Ok(vec![]) } }) + .expect("SandboxContextStore is set when invoking sandboxed functions; qed") }) } } diff --git a/substrate/client/executor/wasmi/src/lib.rs b/substrate/client/executor/wasmi/src/lib.rs index 3c5836c774..6052662fa7 100644 --- a/substrate/client/executor/wasmi/src/lib.rs +++ b/substrate/client/executor/wasmi/src/lib.rs @@ -40,13 +40,8 @@ use wasmi::{ TableRef, }; -#[derive(Clone)] struct FunctionExecutor { - inner: Rc, -} - -struct Inner { - sandbox_store: RefCell>, + sandbox_store: Rc>>, heap: RefCell, memory: MemoryRef, table: Option, @@ -65,68 +60,73 @@ impl FunctionExecutor { missing_functions: Arc>, ) -> Result { Ok(FunctionExecutor { - inner: Rc::new(Inner { - sandbox_store: RefCell::new(sandbox::Store::new(sandbox::SandboxBackend::Wasmi)), - heap: RefCell::new(sc_allocator::FreeingBumpHeapAllocator::new(heap_base)), - memory: m, - table: t, - host_functions, - allow_missing_func_imports, - missing_functions, - }), + sandbox_store: Rc::new(RefCell::new(sandbox::Store::new( + sandbox::SandboxBackend::Wasmi, + ))), + heap: RefCell::new(sc_allocator::FreeingBumpHeapAllocator::new(heap_base)), + memory: m, + table: t, + host_functions, + allow_missing_func_imports, + missing_functions, }) } } -impl sandbox::SandboxCapabilities for FunctionExecutor { - type SupervisorFuncRef = wasmi::FuncRef; +struct SandboxContext<'a> { + executor: &'a mut FunctionExecutor, + dispatch_thunk: wasmi::FuncRef, +} +impl<'a> sandbox::SandboxContext for SandboxContext<'a> { fn invoke( &mut self, - dispatch_thunk: &Self::SupervisorFuncRef, invoke_args_ptr: Pointer, invoke_args_len: WordSize, state: u32, func_idx: sandbox::SupervisorFuncIndex, ) -> Result { let result = wasmi::FuncInstance::invoke( - dispatch_thunk, + &self.dispatch_thunk, &[ RuntimeValue::I32(u32::from(invoke_args_ptr) as i32), RuntimeValue::I32(invoke_args_len as i32), RuntimeValue::I32(state as i32), RuntimeValue::I32(usize::from(func_idx) as i32), ], - self, + self.executor, ); + match result { Ok(Some(RuntimeValue::I64(val))) => Ok(val), Ok(_) => return Err("Supervisor function returned unexpected result!".into()), Err(err) => Err(Error::Trap(err)), } } + + fn supervisor_context(&mut self) -> &mut dyn FunctionContext { + self.executor + } } impl FunctionContext for FunctionExecutor { fn read_memory_into(&self, address: Pointer, dest: &mut [u8]) -> WResult<()> { - self.inner.memory.get_into(address.into(), dest).map_err(|e| e.to_string()) + self.memory.get_into(address.into(), dest).map_err(|e| e.to_string()) } fn write_memory(&mut self, address: Pointer, data: &[u8]) -> WResult<()> { - self.inner.memory.set(address.into(), data).map_err(|e| e.to_string()) + self.memory.set(address.into(), data).map_err(|e| e.to_string()) } fn allocate_memory(&mut self, size: WordSize) -> WResult> { - let heap = &mut self.inner.heap.borrow_mut(); - self.inner - .memory + let heap = &mut self.heap.borrow_mut(); + self.memory .with_direct_access_mut(|mem| heap.allocate(mem, size).map_err(|e| e.to_string())) } fn deallocate_memory(&mut self, ptr: Pointer) -> WResult<()> { - let heap = &mut self.inner.heap.borrow_mut(); - self.inner - .memory + let heap = &mut self.heap.borrow_mut(); + self.memory .with_direct_access_mut(|mem| heap.deallocate(mem, ptr).map_err(|e| e.to_string())) } @@ -144,7 +144,7 @@ impl Sandbox for FunctionExecutor { buf_len: WordSize, ) -> WResult { let sandboxed_memory = - self.inner.sandbox_store.borrow().memory(memory_id).map_err(|e| e.to_string())?; + self.sandbox_store.borrow().memory(memory_id).map_err(|e| e.to_string())?; let len = buf_len as usize; @@ -153,7 +153,7 @@ impl Sandbox for FunctionExecutor { Ok(buffer) => buffer, }; - if let Err(_) = self.inner.memory.set(buf_ptr.into(), &buffer) { + if let Err(_) = self.memory.set(buf_ptr.into(), &buffer) { return Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS) } @@ -168,11 +168,11 @@ impl Sandbox for FunctionExecutor { val_len: WordSize, ) -> WResult { let sandboxed_memory = - self.inner.sandbox_store.borrow().memory(memory_id).map_err(|e| e.to_string())?; + self.sandbox_store.borrow().memory(memory_id).map_err(|e| e.to_string())?; let len = val_len as usize; - let buffer = match self.inner.memory.get(val_ptr.into(), len) { + let buffer = match self.memory.get(val_ptr.into(), len) { Err(_) => return Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS), Ok(buffer) => buffer, }; @@ -185,16 +185,14 @@ impl Sandbox for FunctionExecutor { } fn memory_teardown(&mut self, memory_id: MemoryId) -> WResult<()> { - self.inner - .sandbox_store + self.sandbox_store .borrow_mut() .memory_teardown(memory_id) .map_err(|e| e.to_string()) } fn memory_new(&mut self, initial: u32, maximum: u32) -> WResult { - self.inner - .sandbox_store + self.sandbox_store .borrow_mut() .new_memory(initial, maximum) .map_err(|e| e.to_string()) @@ -218,17 +216,21 @@ impl Sandbox for FunctionExecutor { .map(Into::into) .collect::>(); - let instance = self - .inner + let instance = + self.sandbox_store.borrow().instance(instance_id).map_err(|e| e.to_string())?; + + let dispatch_thunk = self .sandbox_store .borrow() - .instance(instance_id) + .dispatch_thunk(instance_id) .map_err(|e| e.to_string())?; - let result = EXECUTOR - .set(self, || instance.invoke::<_, CapsHolder, ThunkHolder>(export_name, &args, state)); - - match result { + match instance.invoke( + export_name, + &args, + state, + &mut SandboxContext { dispatch_thunk, executor: self }, + ) { Ok(None) => Ok(sandbox_primitives::ERR_OK), Ok(Some(val)) => { // Serialize return value and write it back into the memory. @@ -245,8 +247,7 @@ impl Sandbox for FunctionExecutor { } fn instance_teardown(&mut self, instance_id: u32) -> WResult<()> { - self.inner - .sandbox_store + self.sandbox_store .borrow_mut() .instance_teardown(instance_id) .map_err(|e| e.to_string()) @@ -262,7 +263,6 @@ impl Sandbox for FunctionExecutor { // Extract a dispatch thunk from instance's table by the specified index. let dispatch_thunk = { let table = self - .inner .table .as_ref() .ok_or_else(|| "Runtime doesn't have a table; sandbox is unavailable")?; @@ -272,28 +272,28 @@ impl Sandbox for FunctionExecutor { .ok_or_else(|| "dispatch_thunk_idx points on an empty table entry")? }; - let guest_env = match sandbox::GuestEnvironment::decode( - &*self.inner.sandbox_store.borrow(), - raw_env_def, - ) { - Ok(guest_env) => guest_env, - Err(_) => return Ok(sandbox_primitives::ERR_MODULE as u32), - }; + let guest_env = + match sandbox::GuestEnvironment::decode(&*self.sandbox_store.borrow(), raw_env_def) { + Ok(guest_env) => guest_env, + Err(_) => return Ok(sandbox_primitives::ERR_MODULE as u32), + }; - let store = &mut *self.inner.sandbox_store.borrow_mut(); - let result = EXECUTOR.set(self, || { - DISPATCH_THUNK.set(&dispatch_thunk, || { - store.instantiate::<_, CapsHolder, ThunkHolder>(wasm, guest_env, state) - }) - }); + let store = self.sandbox_store.clone(); + let result = store.borrow_mut().instantiate( + wasm, + guest_env, + state, + &mut SandboxContext { executor: self, dispatch_thunk: dispatch_thunk.clone() }, + ); - let instance_idx_or_err_code: u32 = match result.map(|i| i.register(store)) { - Ok(instance_idx) => instance_idx, - Err(sandbox::InstantiationError::StartTrapped) => sandbox_primitives::ERR_EXECUTION, - Err(_) => sandbox_primitives::ERR_MODULE, - }; + let instance_idx_or_err_code = + match result.map(|i| i.register(&mut store.borrow_mut(), dispatch_thunk)) { + Ok(instance_idx) => instance_idx, + Err(sandbox::InstantiationError::StartTrapped) => sandbox_primitives::ERR_EXECUTION, + Err(_) => sandbox_primitives::ERR_MODULE, + }; - Ok(instance_idx_or_err_code as u32) + Ok(instance_idx_or_err_code) } fn get_global_val( @@ -301,8 +301,7 @@ impl Sandbox for FunctionExecutor { instance_idx: u32, name: &str, ) -> WResult> { - self.inner - .sandbox_store + self.sandbox_store .borrow() .instance(instance_idx) .map(|i| i.get_global_val(name)) @@ -310,48 +309,6 @@ impl Sandbox for FunctionExecutor { } } -/// Wasmi specific implementation of `SandboxCapabilitiesHolder` that provides -/// sandbox with a scoped thread local access to a function executor. -/// This is a way to calm down the borrow checker since host function closures -/// require exclusive access to it. -struct CapsHolder; - -scoped_tls::scoped_thread_local!(static EXECUTOR: FunctionExecutor); - -impl sandbox::SandboxCapabilitiesHolder for CapsHolder { - type SupervisorFuncRef = wasmi::FuncRef; - type SC = FunctionExecutor; - - fn with_sandbox_capabilities R>(f: F) -> R { - assert!(EXECUTOR.is_set(), "wasmi executor is not set"); - EXECUTOR.with(|executor| f(&mut executor.clone())) - } -} - -/// Wasmi specific implementation of `DispatchThunkHolder` that provides -/// sandbox with a scoped thread local access to a dispatch thunk. -/// This is a way to calm down the borrow checker since host function closures -/// require exclusive access to it. -struct ThunkHolder; - -scoped_tls::scoped_thread_local!(static DISPATCH_THUNK: wasmi::FuncRef); - -impl sandbox::DispatchThunkHolder for ThunkHolder { - type DispatchThunk = wasmi::FuncRef; - - fn with_dispatch_thunk R>(f: F) -> R { - assert!(DISPATCH_THUNK.is_set(), "dispatch thunk is not set"); - DISPATCH_THUNK.with(|thunk| f(&mut thunk.clone())) - } - - fn initialize_thunk(s: &Self::DispatchThunk, f: F) -> R - where - F: FnOnce() -> R, - { - DISPATCH_THUNK.set(s, f) - } -} - /// Will be used on initialization of a module to resolve function and memory imports. struct Resolver<'a> { /// All the hot functions that we export for the WASM blob. @@ -470,19 +427,19 @@ impl wasmi::Externals for FunctionExecutor { ) -> Result, wasmi::Trap> { let mut args = args.as_ref().iter().copied().map(Into::into); - if let Some(function) = self.inner.host_functions.clone().get(index) { + if let Some(function) = self.host_functions.clone().get(index) { function .execute(self, &mut args) .map_err(|msg| Error::FunctionExecution(function.name().to_string(), msg)) .map_err(wasmi::Trap::from) .map(|v| v.map(Into::into)) - } else if self.inner.allow_missing_func_imports && - index >= self.inner.host_functions.len() && - index < self.inner.host_functions.len() + self.inner.missing_functions.len() + } else if self.allow_missing_func_imports && + index >= self.host_functions.len() && + index < self.host_functions.len() + self.missing_functions.len() { Err(Error::from(format!( "Function `{}` is only a stub. Calling a stub is not allowed.", - self.inner.missing_functions[index - self.inner.host_functions.len()], + self.missing_functions[index - self.host_functions.len()], )) .into()) } else { diff --git a/substrate/client/executor/wasmtime/src/host.rs b/substrate/client/executor/wasmtime/src/host.rs index 12e5ab0023..8453ec3954 100644 --- a/substrate/client/executor/wasmtime/src/host.rs +++ b/substrate/client/executor/wasmtime/src/host.rs @@ -25,7 +25,7 @@ use log::trace; use sc_allocator::FreeingBumpHeapAllocator; use sc_executor_common::{ error::Result, - sandbox::{self, SandboxCapabilities, SandboxCapabilitiesHolder, SupervisorFuncIndex}, + sandbox::{self, SupervisorFuncIndex}, util::MemoryTransfer, }; use sp_core::sandbox as sandbox_primitives; @@ -33,32 +33,20 @@ use sp_wasm_interface::{FunctionContext, MemoryId, Pointer, Sandbox, WordSize}; use std::{cell::RefCell, rc::Rc}; use wasmtime::{Func, Val}; -/// Wrapper type for pointer to a Wasm table entry. -/// -/// The wrapper type is used to ensure that the function reference is valid as it must be unsafely -/// dereferenced from within the safe method `::invoke`. -#[derive(Clone)] -pub struct SupervisorFuncRef(Func); - /// The state required to construct a HostContext context. The context only lasts for one host /// call, whereas the state is maintained for the duration of a Wasm runtime call, which may make /// many different host calls that must share state. -#[derive(Clone)] pub struct HostState { - inner: Rc, -} - -struct Inner { - // We need some interior mutability here since the host state is shared between all host - // function handlers and the wasmtime backend's `impl WasmRuntime`. - // - // Furthermore, because of recursive calls (e.g. runtime can create and call an sandboxed - // instance which in turn can call the runtime back) we have to be very careful with borrowing - // those. - // - // Basically, most of the interactions should do temporary borrow immediately releasing the - // borrow after performing necessary queries/changes. - sandbox_store: RefCell>, + /// We need some interior mutability here since the host state is shared between all host + /// function handlers and the wasmtime backend's `impl WasmRuntime`. + /// + /// Furthermore, because of recursive calls (e.g. runtime can create and call an sandboxed + /// instance which in turn can call the runtime back) we have to be very careful with borrowing + /// those. + /// + /// Basically, most of the interactions should do temporary borrow immediately releasing the + /// borrow after performing necessary queries/changes. + sandbox_store: Rc>>, allocator: RefCell, instance: Rc, } @@ -67,29 +55,260 @@ impl HostState { /// Constructs a new `HostState`. pub fn new(allocator: FreeingBumpHeapAllocator, instance: Rc) -> Self { HostState { - inner: Rc::new(Inner { - sandbox_store: RefCell::new(sandbox::Store::new( - sandbox::SandboxBackend::TryWasmer, - )), - allocator: RefCell::new(allocator), - instance, - }), + sandbox_store: Rc::new(RefCell::new(sandbox::Store::new( + sandbox::SandboxBackend::TryWasmer, + ))), + allocator: RefCell::new(allocator), + instance, } } + + /// Materialize `HostContext` that can be used to invoke a substrate host `dyn Function`. + pub fn materialize<'a>(&'a self) -> HostContext<'a> { + HostContext(self) + } } -impl SandboxCapabilities for HostState { - type SupervisorFuncRef = SupervisorFuncRef; +/// A `HostContext` implements `FunctionContext` for making host calls from a Wasmtime +/// runtime. The `HostContext` exists only for the lifetime of the call and borrows state from +/// a longer-living `HostState`. +pub struct HostContext<'a>(&'a HostState); + +impl<'a> std::ops::Deref for HostContext<'a> { + type Target = HostState; + fn deref(&self) -> &HostState { + self.0 + } +} + +impl<'a> sp_wasm_interface::FunctionContext for HostContext<'a> { + fn read_memory_into( + &self, + address: Pointer, + dest: &mut [u8], + ) -> sp_wasm_interface::Result<()> { + self.instance.read_memory_into(address, dest).map_err(|e| e.to_string()) + } + + fn write_memory(&mut self, address: Pointer, data: &[u8]) -> sp_wasm_interface::Result<()> { + self.instance.write_memory_from(address, data).map_err(|e| e.to_string()) + } + + fn allocate_memory(&mut self, size: WordSize) -> sp_wasm_interface::Result> { + self.instance + .allocate(&mut *self.allocator.borrow_mut(), size) + .map_err(|e| e.to_string()) + } + + fn deallocate_memory(&mut self, ptr: Pointer) -> sp_wasm_interface::Result<()> { + self.instance + .deallocate(&mut *self.allocator.borrow_mut(), ptr) + .map_err(|e| e.to_string()) + } + + fn sandbox(&mut self) -> &mut dyn Sandbox { + self + } +} + +impl<'a> Sandbox for HostContext<'a> { + fn memory_get( + &mut self, + memory_id: MemoryId, + offset: WordSize, + buf_ptr: Pointer, + buf_len: WordSize, + ) -> sp_wasm_interface::Result { + let sandboxed_memory = + self.sandbox_store.borrow().memory(memory_id).map_err(|e| e.to_string())?; + + let len = buf_len as usize; + + let buffer = match sandboxed_memory.read(Pointer::new(offset as u32), len) { + Err(_) => return Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS), + Ok(buffer) => buffer, + }; + + if let Err(_) = self.instance.write_memory_from(buf_ptr, &buffer) { + return Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS) + } + + Ok(sandbox_primitives::ERR_OK) + } + + fn memory_set( + &mut self, + memory_id: MemoryId, + offset: WordSize, + val_ptr: Pointer, + val_len: WordSize, + ) -> sp_wasm_interface::Result { + let sandboxed_memory = + self.sandbox_store.borrow().memory(memory_id).map_err(|e| e.to_string())?; + + let len = val_len as usize; + + let buffer = match self.instance.read_memory(val_ptr, len) { + Err(_) => return Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS), + Ok(buffer) => buffer, + }; + + if let Err(_) = sandboxed_memory.write_from(Pointer::new(offset as u32), &buffer) { + return Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS) + } + + Ok(sandbox_primitives::ERR_OK) + } + + fn memory_teardown(&mut self, memory_id: MemoryId) -> sp_wasm_interface::Result<()> { + self.sandbox_store + .borrow_mut() + .memory_teardown(memory_id) + .map_err(|e| e.to_string()) + } + + fn memory_new(&mut self, initial: u32, maximum: u32) -> sp_wasm_interface::Result { + self.sandbox_store + .borrow_mut() + .new_memory(initial, maximum) + .map_err(|e| e.to_string()) + } fn invoke( &mut self, - dispatch_thunk: &Self::SupervisorFuncRef, + instance_id: u32, + export_name: &str, + args: &[u8], + return_val: Pointer, + return_val_len: u32, + state: u32, + ) -> sp_wasm_interface::Result { + trace!(target: "sp-sandbox", "invoke, instance_idx={}", instance_id); + + // Deserialize arguments and convert them into wasmi types. + let args = Vec::::decode(&mut &args[..]) + .map_err(|_| "Can't decode serialized arguments for the invocation")? + .into_iter() + .map(Into::into) + .collect::>(); + + let instance = + self.sandbox_store.borrow().instance(instance_id).map_err(|e| e.to_string())?; + + let dispatch_thunk = self + .sandbox_store + .borrow() + .dispatch_thunk(instance_id) + .map_err(|e| e.to_string())?; + + let result = instance.invoke( + export_name, + &args, + state, + &mut SandboxContext { host_context: self, dispatch_thunk }, + ); + + match result { + Ok(None) => Ok(sandbox_primitives::ERR_OK), + Ok(Some(val)) => { + // Serialize return value and write it back into the memory. + sp_wasm_interface::ReturnValue::Value(val.into()).using_encoded(|val| { + if val.len() > return_val_len as usize { + Err("Return value buffer is too small")?; + } + ::write_memory(self, return_val, val) + .map_err(|_| "can't write return value")?; + Ok(sandbox_primitives::ERR_OK) + }) + }, + Err(_) => Ok(sandbox_primitives::ERR_EXECUTION), + } + } + + fn instance_teardown(&mut self, instance_id: u32) -> sp_wasm_interface::Result<()> { + self.sandbox_store + .borrow_mut() + .instance_teardown(instance_id) + .map_err(|e| e.to_string()) + } + + fn instance_new( + &mut self, + dispatch_thunk_id: u32, + wasm: &[u8], + raw_env_def: &[u8], + state: u32, + ) -> sp_wasm_interface::Result { + // Extract a dispatch thunk from the instance's table by the specified index. + let dispatch_thunk = { + let table_item = self + .instance + .table() + .as_ref() + .ok_or_else(|| "Runtime doesn't have a table; sandbox is unavailable")? + .get(dispatch_thunk_id); + + table_item + .ok_or_else(|| "dispatch_thunk_id is out of bounds")? + .funcref() + .ok_or_else(|| "dispatch_thunk_idx should be a funcref")? + .ok_or_else(|| "dispatch_thunk_idx should point to actual func")? + .clone() + }; + + let guest_env = + match sandbox::GuestEnvironment::decode(&*self.sandbox_store.borrow(), raw_env_def) { + Ok(guest_env) => guest_env, + Err(_) => return Ok(sandbox_primitives::ERR_MODULE as u32), + }; + + let store = self.sandbox_store.clone(); + let store = &mut store.borrow_mut(); + let result = store + .instantiate( + wasm, + guest_env, + state, + &mut SandboxContext { host_context: self, dispatch_thunk: dispatch_thunk.clone() }, + ) + .map(|i| i.register(store, dispatch_thunk)); + + let instance_idx_or_err_code = match result { + Ok(instance_idx) => instance_idx, + Err(sandbox::InstantiationError::StartTrapped) => sandbox_primitives::ERR_EXECUTION, + Err(_) => sandbox_primitives::ERR_MODULE, + }; + + Ok(instance_idx_or_err_code as u32) + } + + fn get_global_val( + &self, + instance_idx: u32, + name: &str, + ) -> sp_wasm_interface::Result> { + self.sandbox_store + .borrow() + .instance(instance_idx) + .map(|i| i.get_global_val(name)) + .map_err(|e| e.to_string()) + } +} + +struct SandboxContext<'a, 'b> { + host_context: &'a mut HostContext<'b>, + dispatch_thunk: Func, +} + +impl<'a, 'b> sandbox::SandboxContext for SandboxContext<'a, 'b> { + fn invoke( + &mut self, invoke_args_ptr: Pointer, invoke_args_len: WordSize, state: u32, func_idx: SupervisorFuncIndex, ) -> Result { - let result = dispatch_thunk.0.call(&[ + let result = self.dispatch_thunk.call(&[ Val::I32(u32::from(invoke_args_ptr) as i32), Val::I32(invoke_args_len as i32), Val::I32(state as i32), @@ -116,256 +335,8 @@ impl SandboxCapabilities for HostState { Err(err) => Err(err.to_string().into()), } } -} -impl sp_wasm_interface::FunctionContext for HostState { - fn read_memory_into( - &self, - address: Pointer, - dest: &mut [u8], - ) -> sp_wasm_interface::Result<()> { - self.inner.instance.read_memory_into(address, dest).map_err(|e| e.to_string()) - } - - fn write_memory(&mut self, address: Pointer, data: &[u8]) -> sp_wasm_interface::Result<()> { - self.inner.instance.write_memory_from(address, data).map_err(|e| e.to_string()) - } - - fn allocate_memory(&mut self, size: WordSize) -> sp_wasm_interface::Result> { - self.inner - .instance - .allocate(&mut *self.inner.allocator.borrow_mut(), size) - .map_err(|e| e.to_string()) - } - - fn deallocate_memory(&mut self, ptr: Pointer) -> sp_wasm_interface::Result<()> { - self.inner - .instance - .deallocate(&mut *self.inner.allocator.borrow_mut(), ptr) - .map_err(|e| e.to_string()) - } - - fn sandbox(&mut self) -> &mut dyn Sandbox { - self - } -} - -impl Sandbox for HostState { - fn memory_get( - &mut self, - memory_id: MemoryId, - offset: WordSize, - buf_ptr: Pointer, - buf_len: WordSize, - ) -> sp_wasm_interface::Result { - let sandboxed_memory = - self.inner.sandbox_store.borrow().memory(memory_id).map_err(|e| e.to_string())?; - - let len = buf_len as usize; - - let buffer = match sandboxed_memory.read(Pointer::new(offset as u32), len) { - Err(_) => return Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS), - Ok(buffer) => buffer, - }; - - if let Err(_) = self.inner.instance.write_memory_from(buf_ptr, &buffer) { - return Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS) - } - - Ok(sandbox_primitives::ERR_OK) - } - - fn memory_set( - &mut self, - memory_id: MemoryId, - offset: WordSize, - val_ptr: Pointer, - val_len: WordSize, - ) -> sp_wasm_interface::Result { - let sandboxed_memory = - self.inner.sandbox_store.borrow().memory(memory_id).map_err(|e| e.to_string())?; - - let len = val_len as usize; - - let buffer = match self.inner.instance.read_memory(val_ptr, len) { - Err(_) => return Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS), - Ok(buffer) => buffer, - }; - - if let Err(_) = sandboxed_memory.write_from(Pointer::new(offset as u32), &buffer) { - return Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS) - } - - Ok(sandbox_primitives::ERR_OK) - } - - fn memory_teardown(&mut self, memory_id: MemoryId) -> sp_wasm_interface::Result<()> { - self.inner - .sandbox_store - .borrow_mut() - .memory_teardown(memory_id) - .map_err(|e| e.to_string()) - } - - fn memory_new(&mut self, initial: u32, maximum: u32) -> sp_wasm_interface::Result { - self.inner - .sandbox_store - .borrow_mut() - .new_memory(initial, maximum) - .map_err(|e| e.to_string()) - } - - fn invoke( - &mut self, - instance_id: u32, - export_name: &str, - args: &[u8], - return_val: Pointer, - return_val_len: u32, - state: u32, - ) -> sp_wasm_interface::Result { - trace!(target: "sp-sandbox", "invoke, instance_idx={}", instance_id); - - // Deserialize arguments and convert them into wasmi types. - let args = Vec::::decode(&mut &args[..]) - .map_err(|_| "Can't decode serialized arguments for the invocation")? - .into_iter() - .map(Into::into) - .collect::>(); - - let instance = self - .inner - .sandbox_store - .borrow() - .instance(instance_id) - .map_err(|e| e.to_string())?; - - let result = instance.invoke::<_, CapsHolder, ThunkHolder>(export_name, &args, state); - - match result { - Ok(None) => Ok(sandbox_primitives::ERR_OK), - Ok(Some(val)) => { - // Serialize return value and write it back into the memory. - sp_wasm_interface::ReturnValue::Value(val.into()).using_encoded(|val| { - if val.len() > return_val_len as usize { - Err("Return value buffer is too small")?; - } - ::write_memory(self, return_val, val) - .map_err(|_| "can't write return value")?; - Ok(sandbox_primitives::ERR_OK) - }) - }, - Err(_) => Ok(sandbox_primitives::ERR_EXECUTION), - } - } - - fn instance_teardown(&mut self, instance_id: u32) -> sp_wasm_interface::Result<()> { - self.inner - .sandbox_store - .borrow_mut() - .instance_teardown(instance_id) - .map_err(|e| e.to_string()) - } - - fn instance_new( - &mut self, - dispatch_thunk_id: u32, - wasm: &[u8], - raw_env_def: &[u8], - state: u32, - ) -> sp_wasm_interface::Result { - // Extract a dispatch thunk from the instance's table by the specified index. - let dispatch_thunk = { - let table_item = self - .inner - .instance - .table() - .as_ref() - .ok_or_else(|| "Runtime doesn't have a table; sandbox is unavailable")? - .get(dispatch_thunk_id); - - let func_ref = table_item - .ok_or_else(|| "dispatch_thunk_id is out of bounds")? - .funcref() - .ok_or_else(|| "dispatch_thunk_idx should be a funcref")? - .ok_or_else(|| "dispatch_thunk_idx should point to actual func")? - .clone(); - SupervisorFuncRef(func_ref) - }; - - let guest_env = match sandbox::GuestEnvironment::decode( - &*self.inner.sandbox_store.borrow(), - raw_env_def, - ) { - Ok(guest_env) => guest_env, - Err(_) => return Ok(sandbox_primitives::ERR_MODULE as u32), - }; - - let store = &mut *self.inner.sandbox_store.borrow_mut(); - let result = DISPATCH_THUNK.set(&dispatch_thunk, || { - store - .instantiate::<_, CapsHolder, ThunkHolder>(wasm, guest_env, state) - .map(|i| i.register(store)) - }); - - let instance_idx_or_err_code = match result { - Ok(instance_idx) => instance_idx, - Err(sandbox::InstantiationError::StartTrapped) => sandbox_primitives::ERR_EXECUTION, - Err(_) => sandbox_primitives::ERR_MODULE, - }; - - Ok(instance_idx_or_err_code as u32) - } - - fn get_global_val( - &self, - instance_idx: u32, - name: &str, - ) -> sp_wasm_interface::Result> { - self.inner - .sandbox_store - .borrow() - .instance(instance_idx) - .map(|i| i.get_global_val(name)) - .map_err(|e| e.to_string()) - } -} - -/// Wasmtime specific implementation of `SandboxCapabilitiesHolder` that provides -/// sandbox with a scoped thread local access to a function executor. -/// This is a way to calm down the borrow checker since host function closures -/// require exclusive access to it. -struct CapsHolder; - -impl SandboxCapabilitiesHolder for CapsHolder { - type SupervisorFuncRef = SupervisorFuncRef; - type SC = HostState; - - fn with_sandbox_capabilities R>(f: F) -> R { - crate::state_holder::with_context(|ctx| f(&mut ctx.expect("wasmtime executor is not set"))) - } -} - -/// Wasmtime specific implementation of `DispatchThunkHolder` that provides -/// sandbox with a scoped thread local access to a dispatch thunk. -/// This is a way to calm down the borrow checker since host function closures -/// require exclusive access to it. -struct ThunkHolder; - -scoped_tls::scoped_thread_local!(static DISPATCH_THUNK: SupervisorFuncRef); - -impl sandbox::DispatchThunkHolder for ThunkHolder { - type DispatchThunk = SupervisorFuncRef; - - fn with_dispatch_thunk R>(f: F) -> R { - assert!(DISPATCH_THUNK.is_set(), "dispatch thunk is not set"); - DISPATCH_THUNK.with(|thunk| f(&mut thunk.clone())) - } - - fn initialize_thunk(s: &Self::DispatchThunk, f: F) -> R - where - F: FnOnce() -> R, - { - DISPATCH_THUNK.set(s, f) + fn supervisor_context(&mut self) -> &mut dyn FunctionContext { + self.host_context } } diff --git a/substrate/client/executor/wasmtime/src/state_holder.rs b/substrate/client/executor/wasmtime/src/state_holder.rs index 45bddc841b..0e2684cd25 100644 --- a/substrate/client/executor/wasmtime/src/state_holder.rs +++ b/substrate/client/executor/wasmtime/src/state_holder.rs @@ -16,7 +16,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use crate::host::HostState; +use crate::host::{HostContext, HostState}; scoped_tls::scoped_thread_local!(static HOST_STATE: HostState); @@ -36,10 +36,10 @@ where /// context will be `None`. pub fn with_context(f: F) -> R where - F: FnOnce(Option) -> R, + F: FnOnce(Option) -> R, { if !HOST_STATE.is_set() { return f(None) } - HOST_STATE.with(|state| f(Some(state.clone()))) + HOST_STATE.with(|state| f(Some(state.materialize()))) }