Remove sandboxing host function interface (#12852)

* Remove sandboxing interface

* Remove unused struct
This commit is contained in:
Alexander Theißen
2022-12-07 13:48:30 +01:00
committed by GitHub
parent 198faaa6f9
commit 32578cb010
31 changed files with 34 additions and 4478 deletions
@@ -30,9 +30,6 @@ 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>),
@@ -23,6 +23,5 @@
pub mod error;
pub mod runtime_blob;
pub mod sandbox;
pub mod util;
pub mod wasm_runtime;
@@ -1,585 +0,0 @@
// This file is part of Substrate.
// Copyright (C) 2018-2022 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/>.
//! This module implements sandboxing support in the runtime.
//!
//! Sandboxing is backed by wasmi and wasmer, depending on the configuration.
#[cfg(feature = "wasmer-sandbox")]
mod wasmer_backend;
mod wasmi_backend;
use std::{collections::HashMap, rc::Rc};
use codec::Decode;
use sp_sandbox::env as sandbox_env;
use sp_wasm_interface::{FunctionContext, Pointer, WordSize};
use crate::{
error::{self, Result},
util,
};
#[cfg(feature = "wasmer-sandbox")]
use self::wasmer_backend::{
get_global as wasmer_get_global, instantiate as wasmer_instantiate, invoke as wasmer_invoke,
new_memory as wasmer_new_memory, Backend as WasmerBackend,
MemoryWrapper as WasmerMemoryWrapper,
};
use self::wasmi_backend::{
get_global as wasmi_get_global, instantiate as wasmi_instantiate, invoke as wasmi_invoke,
new_memory as wasmi_new_memory, MemoryWrapper as WasmiMemoryWrapper,
};
/// Index of a function inside the supervisor.
///
/// This is a typically an index in the default table of the supervisor, however
/// the exact meaning of this index is depends on the implementation of dispatch function.
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct SupervisorFuncIndex(usize);
impl From<SupervisorFuncIndex> for usize {
fn from(index: SupervisorFuncIndex) -> Self {
index.0
}
}
/// Index of a function within guest index space.
///
/// 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>,
/// 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()
}
}
/// The sandbox context used to execute sandboxed functions.
pub trait SandboxContext {
/// Invoke a function in the supervisor environment.
///
/// This first invokes the dispatch thunk function, passing in the function index of the
/// 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.
///
/// # Errors
///
/// Returns `Err` if the dispatch_thunk function has an incorrect signature or traps during
/// execution.
fn invoke(
&mut self,
invoke_args_ptr: Pointer<u8>,
invoke_args_len: WordSize,
state: u32,
func_idx: SupervisorFuncIndex,
) -> Result<i64>;
/// Returns the supervisor context.
fn supervisor_context(&mut self) -> &mut dyn FunctionContext;
}
/// Implementation of [`Externals`] that allows execution of guest module with
/// [externals][`Externals`] that might refer functions defined by supervisor.
///
/// [`Externals`]: ../wasmi/trait.Externals.html
pub struct GuestExternals<'a> {
/// Instance of sandboxed module to be dispatched
sandbox_instance: &'a SandboxInstance,
/// External state passed to guest environment, see the `instantiate` function
state: u32,
}
/// 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.
///
/// All imports of this instance are specified at the creation time and
/// imports are implemented by the supervisor.
///
/// Hence, in order to invoke an exported function on a sandboxed module instance,
/// it's required to provide supervisor externals: it will be used to execute
/// code in the supervisor context.
///
/// This is generic over a supervisor function reference type.
///
/// [`invoke`]: #method.invoke
pub struct SandboxInstance {
backend_instance: BackendInstance,
guest_to_supervisor_mapping: GuestToSupervisorFunctionMapping,
}
impl SandboxInstance {
/// Invoke an exported function by a name.
///
/// `supervisor_externals` is required to execute the implementations
/// of the syscalls that published to a sandboxed module instance.
///
/// The `state` parameter can be used to provide custom data for
/// these syscall implementations.
pub fn invoke(
&self,
export_name: &str,
args: &[sp_wasm_interface::Value],
state: u32,
sandbox_context: &mut dyn SandboxContext,
) -> std::result::Result<Option<sp_wasm_interface::Value>, error::Error> {
match &self.backend_instance {
BackendInstance::Wasmi(wasmi_instance) =>
wasmi_invoke(self, wasmi_instance, export_name, args, state, sandbox_context),
#[cfg(feature = "wasmer-sandbox")]
BackendInstance::Wasmer(wasmer_instance) =>
wasmer_invoke(wasmer_instance, export_name, args, state, sandbox_context),
}
}
/// Get the value from a global with the given `name`.
///
/// Returns `Some(_)` if the global could be found.
pub fn get_global_val(&self, name: &str) -> Option<sp_wasm_interface::Value> {
match &self.backend_instance {
BackendInstance::Wasmi(wasmi_instance) => wasmi_get_global(wasmi_instance, name),
#[cfg(feature = "wasmer-sandbox")]
BackendInstance::Wasmer(wasmer_instance) => wasmer_get_global(wasmer_instance, name),
}
}
}
/// Error occurred during instantiation of a sandboxed module.
pub enum InstantiationError {
/// Something wrong with the environment definition. It either can't
/// be decoded, have a reference to a non-existent or torn down memory instance.
EnvironmentDefinitionCorrupted,
/// Provided module isn't recognized as a valid webassembly binary.
ModuleDecoding,
/// Module is a well-formed webassembly binary but could not be instantiated. This could
/// happen because, e.g. the module imports entries not provided by the environment.
Instantiation,
/// Module is well-formed, instantiated and linked, but while executing the start function
/// a trap was generated.
StartTrapped,
/// The code was compiled with a CPU feature not available on the host.
CpuFeature,
}
fn decode_environment_definition(
mut raw_env_def: &[u8],
memories: &[Option<Memory>],
) -> std::result::Result<(Imports, GuestToSupervisorFunctionMapping), InstantiationError> {
let env_def = sandbox_env::EnvironmentDefinition::decode(&mut raw_env_def)
.map_err(|_| InstantiationError::EnvironmentDefinitionCorrupted)?;
let mut func_map = HashMap::new();
let mut memories_map = HashMap::new();
let mut guest_to_supervisor_mapping = GuestToSupervisorFunctionMapping::new();
for entry in &env_def.entries {
let module = entry.module_name.clone();
let field = entry.field_name.clone();
match entry.entity {
sandbox_env::ExternEntity::Function(func_idx) => {
let externals_idx =
guest_to_supervisor_mapping.define(SupervisorFuncIndex(func_idx as usize));
func_map.insert((module, field), externals_idx);
},
sandbox_env::ExternEntity::Memory(memory_idx) => {
let memory_ref = memories
.get(memory_idx as usize)
.cloned()
.ok_or(InstantiationError::EnvironmentDefinitionCorrupted)?
.ok_or(InstantiationError::EnvironmentDefinitionCorrupted)?;
memories_map.insert((module, field), memory_ref);
},
}
}
Ok((Imports { func_map, memories_map }, guest_to_supervisor_mapping))
}
/// 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,
}
impl GuestEnvironment {
/// Decodes an environment definition from the given raw bytes.
///
/// Returns `Err` if the definition cannot be decoded.
pub fn decode<DT>(
store: &Store<DT>,
raw_env_def: &[u8],
) -> std::result::Result<Self, InstantiationError> {
let (imports, guest_to_supervisor_mapping) =
decode_environment_definition(raw_env_def, &store.memories)?;
Ok(Self { imports, guest_to_supervisor_mapping })
}
}
/// An unregistered sandboxed instance.
///
/// To finish off the instantiation the user must call `register`.
#[must_use]
pub struct UnregisteredInstance {
sandbox_instance: Rc<SandboxInstance>,
}
impl UnregisteredInstance {
/// Finalizes instantiation of this module.
pub fn register<DT>(self, store: &mut Store<DT>, dispatch_thunk: DT) -> u32 {
// At last, register the instance.
store.register_sandbox_instance(self.sandbox_instance, dispatch_thunk)
}
}
/// 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),
}
}
}
/// 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 =>
BackendContext::Wasmer(WasmerBackend::new()),
}
}
}
/// This struct keeps track of all sandboxed components.
///
/// This is generic over a supervisor function reference type.
pub struct Store<DT> {
/// Stores the instance and the dispatch thunk associated to per instance.
///
/// Instances are `Some` until torn down.
instances: Vec<Option<(Rc<SandboxInstance>, DT)>>,
/// Memories are `Some` until torn down.
memories: Vec<Option<Memory>>,
backend_context: BackendContext,
}
impl<DT: Clone> Store<DT> {
/// Create a new empty sandbox store.
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.
///
/// # Errors
///
/// 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_env::MEM_UNLIMITED => None,
specified_limit => Some(specified_limit),
};
let memory = match &backend_context {
BackendContext::Wasmi => wasmi_new_memory(initial, maximum)?,
#[cfg(feature = "wasmer-sandbox")]
BackendContext::Wasmer(context) => wasmer_new_memory(context, initial, maximum)?,
};
let mem_idx = memories.len();
memories.push(Some(memory));
Ok(mem_idx as u32)
}
/// Returns `SandboxInstance` by `instance_idx`.
///
/// # Errors
///
/// Returns `Err` If `instance_idx` isn't a valid index of an instance or
/// instance is already torndown.
pub fn instance(&self, instance_idx: u32) -> Result<Rc<SandboxInstance>> {
self.instances
.get(instance_idx as usize)
.ok_or("Trying to access a non-existent instance")?
.as_ref()
.map(|v| v.0.clone())
.ok_or_else(|| "Trying to access a torndown instance".into())
}
/// Returns dispatch thunk by `instance_idx`.
///
/// # Errors
///
/// Returns `Err` If `instance_idx` isn't a valid index of an instance or
/// instance is already torndown.
pub fn dispatch_thunk(&self, instance_idx: u32) -> Result<DT> {
self.instances
.get(instance_idx as usize)
.as_ref()
.ok_or("Trying to access a non-existent instance")?
.as_ref()
.map(|v| v.1.clone())
.ok_or_else(|| "Trying to access a torndown instance".into())
}
/// Returns reference to a memory instance by `memory_idx`.
///
/// # Errors
///
/// 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<Memory> {
self.memories
.get(memory_idx as usize)
.cloned()
.ok_or("Trying to access a non-existent sandboxed memory")?
.ok_or_else(|| "Trying to access a torndown sandboxed memory".into())
}
/// Tear down the memory at the specified index.
///
/// # Errors
///
/// Returns `Err` if `memory_idx` isn't a valid index of an memory or
/// if it has been torn down.
pub fn memory_teardown(&mut self, memory_idx: u32) -> Result<()> {
match self.memories.get_mut(memory_idx as usize) {
None => Err("Trying to teardown a non-existent sandboxed memory".into()),
Some(None) => Err("Double teardown of a sandboxed memory".into()),
Some(memory) => {
*memory = None;
Ok(())
},
}
}
/// Tear down the instance at the specified index.
///
/// # Errors
///
/// Returns `Err` if `instance_idx` isn't a valid index of an instance or
/// if it has been torn down.
pub fn instance_teardown(&mut self, instance_idx: u32) -> Result<()> {
match self.instances.get_mut(instance_idx as usize) {
None => Err("Trying to teardown a non-existent instance".into()),
Some(None) => Err("Double teardown of an instance".into()),
Some(instance) => {
*instance = None;
Ok(())
},
}
}
/// 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(
&mut self,
wasm: &[u8],
guest_env: GuestEnvironment,
state: u32,
sandbox_context: &mut dyn SandboxContext,
) -> std::result::Result<UnregisteredInstance, InstantiationError> {
let sandbox_instance = match self.backend_context {
BackendContext::Wasmi => wasmi_instantiate(wasm, guest_env, state, sandbox_context)?,
#[cfg(feature = "wasmer-sandbox")]
BackendContext::Wasmer(ref context) =>
wasmer_instantiate(context, wasm, guest_env, state, sandbox_context)?,
};
Ok(UnregisteredInstance { sandbox_instance })
}
}
// Private routines
impl<DT> Store<DT> {
fn register_sandbox_instance(
&mut self,
sandbox_instance: Rc<SandboxInstance>,
dispatch_thunk: DT,
) -> u32 {
let instance_idx = self.instances.len();
self.instances.push(Some((sandbox_instance, dispatch_thunk)));
instance_idx as u32
}
}
@@ -1,449 +0,0 @@
// 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 std::{cell::RefCell, collections::HashMap, rc::Rc};
use wasmer::RuntimeError;
use codec::{Decode, Encode};
use sp_sandbox::HostError;
use sp_wasm_interface::{FunctionContext, Pointer, ReturnValue, Value, WordSize};
use crate::{
error::{Error, Result},
sandbox::{
BackendInstance, GuestEnvironment, InstantiationError, Memory, SandboxContext,
SandboxInstance, SupervisorFuncIndex,
},
util::{checked_range, MemoryTransfer},
};
environmental::environmental!(SandboxContextStore: trait SandboxContext);
/// Wasmer specific context
pub struct Backend {
store: wasmer::Store,
}
impl Backend {
pub fn new() -> Self {
let compiler = wasmer::Singlepass::default();
Backend { store: wasmer::Store::new(&wasmer::Universal::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() {
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_with(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_with(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,
wasmer::InstantiationError::CpuFeature(_) => InstantiationError::CpuFeature,
})
})?;
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(())
}
}
}
/// Get global value by name
pub fn get_global(instance: &wasmer::Instance, name: &str) -> Option<Value> {
let global = 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)
}
@@ -1,339 +0,0 @@
// 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 std::{fmt, rc::Rc};
use codec::{Decode, Encode};
use sp_sandbox::HostError;
use sp_wasm_interface::{FunctionContext, Pointer, ReturnValue, Value, WordSize};
use wasmi::{
memory_units::Pages, ImportResolver, MemoryInstance, Module, ModuleInstance, RuntimeArgs,
RuntimeValue, Trap,
};
use crate::{
error::{self, Error},
sandbox::{
BackendInstance, GuestEnvironment, GuestExternals, GuestFuncIndex, Imports,
InstantiationError, Memory, SandboxContext, SandboxInstance,
},
util::{checked_range, MemoryTransfer},
};
environmental::environmental!(SandboxContextStore: trait SandboxContext);
#[derive(Debug)]
struct CustomHostError(String);
impl fmt::Display for CustomHostError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "HostError: {}", self.0)
}
}
impl wasmi::HostError for CustomHostError {}
/// Construct trap error from specified message
fn trap(msg: &'static str) -> Trap {
Trap::host(CustomHostError(msg.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;
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(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()))
})
})
}
/// Get global value by name
pub fn get_global(instance: &wasmi::ModuleRef, name: &str) -> Option<Value> {
Some(instance.export_by_name(name)?.as_global()?.get().into())
}