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