mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-13 10:31:04 +00:00
Remove sandboxing host function interface (#12852)
* Remove sandboxing interface * Remove unused struct
This commit is contained in:
committed by
GitHub
parent
198faaa6f9
commit
32578cb010
@@ -14,7 +14,6 @@ readme = "README.md"
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[dependencies]
|
||||
lazy_static = "1.4.0"
|
||||
lru = "0.8.1"
|
||||
parking_lot = "0.12.1"
|
||||
tracing = "0.1.29"
|
||||
@@ -26,7 +25,6 @@ sc-executor-wasmi = { version = "0.10.0-dev", path = "wasmi" }
|
||||
sc-executor-wasmtime = { version = "0.10.0-dev", path = "wasmtime" }
|
||||
sp-api = { version = "4.0.0-dev", path = "../../primitives/api" }
|
||||
sp-core = { version = "7.0.0", path = "../../primitives/core" }
|
||||
sp-core-hashing-proc-macro = { version = "5.0.0", path = "../../primitives/core/hashing/proc-macro" }
|
||||
sp-externalities = { version = "0.13.0", path = "../../primitives/externalities" }
|
||||
sp-io = { version = "7.0.0", path = "../../primitives/io" }
|
||||
sp-panic-handler = { version = "5.0.0", path = "../../primitives/panic-handler" }
|
||||
@@ -61,4 +59,3 @@ default = ["std"]
|
||||
# This crate does not have `no_std` support, we just require this for tests
|
||||
std = []
|
||||
wasm-extern-trace = []
|
||||
wasmer-sandbox = ["sc-executor-common/wasmer-sandbox"]
|
||||
|
||||
@@ -14,19 +14,12 @@ readme = "README.md"
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[dependencies]
|
||||
codec = { package = "parity-scale-codec", version = "3.0.0" }
|
||||
environmental = "1.1.3"
|
||||
thiserror = "1.0.30"
|
||||
wasm-instrument = "0.3"
|
||||
wasmer = { version = "2.2", features = ["singlepass"], optional = true }
|
||||
wasmi = "0.13"
|
||||
sc-allocator = { version = "4.1.0-dev", path = "../../allocator" }
|
||||
sp-maybe-compressed-blob = { version = "4.1.0-dev", path = "../../../primitives/maybe-compressed-blob" }
|
||||
sp-sandbox = { version = "0.10.0-dev", path = "../../../primitives/sandbox" }
|
||||
sp-wasm-interface = { version = "7.0.0", path = "../../../primitives/wasm-interface" }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
wasmer-sandbox = [
|
||||
"wasmer",
|
||||
]
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
@@ -13,11 +13,9 @@ repository = "https://github.com/paritytech/substrate/"
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[dependencies]
|
||||
paste = "1.0.6"
|
||||
sp-core = { version = "7.0.0", default-features = false, path = "../../../primitives/core" }
|
||||
sp-io = { version = "7.0.0", default-features = false, features = ["improved_panic_error_reporting"], path = "../../../primitives/io" }
|
||||
sp-runtime = { version = "7.0.0", default-features = false, path = "../../../primitives/runtime" }
|
||||
sp-sandbox = { version = "0.10.0-dev", default-features = false, path = "../../../primitives/sandbox" }
|
||||
sp-std = { version = "5.0.0", default-features = false, path = "../../../primitives/std" }
|
||||
|
||||
[build-dependencies]
|
||||
@@ -29,6 +27,5 @@ std = [
|
||||
"sp-core/std",
|
||||
"sp-io/std",
|
||||
"sp-runtime/std",
|
||||
"sp-sandbox/std",
|
||||
"sp-std/std",
|
||||
]
|
||||
|
||||
@@ -29,8 +29,6 @@ use sp_runtime::{
|
||||
print,
|
||||
traits::{BlakeTwo256, Hash},
|
||||
};
|
||||
#[cfg(not(feature = "std"))]
|
||||
use sp_sandbox::{SandboxEnvironmentBuilder, SandboxInstance, SandboxMemory, Value};
|
||||
|
||||
extern "C" {
|
||||
#[allow(dead_code)]
|
||||
@@ -339,160 +337,3 @@ sp_core::wasm_export_functions! {
|
||||
return 1234;
|
||||
}
|
||||
}
|
||||
|
||||
/// A macro to define a test entrypoint for each available sandbox executor.
|
||||
macro_rules! wasm_export_sandbox_test_functions {
|
||||
(
|
||||
$(
|
||||
fn $name:ident<T>(
|
||||
$( $arg_name:ident: $arg_ty:ty ),* $(,)?
|
||||
) $( -> $ret_ty:ty )? where T: SandboxInstance<$state:ty> $(,)?
|
||||
{ $( $fn_impl:tt )* }
|
||||
)*
|
||||
) => {
|
||||
$(
|
||||
#[cfg(not(feature = "std"))]
|
||||
fn $name<T>( $($arg_name: $arg_ty),* ) $( -> $ret_ty )? where T: SandboxInstance<$state> {
|
||||
$( $fn_impl )*
|
||||
}
|
||||
|
||||
paste::paste! {
|
||||
sp_core::wasm_export_functions! {
|
||||
fn [<$name _host>]( $($arg_name: $arg_ty),* ) $( -> $ret_ty )? {
|
||||
$name::<sp_sandbox::host_executor::Instance<$state>>( $( $arg_name ),* )
|
||||
}
|
||||
|
||||
fn [<$name _embedded>]( $($arg_name: $arg_ty),* ) $( -> $ret_ty )? {
|
||||
$name::<sp_sandbox::embedded_executor::Instance<$state>>( $( $arg_name ),* )
|
||||
}
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
wasm_export_sandbox_test_functions! {
|
||||
fn test_sandbox<T>(code: Vec<u8>) -> bool
|
||||
where
|
||||
T: SandboxInstance<State>,
|
||||
{
|
||||
execute_sandboxed::<T>(&code, &[]).is_ok()
|
||||
}
|
||||
|
||||
fn test_sandbox_args<T>(code: Vec<u8>) -> bool
|
||||
where
|
||||
T: SandboxInstance<State>,
|
||||
{
|
||||
execute_sandboxed::<T>(&code, &[Value::I32(0x12345678), Value::I64(0x1234567887654321)])
|
||||
.is_ok()
|
||||
}
|
||||
|
||||
fn test_sandbox_return_val<T>(code: Vec<u8>) -> bool
|
||||
where
|
||||
T: SandboxInstance<State>,
|
||||
{
|
||||
let ok = match execute_sandboxed::<T>(&code, &[Value::I32(0x1336)]) {
|
||||
Ok(sp_sandbox::ReturnValue::Value(Value::I32(0x1337))) => true,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
ok
|
||||
}
|
||||
|
||||
fn test_sandbox_instantiate<T>(code: Vec<u8>) -> u8
|
||||
where
|
||||
T: SandboxInstance<()>,
|
||||
{
|
||||
let env_builder = T::EnvironmentBuilder::new();
|
||||
let code = match T::new(&code, &env_builder, &mut ()) {
|
||||
Ok(_) => 0,
|
||||
Err(sp_sandbox::Error::Module) => 1,
|
||||
Err(sp_sandbox::Error::Execution) => 2,
|
||||
Err(sp_sandbox::Error::OutOfBounds) => 3,
|
||||
};
|
||||
|
||||
code
|
||||
}
|
||||
|
||||
fn test_sandbox_get_global_val<T>(code: Vec<u8>) -> i64
|
||||
where
|
||||
T: SandboxInstance<()>,
|
||||
{
|
||||
let env_builder = T::EnvironmentBuilder::new();
|
||||
let instance = if let Ok(i) = T::new(&code, &env_builder, &mut ()) {
|
||||
i
|
||||
} else {
|
||||
return 20
|
||||
};
|
||||
|
||||
match instance.get_global_val("test_global") {
|
||||
Some(sp_sandbox::Value::I64(val)) => val,
|
||||
None => 30,
|
||||
_ => 40,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
struct State {
|
||||
counter: u32,
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
fn execute_sandboxed<T>(
|
||||
code: &[u8],
|
||||
args: &[Value],
|
||||
) -> Result<sp_sandbox::ReturnValue, sp_sandbox::HostError>
|
||||
where
|
||||
T: sp_sandbox::SandboxInstance<State>,
|
||||
{
|
||||
fn env_assert(
|
||||
_e: &mut State,
|
||||
args: &[Value],
|
||||
) -> Result<sp_sandbox::ReturnValue, sp_sandbox::HostError> {
|
||||
if args.len() != 1 {
|
||||
return Err(sp_sandbox::HostError)
|
||||
}
|
||||
let condition = args[0].as_i32().ok_or_else(|| sp_sandbox::HostError)?;
|
||||
if condition != 0 {
|
||||
Ok(sp_sandbox::ReturnValue::Unit)
|
||||
} else {
|
||||
Err(sp_sandbox::HostError)
|
||||
}
|
||||
}
|
||||
fn env_inc_counter(
|
||||
e: &mut State,
|
||||
args: &[Value],
|
||||
) -> Result<sp_sandbox::ReturnValue, sp_sandbox::HostError> {
|
||||
if args.len() != 1 {
|
||||
return Err(sp_sandbox::HostError)
|
||||
}
|
||||
let inc_by = args[0].as_i32().ok_or_else(|| sp_sandbox::HostError)?;
|
||||
e.counter += inc_by as u32;
|
||||
Ok(sp_sandbox::ReturnValue::Value(Value::I32(e.counter as i32)))
|
||||
}
|
||||
|
||||
let mut state = State { counter: 0 };
|
||||
|
||||
let env_builder = {
|
||||
let mut env_builder = T::EnvironmentBuilder::new();
|
||||
env_builder.add_host_func("env", "assert", env_assert);
|
||||
env_builder.add_host_func("env", "inc_counter", env_inc_counter);
|
||||
let memory = match T::Memory::new(1, Some(16)) {
|
||||
Ok(m) => m,
|
||||
Err(_) => unreachable!(
|
||||
"
|
||||
Memory::new() can return Err only if parameters are borked; \
|
||||
We passing params here explicitly and they're correct; \
|
||||
Memory::new() can't return a Error qed"
|
||||
),
|
||||
};
|
||||
env_builder.add_memory("env", "memory", memory);
|
||||
env_builder
|
||||
};
|
||||
|
||||
let mut instance = T::new(code, &env_builder, &mut state)?;
|
||||
let result = instance.invoke("call", args, &mut state);
|
||||
|
||||
result.map_err(|_| sp_sandbox::HostError)
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
mod linux;
|
||||
mod sandbox;
|
||||
|
||||
use codec::{Decode, Encode};
|
||||
use sc_executor_common::{error::Error, runtime_blob::RuntimeBlob, wasm_runtime::WasmModule};
|
||||
@@ -98,111 +97,6 @@ macro_rules! test_wasm_execution {
|
||||
};
|
||||
}
|
||||
|
||||
/// A macro to run a given test for each available WASM execution method *and* for each
|
||||
/// sandbox execution method.
|
||||
#[macro_export]
|
||||
macro_rules! test_wasm_execution_sandbox {
|
||||
($method_name:ident) => {
|
||||
paste::item! {
|
||||
#[test]
|
||||
fn [<$method_name _interpreted_host_executor>]() {
|
||||
$method_name(WasmExecutionMethod::Interpreted, "_host");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn [<$method_name _interpreted_embedded_executor>]() {
|
||||
$method_name(WasmExecutionMethod::Interpreted, "_embedded");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn [<$method_name _compiled_pooling_cow_host_executor>]() {
|
||||
$method_name(WasmExecutionMethod::Compiled {
|
||||
instantiation_strategy: sc_executor_wasmtime::InstantiationStrategy::PoolingCopyOnWrite
|
||||
}, "_host");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn [<$method_name _compiled_pooling_cow_embedded_executor>]() {
|
||||
$method_name(WasmExecutionMethod::Compiled {
|
||||
instantiation_strategy: sc_executor_wasmtime::InstantiationStrategy::PoolingCopyOnWrite
|
||||
}, "_embedded");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn [<$method_name _compiled_pooling_vanilla_host_executor>]() {
|
||||
$method_name(WasmExecutionMethod::Compiled {
|
||||
instantiation_strategy: sc_executor_wasmtime::InstantiationStrategy::Pooling
|
||||
}, "_host");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn [<$method_name _compiled_pooling_vanilla_embedded_executor>]() {
|
||||
$method_name(WasmExecutionMethod::Compiled {
|
||||
instantiation_strategy: sc_executor_wasmtime::InstantiationStrategy::Pooling
|
||||
}, "_embedded");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn [<$method_name _compiled_recreate_instance_cow_host_executor>]() {
|
||||
$method_name(WasmExecutionMethod::Compiled {
|
||||
instantiation_strategy: sc_executor_wasmtime::InstantiationStrategy::RecreateInstanceCopyOnWrite
|
||||
}, "_host");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn [<$method_name _compiled_recreate_instance_cow_embedded_executor>]() {
|
||||
$method_name(WasmExecutionMethod::Compiled {
|
||||
instantiation_strategy: sc_executor_wasmtime::InstantiationStrategy::RecreateInstanceCopyOnWrite
|
||||
}, "_embedded");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn [<$method_name _compiled_recreate_instance_vanilla_host_executor>]() {
|
||||
$method_name(WasmExecutionMethod::Compiled {
|
||||
instantiation_strategy: sc_executor_wasmtime::InstantiationStrategy::RecreateInstance
|
||||
}, "_host");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn [<$method_name _compiled_recreate_instance_vanilla_embedded_executor>]() {
|
||||
$method_name(WasmExecutionMethod::Compiled {
|
||||
instantiation_strategy: sc_executor_wasmtime::InstantiationStrategy::RecreateInstance
|
||||
}, "_embedded");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn [<$method_name _compiled_legacy_instance_reuse_host_executor>]() {
|
||||
$method_name(WasmExecutionMethod::Compiled {
|
||||
instantiation_strategy: sc_executor_wasmtime::InstantiationStrategy::LegacyInstanceReuse
|
||||
}, "_host");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn [<$method_name _compiled_legacy_instance_reuse_embedded_executor>]() {
|
||||
$method_name(WasmExecutionMethod::Compiled {
|
||||
instantiation_strategy: sc_executor_wasmtime::InstantiationStrategy::LegacyInstanceReuse
|
||||
}, "_embedded");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
(interpreted_only $method_name:ident) => {
|
||||
paste::item! {
|
||||
#[test]
|
||||
fn [<$method_name _interpreted_host_executor>]() {
|
||||
$method_name(WasmExecutionMethod::Interpreted, "_host");
|
||||
}
|
||||
}
|
||||
|
||||
paste::item! {
|
||||
#[test]
|
||||
fn [<$method_name _interpreted_embedded_executor>]() {
|
||||
$method_name(WasmExecutionMethod::Interpreted, "_embedded");
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn call_in_wasm<E: Externalities>(
|
||||
function: &str,
|
||||
call_data: &[u8],
|
||||
|
||||
@@ -1,339 +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/>.
|
||||
|
||||
use super::{call_in_wasm, TestExternalities};
|
||||
use crate::{test_wasm_execution_sandbox, WasmExecutionMethod};
|
||||
|
||||
use codec::Encode;
|
||||
|
||||
test_wasm_execution_sandbox!(sandbox_should_work);
|
||||
fn sandbox_should_work(wasm_method: WasmExecutionMethod, fn_suffix: &str) {
|
||||
let mut ext = TestExternalities::default();
|
||||
let mut ext = ext.ext();
|
||||
|
||||
let code = wat::parse_str(
|
||||
r#"
|
||||
(module
|
||||
(import "env" "assert" (func $assert (param i32)))
|
||||
(import "env" "inc_counter" (func $inc_counter (param i32) (result i32)))
|
||||
(func (export "call")
|
||||
(drop
|
||||
(call $inc_counter (i32.const 5))
|
||||
)
|
||||
|
||||
(call $inc_counter (i32.const 3))
|
||||
;; current counter value is on the stack
|
||||
|
||||
;; check whether current == 8
|
||||
i32.const 8
|
||||
i32.eq
|
||||
|
||||
call $assert
|
||||
)
|
||||
)
|
||||
"#,
|
||||
)
|
||||
.unwrap()
|
||||
.encode();
|
||||
|
||||
assert_eq!(
|
||||
call_in_wasm(&format!("test_sandbox{}", fn_suffix), &code, wasm_method, &mut ext).unwrap(),
|
||||
true.encode()
|
||||
);
|
||||
}
|
||||
|
||||
test_wasm_execution_sandbox!(sandbox_trap);
|
||||
fn sandbox_trap(wasm_method: WasmExecutionMethod, fn_suffix: &str) {
|
||||
let mut ext = TestExternalities::default();
|
||||
let mut ext = ext.ext();
|
||||
|
||||
let code = wat::parse_str(
|
||||
r#"
|
||||
(module
|
||||
(import "env" "assert" (func $assert (param i32)))
|
||||
(func (export "call")
|
||||
i32.const 0
|
||||
call $assert
|
||||
)
|
||||
)
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
call_in_wasm(&format!("test_sandbox{}", fn_suffix), &code, wasm_method, &mut ext).unwrap(),
|
||||
vec![0]
|
||||
);
|
||||
}
|
||||
|
||||
test_wasm_execution_sandbox!(start_called);
|
||||
fn start_called(wasm_method: WasmExecutionMethod, fn_suffix: &str) {
|
||||
let mut ext = TestExternalities::default();
|
||||
let mut ext = ext.ext();
|
||||
|
||||
let code = wat::parse_str(
|
||||
r#"
|
||||
(module
|
||||
(import "env" "assert" (func $assert (param i32)))
|
||||
(import "env" "inc_counter" (func $inc_counter (param i32) (result i32)))
|
||||
|
||||
;; Start function
|
||||
(start $start)
|
||||
(func $start
|
||||
;; Increment counter by 1
|
||||
(drop
|
||||
(call $inc_counter (i32.const 1))
|
||||
)
|
||||
)
|
||||
|
||||
(func (export "call")
|
||||
;; Increment counter by 1. The current value is placed on the stack.
|
||||
(call $inc_counter (i32.const 1))
|
||||
|
||||
;; Counter is incremented twice by 1, once there and once in `start` func.
|
||||
;; So check the returned value is equal to 2.
|
||||
i32.const 2
|
||||
i32.eq
|
||||
call $assert
|
||||
)
|
||||
)
|
||||
"#,
|
||||
)
|
||||
.unwrap()
|
||||
.encode();
|
||||
|
||||
assert_eq!(
|
||||
call_in_wasm(&format!("test_sandbox{}", fn_suffix), &code, wasm_method, &mut ext).unwrap(),
|
||||
true.encode()
|
||||
);
|
||||
}
|
||||
|
||||
test_wasm_execution_sandbox!(invoke_args);
|
||||
fn invoke_args(wasm_method: WasmExecutionMethod, fn_suffix: &str) {
|
||||
let mut ext = TestExternalities::default();
|
||||
let mut ext = ext.ext();
|
||||
|
||||
let code = wat::parse_str(
|
||||
r#"
|
||||
(module
|
||||
(import "env" "assert" (func $assert (param i32)))
|
||||
|
||||
(func (export "call") (param $x i32) (param $y i64)
|
||||
;; assert that $x = 0x12345678
|
||||
(call $assert
|
||||
(i32.eq
|
||||
(get_local $x)
|
||||
(i32.const 0x12345678)
|
||||
)
|
||||
)
|
||||
|
||||
(call $assert
|
||||
(i64.eq
|
||||
(get_local $y)
|
||||
(i64.const 0x1234567887654321)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
"#,
|
||||
)
|
||||
.unwrap()
|
||||
.encode();
|
||||
|
||||
assert_eq!(
|
||||
call_in_wasm(&format!("test_sandbox_args{}", fn_suffix), &code, wasm_method, &mut ext,)
|
||||
.unwrap(),
|
||||
true.encode(),
|
||||
);
|
||||
}
|
||||
|
||||
test_wasm_execution_sandbox!(return_val);
|
||||
fn return_val(wasm_method: WasmExecutionMethod, fn_suffix: &str) {
|
||||
let mut ext = TestExternalities::default();
|
||||
let mut ext = ext.ext();
|
||||
|
||||
let code = wat::parse_str(
|
||||
r#"
|
||||
(module
|
||||
(func (export "call") (param $x i32) (result i32)
|
||||
(i32.add
|
||||
(get_local $x)
|
||||
(i32.const 1)
|
||||
)
|
||||
)
|
||||
)
|
||||
"#,
|
||||
)
|
||||
.unwrap()
|
||||
.encode();
|
||||
|
||||
assert_eq!(
|
||||
call_in_wasm(
|
||||
&format!("test_sandbox_return_val{}", fn_suffix),
|
||||
&code,
|
||||
wasm_method,
|
||||
&mut ext,
|
||||
)
|
||||
.unwrap(),
|
||||
true.encode(),
|
||||
);
|
||||
}
|
||||
|
||||
test_wasm_execution_sandbox!(unlinkable_module);
|
||||
fn unlinkable_module(wasm_method: WasmExecutionMethod, fn_suffix: &str) {
|
||||
let mut ext = TestExternalities::default();
|
||||
let mut ext = ext.ext();
|
||||
|
||||
let code = wat::parse_str(
|
||||
r#"
|
||||
(module
|
||||
(import "env" "non-existent" (func))
|
||||
|
||||
(func (export "call")
|
||||
)
|
||||
)
|
||||
"#,
|
||||
)
|
||||
.unwrap()
|
||||
.encode();
|
||||
|
||||
assert_eq!(
|
||||
call_in_wasm(
|
||||
&format!("test_sandbox_instantiate{}", fn_suffix),
|
||||
&code,
|
||||
wasm_method,
|
||||
&mut ext,
|
||||
)
|
||||
.unwrap(),
|
||||
1u8.encode(),
|
||||
);
|
||||
}
|
||||
|
||||
test_wasm_execution_sandbox!(corrupted_module);
|
||||
fn corrupted_module(wasm_method: WasmExecutionMethod, fn_suffix: &str) {
|
||||
let mut ext = TestExternalities::default();
|
||||
let mut ext = ext.ext();
|
||||
|
||||
// Corrupted wasm file
|
||||
let code = vec![0u8, 0, 0, 0, 1, 0, 0, 0].encode();
|
||||
|
||||
assert_eq!(
|
||||
call_in_wasm(
|
||||
&format!("test_sandbox_instantiate{}", fn_suffix),
|
||||
&code,
|
||||
wasm_method,
|
||||
&mut ext,
|
||||
)
|
||||
.unwrap(),
|
||||
1u8.encode(),
|
||||
);
|
||||
}
|
||||
|
||||
test_wasm_execution_sandbox!(start_fn_ok);
|
||||
fn start_fn_ok(wasm_method: WasmExecutionMethod, fn_suffix: &str) {
|
||||
let mut ext = TestExternalities::default();
|
||||
let mut ext = ext.ext();
|
||||
|
||||
let code = wat::parse_str(
|
||||
r#"
|
||||
(module
|
||||
(func (export "call")
|
||||
)
|
||||
|
||||
(func $start
|
||||
)
|
||||
|
||||
(start $start)
|
||||
)
|
||||
"#,
|
||||
)
|
||||
.unwrap()
|
||||
.encode();
|
||||
|
||||
assert_eq!(
|
||||
call_in_wasm(
|
||||
&format!("test_sandbox_instantiate{}", fn_suffix),
|
||||
&code,
|
||||
wasm_method,
|
||||
&mut ext,
|
||||
)
|
||||
.unwrap(),
|
||||
0u8.encode(),
|
||||
);
|
||||
}
|
||||
|
||||
test_wasm_execution_sandbox!(start_fn_traps);
|
||||
fn start_fn_traps(wasm_method: WasmExecutionMethod, fn_suffix: &str) {
|
||||
let mut ext = TestExternalities::default();
|
||||
let mut ext = ext.ext();
|
||||
|
||||
let code = wat::parse_str(
|
||||
r#"
|
||||
(module
|
||||
(func (export "call")
|
||||
)
|
||||
|
||||
(func $start
|
||||
unreachable
|
||||
)
|
||||
|
||||
(start $start)
|
||||
)
|
||||
"#,
|
||||
)
|
||||
.unwrap()
|
||||
.encode();
|
||||
|
||||
assert_eq!(
|
||||
call_in_wasm(
|
||||
&format!("test_sandbox_instantiate{}", fn_suffix),
|
||||
&code,
|
||||
wasm_method,
|
||||
&mut ext,
|
||||
)
|
||||
.unwrap(),
|
||||
2u8.encode(),
|
||||
);
|
||||
}
|
||||
|
||||
test_wasm_execution_sandbox!(get_global_val_works);
|
||||
fn get_global_val_works(wasm_method: WasmExecutionMethod, fn_suffix: &str) {
|
||||
let mut ext = TestExternalities::default();
|
||||
let mut ext = ext.ext();
|
||||
|
||||
let code = wat::parse_str(
|
||||
r#"
|
||||
(module
|
||||
(global (export "test_global") i64 (i64.const 500))
|
||||
)
|
||||
"#,
|
||||
)
|
||||
.unwrap()
|
||||
.encode();
|
||||
|
||||
assert_eq!(
|
||||
call_in_wasm(
|
||||
&format!("test_sandbox_get_global_val{}", fn_suffix),
|
||||
&code,
|
||||
wasm_method,
|
||||
&mut ext,
|
||||
)
|
||||
.unwrap(),
|
||||
500i64.encode(),
|
||||
);
|
||||
}
|
||||
@@ -49,7 +49,7 @@ pub use sp_wasm_interface;
|
||||
pub use wasm_runtime::{read_embedded_version, WasmExecutionMethod};
|
||||
pub use wasmi;
|
||||
|
||||
pub use sc_executor_common::{error, sandbox};
|
||||
pub use sc_executor_common::error;
|
||||
pub use sc_executor_wasmtime::InstantiationStrategy as WasmtimeInstantiationStrategy;
|
||||
|
||||
/// Extracts the runtime version of a given runtime code.
|
||||
|
||||
@@ -14,11 +14,9 @@ readme = "README.md"
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[dependencies]
|
||||
codec = { package = "parity-scale-codec", version = "3.0.0" }
|
||||
log = "0.4.17"
|
||||
wasmi = "0.13"
|
||||
sc-allocator = { version = "4.1.0-dev", path = "../../allocator" }
|
||||
sc-executor-common = { version = "0.10.0-dev", path = "../common" }
|
||||
sp-runtime-interface = { version = "7.0.0", path = "../../../primitives/runtime-interface" }
|
||||
sp-sandbox = { version = "0.10.0-dev", path = "../../../primitives/sandbox" }
|
||||
sp-wasm-interface = { version = "7.0.0", path = "../../../primitives/wasm-interface" }
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
//! This crate provides an implementation of `WasmModule` that is baked by wasmi.
|
||||
|
||||
use std::{cell::RefCell, rc::Rc, str, sync::Arc};
|
||||
use std::{cell::RefCell, str, sync::Arc};
|
||||
|
||||
use log::{debug, error, trace};
|
||||
use wasmi::{
|
||||
@@ -28,26 +28,18 @@ use wasmi::{
|
||||
TableRef,
|
||||
};
|
||||
|
||||
use codec::{Decode, Encode};
|
||||
use sc_allocator::AllocationStats;
|
||||
use sc_executor_common::{
|
||||
error::{Error, MessageWithBacktrace, WasmError},
|
||||
runtime_blob::{DataSegmentsSnapshot, RuntimeBlob},
|
||||
sandbox,
|
||||
util::MemoryTransfer,
|
||||
wasm_runtime::{InvokeMethod, WasmInstance, WasmModule},
|
||||
};
|
||||
use sp_runtime_interface::unpack_ptr_and_len;
|
||||
use sp_sandbox::env as sandbox_env;
|
||||
use sp_wasm_interface::{
|
||||
Function, FunctionContext, MemoryId, Pointer, Result as WResult, Sandbox, WordSize,
|
||||
};
|
||||
use sp_wasm_interface::{Function, FunctionContext, Pointer, Result as WResult, WordSize};
|
||||
|
||||
struct FunctionExecutor {
|
||||
sandbox_store: Rc<RefCell<sandbox::Store<wasmi::FuncRef>>>,
|
||||
heap: RefCell<sc_allocator::FreeingBumpHeapAllocator>,
|
||||
memory: MemoryRef,
|
||||
table: Option<TableRef>,
|
||||
host_functions: Arc<Vec<&'static dyn Function>>,
|
||||
allow_missing_func_imports: bool,
|
||||
missing_functions: Arc<Vec<String>>,
|
||||
@@ -58,18 +50,13 @@ impl FunctionExecutor {
|
||||
fn new(
|
||||
m: MemoryRef,
|
||||
heap_base: u32,
|
||||
t: Option<TableRef>,
|
||||
host_functions: Arc<Vec<&'static dyn Function>>,
|
||||
allow_missing_func_imports: bool,
|
||||
missing_functions: Arc<Vec<String>>,
|
||||
) -> Result<Self, Error> {
|
||||
Ok(FunctionExecutor {
|
||||
sandbox_store: Rc::new(RefCell::new(sandbox::Store::new(
|
||||
sandbox::SandboxBackend::Wasmi,
|
||||
))),
|
||||
heap: RefCell::new(sc_allocator::FreeingBumpHeapAllocator::new(heap_base)),
|
||||
memory: m,
|
||||
table: t,
|
||||
host_functions,
|
||||
allow_missing_func_imports,
|
||||
missing_functions,
|
||||
@@ -78,42 +65,6 @@ impl FunctionExecutor {
|
||||
}
|
||||
}
|
||||
|
||||
struct SandboxContext<'a> {
|
||||
executor: &'a mut FunctionExecutor,
|
||||
dispatch_thunk: wasmi::FuncRef,
|
||||
}
|
||||
|
||||
impl<'a> sandbox::SandboxContext for SandboxContext<'a> {
|
||||
fn invoke(
|
||||
&mut self,
|
||||
invoke_args_ptr: Pointer<u8>,
|
||||
invoke_args_len: WordSize,
|
||||
state: u32,
|
||||
func_idx: sandbox::SupervisorFuncIndex,
|
||||
) -> Result<i64, Error> {
|
||||
let result = wasmi::FuncInstance::invoke(
|
||||
&self.dispatch_thunk,
|
||||
&[
|
||||
RuntimeValue::I32(u32::from(invoke_args_ptr) as i32),
|
||||
RuntimeValue::I32(invoke_args_len as i32),
|
||||
RuntimeValue::I32(state as i32),
|
||||
RuntimeValue::I32(usize::from(func_idx) as i32),
|
||||
],
|
||||
self.executor,
|
||||
);
|
||||
|
||||
match result {
|
||||
Ok(Some(RuntimeValue::I64(val))) => Ok(val),
|
||||
Ok(_) => Err("Supervisor function returned unexpected result!".into()),
|
||||
Err(err) => Err(Error::Sandbox(err.to_string())),
|
||||
}
|
||||
}
|
||||
|
||||
fn supervisor_context(&mut self) -> &mut dyn FunctionContext {
|
||||
self.executor
|
||||
}
|
||||
}
|
||||
|
||||
impl FunctionContext for FunctionExecutor {
|
||||
fn read_memory_into(&self, address: Pointer<u8>, dest: &mut [u8]) -> WResult<()> {
|
||||
self.memory.get_into(address.into(), dest).map_err(|e| e.to_string())
|
||||
@@ -135,189 +86,11 @@ impl FunctionContext for FunctionExecutor {
|
||||
.with_direct_access_mut(|mem| heap.deallocate(mem, ptr).map_err(|e| e.to_string()))
|
||||
}
|
||||
|
||||
fn sandbox(&mut self) -> &mut dyn Sandbox {
|
||||
self
|
||||
}
|
||||
|
||||
fn register_panic_error_message(&mut self, message: &str) {
|
||||
self.panic_message = Some(message.to_owned());
|
||||
}
|
||||
}
|
||||
|
||||
impl Sandbox for FunctionExecutor {
|
||||
fn memory_get(
|
||||
&mut self,
|
||||
memory_id: MemoryId,
|
||||
offset: WordSize,
|
||||
buf_ptr: Pointer<u8>,
|
||||
buf_len: WordSize,
|
||||
) -> WResult<u32> {
|
||||
let sandboxed_memory =
|
||||
self.sandbox_store.borrow().memory(memory_id).map_err(|e| e.to_string())?;
|
||||
|
||||
let len = buf_len as usize;
|
||||
|
||||
let buffer = match sandboxed_memory.read(Pointer::new(offset as u32), len) {
|
||||
Err(_) => return Ok(sandbox_env::ERR_OUT_OF_BOUNDS),
|
||||
Ok(buffer) => buffer,
|
||||
};
|
||||
|
||||
if self.memory.set(buf_ptr.into(), &buffer).is_err() {
|
||||
return Ok(sandbox_env::ERR_OUT_OF_BOUNDS)
|
||||
}
|
||||
|
||||
Ok(sandbox_env::ERR_OK)
|
||||
}
|
||||
|
||||
fn memory_set(
|
||||
&mut self,
|
||||
memory_id: MemoryId,
|
||||
offset: WordSize,
|
||||
val_ptr: Pointer<u8>,
|
||||
val_len: WordSize,
|
||||
) -> WResult<u32> {
|
||||
let sandboxed_memory =
|
||||
self.sandbox_store.borrow().memory(memory_id).map_err(|e| e.to_string())?;
|
||||
|
||||
let len = val_len as usize;
|
||||
|
||||
#[allow(deprecated)]
|
||||
let buffer = match self.memory.get(val_ptr.into(), len) {
|
||||
Err(_) => return Ok(sandbox_env::ERR_OUT_OF_BOUNDS),
|
||||
Ok(buffer) => buffer,
|
||||
};
|
||||
|
||||
if sandboxed_memory.write_from(Pointer::new(offset as u32), &buffer).is_err() {
|
||||
return Ok(sandbox_env::ERR_OUT_OF_BOUNDS)
|
||||
}
|
||||
|
||||
Ok(sandbox_env::ERR_OK)
|
||||
}
|
||||
|
||||
fn memory_teardown(&mut self, memory_id: MemoryId) -> WResult<()> {
|
||||
self.sandbox_store
|
||||
.borrow_mut()
|
||||
.memory_teardown(memory_id)
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
fn memory_new(&mut self, initial: u32, maximum: u32) -> WResult<MemoryId> {
|
||||
self.sandbox_store
|
||||
.borrow_mut()
|
||||
.new_memory(initial, maximum)
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
fn invoke(
|
||||
&mut self,
|
||||
instance_id: u32,
|
||||
export_name: &str,
|
||||
mut args: &[u8],
|
||||
return_val: Pointer<u8>,
|
||||
return_val_len: WordSize,
|
||||
state: u32,
|
||||
) -> WResult<u32> {
|
||||
trace!(target: "sp-sandbox", "invoke, instance_idx={}", instance_id);
|
||||
|
||||
// Deserialize arguments and convert them into wasmi types.
|
||||
let args = Vec::<sp_wasm_interface::Value>::decode(&mut args)
|
||||
.map_err(|_| "Can't decode serialized arguments for the invocation")?
|
||||
.into_iter()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let instance =
|
||||
self.sandbox_store.borrow().instance(instance_id).map_err(|e| e.to_string())?;
|
||||
|
||||
let dispatch_thunk = self
|
||||
.sandbox_store
|
||||
.borrow()
|
||||
.dispatch_thunk(instance_id)
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
match instance.invoke(
|
||||
export_name,
|
||||
&args,
|
||||
state,
|
||||
&mut SandboxContext { dispatch_thunk, executor: self },
|
||||
) {
|
||||
Ok(None) => Ok(sandbox_env::ERR_OK),
|
||||
Ok(Some(val)) => {
|
||||
// Serialize return value and write it back into the memory.
|
||||
sp_wasm_interface::ReturnValue::Value(val).using_encoded(|val| {
|
||||
if val.len() > return_val_len as usize {
|
||||
return Err("Return value buffer is too small".into())
|
||||
}
|
||||
self.write_memory(return_val, val).map_err(|_| "Return value buffer is OOB")?;
|
||||
Ok(sandbox_env::ERR_OK)
|
||||
})
|
||||
},
|
||||
Err(_) => Ok(sandbox_env::ERR_EXECUTION),
|
||||
}
|
||||
}
|
||||
|
||||
fn instance_teardown(&mut self, instance_id: u32) -> WResult<()> {
|
||||
self.sandbox_store
|
||||
.borrow_mut()
|
||||
.instance_teardown(instance_id)
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
fn instance_new(
|
||||
&mut self,
|
||||
dispatch_thunk_id: u32,
|
||||
wasm: &[u8],
|
||||
raw_env_def: &[u8],
|
||||
state: u32,
|
||||
) -> WResult<u32> {
|
||||
// Extract a dispatch thunk from instance's table by the specified index.
|
||||
let dispatch_thunk = {
|
||||
let table = self
|
||||
.table
|
||||
.as_ref()
|
||||
.ok_or("Runtime doesn't have a table; sandbox is unavailable")?;
|
||||
table
|
||||
.get(dispatch_thunk_id)
|
||||
.map_err(|_| "dispatch_thunk_idx is out of the table bounds")?
|
||||
.ok_or("dispatch_thunk_idx points on an empty table entry")?
|
||||
};
|
||||
|
||||
let guest_env =
|
||||
match sandbox::GuestEnvironment::decode(&*self.sandbox_store.borrow(), raw_env_def) {
|
||||
Ok(guest_env) => guest_env,
|
||||
Err(_) => return Ok(sandbox_env::ERR_MODULE as u32),
|
||||
};
|
||||
|
||||
let store = self.sandbox_store.clone();
|
||||
let result = store.borrow_mut().instantiate(
|
||||
wasm,
|
||||
guest_env,
|
||||
state,
|
||||
&mut SandboxContext { executor: self, dispatch_thunk: dispatch_thunk.clone() },
|
||||
);
|
||||
|
||||
let instance_idx_or_err_code =
|
||||
match result.map(|i| i.register(&mut store.borrow_mut(), dispatch_thunk)) {
|
||||
Ok(instance_idx) => instance_idx,
|
||||
Err(sandbox::InstantiationError::StartTrapped) => sandbox_env::ERR_EXECUTION,
|
||||
Err(_) => sandbox_env::ERR_MODULE,
|
||||
};
|
||||
|
||||
Ok(instance_idx_or_err_code)
|
||||
}
|
||||
|
||||
fn get_global_val(
|
||||
&self,
|
||||
instance_idx: u32,
|
||||
name: &str,
|
||||
) -> WResult<Option<sp_wasm_interface::Value>> {
|
||||
self.sandbox_store
|
||||
.borrow()
|
||||
.instance(instance_idx)
|
||||
.map(|i| i.get_global_val(name))
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
/// Will be used on initialization of a module to resolve function and memory imports.
|
||||
struct Resolver<'a> {
|
||||
/// All the hot functions that we export for the WASM blob.
|
||||
@@ -502,7 +275,6 @@ fn call_in_wasm_module(
|
||||
let mut function_executor = FunctionExecutor::new(
|
||||
memory.clone(),
|
||||
heap_base,
|
||||
table.clone(),
|
||||
host_functions,
|
||||
allow_missing_func_imports,
|
||||
missing_functions,
|
||||
|
||||
@@ -14,10 +14,8 @@ targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[dependencies]
|
||||
cfg-if = "1.0"
|
||||
codec = { package = "parity-scale-codec", version = "3.0.0" }
|
||||
libc = "0.2.121"
|
||||
log = "0.4.17"
|
||||
parity-wasm = "0.45"
|
||||
|
||||
# When bumping wasmtime do not forget to also bump rustix
|
||||
# to exactly the same version as used by wasmtime!
|
||||
@@ -32,7 +30,6 @@ wasmtime = { version = "1.0.0", default-features = false, features = [
|
||||
sc-allocator = { version = "4.1.0-dev", path = "../../allocator" }
|
||||
sc-executor-common = { version = "0.10.0-dev", path = "../common" }
|
||||
sp-runtime-interface = { version = "7.0.0", path = "../../../primitives/runtime-interface" }
|
||||
sp-sandbox = { version = "0.10.0-dev", path = "../../../primitives/sandbox" }
|
||||
sp-wasm-interface = { version = "7.0.0", path = "../../../primitives/wasm-interface" }
|
||||
|
||||
# Here we include the rustix crate in the exactly same semver-compatible version as used by
|
||||
@@ -50,3 +47,4 @@ sc-runtime-test = { version = "2.0.0", path = "../runtime-test" }
|
||||
sp-io = { version = "7.0.0", path = "../../../primitives/io" }
|
||||
tempfile = "3.3.0"
|
||||
paste = "1.0"
|
||||
codec = { package = "parity-scale-codec", version = "3.0.0" }
|
||||
|
||||
@@ -19,33 +19,17 @@
|
||||
//! This module defines `HostState` and `HostContext` structs which provide logic and state
|
||||
//! required for execution of host.
|
||||
|
||||
use log::trace;
|
||||
use wasmtime::{Caller, Func, Val};
|
||||
use wasmtime::Caller;
|
||||
|
||||
use codec::{Decode, Encode};
|
||||
use sc_allocator::{AllocationStats, FreeingBumpHeapAllocator};
|
||||
use sc_executor_common::{
|
||||
error::Result,
|
||||
sandbox::{self, SupervisorFuncIndex},
|
||||
util::MemoryTransfer,
|
||||
};
|
||||
use sp_sandbox::env as sandbox_env;
|
||||
use sp_wasm_interface::{FunctionContext, MemoryId, Pointer, Sandbox, WordSize};
|
||||
use sp_wasm_interface::{Pointer, WordSize};
|
||||
|
||||
use crate::{runtime::StoreData, util};
|
||||
|
||||
// The sandbox store is inside of a Option<Box<..>>> so that we can temporarily borrow it.
|
||||
struct SandboxStore(Option<Box<sandbox::Store<Func>>>);
|
||||
|
||||
// There are a bunch of `Rc`s within the sandbox store, however we only manipulate
|
||||
// those within one thread so this should be safe.
|
||||
unsafe impl Send for SandboxStore {}
|
||||
|
||||
/// The state required to construct a HostContext context. The context only lasts for one host
|
||||
/// call, whereas the state is maintained for the duration of a Wasm runtime call, which may make
|
||||
/// many different host calls that must share state.
|
||||
pub struct HostState {
|
||||
sandbox_store: SandboxStore,
|
||||
allocator: FreeingBumpHeapAllocator,
|
||||
panic_message: Option<String>,
|
||||
}
|
||||
@@ -53,13 +37,7 @@ pub struct HostState {
|
||||
impl HostState {
|
||||
/// Constructs a new `HostState`.
|
||||
pub fn new(allocator: FreeingBumpHeapAllocator) -> Self {
|
||||
HostState {
|
||||
sandbox_store: SandboxStore(Some(Box::new(sandbox::Store::new(
|
||||
sandbox::SandboxBackend::TryWasmer,
|
||||
)))),
|
||||
allocator,
|
||||
panic_message: None,
|
||||
}
|
||||
HostState { allocator, panic_message: None }
|
||||
}
|
||||
|
||||
/// Takes the error message out of the host state, leaving a `None` in its place.
|
||||
@@ -80,35 +58,12 @@ pub(crate) struct HostContext<'a> {
|
||||
}
|
||||
|
||||
impl<'a> HostContext<'a> {
|
||||
fn host_state(&self) -> &HostState {
|
||||
self.caller
|
||||
.data()
|
||||
.host_state()
|
||||
.expect("host state is not empty when calling a function in wasm; qed")
|
||||
}
|
||||
|
||||
fn host_state_mut(&mut self) -> &mut HostState {
|
||||
self.caller
|
||||
.data_mut()
|
||||
.host_state_mut()
|
||||
.expect("host state is not empty when calling a function in wasm; qed")
|
||||
}
|
||||
|
||||
fn sandbox_store(&self) -> &sandbox::Store<Func> {
|
||||
self.host_state()
|
||||
.sandbox_store
|
||||
.0
|
||||
.as_ref()
|
||||
.expect("sandbox store is only empty when temporarily borrowed")
|
||||
}
|
||||
|
||||
fn sandbox_store_mut(&mut self) -> &mut sandbox::Store<Func> {
|
||||
self.host_state_mut()
|
||||
.sandbox_store
|
||||
.0
|
||||
.as_mut()
|
||||
.expect("sandbox store is only empty when temporarily borrowed")
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> sp_wasm_interface::FunctionContext for HostContext<'a> {
|
||||
@@ -144,233 +99,7 @@ impl<'a> sp_wasm_interface::FunctionContext for HostContext<'a> {
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
fn sandbox(&mut self) -> &mut dyn Sandbox {
|
||||
self
|
||||
}
|
||||
|
||||
fn register_panic_error_message(&mut self, message: &str) {
|
||||
self.host_state_mut().panic_message = Some(message.to_owned());
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Sandbox for HostContext<'a> {
|
||||
fn memory_get(
|
||||
&mut self,
|
||||
memory_id: MemoryId,
|
||||
offset: WordSize,
|
||||
buf_ptr: Pointer<u8>,
|
||||
buf_len: WordSize,
|
||||
) -> sp_wasm_interface::Result<u32> {
|
||||
let sandboxed_memory = self.sandbox_store().memory(memory_id).map_err(|e| e.to_string())?;
|
||||
|
||||
let len = buf_len as usize;
|
||||
|
||||
let buffer = match sandboxed_memory.read(Pointer::new(offset as u32), len) {
|
||||
Err(_) => return Ok(sandbox_env::ERR_OUT_OF_BOUNDS),
|
||||
Ok(buffer) => buffer,
|
||||
};
|
||||
|
||||
if util::write_memory_from(&mut self.caller, buf_ptr, &buffer).is_err() {
|
||||
return Ok(sandbox_env::ERR_OUT_OF_BOUNDS)
|
||||
}
|
||||
|
||||
Ok(sandbox_env::ERR_OK)
|
||||
}
|
||||
|
||||
fn memory_set(
|
||||
&mut self,
|
||||
memory_id: MemoryId,
|
||||
offset: WordSize,
|
||||
val_ptr: Pointer<u8>,
|
||||
val_len: WordSize,
|
||||
) -> sp_wasm_interface::Result<u32> {
|
||||
let sandboxed_memory = self.sandbox_store().memory(memory_id).map_err(|e| e.to_string())?;
|
||||
|
||||
let len = val_len as usize;
|
||||
|
||||
let buffer = match util::read_memory(&self.caller, val_ptr, len) {
|
||||
Err(_) => return Ok(sandbox_env::ERR_OUT_OF_BOUNDS),
|
||||
Ok(buffer) => buffer,
|
||||
};
|
||||
|
||||
if sandboxed_memory.write_from(Pointer::new(offset as u32), &buffer).is_err() {
|
||||
return Ok(sandbox_env::ERR_OUT_OF_BOUNDS)
|
||||
}
|
||||
|
||||
Ok(sandbox_env::ERR_OK)
|
||||
}
|
||||
|
||||
fn memory_teardown(&mut self, memory_id: MemoryId) -> sp_wasm_interface::Result<()> {
|
||||
self.sandbox_store_mut().memory_teardown(memory_id).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
fn memory_new(&mut self, initial: u32, maximum: u32) -> sp_wasm_interface::Result<u32> {
|
||||
self.sandbox_store_mut().new_memory(initial, maximum).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
fn invoke(
|
||||
&mut self,
|
||||
instance_id: u32,
|
||||
export_name: &str,
|
||||
mut args: &[u8],
|
||||
return_val: Pointer<u8>,
|
||||
return_val_len: u32,
|
||||
state: u32,
|
||||
) -> sp_wasm_interface::Result<u32> {
|
||||
trace!(target: "sp-sandbox", "invoke, instance_idx={}", instance_id);
|
||||
|
||||
// Deserialize arguments and convert them into wasmi types.
|
||||
let args = Vec::<sp_wasm_interface::Value>::decode(&mut args)
|
||||
.map_err(|_| "Can't decode serialized arguments for the invocation")?
|
||||
.into_iter()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let instance = self.sandbox_store().instance(instance_id).map_err(|e| e.to_string())?;
|
||||
|
||||
let dispatch_thunk =
|
||||
self.sandbox_store().dispatch_thunk(instance_id).map_err(|e| e.to_string())?;
|
||||
|
||||
let result = instance.invoke(
|
||||
export_name,
|
||||
&args,
|
||||
state,
|
||||
&mut SandboxContext { host_context: self, dispatch_thunk },
|
||||
);
|
||||
|
||||
match result {
|
||||
Ok(None) => Ok(sandbox_env::ERR_OK),
|
||||
Ok(Some(val)) => {
|
||||
// Serialize return value and write it back into the memory.
|
||||
sp_wasm_interface::ReturnValue::Value(val.into()).using_encoded(|val| {
|
||||
if val.len() > return_val_len as usize {
|
||||
return Err("Return value buffer is too small".into())
|
||||
}
|
||||
<HostContext as FunctionContext>::write_memory(self, return_val, val)
|
||||
.map_err(|_| "can't write return value")?;
|
||||
Ok(sandbox_env::ERR_OK)
|
||||
})
|
||||
},
|
||||
Err(_) => Ok(sandbox_env::ERR_EXECUTION),
|
||||
}
|
||||
}
|
||||
|
||||
fn instance_teardown(&mut self, instance_id: u32) -> sp_wasm_interface::Result<()> {
|
||||
self.sandbox_store_mut()
|
||||
.instance_teardown(instance_id)
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
fn instance_new(
|
||||
&mut self,
|
||||
dispatch_thunk_id: u32,
|
||||
wasm: &[u8],
|
||||
raw_env_def: &[u8],
|
||||
state: u32,
|
||||
) -> sp_wasm_interface::Result<u32> {
|
||||
// Extract a dispatch thunk from the instance's table by the specified index.
|
||||
let dispatch_thunk = {
|
||||
let table = self
|
||||
.caller
|
||||
.data()
|
||||
.table()
|
||||
.ok_or("Runtime doesn't have a table; sandbox is unavailable")?;
|
||||
let table_item = table.get(&mut self.caller, dispatch_thunk_id);
|
||||
|
||||
*table_item
|
||||
.ok_or("dispatch_thunk_id is out of bounds")?
|
||||
.funcref()
|
||||
.ok_or("dispatch_thunk_idx should be a funcref")?
|
||||
.ok_or("dispatch_thunk_idx should point to actual func")?
|
||||
};
|
||||
|
||||
let guest_env = match sandbox::GuestEnvironment::decode(self.sandbox_store(), raw_env_def) {
|
||||
Ok(guest_env) => guest_env,
|
||||
Err(_) => return Ok(sandbox_env::ERR_MODULE as u32),
|
||||
};
|
||||
|
||||
let mut store = self
|
||||
.host_state_mut()
|
||||
.sandbox_store
|
||||
.0
|
||||
.take()
|
||||
.expect("sandbox store is only empty when borrowed");
|
||||
|
||||
// Catch any potential panics so that we can properly restore the sandbox store
|
||||
// which we've destructively borrowed.
|
||||
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
|
||||
store.instantiate(
|
||||
wasm,
|
||||
guest_env,
|
||||
state,
|
||||
&mut SandboxContext { host_context: self, dispatch_thunk },
|
||||
)
|
||||
}));
|
||||
|
||||
self.host_state_mut().sandbox_store.0 = Some(store);
|
||||
|
||||
let result = match result {
|
||||
Ok(result) => result,
|
||||
Err(error) => std::panic::resume_unwind(error),
|
||||
};
|
||||
|
||||
let instance_idx_or_err_code = match result {
|
||||
Ok(instance) => instance.register(self.sandbox_store_mut(), dispatch_thunk),
|
||||
Err(sandbox::InstantiationError::StartTrapped) => sandbox_env::ERR_EXECUTION,
|
||||
Err(_) => sandbox_env::ERR_MODULE,
|
||||
};
|
||||
|
||||
Ok(instance_idx_or_err_code as u32)
|
||||
}
|
||||
|
||||
fn get_global_val(
|
||||
&self,
|
||||
instance_idx: u32,
|
||||
name: &str,
|
||||
) -> sp_wasm_interface::Result<Option<sp_wasm_interface::Value>> {
|
||||
self.sandbox_store()
|
||||
.instance(instance_idx)
|
||||
.map(|i| i.get_global_val(name))
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
struct SandboxContext<'a, 'b> {
|
||||
host_context: &'a mut HostContext<'b>,
|
||||
dispatch_thunk: Func,
|
||||
}
|
||||
|
||||
impl<'a, 'b> sandbox::SandboxContext for SandboxContext<'a, 'b> {
|
||||
fn invoke(
|
||||
&mut self,
|
||||
invoke_args_ptr: Pointer<u8>,
|
||||
invoke_args_len: WordSize,
|
||||
state: u32,
|
||||
func_idx: SupervisorFuncIndex,
|
||||
) -> Result<i64> {
|
||||
let mut ret_vals = [Val::null()];
|
||||
let result = self.dispatch_thunk.call(
|
||||
&mut self.host_context.caller,
|
||||
&[
|
||||
Val::I32(u32::from(invoke_args_ptr) as i32),
|
||||
Val::I32(invoke_args_len as i32),
|
||||
Val::I32(state as i32),
|
||||
Val::I32(usize::from(func_idx) as i32),
|
||||
],
|
||||
&mut ret_vals,
|
||||
);
|
||||
|
||||
match result {
|
||||
Ok(()) =>
|
||||
if let Some(ret_val) = ret_vals[0].i64() {
|
||||
Ok(ret_val)
|
||||
} else {
|
||||
Err("Supervisor function returned unexpected result!".into())
|
||||
},
|
||||
Err(err) => Err(err.to_string().into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn supervisor_context(&mut self) -> &mut dyn FunctionContext {
|
||||
self.host_context
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,11 +56,6 @@ pub(crate) struct StoreData {
|
||||
}
|
||||
|
||||
impl StoreData {
|
||||
/// Returns a reference to the host state.
|
||||
pub fn host_state(&self) -> Option<&HostState> {
|
||||
self.host_state.as_ref()
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the host state.
|
||||
pub fn host_state_mut(&mut self) -> Option<&mut HostState> {
|
||||
self.host_state.as_mut()
|
||||
@@ -70,11 +65,6 @@ impl StoreData {
|
||||
pub fn memory(&self) -> Memory {
|
||||
self.memory.expect("memory is always set; qed")
|
||||
}
|
||||
|
||||
/// Returns the host table.
|
||||
pub fn table(&self) -> Option<Table> {
|
||||
self.table
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) type Store = wasmtime::Store<StoreData>;
|
||||
|
||||
@@ -48,24 +48,6 @@ pub fn into_wasmtime_val(value: Value) -> wasmtime::Val {
|
||||
}
|
||||
}
|
||||
|
||||
/// Read data from a slice of memory into a newly allocated buffer.
|
||||
///
|
||||
/// Returns an error if the read would go out of the memory bounds.
|
||||
pub(crate) fn read_memory(
|
||||
ctx: impl AsContext<Data = StoreData>,
|
||||
source_addr: Pointer<u8>,
|
||||
size: usize,
|
||||
) -> Result<Vec<u8>> {
|
||||
let range =
|
||||
checked_range(source_addr.into(), size, ctx.as_context().data().memory().data_size(&ctx))
|
||||
.ok_or_else(|| Error::Other("memory read is out of bounds".into()))?;
|
||||
|
||||
let mut buffer = vec![0; range.len()];
|
||||
read_memory_into(ctx, source_addr, &mut buffer)?;
|
||||
|
||||
Ok(buffer)
|
||||
}
|
||||
|
||||
/// Read data from the instance memory into a slice.
|
||||
///
|
||||
/// Returns an error if the read would go out of the memory bounds.
|
||||
|
||||
Reference in New Issue
Block a user