mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-30 00:01:03 +00:00
Integrate Wasmer into Substrate sandbox environment (#5920)
* Add comments and refactor Sandbox module * Adds some comments * Add wasmtime instance to the sandbox and delegate calls * Adds module imports stub * WIP state holder via *mut * My take at the problem * Brings back invoke and instantiate implementation details * Removes redundant bound * Code cleanup * Fixes invoke closure * Refactors FunctionExecutor to eliminate lifetime * Wraps `FunctionExecutor::sandbox_store` in `RefCell` * Renames `FunctionExecutor::heap` to `allocator` * Wraps `FunctionExecutor::allocator` in `RefCell` * Refactors FunctionExecutor to `Rc<Inner>` pattern * Implements scoped TLS for FunctionExecutor * Fixes wasmi instancing * Fixes sandbox asserts * Makes sandbox compile after wasmtime API change * Uses Vurich/wasmtime for the Lightbeam backend * Uses wasmtime instead of wasmi for sandbox API results * Refactors sandbox to use one of the execution backends at a time * Fixes wasmtime module instantiation * TEMP vurich branch stuff * Adds wasmer impl stub * Adds get global * Fixes warnings * Adds wasmer invoke impl * Implements host function interface for wasmer * Fixes wasmer instantiation result * Adds workaround to remove debug_assert * Fixes import object generation for wasmer * Attempt to propagate wasmer::Store through sandbox::Store * Wraps `sandbox::Store::memories` in `RefCell` * Moves `sandbox::instantiate` to `sandbox::Store` * Eliminate `RefCell<memories>` * Implements `HostState::memory_get/set`, removes accidental `borrow_mut` * Fixes sandbox memory handling for wasmi * Fix memory allocation * Resets Cargo.lock to match master * Fixes compilation * Refactors sandbox to use TLS for dispatch_thunk propagation to wasmer * Pass dispatch thunk to the sandbox as a TLS * Initialize dispatch thunk holder in `SandboxInstance` * Comment out Wasmtime/Lightbeam sandbox backend * Revert wasmtime back to mainstream * Adds SandboxExecutionMethod enum for cli param * Cleanup sandbox code * Allow wasmi to access wasmer memory regions * More cleanup * Remove debug logging, replace asserts with runtime errors * Revert "Adds SandboxExecutionMethod enum for cli param" This reverts commit dcb2b1d3b54145ab51ad2e3fef0d980ba215b596. * Fixes warnings * Fixes indentation and line width * Fix return types condition * Puts everything related under the `wasmer-sandbox` feature flag * Fixes warnings * Address grumbles * Split instantiate per backend * More splits * Refacmemory allocation * Nitpicks * Attempt to wrap wasmer memory in protoco enforcing type * Revert renaming * WIP wasm buffer proxy API * Reimplement util::wasmer::MemoryRef to use buffers instead of memory slices * Adds WasmiMemoryWrapper and MemoryTransfer trait * Refactor naming * Perform all memory transfers using MemoryTransfer * Adds allocating `read` * Adds comments * Removes unused imports * Removes now unused function * Pulls Cargo.lock from origin/master * Fix rustdoc * Removes unused `TransferError` * Update Cargo.lock * Removes unused import * cargo fmt * Fix feature dependency graph * Feature should flow from the top level crate * We should not assume a specific workspace structure * sc-executor-wasmi does not use the feature * sc-executor-wasmtime should not know about the feature * Fix doc typo * Enable wasmer-sandbox by default (for now) It will be removed before merge. It is so that the benchbot uses the wasmer sandbox. * cargo run --quiet --release --features=runtime-benchmarks --manifest-path=bin/node/cli/Cargo.toml -- benchmark --chain=dev --steps=50 --repeat=20 --pallet=pallet_contracts --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --output=./frame/contracts/src/weights.rs --template=./.maintain/frame-weight-template.hbs * Revert "cargo run --quiet --release --features=runtime-benchmarks --manifest-path=bin/node/cli/Cargo.toml -- benchmark --chain=dev --steps=50 --repeat=20 --pallet=pallet_contracts --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --output=./frame/contracts/src/weights.rs --template=./.maintain/frame-weight-template.hbs" This reverts commit d713590ba45387c4204b2ad97c8bd6f6ebabda4e. * cargo fmt * Add ci-check to prevent wasmer sandbox build breaking * Run tests with wasmer-sandbox enabled * Revert "Run tests with wasmer-sandbox enabled" This reverts commit cff63156a162f9ffdab23e7cb94a30f44e320f8a. Co-authored-by: Sergei Shulepov <s.pepyakin@gmail.com> Co-authored-by: Andrew Jones <ascjones@gmail.com> Co-authored-by: Alexander Theißen <alex.theissen@me.com> Co-authored-by: Parity Benchmarking Bot <admin@parity.io>
This commit is contained in:
@@ -55,3 +55,4 @@ std = []
|
||||
wasm-extern-trace = []
|
||||
wasmtime = ["sc-executor-wasmtime"]
|
||||
wasmi-errno = ["wasmi/errno"]
|
||||
wasmer-sandbox = ["sc-executor-common/wasmer-sandbox"]
|
||||
|
||||
@@ -25,5 +25,12 @@ sp-maybe-compressed-blob = { version = "4.0.0-dev", path = "../../../primitives/
|
||||
sp-serializer = { version = "3.0.0", path = "../../../primitives/serializer" }
|
||||
thiserror = "1.0.21"
|
||||
|
||||
wasmer = { version = "1.0", optional = true }
|
||||
wasmer-compiler-singlepass = { version = "1.0", optional = true }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
wasmer-sandbox = [
|
||||
"wasmer",
|
||||
"wasmer-compiler-singlepass",
|
||||
]
|
||||
|
||||
@@ -24,4 +24,5 @@
|
||||
pub mod error;
|
||||
pub mod runtime_blob;
|
||||
pub mod sandbox;
|
||||
pub mod util;
|
||||
pub mod wasm_runtime;
|
||||
|
||||
@@ -18,19 +18,25 @@
|
||||
|
||||
//! This module implements sandboxing support in the runtime.
|
||||
//!
|
||||
//! Sandboxing is baked by wasmi at the moment. In future, however, we would like to add/switch to
|
||||
//! a compiled execution engine.
|
||||
//! Sandboxing is backed by wasmi and wasmer, depending on the configuration.
|
||||
|
||||
use crate::error::{Error, Result};
|
||||
use crate::{
|
||||
error::{Error, Result},
|
||||
util,
|
||||
};
|
||||
use codec::{Decode, Encode};
|
||||
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, MemoryRef, Module,
|
||||
ModuleInstance, ModuleRef, RuntimeArgs, RuntimeValue, Trap, TrapKind,
|
||||
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;
|
||||
|
||||
/// Index of a function inside the supervisor.
|
||||
///
|
||||
/// This is a typically an index in the default table of the supervisor, however
|
||||
@@ -46,34 +52,59 @@ impl From<SupervisorFuncIndex> for usize {
|
||||
|
||||
/// Index of a function within guest index space.
|
||||
///
|
||||
/// This index is supposed to be used with as index for `Externals`.
|
||||
/// This index is supposed to be used as index for `Externals`.
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
struct GuestFuncIndex(usize);
|
||||
|
||||
/// This struct holds a mapping from guest index space to supervisor.
|
||||
struct GuestToSupervisorFunctionMapping {
|
||||
/// Position of elements in this vector are interpreted
|
||||
/// as indices of guest functions and are mapped to
|
||||
/// corresponding supervisor function indices.
|
||||
funcs: Vec<SupervisorFuncIndex>,
|
||||
}
|
||||
|
||||
impl GuestToSupervisorFunctionMapping {
|
||||
/// Create an empty function mapping
|
||||
fn new() -> GuestToSupervisorFunctionMapping {
|
||||
GuestToSupervisorFunctionMapping { funcs: Vec::new() }
|
||||
}
|
||||
|
||||
/// Add a new supervisor function to the mapping.
|
||||
/// Returns a newly assigned guest function index.
|
||||
fn define(&mut self, supervisor_func: SupervisorFuncIndex) -> GuestFuncIndex {
|
||||
let idx = self.funcs.len();
|
||||
self.funcs.push(supervisor_func);
|
||||
GuestFuncIndex(idx)
|
||||
}
|
||||
|
||||
/// Find supervisor function index by its corresponding guest function index
|
||||
fn func_by_guest_index(&self, guest_func_idx: GuestFuncIndex) -> Option<SupervisorFuncIndex> {
|
||||
self.funcs.get(guest_func_idx.0).cloned()
|
||||
}
|
||||
}
|
||||
|
||||
/// Holds sandbox function and memory imports and performs name resolution
|
||||
struct Imports {
|
||||
/// Maps qualified function name to its guest function index
|
||||
func_map: HashMap<(Vec<u8>, Vec<u8>), GuestFuncIndex>,
|
||||
memories_map: HashMap<(Vec<u8>, Vec<u8>), MemoryRef>,
|
||||
|
||||
/// Maps qualified field name to its memory reference
|
||||
memories_map: HashMap<(Vec<u8>, Vec<u8>), Memory>,
|
||||
}
|
||||
|
||||
impl Imports {
|
||||
fn func_by_name(&self, module_name: &str, func_name: &str) -> Option<GuestFuncIndex> {
|
||||
self.func_map
|
||||
.get(&(module_name.as_bytes().to_owned(), func_name.as_bytes().to_owned()))
|
||||
.cloned()
|
||||
}
|
||||
|
||||
fn memory_by_name(&self, module_name: &str, memory_name: &str) -> Option<Memory> {
|
||||
self.memories_map
|
||||
.get(&(module_name.as_bytes().to_owned(), memory_name.as_bytes().to_owned()))
|
||||
.cloned()
|
||||
}
|
||||
}
|
||||
|
||||
impl ImportResolver for Imports {
|
||||
@@ -83,10 +114,10 @@ impl ImportResolver for Imports {
|
||||
field_name: &str,
|
||||
signature: &::wasmi::Signature,
|
||||
) -> std::result::Result<wasmi::FuncRef, wasmi::Error> {
|
||||
let key = (module_name.as_bytes().to_owned(), field_name.as_bytes().to_owned());
|
||||
let idx = *self.func_map.get(&key).ok_or_else(|| {
|
||||
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))
|
||||
}
|
||||
|
||||
@@ -95,18 +126,22 @@ impl ImportResolver for Imports {
|
||||
module_name: &str,
|
||||
field_name: &str,
|
||||
_memory_type: &::wasmi::MemoryDescriptor,
|
||||
) -> std::result::Result<MemoryRef, wasmi::Error> {
|
||||
let key = (module_name.as_bytes().to_vec(), field_name.as_bytes().to_vec());
|
||||
let mem = self
|
||||
.memories_map
|
||||
.get(&key)
|
||||
.ok_or_else(|| {
|
||||
wasmi::Error::Instantiation(format!(
|
||||
"Export {}:{} not found",
|
||||
module_name, field_name
|
||||
))
|
||||
})?
|
||||
.clone();
|
||||
) -> std::result::Result<wasmi::MemoryRef, wasmi::Error> {
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -134,6 +169,7 @@ impl ImportResolver for Imports {
|
||||
/// 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;
|
||||
|
||||
/// Invoke a function in the supervisor environment.
|
||||
@@ -141,9 +177,9 @@ pub trait SandboxCapabilities: FunctionContext {
|
||||
/// 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 i64,
|
||||
/// with the upper 32 bits representing the pointer and the lower 32 bits representing the
|
||||
/// length.
|
||||
/// reference. The pointer to and length of the result in linear memory is encoded into an
|
||||
/// `i64`, with the upper 32 bits representing the pointer and the lower 32 bits representing
|
||||
/// the length.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
@@ -164,11 +200,17 @@ pub trait SandboxCapabilities: FunctionContext {
|
||||
///
|
||||
/// [`Externals`]: ../wasmi/trait.Externals.html
|
||||
pub struct GuestExternals<'a, FE: SandboxCapabilities + 'a> {
|
||||
/// Supervisor function environment
|
||||
supervisor_externals: &'a mut FE,
|
||||
|
||||
/// Instance of sandboxed module to be dispatched
|
||||
sandbox_instance: &'a SandboxInstance<FE::SupervisorFuncRef>,
|
||||
|
||||
/// External state passed to guest environment, see the `instantiate` function
|
||||
state: u32,
|
||||
}
|
||||
|
||||
/// Construct trap error from specified message
|
||||
fn trap(msg: &'static str) -> Trap {
|
||||
TrapKind::Host(Box::new(Error::Other(msg.into()))).into()
|
||||
}
|
||||
@@ -199,14 +241,15 @@ impl<'a, FE: SandboxCapabilities + 'a> Externals for GuestExternals<'a, FE> {
|
||||
// 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 was obtained from `guest_to_supervisor_mapping`;
|
||||
`func_by_guest_index` called with `index` can't return `None`;
|
||||
qed"
|
||||
`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.
|
||||
@@ -220,7 +263,7 @@ impl<'a, FE: SandboxCapabilities + 'a> Externals for GuestExternals<'a, FE> {
|
||||
|
||||
let state = self.state;
|
||||
|
||||
// Move serialized arguments inside the memory and invoke dispatch thunk and
|
||||
// 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
|
||||
@@ -299,6 +342,16 @@ where
|
||||
f(&mut guest_externals)
|
||||
}
|
||||
|
||||
/// Module instance in terms of selected backend
|
||||
enum BackendInstance {
|
||||
/// Wasmi module instance
|
||||
Wasmi(wasmi::ModuleRef),
|
||||
|
||||
/// Wasmer module instance
|
||||
#[cfg(feature = "wasmer-sandbox")]
|
||||
Wasmer(wasmer::Instance),
|
||||
}
|
||||
|
||||
/// Sandboxed instance of a wasm module.
|
||||
///
|
||||
/// It's primary purpose is to [`invoke`] exported functions on it.
|
||||
@@ -314,7 +367,7 @@ where
|
||||
///
|
||||
/// [`invoke`]: #method.invoke
|
||||
pub struct SandboxInstance<FR> {
|
||||
instance: ModuleRef,
|
||||
backend_instance: BackendInstance,
|
||||
dispatch_thunk: FR,
|
||||
guest_to_supervisor_mapping: GuestToSupervisorFunctionMapping,
|
||||
}
|
||||
@@ -327,15 +380,78 @@ impl<FR> SandboxInstance<FR> {
|
||||
///
|
||||
/// The `state` parameter can be used to provide custom data for
|
||||
/// these syscall implementations.
|
||||
pub fn invoke<FE: SandboxCapabilities<SupervisorFuncRef = FR>>(
|
||||
pub fn invoke<'a, FE, SCH, DTH>(
|
||||
&self,
|
||||
|
||||
// function to call that is exported from the module
|
||||
export_name: &str,
|
||||
|
||||
// arguments passed to the function
|
||||
args: &[RuntimeValue],
|
||||
supervisor_externals: &mut FE,
|
||||
|
||||
// arbitraty context data of the call
|
||||
state: u32,
|
||||
) -> std::result::Result<Option<wasmi::RuntimeValue>, wasmi::Error> {
|
||||
with_guest_externals(supervisor_externals, self, state, |guest_externals| {
|
||||
self.instance.invoke_export(export_name, args, guest_externals)
|
||||
) -> std::result::Result<Option<wasmi::RuntimeValue>, wasmi::Error>
|
||||
where
|
||||
FE: SandboxCapabilities<SupervisorFuncRef = FR> + 'a,
|
||||
SCH: SandboxCapabilitiesHolder<SupervisorFuncRef = FR, SC = FE>,
|
||||
DTH: DispatchThunkHolder<DispatchThunk = FR>,
|
||||
{
|
||||
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)
|
||||
},
|
||||
|
||||
#[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<wasmer::Val> = 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 =
|
||||
DTH::initialize_thunk(&self.dispatch_thunk, || 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".to_owned(),
|
||||
))
|
||||
}
|
||||
|
||||
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!(),
|
||||
};
|
||||
|
||||
Some(wasmer_value)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok(wasmer_result)
|
||||
},
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -343,9 +459,29 @@ impl<FR> SandboxInstance<FR> {
|
||||
///
|
||||
/// Returns `Some(_)` if the global could be found.
|
||||
pub fn get_global_val(&self, name: &str) -> Option<sp_wasm_interface::Value> {
|
||||
let global = self.instance.export_by_name(name)?.as_global()?.get();
|
||||
match &self.backend_instance {
|
||||
BackendInstance::Wasmi(wasmi_instance) => {
|
||||
let wasmi_global = wasmi_instance.export_by_name(name)?.as_global()?.get();
|
||||
|
||||
Some(global.into())
|
||||
Some(wasmi_global.into())
|
||||
},
|
||||
|
||||
#[cfg(feature = "wasmer-sandbox")]
|
||||
BackendInstance::Wasmer(wasmer_instance) => {
|
||||
use sp_wasm_interface::Value;
|
||||
|
||||
let global = wasmer_instance.exports.get_global(name).ok()?;
|
||||
let wasmtime_value = match global.get() {
|
||||
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)),
|
||||
_ => None?,
|
||||
};
|
||||
|
||||
Some(wasmtime_value)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -366,7 +502,7 @@ pub enum InstantiationError {
|
||||
|
||||
fn decode_environment_definition(
|
||||
mut raw_env_def: &[u8],
|
||||
memories: &[Option<MemoryRef>],
|
||||
memories: &[Option<Memory>],
|
||||
) -> std::result::Result<(Imports, GuestToSupervisorFunctionMapping), InstantiationError> {
|
||||
let env_def = sandbox_primitives::EnvironmentDefinition::decode(&mut raw_env_def)
|
||||
.map_err(|_| InstantiationError::EnvironmentDefinitionCorrupted)?;
|
||||
@@ -401,7 +537,10 @@ fn decode_environment_definition(
|
||||
|
||||
/// An environment in which the guest module is instantiated.
|
||||
pub struct GuestEnvironment {
|
||||
/// Function and memory imports of the guest module
|
||||
imports: Imports,
|
||||
|
||||
/// Supervisor functinons mapped to guest index space
|
||||
guest_to_supervisor_mapping: GuestToSupervisorFunctionMapping,
|
||||
}
|
||||
|
||||
@@ -436,47 +575,142 @@ impl<FR> UnregisteredInstance<FR> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Instantiate a guest module and return it's index in the store.
|
||||
///
|
||||
/// The guest module's code is specified in `wasm`. Environment that will be available to
|
||||
/// guest module is specified in `raw_env_def` (serialized version of [`EnvironmentDefinition`]).
|
||||
/// `dispatch_thunk` is used as function that handle calls from guests.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns `Err` if any of the following conditions happens:
|
||||
///
|
||||
/// - `raw_env_def` can't be deserialized as a [`EnvironmentDefinition`].
|
||||
/// - Module in `wasm` is invalid or couldn't be instantiated.
|
||||
///
|
||||
/// [`EnvironmentDefinition`]: ../sandbox/struct.EnvironmentDefinition.html
|
||||
pub fn instantiate<'a, FE: SandboxCapabilities>(
|
||||
supervisor_externals: &mut FE,
|
||||
dispatch_thunk: FE::SupervisorFuncRef,
|
||||
wasm: &[u8],
|
||||
host_env: GuestEnvironment,
|
||||
state: u32,
|
||||
) -> std::result::Result<UnregisteredInstance<FE::SupervisorFuncRef>, InstantiationError> {
|
||||
let module = Module::from_buffer(wasm).map_err(|_| InstantiationError::ModuleDecoding)?;
|
||||
let instance = ModuleInstance::new(&module, &host_env.imports)
|
||||
.map_err(|_| InstantiationError::Instantiation)?;
|
||||
/// Helper type to provide sandbox capabilities to the inner context
|
||||
pub trait SandboxCapabilitiesHolder {
|
||||
/// Supervisor function reference
|
||||
type SupervisorFuncRef;
|
||||
|
||||
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.
|
||||
instance: instance.not_started_instance().clone(),
|
||||
dispatch_thunk,
|
||||
guest_to_supervisor_mapping: host_env.guest_to_supervisor_mapping,
|
||||
});
|
||||
/// Capabilities trait
|
||||
type SC: SandboxCapabilities<SupervisorFuncRef = Self::SupervisorFuncRef>;
|
||||
|
||||
with_guest_externals(supervisor_externals, &sandbox_instance, state, |guest_externals| {
|
||||
instance
|
||||
.run_start(guest_externals)
|
||||
.map_err(|_| InstantiationError::StartTrapped)
|
||||
})?;
|
||||
/// Wrapper that provides sandbox capabilities in a limited context
|
||||
fn with_sandbox_capabilities<R, F: FnOnce(&mut Self::SC) -> R>(f: F) -> R;
|
||||
}
|
||||
|
||||
Ok(UnregisteredInstance { sandbox_instance })
|
||||
/// 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<R, F>(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: FnOnce(&mut Self::DispatchThunk) -> R>(f: F) -> R;
|
||||
}
|
||||
|
||||
/// Sandbox backend to use
|
||||
pub enum SandboxBackend {
|
||||
/// Wasm interpreter
|
||||
Wasmi,
|
||||
|
||||
/// Wasmer environment
|
||||
#[cfg(feature = "wasmer-sandbox")]
|
||||
Wasmer,
|
||||
|
||||
/// Use wasmer backend if available. Fall back to wasmi otherwise.
|
||||
TryWasmer,
|
||||
}
|
||||
|
||||
/// Memory reference in terms of a selected backend
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Memory {
|
||||
/// Wasmi memory reference
|
||||
Wasmi(WasmiMemoryWrapper),
|
||||
|
||||
/// Wasmer memory refernce
|
||||
#[cfg(feature = "wasmer-sandbox")]
|
||||
Wasmer(WasmerMemoryWrapper),
|
||||
}
|
||||
|
||||
impl Memory {
|
||||
/// View as wasmi memory
|
||||
pub fn as_wasmi(&self) -> Option<WasmiMemoryWrapper> {
|
||||
match self {
|
||||
Memory::Wasmi(memory) => Some(memory.clone()),
|
||||
|
||||
#[cfg(feature = "wasmer-sandbox")]
|
||||
Memory::Wasmer(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// View as wasmer memory
|
||||
#[cfg(feature = "wasmer-sandbox")]
|
||||
pub fn as_wasmer(&self) -> Option<WasmerMemoryWrapper> {
|
||||
match self {
|
||||
Memory::Wasmer(memory) => Some(memory.clone()),
|
||||
Memory::Wasmi(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl util::MemoryTransfer for Memory {
|
||||
fn read(&self, source_addr: Pointer<u8>, size: usize) -> Result<Vec<u8>> {
|
||||
match self {
|
||||
Memory::Wasmi(sandboxed_memory) => sandboxed_memory.read(source_addr, size),
|
||||
|
||||
#[cfg(feature = "wasmer-sandbox")]
|
||||
Memory::Wasmer(sandboxed_memory) => sandboxed_memory.read(source_addr, size),
|
||||
}
|
||||
}
|
||||
|
||||
fn read_into(&self, source_addr: Pointer<u8>, destination: &mut [u8]) -> Result<()> {
|
||||
match self {
|
||||
Memory::Wasmi(sandboxed_memory) => sandboxed_memory.read_into(source_addr, destination),
|
||||
|
||||
#[cfg(feature = "wasmer-sandbox")]
|
||||
Memory::Wasmer(sandboxed_memory) => sandboxed_memory.read_into(source_addr, destination),
|
||||
}
|
||||
}
|
||||
|
||||
fn write_from(&self, dest_addr: Pointer<u8>, source: &[u8]) -> Result<()> {
|
||||
match self {
|
||||
Memory::Wasmi(sandboxed_memory) => sandboxed_memory.write_from(dest_addr, source),
|
||||
|
||||
#[cfg(feature = "wasmer-sandbox")]
|
||||
Memory::Wasmer(sandboxed_memory) => sandboxed_memory.write_from(dest_addr, source),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Wasmer specific context
|
||||
#[cfg(feature = "wasmer-sandbox")]
|
||||
struct WasmerBackend {
|
||||
store: wasmer::Store,
|
||||
}
|
||||
|
||||
/// Information specific to a particular execution backend
|
||||
enum BackendContext {
|
||||
/// Wasmi specific context
|
||||
Wasmi,
|
||||
|
||||
/// Wasmer specific context
|
||||
#[cfg(feature = "wasmer-sandbox")]
|
||||
Wasmer(WasmerBackend),
|
||||
}
|
||||
|
||||
impl BackendContext {
|
||||
pub fn new(backend: SandboxBackend) -> BackendContext {
|
||||
match backend {
|
||||
SandboxBackend::Wasmi => BackendContext::Wasmi,
|
||||
|
||||
#[cfg(not(feature = "wasmer-sandbox"))]
|
||||
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()),
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This struct keeps track of all sandboxed components.
|
||||
@@ -485,13 +719,18 @@ pub fn instantiate<'a, FE: SandboxCapabilities>(
|
||||
pub struct Store<FR> {
|
||||
// Memories and instances are `Some` until torn down.
|
||||
instances: Vec<Option<Rc<SandboxInstance<FR>>>>,
|
||||
memories: Vec<Option<MemoryRef>>,
|
||||
memories: Vec<Option<Memory>>,
|
||||
backend_context: BackendContext,
|
||||
}
|
||||
|
||||
impl<FR> Store<FR> {
|
||||
/// Create a new empty sandbox store.
|
||||
pub fn new() -> Self {
|
||||
Store { instances: Vec::new(), memories: Vec::new() }
|
||||
pub fn new(backend: SandboxBackend) -> Self {
|
||||
Store {
|
||||
instances: Vec::new(),
|
||||
memories: Vec::new(),
|
||||
backend_context: BackendContext::new(backend),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new memory instance and return it's index.
|
||||
@@ -501,15 +740,33 @@ impl<FR> Store<FR> {
|
||||
/// Returns `Err` if the memory couldn't be created.
|
||||
/// Typically happens if `initial` is more than `maximum`.
|
||||
pub fn new_memory(&mut self, initial: u32, maximum: u32) -> Result<u32> {
|
||||
let memories = &mut self.memories;
|
||||
let backend_context = &self.backend_context;
|
||||
|
||||
let maximum = match maximum {
|
||||
sandbox_primitives::MEM_UNLIMITED => None,
|
||||
specified_limit => Some(Pages(specified_limit as usize)),
|
||||
specified_limit => Some(specified_limit),
|
||||
};
|
||||
|
||||
let mem = MemoryInstance::alloc(Pages(initial as usize), maximum)?;
|
||||
let memory = match &backend_context {
|
||||
BackendContext::Wasmi => Memory::Wasmi(WasmiMemoryWrapper::new(MemoryInstance::alloc(
|
||||
Pages(initial as usize),
|
||||
maximum.map(|m| Pages(m as usize)),
|
||||
)?)),
|
||||
|
||||
#[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)?,
|
||||
))
|
||||
},
|
||||
};
|
||||
|
||||
let mem_idx = memories.len();
|
||||
memories.push(Some(memory.clone()));
|
||||
|
||||
let mem_idx = self.memories.len();
|
||||
self.memories.push(Some(mem));
|
||||
Ok(mem_idx as u32)
|
||||
}
|
||||
|
||||
@@ -533,7 +790,7 @@ impl<FR> Store<FR> {
|
||||
///
|
||||
/// Returns `Err` If `memory_idx` isn't a valid index of an memory or
|
||||
/// if memory has been torn down.
|
||||
pub fn memory(&self, memory_idx: u32) -> Result<MemoryRef> {
|
||||
pub fn memory(&self, memory_idx: u32) -> Result<Memory> {
|
||||
self.memories
|
||||
.get(memory_idx as usize)
|
||||
.cloned()
|
||||
@@ -575,9 +832,307 @@ impl<FR> Store<FR> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Instantiate a guest module and return it's index in the store.
|
||||
///
|
||||
/// The guest module's code is specified in `wasm`. Environment that will be available to
|
||||
/// guest module is specified in `guest_env`. A dispatch thunk is used as function that
|
||||
/// handle calls from guests. `state` is an opaque pointer to caller's arbitrary context
|
||||
/// normally created by `sp_sandbox::Instance` primitive.
|
||||
///
|
||||
/// 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>(
|
||||
&mut self,
|
||||
wasm: &[u8],
|
||||
guest_env: GuestEnvironment,
|
||||
state: u32,
|
||||
) -> std::result::Result<UnregisteredInstance<FR>, InstantiationError>
|
||||
where
|
||||
FR: Clone + 'static,
|
||||
FE: SandboxCapabilities<SupervisorFuncRef = FR> + 'a,
|
||||
SCH: SandboxCapabilitiesHolder<SupervisorFuncRef = FR, SC = FE>,
|
||||
DTH: DispatchThunkHolder<DispatchThunk = FR>,
|
||||
{
|
||||
let backend_context = &self.backend_context;
|
||||
|
||||
let sandbox_instance = match backend_context {
|
||||
BackendContext::Wasmi =>
|
||||
Self::instantiate_wasmi::<FE, SCH, DTH>(wasm, guest_env, state)?,
|
||||
|
||||
#[cfg(feature = "wasmer-sandbox")]
|
||||
BackendContext::Wasmer(context) =>
|
||||
Self::instantiate_wasmer::<FE, SCH, DTH>(context, wasm, guest_env, state)?,
|
||||
};
|
||||
|
||||
Ok(UnregisteredInstance { sandbox_instance })
|
||||
}
|
||||
}
|
||||
|
||||
// Private routines
|
||||
impl<FR> Store<FR> {
|
||||
fn register_sandbox_instance(&mut self, sandbox_instance: Rc<SandboxInstance<FR>>) -> u32 {
|
||||
let instance_idx = self.instances.len();
|
||||
self.instances.push(Some(sandbox_instance));
|
||||
instance_idx as u32
|
||||
}
|
||||
|
||||
fn instantiate_wasmi<'a, FE, SCH, DTH>(
|
||||
wasm: &[u8],
|
||||
guest_env: GuestEnvironment,
|
||||
state: u32,
|
||||
) -> std::result::Result<Rc<SandboxInstance<FR>>, InstantiationError>
|
||||
where
|
||||
FR: Clone + 'static,
|
||||
FE: SandboxCapabilities<SupervisorFuncRef = FR> + 'a,
|
||||
SCH: SandboxCapabilitiesHolder<SupervisorFuncRef = FR, SC = FE>,
|
||||
DTH: DispatchThunkHolder<DispatchThunk = FR>,
|
||||
{
|
||||
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,
|
||||
})
|
||||
});
|
||||
|
||||
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)
|
||||
|
||||
// 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>(
|
||||
context: &WasmerBackend,
|
||||
wasm: &[u8],
|
||||
guest_env: GuestEnvironment,
|
||||
state: u32,
|
||||
) -> std::result::Result<Rc<SandboxInstance<FR>>, InstantiationError>
|
||||
where
|
||||
FR: Clone + 'static,
|
||||
FE: SandboxCapabilities<SupervisorFuncRef = FR> + 'a,
|
||||
SCH: SandboxCapabilitiesHolder<SupervisorFuncRef = FR, SC = FE>,
|
||||
DTH: DispatchThunkHolder<DispatchThunk = FR>,
|
||||
{
|
||||
let module = wasmer::Module::new(&context.store, wasm)
|
||||
.map_err(|_| InstantiationError::ModuleDecoding)?;
|
||||
|
||||
type Exports = HashMap<String, wasmer::Exports>;
|
||||
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::<FE, SCH, DTH>(
|
||||
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 =
|
||||
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>(
|
||||
supervisor_func_index: SupervisorFuncIndex,
|
||||
store: &wasmer::Store,
|
||||
func_ty: &wasmer::FunctionType,
|
||||
state: u32,
|
||||
) -> wasmer::Function
|
||||
where
|
||||
FR: Clone + 'static,
|
||||
FE: SandboxCapabilities<SupervisorFuncRef = FR> + 'a,
|
||||
SCH: SandboxCapabilitiesHolder<SupervisorFuncRef = FR, SC = FE>,
|
||||
DTH: DispatchThunkHolder<DispatchThunk = FR>,
|
||||
{
|
||||
wasmer::Function::new(store, func_ty, move |params| {
|
||||
SCH::with_sandbox_capabilities(|supervisor_externals| {
|
||||
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!(),
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.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(|_| {
|
||||
wasmer::RuntimeError::new(
|
||||
"Can't allocate memory in supervisor for the arguments",
|
||||
)
|
||||
})?;
|
||||
|
||||
let deallocate = |fe: &mut FE, 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() {
|
||||
deallocate(
|
||||
supervisor_externals,
|
||||
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 = 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()))?;
|
||||
|
||||
// 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 = supervisor_externals
|
||||
.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(
|
||||
supervisor_externals,
|
||||
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![])
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,241 @@
|
||||
// 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
//! Utilities used by all backends
|
||||
|
||||
use crate::error::{Error, Result};
|
||||
use sp_wasm_interface::Pointer;
|
||||
use std::ops::Range;
|
||||
|
||||
/// Construct a range from an offset to a data length after the offset.
|
||||
/// Returns None if the end of the range would exceed some maximum offset.
|
||||
pub fn checked_range(offset: usize, len: usize, max: usize) -> Option<Range<usize>> {
|
||||
let end = offset.checked_add(len)?;
|
||||
if end <= max {
|
||||
Some(offset..end)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Provides safe memory access interface using an external buffer
|
||||
pub trait MemoryTransfer {
|
||||
/// Read data from a slice of memory into a newly allocated buffer.
|
||||
///
|
||||
/// Returns an error if the read would go out of the memory bounds.
|
||||
fn read(&self, source_addr: Pointer<u8>, size: usize) -> Result<Vec<u8>>;
|
||||
|
||||
/// Read data from a slice of memory into a destination buffer.
|
||||
///
|
||||
/// Returns an error if the read would go out of the memory bounds.
|
||||
fn read_into(&self, source_addr: Pointer<u8>, destination: &mut [u8]) -> Result<()>;
|
||||
|
||||
/// Write data to a slice of memory.
|
||||
///
|
||||
/// Returns an error if the write would go out of the memory bounds.
|
||||
fn write_from(&self, dest_addr: Pointer<u8>, 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<u8>, size: usize) -> Result<Vec<u8>> {
|
||||
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<u8>, 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<u8>, 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()))?;
|
||||
|
||||
&mut 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<RefCell<wasmer::Memory>>,
|
||||
}
|
||||
|
||||
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<u8>, size: usize) -> Result<Vec<u8>> {
|
||||
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<u8>, 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<u8>, 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()))?;
|
||||
|
||||
&mut destination[range].copy_from_slice(source);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,3 +22,4 @@ sc-allocator = { version = "4.0.0-dev", path = "../../allocator" }
|
||||
sp-wasm-interface = { version = "4.0.0-dev", path = "../../../primitives/wasm-interface" }
|
||||
sp-runtime-interface = { version = "4.0.0-dev", path = "../../../primitives/runtime-interface" }
|
||||
sp-core = { version = "4.0.0-dev", path = "../../../primitives/core" }
|
||||
scoped-tls = "1.0"
|
||||
|
||||
@@ -24,6 +24,7 @@ use sc_executor_common::{
|
||||
error::{Error, WasmError},
|
||||
runtime_blob::{DataSegmentsSnapshot, RuntimeBlob},
|
||||
sandbox,
|
||||
util::MemoryTransfer,
|
||||
wasm_runtime::{InvokeMethod, WasmInstance, WasmModule},
|
||||
};
|
||||
use sp_core::sandbox as sandbox_primitives;
|
||||
@@ -31,7 +32,7 @@ use sp_runtime_interface::unpack_ptr_and_len;
|
||||
use sp_wasm_interface::{
|
||||
Function, FunctionContext, MemoryId, Pointer, Result as WResult, Sandbox, WordSize,
|
||||
};
|
||||
use std::{cell::RefCell, str, sync::Arc};
|
||||
use std::{cell::RefCell, rc::Rc, str, sync::Arc};
|
||||
use wasmi::{
|
||||
memory_units::Pages,
|
||||
FuncInstance, ImportsBuilder, MemoryInstance, MemoryRef, Module, ModuleInstance, ModuleRef,
|
||||
@@ -39,38 +40,45 @@ use wasmi::{
|
||||
TableRef,
|
||||
};
|
||||
|
||||
struct FunctionExecutor<'a> {
|
||||
sandbox_store: sandbox::Store<wasmi::FuncRef>,
|
||||
heap: sc_allocator::FreeingBumpHeapAllocator,
|
||||
memory: MemoryRef,
|
||||
table: Option<TableRef>,
|
||||
host_functions: &'a [&'static dyn Function],
|
||||
allow_missing_func_imports: bool,
|
||||
missing_functions: &'a [String],
|
||||
#[derive(Clone)]
|
||||
struct FunctionExecutor {
|
||||
inner: Rc<Inner>,
|
||||
}
|
||||
|
||||
impl<'a> FunctionExecutor<'a> {
|
||||
struct Inner {
|
||||
sandbox_store: RefCell<sandbox::Store<wasmi::FuncRef>>,
|
||||
heap: RefCell<sc_allocator::FreeingBumpHeapAllocator>,
|
||||
memory: MemoryRef,
|
||||
table: Option<TableRef>,
|
||||
host_functions: Arc<Vec<&'static dyn Function>>,
|
||||
allow_missing_func_imports: bool,
|
||||
missing_functions: Arc<Vec<String>>,
|
||||
}
|
||||
|
||||
impl FunctionExecutor {
|
||||
fn new(
|
||||
m: MemoryRef,
|
||||
heap_base: u32,
|
||||
t: Option<TableRef>,
|
||||
host_functions: &'a [&'static dyn Function],
|
||||
host_functions: Arc<Vec<&'static dyn Function>>,
|
||||
allow_missing_func_imports: bool,
|
||||
missing_functions: &'a [String],
|
||||
missing_functions: Arc<Vec<String>>,
|
||||
) -> Result<Self, Error> {
|
||||
Ok(FunctionExecutor {
|
||||
sandbox_store: sandbox::Store::new(),
|
||||
heap: sc_allocator::FreeingBumpHeapAllocator::new(heap_base),
|
||||
memory: m,
|
||||
table: t,
|
||||
host_functions,
|
||||
allow_missing_func_imports,
|
||||
missing_functions,
|
||||
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,
|
||||
}),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> sandbox::SandboxCapabilities for FunctionExecutor<'a> {
|
||||
impl sandbox::SandboxCapabilities for FunctionExecutor {
|
||||
type SupervisorFuncRef = wasmi::FuncRef;
|
||||
|
||||
fn invoke(
|
||||
@@ -99,24 +107,26 @@ impl<'a> sandbox::SandboxCapabilities for FunctionExecutor<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> FunctionContext for FunctionExecutor<'a> {
|
||||
impl FunctionContext for FunctionExecutor {
|
||||
fn read_memory_into(&self, address: Pointer<u8>, dest: &mut [u8]) -> WResult<()> {
|
||||
self.memory.get_into(address.into(), dest).map_err(|e| e.to_string())
|
||||
self.inner.memory.get_into(address.into(), dest).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
fn write_memory(&mut self, address: Pointer<u8>, data: &[u8]) -> WResult<()> {
|
||||
self.memory.set(address.into(), data).map_err(|e| e.to_string())
|
||||
self.inner.memory.set(address.into(), data).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
fn allocate_memory(&mut self, size: WordSize) -> WResult<Pointer<u8>> {
|
||||
let heap = &mut self.heap;
|
||||
self.memory
|
||||
let heap = &mut self.inner.heap.borrow_mut();
|
||||
self.inner
|
||||
.memory
|
||||
.with_direct_access_mut(|mem| heap.allocate(mem, size).map_err(|e| e.to_string()))
|
||||
}
|
||||
|
||||
fn deallocate_memory(&mut self, ptr: Pointer<u8>) -> WResult<()> {
|
||||
let heap = &mut self.heap;
|
||||
self.memory
|
||||
let heap = &mut self.inner.heap.borrow_mut();
|
||||
self.inner
|
||||
.memory
|
||||
.with_direct_access_mut(|mem| heap.deallocate(mem, ptr).map_err(|e| e.to_string()))
|
||||
}
|
||||
|
||||
@@ -125,7 +135,7 @@ impl<'a> FunctionContext for FunctionExecutor<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Sandbox for FunctionExecutor<'a> {
|
||||
impl Sandbox for FunctionExecutor {
|
||||
fn memory_get(
|
||||
&mut self,
|
||||
memory_id: MemoryId,
|
||||
@@ -133,18 +143,21 @@ impl<'a> Sandbox for FunctionExecutor<'a> {
|
||||
buf_ptr: Pointer<u8>,
|
||||
buf_len: WordSize,
|
||||
) -> WResult<u32> {
|
||||
let sandboxed_memory = self.sandbox_store.memory(memory_id).map_err(|e| e.to_string())?;
|
||||
let sandboxed_memory =
|
||||
self.inner.sandbox_store.borrow().memory(memory_id).map_err(|e| e.to_string())?;
|
||||
|
||||
match MemoryInstance::transfer(
|
||||
&sandboxed_memory,
|
||||
offset as usize,
|
||||
&self.memory,
|
||||
buf_ptr.into(),
|
||||
buf_len as usize,
|
||||
) {
|
||||
Ok(()) => Ok(sandbox_primitives::ERR_OK),
|
||||
Err(_) => Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS),
|
||||
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.memory.set(buf_ptr.into(), &buffer) {
|
||||
return Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS)
|
||||
}
|
||||
|
||||
Ok(sandbox_primitives::ERR_OK)
|
||||
}
|
||||
|
||||
fn memory_set(
|
||||
@@ -154,26 +167,37 @@ impl<'a> Sandbox for FunctionExecutor<'a> {
|
||||
val_ptr: Pointer<u8>,
|
||||
val_len: WordSize,
|
||||
) -> WResult<u32> {
|
||||
let sandboxed_memory = self.sandbox_store.memory(memory_id).map_err(|e| e.to_string())?;
|
||||
let sandboxed_memory =
|
||||
self.inner.sandbox_store.borrow().memory(memory_id).map_err(|e| e.to_string())?;
|
||||
|
||||
match MemoryInstance::transfer(
|
||||
&self.memory,
|
||||
val_ptr.into(),
|
||||
&sandboxed_memory,
|
||||
offset as usize,
|
||||
val_len as usize,
|
||||
) {
|
||||
Ok(()) => Ok(sandbox_primitives::ERR_OK),
|
||||
Err(_) => Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS),
|
||||
let len = val_len as usize;
|
||||
|
||||
let buffer = match self.inner.memory.get(val_ptr.into(), 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) -> WResult<()> {
|
||||
self.sandbox_store.memory_teardown(memory_id).map_err(|e| e.to_string())
|
||||
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) -> WResult<MemoryId> {
|
||||
self.sandbox_store.new_memory(initial, maximum).map_err(|e| e.to_string())
|
||||
self.inner
|
||||
.sandbox_store
|
||||
.borrow_mut()
|
||||
.new_memory(initial, maximum)
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
fn invoke(
|
||||
@@ -194,8 +218,15 @@ impl<'a> Sandbox for FunctionExecutor<'a> {
|
||||
.map(Into::into)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let instance = self.sandbox_store.instance(instance_id).map_err(|e| e.to_string())?;
|
||||
let result = instance.invoke(export_name, &args, self, state);
|
||||
let instance = self
|
||||
.inner
|
||||
.sandbox_store
|
||||
.borrow()
|
||||
.instance(instance_id)
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
let result = EXECUTOR
|
||||
.set(self, || instance.invoke::<_, CapsHolder, ThunkHolder>(export_name, &args, state));
|
||||
|
||||
match result {
|
||||
Ok(None) => Ok(sandbox_primitives::ERR_OK),
|
||||
@@ -214,7 +245,11 @@ impl<'a> Sandbox for FunctionExecutor<'a> {
|
||||
}
|
||||
|
||||
fn instance_teardown(&mut self, instance_id: u32) -> WResult<()> {
|
||||
self.sandbox_store.instance_teardown(instance_id).map_err(|e| e.to_string())
|
||||
self.inner
|
||||
.sandbox_store
|
||||
.borrow_mut()
|
||||
.instance_teardown(instance_id)
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
fn instance_new(
|
||||
@@ -227,6 +262,7 @@ impl<'a> Sandbox for FunctionExecutor<'a> {
|
||||
// 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")?;
|
||||
@@ -236,19 +272,26 @@ impl<'a> Sandbox for FunctionExecutor<'a> {
|
||||
.ok_or_else(|| "dispatch_thunk_idx points on an empty table entry")?
|
||||
};
|
||||
|
||||
let guest_env = match sandbox::GuestEnvironment::decode(&self.sandbox_store, raw_env_def) {
|
||||
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 instance_idx_or_err_code =
|
||||
match sandbox::instantiate(self, dispatch_thunk, wasm, guest_env, state)
|
||||
.map(|i| i.register(&mut self.sandbox_store))
|
||||
{
|
||||
Ok(instance_idx) => instance_idx,
|
||||
Err(sandbox::InstantiationError::StartTrapped) => sandbox_primitives::ERR_EXECUTION,
|
||||
Err(_) => sandbox_primitives::ERR_MODULE,
|
||||
};
|
||||
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 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,
|
||||
};
|
||||
|
||||
Ok(instance_idx_or_err_code as u32)
|
||||
}
|
||||
@@ -258,13 +301,57 @@ impl<'a> Sandbox for FunctionExecutor<'a> {
|
||||
instance_idx: u32,
|
||||
name: &str,
|
||||
) -> WResult<Option<sp_wasm_interface::Value>> {
|
||||
self.sandbox_store
|
||||
self.inner
|
||||
.sandbox_store
|
||||
.borrow()
|
||||
.instance(instance_idx)
|
||||
.map(|i| i.get_global_val(name))
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
/// 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: FnOnce(&mut Self::SC) -> 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: FnOnce(&mut Self::DispatchThunk) -> 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<R, F>(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.
|
||||
@@ -375,7 +462,7 @@ impl<'a> wasmi::ModuleImportResolver for Resolver<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> wasmi::Externals for FunctionExecutor<'a> {
|
||||
impl wasmi::Externals for FunctionExecutor {
|
||||
fn invoke_index(
|
||||
&mut self,
|
||||
index: usize,
|
||||
@@ -383,19 +470,19 @@ impl<'a> wasmi::Externals for FunctionExecutor<'a> {
|
||||
) -> Result<Option<wasmi::RuntimeValue>, wasmi::Trap> {
|
||||
let mut args = args.as_ref().iter().copied().map(Into::into);
|
||||
|
||||
if let Some(function) = self.host_functions.get(index) {
|
||||
if let Some(function) = self.inner.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.allow_missing_func_imports &&
|
||||
index >= self.host_functions.len() &&
|
||||
index < self.host_functions.len() + self.missing_functions.len()
|
||||
} 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()
|
||||
{
|
||||
Err(Error::from(format!(
|
||||
"Function `{}` is only a stub. Calling a stub is not allowed.",
|
||||
self.missing_functions[index - self.host_functions.len()],
|
||||
self.inner.missing_functions[index - self.inner.host_functions.len()],
|
||||
))
|
||||
.into())
|
||||
} else {
|
||||
@@ -435,9 +522,9 @@ fn call_in_wasm_module(
|
||||
memory: &MemoryRef,
|
||||
method: InvokeMethod,
|
||||
data: &[u8],
|
||||
host_functions: &[&'static dyn Function],
|
||||
host_functions: Arc<Vec<&'static dyn Function>>,
|
||||
allow_missing_func_imports: bool,
|
||||
missing_functions: &Vec<String>,
|
||||
missing_functions: Arc<Vec<String>>,
|
||||
) -> Result<Vec<u8>, Error> {
|
||||
// Initialize FunctionExecutor.
|
||||
let table: Option<TableRef> = module_instance
|
||||
@@ -628,7 +715,7 @@ impl WasmModule for WasmiRuntime {
|
||||
data_segments_snapshot: self.data_segments_snapshot.clone(),
|
||||
host_functions: self.host_functions.clone(),
|
||||
allow_missing_func_imports: self.allow_missing_func_imports,
|
||||
missing_functions,
|
||||
missing_functions: Arc::new(missing_functions),
|
||||
}))
|
||||
}
|
||||
}
|
||||
@@ -684,7 +771,7 @@ pub struct WasmiInstance {
|
||||
/// These stubs will error when the wasm blob trie to call them.
|
||||
allow_missing_func_imports: bool,
|
||||
/// List of missing functions detected during function resolution
|
||||
missing_functions: Vec<String>,
|
||||
missing_functions: Arc<Vec<String>>,
|
||||
}
|
||||
|
||||
// This is safe because `WasmiInstance` does not leak any references to `self.memory` and
|
||||
@@ -717,9 +804,9 @@ impl WasmInstance for WasmiInstance {
|
||||
&self.memory,
|
||||
method,
|
||||
data,
|
||||
self.host_functions.as_ref(),
|
||||
self.host_functions.clone(),
|
||||
self.allow_missing_func_imports,
|
||||
self.missing_functions.as_ref(),
|
||||
self.missing_functions.clone(),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -19,13 +19,14 @@
|
||||
//! This module defines `HostState` and `HostContext` structs which provide logic and state
|
||||
//! required for execution of host.
|
||||
|
||||
use crate::{instance_wrapper::InstanceWrapper, util};
|
||||
use crate::instance_wrapper::InstanceWrapper;
|
||||
use codec::{Decode, Encode};
|
||||
use log::trace;
|
||||
use sc_allocator::FreeingBumpHeapAllocator;
|
||||
use sc_executor_common::{
|
||||
error::Result,
|
||||
sandbox::{self, SandboxCapabilities, SupervisorFuncIndex},
|
||||
sandbox::{self, SandboxCapabilities, SandboxCapabilitiesHolder, SupervisorFuncIndex},
|
||||
util::MemoryTransfer,
|
||||
};
|
||||
use sp_core::sandbox as sandbox_primitives;
|
||||
use sp_wasm_interface::{FunctionContext, MemoryId, Pointer, Sandbox, WordSize};
|
||||
@@ -42,7 +43,12 @@ 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<Inner>,
|
||||
}
|
||||
|
||||
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`.
|
||||
//
|
||||
@@ -61,31 +67,18 @@ impl HostState {
|
||||
/// Constructs a new `HostState`.
|
||||
pub fn new(allocator: FreeingBumpHeapAllocator, instance: Rc<InstanceWrapper>) -> Self {
|
||||
HostState {
|
||||
sandbox_store: RefCell::new(sandbox::Store::new()),
|
||||
allocator: RefCell::new(allocator),
|
||||
instance,
|
||||
inner: Rc::new(Inner {
|
||||
sandbox_store: 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)
|
||||
}
|
||||
}
|
||||
|
||||
/// 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> SandboxCapabilities for HostContext<'a> {
|
||||
impl SandboxCapabilities for HostState {
|
||||
type SupervisorFuncRef = SupervisorFuncRef;
|
||||
|
||||
fn invoke(
|
||||
@@ -125,28 +118,30 @@ impl<'a> SandboxCapabilities for HostContext<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> sp_wasm_interface::FunctionContext for HostContext<'a> {
|
||||
impl sp_wasm_interface::FunctionContext for HostState {
|
||||
fn read_memory_into(
|
||||
&self,
|
||||
address: Pointer<u8>,
|
||||
dest: &mut [u8],
|
||||
) -> sp_wasm_interface::Result<()> {
|
||||
self.instance.read_memory_into(address, dest).map_err(|e| e.to_string())
|
||||
self.inner.instance.read_memory_into(address, dest).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
fn write_memory(&mut self, address: Pointer<u8>, data: &[u8]) -> sp_wasm_interface::Result<()> {
|
||||
self.instance.write_memory_from(address, data).map_err(|e| e.to_string())
|
||||
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<Pointer<u8>> {
|
||||
self.instance
|
||||
.allocate(&mut *self.allocator.borrow_mut(), size)
|
||||
self.inner
|
||||
.instance
|
||||
.allocate(&mut *self.inner.allocator.borrow_mut(), size)
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
fn deallocate_memory(&mut self, ptr: Pointer<u8>) -> sp_wasm_interface::Result<()> {
|
||||
self.instance
|
||||
.deallocate(&mut *self.allocator.borrow_mut(), ptr)
|
||||
self.inner
|
||||
.instance
|
||||
.deallocate(&mut *self.inner.allocator.borrow_mut(), ptr)
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
@@ -155,7 +150,7 @@ impl<'a> sp_wasm_interface::FunctionContext for HostContext<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Sandbox for HostContext<'a> {
|
||||
impl Sandbox for HostState {
|
||||
fn memory_get(
|
||||
&mut self,
|
||||
memory_id: MemoryId,
|
||||
@@ -164,27 +159,20 @@ impl<'a> Sandbox for HostContext<'a> {
|
||||
buf_len: WordSize,
|
||||
) -> sp_wasm_interface::Result<u32> {
|
||||
let sandboxed_memory =
|
||||
self.sandbox_store.borrow().memory(memory_id).map_err(|e| e.to_string())?;
|
||||
sandboxed_memory.with_direct_access(|sandboxed_memory| {
|
||||
let len = buf_len as usize;
|
||||
let src_range = match util::checked_range(offset as usize, len, sandboxed_memory.len())
|
||||
{
|
||||
Some(range) => range,
|
||||
None => return Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS),
|
||||
};
|
||||
let supervisor_mem_size = self.instance.memory_size() as usize;
|
||||
let dst_range = match util::checked_range(buf_ptr.into(), len, supervisor_mem_size) {
|
||||
Some(range) => range,
|
||||
None => return Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS),
|
||||
};
|
||||
self.instance
|
||||
.write_memory_from(
|
||||
Pointer::new(dst_range.start as u32),
|
||||
&sandboxed_memory[src_range],
|
||||
)
|
||||
.expect("ranges are checked above; write can't fail; qed");
|
||||
Ok(sandbox_primitives::ERR_OK)
|
||||
})
|
||||
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(
|
||||
@@ -195,38 +183,33 @@ impl<'a> Sandbox for HostContext<'a> {
|
||||
val_len: WordSize,
|
||||
) -> sp_wasm_interface::Result<u32> {
|
||||
let sandboxed_memory =
|
||||
self.sandbox_store.borrow().memory(memory_id).map_err(|e| e.to_string())?;
|
||||
sandboxed_memory.with_direct_access_mut(|sandboxed_memory| {
|
||||
let len = val_len as usize;
|
||||
let supervisor_mem_size = self.instance.memory_size() as usize;
|
||||
let src_range = match util::checked_range(val_ptr.into(), len, supervisor_mem_size) {
|
||||
Some(range) => range,
|
||||
None => return Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS),
|
||||
};
|
||||
let dst_range = match util::checked_range(offset as usize, len, sandboxed_memory.len())
|
||||
{
|
||||
Some(range) => range,
|
||||
None => return Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS),
|
||||
};
|
||||
self.instance
|
||||
.read_memory_into(
|
||||
Pointer::new(src_range.start as u32),
|
||||
&mut sandboxed_memory[dst_range],
|
||||
)
|
||||
.expect("ranges are checked above; read can't fail; qed");
|
||||
Ok(sandbox_primitives::ERR_OK)
|
||||
})
|
||||
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.sandbox_store
|
||||
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<u32> {
|
||||
self.sandbox_store
|
||||
self.inner
|
||||
.sandbox_store
|
||||
.borrow_mut()
|
||||
.new_memory(initial, maximum)
|
||||
.map_err(|e| e.to_string())
|
||||
@@ -250,9 +233,14 @@ impl<'a> Sandbox for HostContext<'a> {
|
||||
.map(Into::into)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let instance =
|
||||
self.sandbox_store.borrow().instance(instance_id).map_err(|e| e.to_string())?;
|
||||
let result = instance.invoke(export_name, &args, self, state);
|
||||
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),
|
||||
@@ -262,7 +250,7 @@ impl<'a> Sandbox for HostContext<'a> {
|
||||
if val.len() > return_val_len as usize {
|
||||
Err("Return value buffer is too small")?;
|
||||
}
|
||||
<HostContext as FunctionContext>::write_memory(self, return_val, val)
|
||||
<HostState as FunctionContext>::write_memory(self, return_val, val)
|
||||
.map_err(|_| "can't write return value")?;
|
||||
Ok(sandbox_primitives::ERR_OK)
|
||||
})
|
||||
@@ -272,7 +260,8 @@ impl<'a> Sandbox for HostContext<'a> {
|
||||
}
|
||||
|
||||
fn instance_teardown(&mut self, instance_id: u32) -> sp_wasm_interface::Result<()> {
|
||||
self.sandbox_store
|
||||
self.inner
|
||||
.sandbox_store
|
||||
.borrow_mut()
|
||||
.instance_teardown(instance_id)
|
||||
.map_err(|e| e.to_string())
|
||||
@@ -288,6 +277,7 @@ impl<'a> Sandbox for HostContext<'a> {
|
||||
// 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()
|
||||
@@ -303,20 +293,26 @@ impl<'a> Sandbox for HostContext<'a> {
|
||||
SupervisorFuncRef(func_ref)
|
||||
};
|
||||
|
||||
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 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 instance_idx_or_err_code =
|
||||
match sandbox::instantiate(self, dispatch_thunk, wasm, guest_env, state)
|
||||
.map(|i| i.register(&mut *self.sandbox_store.borrow_mut()))
|
||||
{
|
||||
Ok(instance_idx) => instance_idx,
|
||||
Err(sandbox::InstantiationError::StartTrapped) => sandbox_primitives::ERR_EXECUTION,
|
||||
Err(_) => sandbox_primitives::ERR_MODULE,
|
||||
};
|
||||
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)
|
||||
}
|
||||
@@ -326,10 +322,50 @@ impl<'a> Sandbox for HostContext<'a> {
|
||||
instance_idx: u32,
|
||||
name: &str,
|
||||
) -> sp_wasm_interface::Result<Option<sp_wasm_interface::Value>> {
|
||||
self.sandbox_store
|
||||
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: FnOnce(&mut Self::SC) -> 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: FnOnce(&mut Self::DispatchThunk) -> 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<R, F>(s: &Self::DispatchThunk, f: F) -> R
|
||||
where
|
||||
F: FnOnce() -> R,
|
||||
{
|
||||
DISPATCH_THUNK.set(s, f)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -255,7 +255,9 @@ impl MissingHostFuncHandler {
|
||||
fn wasmtime_func_sig(func: &dyn Function) -> wasmtime::FuncType {
|
||||
let signature = func.signature();
|
||||
let params = signature.args.iter().cloned().map(into_wasmtime_val_type);
|
||||
|
||||
let results = signature.return_value.iter().cloned().map(into_wasmtime_val_type);
|
||||
|
||||
wasmtime::FuncType::new(params, results)
|
||||
}
|
||||
|
||||
|
||||
@@ -19,11 +19,15 @@
|
||||
//! Defines data and logic needed for interaction with an WebAssembly instance of a substrate
|
||||
//! runtime module.
|
||||
|
||||
use crate::{imports::Imports, util};
|
||||
use crate::{
|
||||
imports::Imports,
|
||||
util::{from_wasmtime_val, into_wasmtime_val},
|
||||
};
|
||||
|
||||
use sc_executor_common::{
|
||||
error::{Error, Result},
|
||||
runtime_blob,
|
||||
util::checked_range,
|
||||
wasm_runtime::InvokeMethod,
|
||||
};
|
||||
use sp_wasm_interface::{Pointer, Value, WordSize};
|
||||
@@ -96,12 +100,16 @@ impl EntryPoint {
|
||||
/// routines.
|
||||
pub struct InstanceWrapper {
|
||||
instance: Instance,
|
||||
|
||||
// The memory instance of the `instance`.
|
||||
//
|
||||
// It is important to make sure that we don't make any copies of this to make it easier to
|
||||
// proof See `memory_as_slice` and `memory_as_slice_mut`.
|
||||
memory: Memory,
|
||||
|
||||
/// Indirect functions table of the module
|
||||
table: Option<Table>,
|
||||
|
||||
// Make this struct explicitly !Send & !Sync.
|
||||
_not_send_nor_sync: marker::PhantomData<*const ()>,
|
||||
}
|
||||
@@ -147,7 +155,7 @@ impl InstanceWrapper {
|
||||
None => {
|
||||
let memory = get_linear_memory(&instance)?;
|
||||
if !memory.grow(heap_pages).is_ok() {
|
||||
return Err("failed top increase the linear memory size".into())
|
||||
return Err("failed to increase the linear memory size".into())
|
||||
}
|
||||
memory
|
||||
},
|
||||
@@ -223,11 +231,6 @@ impl InstanceWrapper {
|
||||
self.table.as_ref()
|
||||
}
|
||||
|
||||
/// Returns the byte size of the linear memory instance attached to this instance.
|
||||
pub fn memory_size(&self) -> u32 {
|
||||
self.memory.data_size() as u32
|
||||
}
|
||||
|
||||
/// Reads `__heap_base: i32` global variable and returns it.
|
||||
///
|
||||
/// If it doesn't exist, not a global or of not i32 type returns an error.
|
||||
@@ -291,32 +294,45 @@ fn get_table(instance: &Instance) -> Option<Table> {
|
||||
|
||||
/// Functions related to memory.
|
||||
impl InstanceWrapper {
|
||||
/// Read data from a slice of memory into a destination buffer.
|
||||
/// Read data from a slice of memory into a newly allocated buffer.
|
||||
///
|
||||
/// Returns an error if the read would go out of the memory bounds.
|
||||
pub fn read_memory_into(&self, address: Pointer<u8>, dest: &mut [u8]) -> Result<()> {
|
||||
pub fn read_memory(&self, source_addr: Pointer<u8>, size: usize) -> Result<Vec<u8>> {
|
||||
let range = checked_range(source_addr.into(), size, self.memory.data_size())
|
||||
.ok_or_else(|| Error::Other("memory read is out of bounds".into()))?;
|
||||
|
||||
let mut buffer = vec![0; range.len()];
|
||||
self.read_memory_into(source_addr, &mut buffer)?;
|
||||
|
||||
Ok(buffer)
|
||||
}
|
||||
|
||||
/// Read data from the instance memory into a slice.
|
||||
///
|
||||
/// Returns an error if the read would go out of the memory bounds.
|
||||
pub fn read_memory_into(&self, source_addr: Pointer<u8>, dest: &mut [u8]) -> Result<()> {
|
||||
unsafe {
|
||||
// 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 memory = self.memory_as_slice();
|
||||
|
||||
let range = util::checked_range(address.into(), dest.len(), memory.len())
|
||||
let range = checked_range(source_addr.into(), dest.len(), memory.len())
|
||||
.ok_or_else(|| Error::Other("memory read is out of bounds".into()))?;
|
||||
dest.copy_from_slice(&memory[range]);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Write data to a slice of memory.
|
||||
/// Write data to the instance memory from a slice.
|
||||
///
|
||||
/// Returns an error if the write would go out of the memory bounds.
|
||||
pub fn write_memory_from(&self, address: Pointer<u8>, data: &[u8]) -> Result<()> {
|
||||
pub fn write_memory_from(&self, dest_addr: Pointer<u8>, data: &[u8]) -> Result<()> {
|
||||
unsafe {
|
||||
// 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 memory = self.memory_as_slice_mut();
|
||||
|
||||
let range = util::checked_range(address.into(), data.len(), memory.len())
|
||||
let range = checked_range(dest_addr.into(), data.len(), memory.len())
|
||||
.ok_or_else(|| Error::Other("memory write is out of bounds".into()))?;
|
||||
memory[range].copy_from_slice(data);
|
||||
Ok(())
|
||||
@@ -442,11 +458,11 @@ impl runtime_blob::InstanceGlobals for InstanceWrapper {
|
||||
}
|
||||
|
||||
fn get_global_value(&self, global: &Self::Global) -> Value {
|
||||
util::from_wasmtime_val(global.get())
|
||||
from_wasmtime_val(global.get())
|
||||
}
|
||||
|
||||
fn set_global_value(&self, global: &Self::Global, value: Value) {
|
||||
global.set(util::into_wasmtime_val(value)).expect(
|
||||
global.set(into_wasmtime_val(value)).expect(
|
||||
"the value is guaranteed to be of the same value; the global is guaranteed to be mutable; qed",
|
||||
);
|
||||
}
|
||||
|
||||
@@ -148,7 +148,7 @@ pub struct WasmtimeInstance {
|
||||
}
|
||||
|
||||
// This is safe because `WasmtimeInstance` does not leak reference to `self.imports`
|
||||
// and all imports don't reference any anything, other than host functions and memory
|
||||
// and all imports don't reference anything, other than host functions and memory
|
||||
unsafe impl Send for WasmtimeInstance {}
|
||||
|
||||
impl WasmInstance for WasmtimeInstance {
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::host::{HostContext, HostState};
|
||||
use crate::host::HostState;
|
||||
|
||||
scoped_tls::scoped_thread_local!(static HOST_STATE: HostState);
|
||||
|
||||
@@ -36,10 +36,10 @@ where
|
||||
/// context will be `None`.
|
||||
pub fn with_context<R, F>(f: F) -> R
|
||||
where
|
||||
F: FnOnce(Option<HostContext>) -> R,
|
||||
F: FnOnce(Option<HostState>) -> R,
|
||||
{
|
||||
if !HOST_STATE.is_set() {
|
||||
return f(None)
|
||||
}
|
||||
HOST_STATE.with(|state| f(Some(state.materialize())))
|
||||
HOST_STATE.with(|state| f(Some(state.clone())))
|
||||
}
|
||||
|
||||
@@ -16,21 +16,8 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
use std::ops::Range;
|
||||
|
||||
use sp_wasm_interface::Value;
|
||||
|
||||
/// Construct a range from an offset to a data length after the offset.
|
||||
/// Returns None if the end of the range would exceed some maximum offset.
|
||||
pub fn checked_range(offset: usize, len: usize, max: usize) -> Option<Range<usize>> {
|
||||
let end = offset.checked_add(len)?;
|
||||
if end <= max {
|
||||
Some(offset..end)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts a [`wasmtime::Val`] into a substrate runtime interface [`Value`].
|
||||
///
|
||||
/// Panics if the given value doesn't have a corresponding variant in `Value`.
|
||||
|
||||
Reference in New Issue
Block a user