From bc8968f4c84ed4cd80acd4d452874c59b5f2fb3b Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Fri, 11 Feb 2022 15:32:06 +0700 Subject: [PATCH] Separate wasmi and wasmer sandbox implementations into their own modules (#10563) * Moves wasmi specific `ImportResolver` and `MemoryTransfer` impls to submodule * Splits context store environmental, moves impl `Externals` to wasmi backend * Adds wasmer sandbox backend stub module * Move sandbox impl code to backend specific modules * Moves wasmi stuff * Fixes value conversion * Makes it all compile * Remove `with_context_store` * Moves `WasmerBackend` to the impl * Reformat the source * Moves wasmer MemoryWrapper * Reformats the source * Fixes mutability * Moves backend impls to a submodule * Fix visibility * Reformat the source * Feature gate wasmer backend module * Moves wasmi memory allocation to backend module * Rename WasmerBackend to Backend * Refactor dispatch result decoding, get rid of Wasmi types in common sandbox code * Reformat the source * Remove redundant prefixes in backend functions * Remove wasmer-sandbox from default features * Post-review changes * Add conversion soundness proof * Remove redundant prefix * Removes now redundant clone_inner * Add `Error::SandboxBackend`, refactor invoke result * Fix comments * Rename `Error::SandboxBackend` to `Sandbox` * Simplifies logic in `wasmer_backend::invoke` * Fixes memory management --- client/executor/common/src/error.rs | 3 + client/executor/common/src/sandbox.rs | 543 +----------------- .../common/src/sandbox/wasmer_backend.rs | 434 ++++++++++++++ .../common/src/sandbox/wasmi_backend.rs | 323 +++++++++++ client/executor/common/src/util.rs | 191 +----- client/executor/wasmi/src/lib.rs | 6 +- client/executor/wasmtime/src/host.rs | 1 - 7 files changed, 788 insertions(+), 713 deletions(-) create mode 100644 client/executor/common/src/sandbox/wasmer_backend.rs create mode 100644 client/executor/common/src/sandbox/wasmi_backend.rs diff --git a/client/executor/common/src/error.rs b/client/executor/common/src/error.rs index c5de737376c15..5ffcafd7e92c4 100644 --- a/client/executor/common/src/error.rs +++ b/client/executor/common/src/error.rs @@ -34,6 +34,9 @@ pub enum Error { #[error(transparent)] Wasmi(#[from] wasmi::Error), + #[error("Sandbox error: {0}")] + Sandbox(String), + #[error("Error calling api function: {0}")] ApiError(Box), diff --git a/client/executor/common/src/sandbox.rs b/client/executor/common/src/sandbox.rs index 34162b7dc4978..3f46ec53bdfe4 100644 --- a/client/executor/common/src/sandbox.rs +++ b/client/executor/common/src/sandbox.rs @@ -20,24 +20,30 @@ //! //! Sandboxing is backed by wasmi and wasmer, depending on the configuration. +#[cfg(feature = "wasmer-sandbox")] +mod wasmer_backend; + +mod wasmi_backend; + use crate::{ - error::{Error, Result}, + error::{self, Result}, util, }; -use codec::{Decode, Encode}; +use codec::Decode; use sp_core::sandbox as sandbox_primitives; use sp_wasm_interface::{FunctionContext, Pointer, WordSize}; use std::{collections::HashMap, rc::Rc}; -use wasmi::{ - memory_units::Pages, Externals, ImportResolver, MemoryInstance, Module, ModuleInstance, - RuntimeArgs, RuntimeValue, Trap, TrapKind, -}; #[cfg(feature = "wasmer-sandbox")] -use crate::util::wasmer::MemoryWrapper as WasmerMemoryWrapper; -use crate::util::wasmi::MemoryWrapper as WasmiMemoryWrapper; +use wasmer_backend::{ + instantiate as wasmer_instantiate, invoke as wasmer_invoke, new_memory as wasmer_new_memory, + Backend as WasmerBackend, MemoryWrapper as WasmerMemoryWrapper, +}; -environmental::environmental!(SandboxContextStore: trait SandboxContext); +use wasmi_backend::{ + instantiate as wasmi_instantiate, invoke as wasmi_invoke, new_memory as wasmi_new_memory, + MemoryWrapper as WasmiMemoryWrapper, +}; /// Index of a function inside the supervisor. /// @@ -109,63 +115,6 @@ impl Imports { } } -impl ImportResolver for Imports { - fn resolve_func( - &self, - module_name: &str, - field_name: &str, - signature: &::wasmi::Signature, - ) -> std::result::Result { - let idx = self.func_by_name(module_name, field_name).ok_or_else(|| { - wasmi::Error::Instantiation(format!("Export {}:{} not found", module_name, field_name)) - })?; - - Ok(wasmi::FuncInstance::alloc_host(signature.clone(), idx.0)) - } - - fn resolve_memory( - &self, - module_name: &str, - field_name: &str, - _memory_type: &::wasmi::MemoryDescriptor, - ) -> std::result::Result { - let mem = self.memory_by_name(module_name, field_name).ok_or_else(|| { - wasmi::Error::Instantiation(format!("Export {}:{} not found", module_name, field_name)) - })?; - - let wrapper = mem.as_wasmi().ok_or_else(|| { - wasmi::Error::Instantiation(format!( - "Unsupported non-wasmi export {}:{}", - module_name, field_name - )) - })?; - - // Here we use inner memory reference only to resolve - // the imports without accessing the memory contents. - let mem = unsafe { wrapper.clone_inner() }; - - Ok(mem) - } - - fn resolve_global( - &self, - module_name: &str, - field_name: &str, - _global_type: &::wasmi::GlobalDescriptor, - ) -> std::result::Result { - Err(wasmi::Error::Instantiation(format!("Export {}:{} not found", module_name, field_name))) - } - - fn resolve_table( - &self, - module_name: &str, - field_name: &str, - _table_type: &::wasmi::TableDescriptor, - ) -> std::result::Result { - Err(wasmi::Error::Instantiation(format!("Export {}:{} not found", module_name, field_name))) - } -} - /// The sandbox context used to execute sandboxed functions. pub trait SandboxContext { /// Invoke a function in the supervisor environment. @@ -205,132 +154,6 @@ pub struct GuestExternals<'a> { state: u32, } -/// Construct trap error from specified message -fn trap(msg: &'static str) -> Trap { - TrapKind::Host(Box::new(Error::Other(msg.into()))).into() -} - -fn deserialize_result( - mut serialized_result: &[u8], -) -> std::result::Result, Trap> { - use self::sandbox_primitives::HostError; - use sp_wasm_interface::ReturnValue; - let result_val = std::result::Result::::decode(&mut serialized_result) - .map_err(|_| trap("Decoding Result failed!"))?; - - match result_val { - Ok(return_value) => Ok(match return_value { - ReturnValue::Unit => None, - ReturnValue::Value(typed_value) => Some(RuntimeValue::from(typed_value)), - }), - Err(HostError) => Err(trap("Supervisor function returned sandbox::HostError")), - } -} - -impl<'a> Externals for GuestExternals<'a> { - fn invoke_index( - &mut self, - index: usize, - args: RuntimeArgs, - ) -> std::result::Result, Trap> { - 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" - ); - - // 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, - ); - - deallocate( - sandbox_context.supervisor_context(), - invoke_args_ptr, - "Can't deallocate memory for dispatch thunk's invoke arguments", - )?; - let result = result?; - - // 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 = 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")); - - 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(sandbox_instance: &SandboxInstance, state: u32, f: F) -> R -where - F: FnOnce(&mut GuestExternals) -> R, -{ - f(&mut GuestExternals { sandbox_instance, state }) -} - /// Module instance in terms of selected backend enum BackendInstance { /// Wasmi module instance @@ -370,74 +193,18 @@ impl SandboxInstance { /// these syscall implementations. pub fn invoke( &self, - - // function to call that is exported from the module export_name: &str, - - // arguments passed to the function - args: &[RuntimeValue], - - // arbitraty context data of the call + args: &[sp_wasm_interface::Value], state: u32, - sandbox_context: &mut dyn SandboxContext, - ) -> std::result::Result, wasmi::Error> { + ) -> std::result::Result, error::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) - })?; - - Ok(wasmi_result) - }), + wasmi_invoke(self, wasmi_instance, export_name, args, state, sandbox_context), #[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 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(); - - let wasmer_result = SandboxContextStore::using(sandbox_context, || { - function.call(&args).map_err(|error| wasmi::Error::Function(error.to_string())) - })?; - - if wasmer_result.len() > 1 { - return Err(wasmi::Error::Function( - "multiple return types are not supported yet".into(), - )) - } - - 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_value) - }) - .transpose() - }, + BackendInstance::Wasmer(wasmer_instance) => + wasmer_invoke(wasmer_instance, export_name, args, state, sandbox_context), } } @@ -634,12 +401,6 @@ impl util::MemoryTransfer for Memory { } } -/// Wasmer specific context -#[cfg(feature = "wasmer-sandbox")] -struct WasmerBackend { - store: wasmer::Store, -} - /// Information specific to a particular execution backend enum BackendContext { /// Wasmi specific context @@ -659,13 +420,8 @@ impl BackendContext { SandboxBackend::TryWasmer => BackendContext::Wasmi, #[cfg(feature = "wasmer-sandbox")] - SandboxBackend::Wasmer | SandboxBackend::TryWasmer => { - let compiler = wasmer_compiler_singlepass::Singlepass::default(); - - BackendContext::Wasmer(WasmerBackend { - store: wasmer::Store::new(&wasmer::JIT::new(compiler).engine()), - }) - }, + SandboxBackend::Wasmer | SandboxBackend::TryWasmer => + BackendContext::Wasmer(WasmerBackend::new()), } } } @@ -709,19 +465,10 @@ impl Store
{ }; let memory = match &backend_context { - BackendContext::Wasmi => Memory::Wasmi(WasmiMemoryWrapper::new(MemoryInstance::alloc( - Pages(initial as usize), - maximum.map(|m| Pages(m as usize)), - )?)), + BackendContext::Wasmi => wasmi_new_memory(initial, maximum)?, #[cfg(feature = "wasmer-sandbox")] - BackendContext::Wasmer(context) => { - let ty = wasmer::MemoryType::new(initial, maximum, false); - Memory::Wasmer(WasmerMemoryWrapper::new( - wasmer::Memory::new(&context.store, ty) - .map_err(|_| Error::InvalidMemoryReference)?, - )) - }, + BackendContext::Wasmer(context) => wasmer_new_memory(context, initial, maximum)?, }; let mem_idx = memories.len(); @@ -827,12 +574,11 @@ impl Store
{ sandbox_context: &mut dyn SandboxContext, ) -> std::result::Result { let sandbox_instance = match self.backend_context { - BackendContext::Wasmi => - Self::instantiate_wasmi(wasm, guest_env, state, sandbox_context)?, + BackendContext::Wasmi => wasmi_instantiate(wasm, guest_env, state, sandbox_context)?, #[cfg(feature = "wasmer-sandbox")] BackendContext::Wasmer(ref context) => - Self::instantiate_wasmer(&context, wasm, guest_env, state, sandbox_context)?, + wasmer_instantiate(&context, wasm, guest_env, state, sandbox_context)?, }; Ok(UnregisteredInstance { sandbox_instance }) @@ -850,241 +596,4 @@ impl
Store
{ self.instances.push(Some((sandbox_instance, dispatch_thunk))); instance_idx as u32 } - - fn instantiate_wasmi( - wasm: &[u8], - guest_env: GuestEnvironment, - state: u32, - 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 = 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, - }); - - 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 - })?; - - Ok(sandbox_instance) - } - - #[cfg(feature = "wasmer-sandbox")] - fn instantiate_wasmer( - context: &WasmerBackend, - wasm: &[u8], - guest_env: GuestEnvironment, - state: u32, - sandbox_context: &mut dyn SandboxContext, - ) -> std::result::Result, InstantiationError> { - let module = wasmer::Module::new(&context.store, wasm) - .map_err(|_| InstantiationError::ModuleDecoding)?; - - type Exports = HashMap; - let mut exports_map = Exports::new(); - - for import in module.imports().into_iter() { - match import.ty() { - // Nothing to do here - wasmer::ExternType::Global(_) | wasmer::ExternType::Table(_) => (), - - wasmer::ExternType::Memory(_) => { - let exports = exports_map - .entry(import.module().to_string()) - .or_insert(wasmer::Exports::new()); - - let memory = guest_env - .imports - .memory_by_name(import.module(), import.name()) - .ok_or(InstantiationError::ModuleDecoding)?; - - let mut wasmer_memory_ref = memory.as_wasmer().expect( - "memory is created by wasmer; \ - exported by the same module and backend; \ - thus the operation can't fail; \ - qed", - ); - - // This is safe since we're only instantiating the module and populating - // the export table, so no memory access can happen at this time. - // All subsequent memory accesses should happen through the wrapper, - // that enforces the memory access protocol. - let wasmer_memory = unsafe { wasmer_memory_ref.clone_inner() }; - - exports.insert(import.name(), wasmer::Extern::Memory(wasmer_memory)); - }, - - wasmer::ExternType::Function(func_ty) => { - let guest_func_index = - guest_env.imports.func_by_name(import.module(), import.name()); - - let guest_func_index = if let Some(index) = guest_func_index { - index - } else { - // Missing import (should we abort here?) - continue - }; - - let supervisor_func_index = guest_env - .guest_to_supervisor_mapping - .func_by_guest_index(guest_func_index) - .ok_or(InstantiationError::ModuleDecoding)?; - - let function = Self::wasmer_dispatch_function( - supervisor_func_index, - &context.store, - func_ty, - state, - ); - - let exports = exports_map - .entry(import.module().to_string()) - .or_insert(wasmer::Exports::new()); - - exports.insert(import.name(), wasmer::Extern::Function(function)); - }, - } - } - - let mut import_object = wasmer::ImportObject::new(); - for (module_name, exports) in exports_map.into_iter() { - import_object.register(module_name, exports); - } - - 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), - guest_to_supervisor_mapping: guest_env.guest_to_supervisor_mapping, - })) - } - - #[cfg(feature = "wasmer-sandbox")] - fn wasmer_dispatch_function( - supervisor_func_index: SupervisorFuncIndex, - store: &wasmer::Store, - func_ty: &wasmer::FunctionType, - state: u32, - ) -> wasmer::Function { - wasmer::Function::new(store, func_ty, move |params| { - 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) => 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::, _>>()? - .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 = 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 dyn FunctionContext, ptr, fail_msg| { - fe.deallocate_memory(ptr).map_err(|_| wasmer::RuntimeError::new(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(wasmer::RuntimeError::new("Can't write invoke args into memory")) - } - - // Perform the actuall call - 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. - let (serialized_result_val_ptr, serialized_result_val_len) = { - // Cast to u64 to use zero-extension. - let v = serialized_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 = sandbox_context - .supervisor_context() - .read_memory(serialized_result_val_ptr, serialized_result_val_len) - .map_err(|_| { - wasmer::RuntimeError::new( - "Can't read the serialized result from dispatch thunk", - ) - }); - - let deserialized_result = 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) - .map_err(|e| wasmer::RuntimeError::new(e.to_string())) - })?; - - if let Some(value) = deserialized_result { - Ok(vec![match value { - 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()), - }]) - } else { - Ok(vec![]) - } - }) - .expect("SandboxContextStore is set when invoking sandboxed functions; qed") - }) - } } diff --git a/client/executor/common/src/sandbox/wasmer_backend.rs b/client/executor/common/src/sandbox/wasmer_backend.rs new file mode 100644 index 0000000000000..dfb26c4a8dedb --- /dev/null +++ b/client/executor/common/src/sandbox/wasmer_backend.rs @@ -0,0 +1,434 @@ +// This file is part of Substrate. + +// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! Wasmer specific impls for sandbox + +use crate::{ + error::{Error, Result}, + sandbox::Memory, + util::{checked_range, MemoryTransfer}, +}; +use codec::{Decode, Encode}; +use sp_core::sandbox::HostError; +use sp_wasm_interface::{FunctionContext, Pointer, ReturnValue, Value, WordSize}; +use std::{cell::RefCell, collections::HashMap, convert::TryInto, rc::Rc}; +use wasmer::RuntimeError; + +use crate::sandbox::{ + BackendInstance, GuestEnvironment, InstantiationError, SandboxContext, SandboxInstance, + SupervisorFuncIndex, +}; + +environmental::environmental!(SandboxContextStore: trait SandboxContext); + +/// Wasmer specific context +pub struct Backend { + store: wasmer::Store, +} + +impl Backend { + pub fn new() -> Self { + let compiler = wasmer_compiler_singlepass::Singlepass::default(); + + Backend { store: wasmer::Store::new(&wasmer::JIT::new(compiler).engine()) } + } +} + +/// Invoke a function within a sandboxed module +pub fn invoke( + instance: &wasmer::Instance, + export_name: &str, + args: &[Value], + _state: u32, + sandbox_context: &mut dyn SandboxContext, +) -> std::result::Result, Error> { + let function = instance + .exports + .get_function(export_name) + .map_err(|error| Error::Sandbox(error.to_string()))?; + + let args: Vec = args + .iter() + .map(|v| match *v { + Value::I32(val) => wasmer::Val::I32(val), + Value::I64(val) => wasmer::Val::I64(val), + Value::F32(val) => wasmer::Val::F32(f32::from_bits(val)), + Value::F64(val) => wasmer::Val::F64(f64::from_bits(val)), + }) + .collect(); + + let wasmer_result = SandboxContextStore::using(sandbox_context, || { + function.call(&args).map_err(|error| Error::Sandbox(error.to_string())) + })?; + + match wasmer_result.as_ref() { + [] => Ok(None), + + [wasm_value] => { + let wasmer_value = match *wasm_value { + 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)), + _ => + return Err(Error::Sandbox(format!( + "Unsupported return value: {:?}", + wasm_value, + ))), + }; + + Ok(Some(wasmer_value)) + }, + + _ => Err(Error::Sandbox("multiple return types are not supported yet".into())), + } +} + +/// Instantiate a module within a sandbox context +pub fn instantiate( + context: &Backend, + wasm: &[u8], + guest_env: GuestEnvironment, + state: u32, + sandbox_context: &mut dyn SandboxContext, +) -> std::result::Result, InstantiationError> { + let module = wasmer::Module::new(&context.store, wasm) + .map_err(|_| InstantiationError::ModuleDecoding)?; + + type Exports = HashMap; + let mut exports_map = Exports::new(); + + for import in module.imports().into_iter() { + match import.ty() { + // Nothing to do here + wasmer::ExternType::Global(_) | wasmer::ExternType::Table(_) => (), + + wasmer::ExternType::Memory(_) => { + let exports = exports_map + .entry(import.module().to_string()) + .or_insert(wasmer::Exports::new()); + + let memory = guest_env + .imports + .memory_by_name(import.module(), import.name()) + .ok_or(InstantiationError::ModuleDecoding)?; + + let wasmer_memory_ref = memory.as_wasmer().expect( + "memory is created by wasmer; \ + exported by the same module and backend; \ + thus the operation can't fail; \ + qed", + ); + + // This is safe since we're only instantiating the module and populating + // the export table, so no memory access can happen at this time. + // All subsequent memory accesses should happen through the wrapper, + // that enforces the memory access protocol. + // + // We take exclusive lock to ensure that we're the only one here, + // since during instantiation phase the memory should only be created + // and not yet accessed. + let wasmer_memory = wasmer_memory_ref + .buffer + .try_borrow_mut() + .map_err(|_| InstantiationError::EnvironmentDefinitionCorrupted)? + .clone(); + + exports.insert(import.name(), wasmer::Extern::Memory(wasmer_memory)); + }, + + wasmer::ExternType::Function(func_ty) => { + let guest_func_index = + guest_env.imports.func_by_name(import.module(), import.name()); + + let guest_func_index = if let Some(index) = guest_func_index { + index + } else { + // Missing import (should we abort here?) + continue + }; + + let supervisor_func_index = guest_env + .guest_to_supervisor_mapping + .func_by_guest_index(guest_func_index) + .ok_or(InstantiationError::ModuleDecoding)?; + + let function = + dispatch_function(supervisor_func_index, &context.store, func_ty, state); + + let exports = exports_map + .entry(import.module().to_string()) + .or_insert(wasmer::Exports::new()); + + exports.insert(import.name(), wasmer::Extern::Function(function)); + }, + } + } + + let mut import_object = wasmer::ImportObject::new(); + for (module_name, exports) in exports_map.into_iter() { + import_object.register(module_name, exports); + } + + 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), + guest_to_supervisor_mapping: guest_env.guest_to_supervisor_mapping, + })) +} + +fn dispatch_function( + supervisor_func_index: SupervisorFuncIndex, + store: &wasmer::Store, + func_ty: &wasmer::FunctionType, + state: u32, +) -> wasmer::Function { + wasmer::Function::new(store, func_ty, move |params| { + SandboxContextStore::with(|sandbox_context| { + // Serialize arguments into a byte vector. + let invoke_args_data = params + .iter() + .map(|val| match val { + 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(RuntimeError::new(format!("Unsupported function argument: {:?}", val))), + }) + .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 = + sandbox_context.supervisor_context().allocate_memory(invoke_args_len).map_err( + |_| RuntimeError::new("Can't allocate memory in supervisor for the arguments"), + )?; + + let deallocate = |fe: &mut dyn FunctionContext, ptr, fail_msg| { + fe.deallocate_memory(ptr).map_err(|_| RuntimeError::new(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(RuntimeError::new("Can't write invoke args into memory")) + } + + // Perform the actuall call + let serialized_result = sandbox_context + .invoke(invoke_args_ptr, invoke_args_len, state, supervisor_func_index) + .map_err(|e| RuntimeError::new(e.to_string())); + + deallocate( + sandbox_context.supervisor_context(), + invoke_args_ptr, + "Failed dealloction after invoke", + )?; + + let serialized_result = serialized_result?; + + // 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 = serialized_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 = sandbox_context + .supervisor_context() + .read_memory(serialized_result_val_ptr, serialized_result_val_len) + .map_err(|_| { + RuntimeError::new("Can't read the serialized result from dispatch thunk") + }); + + deallocate( + sandbox_context.supervisor_context(), + serialized_result_val_ptr, + "Can't deallocate memory for dispatch thunk's result", + )?; + + let serialized_result_val = serialized_result_val?; + + let deserialized_result = std::result::Result::::decode( + &mut serialized_result_val.as_slice(), + ) + .map_err(|_| RuntimeError::new("Decoding Result failed!"))? + .map_err(|_| RuntimeError::new("Supervisor function returned sandbox::HostError"))?; + + let result = match deserialized_result { + ReturnValue::Value(Value::I32(val)) => vec![wasmer::Val::I32(val)], + ReturnValue::Value(Value::I64(val)) => vec![wasmer::Val::I64(val)], + ReturnValue::Value(Value::F32(val)) => vec![wasmer::Val::F32(f32::from_bits(val))], + ReturnValue::Value(Value::F64(val)) => vec![wasmer::Val::F64(f64::from_bits(val))], + + ReturnValue::Unit => vec![], + }; + + Ok(result) + }) + .expect("SandboxContextStore is set when invoking sandboxed functions; qed") + }) +} + +/// Allocate new memory region +pub fn new_memory( + context: &Backend, + initial: u32, + maximum: Option, +) -> crate::error::Result { + let ty = wasmer::MemoryType::new(initial, maximum, false); + let memory = Memory::Wasmer(MemoryWrapper::new( + wasmer::Memory::new(&context.store, ty).map_err(|_| Error::InvalidMemoryReference)?, + )); + + Ok(memory) +} + +/// In order to enforce memory access protocol to the backend memory +/// we wrap it with `RefCell` and encapsulate all memory operations. +#[derive(Debug, Clone)] +pub struct MemoryWrapper { + buffer: Rc>, +} + +impl MemoryWrapper { + /// Take ownership of the memory region and return a wrapper object + pub fn new(memory: wasmer::Memory) -> Self { + Self { buffer: Rc::new(RefCell::new(memory)) } + } + + /// Returns linear memory of the wasm instance as a slice. + /// + /// # Safety + /// + /// Wasmer doesn't provide comprehensive documentation about the exact behavior of the data + /// pointer. If a dynamic style heap is used the base pointer of the heap can change. Since + /// growing, we cannot guarantee the lifetime of the returned slice reference. + unsafe fn memory_as_slice(memory: &wasmer::Memory) -> &[u8] { + let ptr = memory.data_ptr() as *const _; + + let len: usize = memory.data_size().try_into().expect( + "maximum memory object size never exceeds pointer size on any architecture; \ + usize by design and definition is enough to store any memory object size \ + possible on current achitecture; thus the conversion can not fail; qed", + ); + + if len == 0 { + &[] + } else { + core::slice::from_raw_parts(ptr, len) + } + } + + /// Returns linear memory of the wasm instance as a slice. + /// + /// # Safety + /// + /// See `[memory_as_slice]`. In addition to those requirements, since a mutable reference is + /// returned it must be ensured that only one mutable and no shared references to memory + /// exists at the same time. + unsafe fn memory_as_slice_mut(memory: &mut wasmer::Memory) -> &mut [u8] { + let ptr = memory.data_ptr(); + + let len: usize = memory.data_size().try_into().expect( + "maximum memory object size never exceeds pointer size on any architecture; \ + usize by design and definition is enough to store any memory object size \ + possible on current achitecture; thus the conversion can not fail; qed", + ); + + if len == 0 { + &mut [] + } else { + core::slice::from_raw_parts_mut(ptr, len) + } + } +} + +impl MemoryTransfer for MemoryWrapper { + fn read(&self, source_addr: Pointer, size: usize) -> Result> { + let memory = self.buffer.borrow(); + + let data_size: usize = memory.data_size().try_into().expect( + "maximum memory object size never exceeds pointer size on any architecture; \ + usize by design and definition is enough to store any memory object size \ + possible on current achitecture; thus the conversion can not fail; qed", + ); + + let range = checked_range(source_addr.into(), size, data_size) + .ok_or_else(|| Error::Other("memory read is out of bounds".into()))?; + + let mut buffer = vec![0; range.len()]; + self.read_into(source_addr, &mut buffer)?; + + Ok(buffer) + } + + fn read_into(&self, source_addr: Pointer, destination: &mut [u8]) -> Result<()> { + unsafe { + let memory = self.buffer.borrow(); + + // This should be safe since we don't grow up memory while caching this reference + // and we give up the reference before returning from this function. + let source = Self::memory_as_slice(&memory); + + let range = checked_range(source_addr.into(), destination.len(), source.len()) + .ok_or_else(|| Error::Other("memory read is out of bounds".into()))?; + + destination.copy_from_slice(&source[range]); + Ok(()) + } + } + + fn write_from(&self, dest_addr: Pointer, source: &[u8]) -> Result<()> { + unsafe { + let memory = &mut self.buffer.borrow_mut(); + + // This should be safe since we don't grow up memory while caching this reference + // and we give up the reference before returning from this function. + let destination = Self::memory_as_slice_mut(memory); + + let range = checked_range(dest_addr.into(), source.len(), destination.len()) + .ok_or_else(|| Error::Other("memory write is out of bounds".into()))?; + + destination[range].copy_from_slice(source); + Ok(()) + } + } +} diff --git a/client/executor/common/src/sandbox/wasmi_backend.rs b/client/executor/common/src/sandbox/wasmi_backend.rs new file mode 100644 index 0000000000000..92bb0e1e398e0 --- /dev/null +++ b/client/executor/common/src/sandbox/wasmi_backend.rs @@ -0,0 +1,323 @@ +// This file is part of Substrate. + +// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! Wasmi specific impls for sandbox + +use codec::{Decode, Encode}; +use sp_core::sandbox::HostError; +use sp_wasm_interface::{FunctionContext, Pointer, ReturnValue, Value, WordSize}; +use std::rc::Rc; + +use wasmi::{ + memory_units::Pages, ImportResolver, MemoryInstance, Module, ModuleInstance, RuntimeArgs, + RuntimeValue, Trap, TrapKind, +}; + +use crate::{ + error::{self, Error}, + sandbox::{ + BackendInstance, GuestEnvironment, GuestExternals, GuestFuncIndex, Imports, + InstantiationError, Memory, SandboxContext, SandboxInstance, + }, + util::{checked_range, MemoryTransfer}, +}; + +environmental::environmental!(SandboxContextStore: trait SandboxContext); + +/// Construct trap error from specified message +fn trap(msg: &'static str) -> Trap { + TrapKind::Host(Box::new(Error::Other(msg.into()))).into() +} + +impl ImportResolver for Imports { + fn resolve_func( + &self, + module_name: &str, + field_name: &str, + signature: &wasmi::Signature, + ) -> std::result::Result { + let idx = self.func_by_name(module_name, field_name).ok_or_else(|| { + wasmi::Error::Instantiation(format!("Export {}:{} not found", module_name, field_name)) + })?; + + Ok(wasmi::FuncInstance::alloc_host(signature.clone(), idx.0)) + } + + fn resolve_memory( + &self, + module_name: &str, + field_name: &str, + _memory_type: &wasmi::MemoryDescriptor, + ) -> std::result::Result { + let mem = self.memory_by_name(module_name, field_name).ok_or_else(|| { + wasmi::Error::Instantiation(format!("Export {}:{} not found", module_name, field_name)) + })?; + + let wrapper = mem.as_wasmi().ok_or_else(|| { + wasmi::Error::Instantiation(format!( + "Unsupported non-wasmi export {}:{}", + module_name, field_name + )) + })?; + + // Here we use inner memory reference only to resolve the imports + // without accessing the memory contents. All subsequent memory accesses + // should happen through the wrapper, that enforces the memory access protocol. + let mem = wrapper.0.clone(); + + Ok(mem) + } + + fn resolve_global( + &self, + module_name: &str, + field_name: &str, + _global_type: &wasmi::GlobalDescriptor, + ) -> std::result::Result { + Err(wasmi::Error::Instantiation(format!("Export {}:{} not found", module_name, field_name))) + } + + fn resolve_table( + &self, + module_name: &str, + field_name: &str, + _table_type: &wasmi::TableDescriptor, + ) -> std::result::Result { + Err(wasmi::Error::Instantiation(format!("Export {}:{} not found", module_name, field_name))) + } +} + +/// Allocate new memory region +pub fn new_memory(initial: u32, maximum: Option) -> crate::error::Result { + let memory = Memory::Wasmi(MemoryWrapper::new( + MemoryInstance::alloc(Pages(initial as usize), maximum.map(|m| Pages(m as usize))) + .map_err(|error| Error::Sandbox(error.to_string()))?, + )); + + Ok(memory) +} + +/// Wasmi provides direct access to its memory using slices. +/// +/// This wrapper limits the scope where the slice can be taken to +#[derive(Debug, Clone)] +pub struct MemoryWrapper(wasmi::MemoryRef); + +impl MemoryWrapper { + /// Take ownership of the memory region and return a wrapper object + fn new(memory: wasmi::MemoryRef) -> Self { + Self(memory) + } +} + +impl MemoryTransfer for MemoryWrapper { + fn read(&self, source_addr: Pointer, size: usize) -> error::Result> { + self.0.with_direct_access(|source| { + let range = checked_range(source_addr.into(), size, source.len()) + .ok_or_else(|| error::Error::Other("memory read is out of bounds".into()))?; + + Ok(Vec::from(&source[range])) + }) + } + + fn read_into(&self, source_addr: Pointer, destination: &mut [u8]) -> error::Result<()> { + self.0.with_direct_access(|source| { + let range = checked_range(source_addr.into(), destination.len(), source.len()) + .ok_or_else(|| error::Error::Other("memory read is out of bounds".into()))?; + + destination.copy_from_slice(&source[range]); + Ok(()) + }) + } + + fn write_from(&self, dest_addr: Pointer, source: &[u8]) -> error::Result<()> { + self.0.with_direct_access_mut(|destination| { + let range = checked_range(dest_addr.into(), source.len(), destination.len()) + .ok_or_else(|| error::Error::Other("memory write is out of bounds".into()))?; + + destination[range].copy_from_slice(source); + Ok(()) + }) + } +} + +impl<'a> wasmi::Externals for GuestExternals<'a> { + fn invoke_index( + &mut self, + index: usize, + args: RuntimeArgs, + ) -> std::result::Result, Trap> { + 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" + ); + + // 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, + ); + + deallocate( + sandbox_context.supervisor_context(), + invoke_args_ptr, + "Can't deallocate memory for dispatch thunk's invoke arguments", + )?; + let result = result?; + + // 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 = 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")); + + 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| { + let result_val = std::result::Result::::decode(&mut serialized_result_val.as_slice()) + .map_err(|_| trap("Decoding Result failed!"))?; + + match result_val { + Ok(return_value) => Ok(match return_value { + ReturnValue::Unit => None, + ReturnValue::Value(typed_value) => Some(RuntimeValue::from(typed_value)), + }), + Err(HostError) => Err(trap("Supervisor function returned sandbox::HostError")), + } + }) + }).expect("SandboxContextStore is set when invoking sandboxed functions; qed") + } +} + +fn with_guest_externals(sandbox_instance: &SandboxInstance, state: u32, f: F) -> R +where + F: FnOnce(&mut GuestExternals) -> R, +{ + f(&mut GuestExternals { sandbox_instance, state }) +} + +/// Instantiate a module within a sandbox context +pub fn instantiate( + wasm: &[u8], + guest_env: GuestEnvironment, + state: u32, + 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 = 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, + }); + + with_guest_externals(&sandbox_instance, state, |guest_externals| { + SandboxContextStore::using(sandbox_context, || { + wasmi_instance + .run_start(guest_externals) + .map_err(|_| InstantiationError::StartTrapped) + }) + })?; + + Ok(sandbox_instance) +} + +/// Invoke a function within a sandboxed module +pub fn invoke( + instance: &SandboxInstance, + module: &wasmi::ModuleRef, + export_name: &str, + args: &[Value], + state: u32, + sandbox_context: &mut dyn SandboxContext, +) -> std::result::Result, error::Error> { + with_guest_externals(instance, state, |guest_externals| { + SandboxContextStore::using(sandbox_context, || { + let args = args.iter().cloned().map(Into::into).collect::>(); + + module + .invoke_export(export_name, &args, guest_externals) + .map(|result| result.map(Into::into)) + .map_err(|error| error::Error::Sandbox(error.to_string())) + }) + }) +} diff --git a/client/executor/common/src/util.rs b/client/executor/common/src/util.rs index eddf4230504c7..fbae01b556fb1 100644 --- a/client/executor/common/src/util.rs +++ b/client/executor/common/src/util.rs @@ -18,7 +18,7 @@ //! Utilities used by all backends -use crate::error::{Error, Result}; +use crate::error::Result; use sp_wasm_interface::Pointer; use std::ops::Range; @@ -50,192 +50,3 @@ pub trait MemoryTransfer { /// Returns an error if the write would go out of the memory bounds. fn write_from(&self, dest_addr: Pointer, source: &[u8]) -> Result<()>; } - -/// Safe wrapper over wasmi memory reference -pub mod wasmi { - use super::*; - - /// Wasmi provides direct access to its memory using slices. - /// - /// This wrapper limits the scope where the slice can be taken to - #[derive(Debug, Clone)] - pub struct MemoryWrapper(::wasmi::MemoryRef); - - impl MemoryWrapper { - /// Take ownership of the memory region and return a wrapper object - pub fn new(memory: ::wasmi::MemoryRef) -> Self { - Self(memory) - } - - /// Clone the underlying memory object - /// - /// # Safety - /// - /// The sole purpose of `MemoryRef` is to protect the memory from uncontrolled - /// access. By returning the memory object "as is" we bypass all of the checks. - /// - /// Intended to use only during module initialization. - pub unsafe fn clone_inner(&self) -> ::wasmi::MemoryRef { - self.0.clone() - } - } - - impl super::MemoryTransfer for MemoryWrapper { - fn read(&self, source_addr: Pointer, size: usize) -> Result> { - self.0.with_direct_access(|source| { - let range = checked_range(source_addr.into(), size, source.len()) - .ok_or_else(|| Error::Other("memory read is out of bounds".into()))?; - - Ok(Vec::from(&source[range])) - }) - } - - fn read_into(&self, source_addr: Pointer, destination: &mut [u8]) -> Result<()> { - self.0.with_direct_access(|source| { - let range = checked_range(source_addr.into(), destination.len(), source.len()) - .ok_or_else(|| Error::Other("memory read is out of bounds".into()))?; - - destination.copy_from_slice(&source[range]); - Ok(()) - }) - } - - fn write_from(&self, dest_addr: Pointer, source: &[u8]) -> Result<()> { - self.0.with_direct_access_mut(|destination| { - let range = checked_range(dest_addr.into(), source.len(), destination.len()) - .ok_or_else(|| Error::Other("memory write is out of bounds".into()))?; - - destination[range].copy_from_slice(source); - Ok(()) - }) - } - } -} - -// Routines specific to Wasmer runtime. Since sandbox can be invoked from both -/// wasmi and wasmtime runtime executors, we need to have a way to deal with sanbox -/// backends right from the start. -#[cfg(feature = "wasmer-sandbox")] -pub mod wasmer { - use super::checked_range; - use crate::error::{Error, Result}; - use sp_wasm_interface::Pointer; - use std::{cell::RefCell, convert::TryInto, rc::Rc}; - - /// In order to enforce memory access protocol to the backend memory - /// we wrap it with `RefCell` and encapsulate all memory operations. - #[derive(Debug, Clone)] - pub struct MemoryWrapper { - buffer: Rc>, - } - - impl MemoryWrapper { - /// Take ownership of the memory region and return a wrapper object - pub fn new(memory: wasmer::Memory) -> Self { - Self { buffer: Rc::new(RefCell::new(memory)) } - } - - /// Returns linear memory of the wasm instance as a slice. - /// - /// # Safety - /// - /// Wasmer doesn't provide comprehensive documentation about the exact behavior of the data - /// pointer. If a dynamic style heap is used the base pointer of the heap can change. Since - /// growing, we cannot guarantee the lifetime of the returned slice reference. - unsafe fn memory_as_slice(memory: &wasmer::Memory) -> &[u8] { - let ptr = memory.data_ptr() as *const _; - let len: usize = - memory.data_size().try_into().expect("data size should fit into usize"); - - if len == 0 { - &[] - } else { - core::slice::from_raw_parts(ptr, len) - } - } - - /// Returns linear memory of the wasm instance as a slice. - /// - /// # Safety - /// - /// See `[memory_as_slice]`. In addition to those requirements, since a mutable reference is - /// returned it must be ensured that only one mutable and no shared references to memory - /// exists at the same time. - unsafe fn memory_as_slice_mut(memory: &wasmer::Memory) -> &mut [u8] { - let ptr = memory.data_ptr(); - let len: usize = - memory.data_size().try_into().expect("data size should fit into usize"); - - if len == 0 { - &mut [] - } else { - core::slice::from_raw_parts_mut(ptr, len) - } - } - - /// Clone the underlying memory object - /// - /// # Safety - /// - /// The sole purpose of `MemoryRef` is to protect the memory from uncontrolled - /// access. By returning the memory object "as is" we bypass all of the checks. - /// - /// Intended to use only during module initialization. - /// - /// # Panics - /// - /// Will panic if `MemoryRef` is currently in use. - pub unsafe fn clone_inner(&mut self) -> wasmer::Memory { - // We take exclusive lock to ensure that we're the only one here - self.buffer.borrow_mut().clone() - } - } - - impl super::MemoryTransfer for MemoryWrapper { - fn read(&self, source_addr: Pointer, size: usize) -> Result> { - let memory = self.buffer.borrow(); - - let data_size = memory.data_size().try_into().expect("data size does not fit"); - - let range = checked_range(source_addr.into(), size, data_size) - .ok_or_else(|| Error::Other("memory read is out of bounds".into()))?; - - let mut buffer = vec![0; range.len()]; - self.read_into(source_addr, &mut buffer)?; - - Ok(buffer) - } - - fn read_into(&self, source_addr: Pointer, destination: &mut [u8]) -> Result<()> { - unsafe { - let memory = self.buffer.borrow(); - - // This should be safe since we don't grow up memory while caching this reference - // and we give up the reference before returning from this function. - let source = Self::memory_as_slice(&memory); - - let range = checked_range(source_addr.into(), destination.len(), source.len()) - .ok_or_else(|| Error::Other("memory read is out of bounds".into()))?; - - destination.copy_from_slice(&source[range]); - Ok(()) - } - } - - fn write_from(&self, dest_addr: Pointer, source: &[u8]) -> Result<()> { - unsafe { - let memory = self.buffer.borrow_mut(); - - // This should be safe since we don't grow up memory while caching this reference - // and we give up the reference before returning from this function. - let destination = Self::memory_as_slice_mut(&memory); - - let range = checked_range(dest_addr.into(), source.len(), destination.len()) - .ok_or_else(|| Error::Other("memory write is out of bounds".into()))?; - - destination[range].copy_from_slice(source); - Ok(()) - } - } - } -} diff --git a/client/executor/wasmi/src/lib.rs b/client/executor/wasmi/src/lib.rs index f0488972daca9..97c73c3454a4b 100644 --- a/client/executor/wasmi/src/lib.rs +++ b/client/executor/wasmi/src/lib.rs @@ -102,10 +102,7 @@ impl<'a> sandbox::SandboxContext for SandboxContext<'a> { match result { Ok(Some(RuntimeValue::I64(val))) => Ok(val), Ok(_) => return Err("Supervisor function returned unexpected result!".into()), - Err(err) => Err(Error::AbortedDueToTrap(MessageWithBacktrace { - message: err.to_string(), - backtrace: None, - })), + Err(err) => Err(Error::Sandbox(err.to_string())), } } @@ -222,7 +219,6 @@ impl Sandbox for FunctionExecutor { let args = Vec::::decode(&mut args) .map_err(|_| "Can't decode serialized arguments for the invocation")? .into_iter() - .map(Into::into) .collect::>(); let instance = diff --git a/client/executor/wasmtime/src/host.rs b/client/executor/wasmtime/src/host.rs index b310ada24b629..23deacbf93623 100644 --- a/client/executor/wasmtime/src/host.rs +++ b/client/executor/wasmtime/src/host.rs @@ -221,7 +221,6 @@ impl<'a> Sandbox for HostContext<'a> { 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().instance(instance_id).map_err(|e| e.to_string())?;