mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-04-26 13:27:57 +00:00
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
This commit is contained in:
@@ -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<dyn std::error::Error + Send + Sync>),
|
||||
|
||||
|
||||
@@ -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<wasmi::FuncRef, wasmi::Error> {
|
||||
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<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)
|
||||
}
|
||||
|
||||
fn resolve_global(
|
||||
&self,
|
||||
module_name: &str,
|
||||
field_name: &str,
|
||||
_global_type: &::wasmi::GlobalDescriptor,
|
||||
) -> std::result::Result<wasmi::GlobalRef, wasmi::Error> {
|
||||
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<wasmi::TableRef, wasmi::Error> {
|
||||
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<Option<RuntimeValue>, Trap> {
|
||||
use self::sandbox_primitives::HostError;
|
||||
use sp_wasm_interface::ReturnValue;
|
||||
let result_val = std::result::Result::<ReturnValue, HostError>::decode(&mut serialized_result)
|
||||
.map_err(|_| trap("Decoding Result<ReturnValue, HostError> 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<Option<RuntimeValue>, 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<u8> = args
|
||||
.as_ref()
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(sp_wasm_interface::Value::from)
|
||||
.collect::<Vec<_>>()
|
||||
.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<R, F>(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<Option<wasmi::RuntimeValue>, wasmi::Error> {
|
||||
) -> std::result::Result<Option<sp_wasm_interface::Value>, 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<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 = 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<DT: Clone> Store<DT> {
|
||||
};
|
||||
|
||||
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<DT: Clone> Store<DT> {
|
||||
sandbox_context: &mut dyn SandboxContext,
|
||||
) -> std::result::Result<UnregisteredInstance, InstantiationError> {
|
||||
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<DT> Store<DT> {
|
||||
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<Rc<SandboxInstance>, 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<Rc<SandboxInstance>, InstantiationError> {
|
||||
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(
|
||||
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::<std::result::Result<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 = 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")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
//! 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<Option<Value>, Error> {
|
||||
let function = instance
|
||||
.exports
|
||||
.get_function(export_name)
|
||||
.map_err(|error| Error::Sandbox(error.to_string()))?;
|
||||
|
||||
let args: Vec<wasmer::Val> = 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<Rc<SandboxInstance>, InstantiationError> {
|
||||
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 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::<std::result::Result<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 =
|
||||
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::<ReturnValue, HostError>::decode(
|
||||
&mut serialized_result_val.as_slice(),
|
||||
)
|
||||
.map_err(|_| RuntimeError::new("Decoding Result<ReturnValue, HostError> 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<u32>,
|
||||
) -> crate::error::Result<Memory> {
|
||||
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<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(
|
||||
"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<u8>, size: usize) -> Result<Vec<u8>> {
|
||||
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<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 = &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(())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
//! 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<wasmi::FuncRef, wasmi::Error> {
|
||||
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<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. 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<wasmi::GlobalRef, wasmi::Error> {
|
||||
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<wasmi::TableRef, wasmi::Error> {
|
||||
Err(wasmi::Error::Instantiation(format!("Export {}:{} not found", module_name, field_name)))
|
||||
}
|
||||
}
|
||||
|
||||
/// Allocate new memory region
|
||||
pub fn new_memory(initial: u32, maximum: Option<u32>) -> crate::error::Result<Memory> {
|
||||
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<u8>, size: usize) -> error::Result<Vec<u8>> {
|
||||
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<u8>, 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<u8>, 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<Option<RuntimeValue>, 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<u8> = args
|
||||
.as_ref()
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(sp_wasm_interface::Value::from)
|
||||
.collect::<Vec<_>>()
|
||||
.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::<ReturnValue, HostError>::decode(&mut serialized_result_val.as_slice())
|
||||
.map_err(|_| trap("Decoding Result<ReturnValue, HostError> 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<R, F>(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<Rc<SandboxInstance>, 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<Option<Value>, error::Error> {
|
||||
with_guest_externals(instance, state, |guest_externals| {
|
||||
SandboxContextStore::using(sandbox_context, || {
|
||||
let args = args.iter().cloned().map(Into::into).collect::<Vec<_>>();
|
||||
|
||||
module
|
||||
.invoke_export(export_name, &args, guest_externals)
|
||||
.map(|result| result.map(Into::into))
|
||||
.map_err(|error| error::Error::Sandbox(error.to_string()))
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -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<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()))?;
|
||||
|
||||
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()))?;
|
||||
|
||||
destination[range].copy_from_slice(source);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user