Remove sandboxing host function interface (#12852)

* Remove sandboxing interface

* Remove unused struct
This commit is contained in:
Alexander Theißen
2022-12-07 13:48:30 +01:00
committed by GitHub
parent 198faaa6f9
commit 32578cb010
31 changed files with 34 additions and 4478 deletions
-94
View File
@@ -1611,99 +1611,6 @@ mod tracing_setup {
pub use tracing_setup::init_tracing;
/// Wasm-only interface that provides functions for interacting with the sandbox.
#[runtime_interface(wasm_only)]
pub trait Sandbox {
/// Instantiate a new sandbox instance with the given `wasm_code`.
fn instantiate(
&mut self,
dispatch_thunk: u32,
wasm_code: &[u8],
env_def: &[u8],
state_ptr: Pointer<u8>,
) -> u32 {
self.sandbox()
.instance_new(dispatch_thunk, wasm_code, env_def, state_ptr.into())
.expect("Failed to instantiate a new sandbox")
}
/// Invoke `function` in the sandbox with `sandbox_idx`.
fn invoke(
&mut self,
instance_idx: u32,
function: &str,
args: &[u8],
return_val_ptr: Pointer<u8>,
return_val_len: u32,
state_ptr: Pointer<u8>,
) -> u32 {
self.sandbox()
.invoke(instance_idx, function, args, return_val_ptr, return_val_len, state_ptr.into())
.expect("Failed to invoke function with sandbox")
}
/// Create a new memory instance with the given `initial` and `maximum` size.
fn memory_new(&mut self, initial: u32, maximum: u32) -> u32 {
self.sandbox()
.memory_new(initial, maximum)
.expect("Failed to create new memory with sandbox")
}
/// Get the memory starting at `offset` from the instance with `memory_idx` into the buffer.
fn memory_get(
&mut self,
memory_idx: u32,
offset: u32,
buf_ptr: Pointer<u8>,
buf_len: u32,
) -> u32 {
self.sandbox()
.memory_get(memory_idx, offset, buf_ptr, buf_len)
.expect("Failed to get memory with sandbox")
}
/// Set the memory in the given `memory_idx` to the given value at `offset`.
fn memory_set(
&mut self,
memory_idx: u32,
offset: u32,
val_ptr: Pointer<u8>,
val_len: u32,
) -> u32 {
self.sandbox()
.memory_set(memory_idx, offset, val_ptr, val_len)
.expect("Failed to set memory with sandbox")
}
/// Teardown the memory instance with the given `memory_idx`.
fn memory_teardown(&mut self, memory_idx: u32) {
self.sandbox()
.memory_teardown(memory_idx)
.expect("Failed to teardown memory with sandbox")
}
/// Teardown the sandbox instance with the given `instance_idx`.
fn instance_teardown(&mut self, instance_idx: u32) {
self.sandbox()
.instance_teardown(instance_idx)
.expect("Failed to teardown sandbox instance")
}
/// Get the value from a global with the given `name`. The sandbox is determined by the given
/// `instance_idx`.
///
/// Returns `Some(_)` when the requested global variable could be found.
fn get_global_val(
&mut self,
instance_idx: u32,
name: &str,
) -> Option<sp_wasm_interface::Value> {
self.sandbox()
.get_global_val(instance_idx, name)
.expect("Failed to get global from sandbox")
}
}
/// Allocator used by Substrate when executing the Wasm runtime.
#[cfg(all(target_arch = "wasm32", not(feature = "std")))]
struct WasmAllocator;
@@ -1779,7 +1686,6 @@ pub type SubstrateHostFunctions = (
allocator::HostFunctions,
panic_handler::HostFunctions,
logging::HostFunctions,
sandbox::HostFunctions,
crate::trie::HostFunctions,
offchain_index::HostFunctions,
transaction_index::HostFunctions,
-40
View File
@@ -1,40 +0,0 @@
[package]
name = "sp-sandbox"
version = "0.10.0-dev"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2021"
license = "Apache-2.0"
homepage = "https://substrate.io"
repository = "https://github.com/paritytech/substrate/"
description = "This crate provides means to instantiate and execute wasm modules."
readme = "README.md"
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false }
log = { version = "0.4", default-features = false }
wasmi = { version = "0.13", default-features = false }
sp-core = { version = "7.0.0", default-features = false, path = "../core" }
sp-io = { version = "7.0.0", default-features = false, path = "../io" }
sp-std = { version = "5.0.0", default-features = false, path = "../std" }
sp-wasm-interface = { version = "7.0.0", default-features = false, path = "../wasm-interface" }
[dev-dependencies]
assert_matches = "1.3.0"
wat = "1.0"
[features]
default = ["std"]
std = [
"codec/std",
"log/std",
"sp-core/std",
"sp-io/std",
"sp-std/std",
"sp-wasm-interface/std",
"wasmi/std",
]
strict = []
wasmer-sandbox = []
-21
View File
@@ -1,21 +0,0 @@
This crate provides means to instantiate and execute wasm modules.
It works even when the user of this library executes from
inside the wasm VM. In this case the same VM is used for execution
of both the sandbox owner and the sandboxed module, without compromising security
and without the performance penalty of full wasm emulation inside wasm.
This is achieved by using bindings to the wasm VM, which are published by the host API.
This API is thin and consists of only a handful functions. It contains functions for instantiating
modules and executing them, but doesn't contain functions for inspecting the module
structure. The user of this library is supposed to read the wasm module.
When this crate is used in the `std` environment all these functions are implemented by directly
calling the wasm VM.
Examples of possible use-cases for this library are not limited to the following:
- implementing smart-contract runtimes that use wasm for contract code
- executing a wasm substrate runtime inside of a wasm parachain
License: Apache-2.0
@@ -1,478 +0,0 @@
// This file is part of Substrate.
// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! An embedded WASM executor utilizing `wasmi`.
use alloc::string::String;
use wasmi::{
memory_units::Pages, Externals, FuncInstance, FuncRef, GlobalDescriptor, GlobalRef,
ImportResolver, MemoryDescriptor, MemoryInstance, MemoryRef, Module, ModuleInstance, ModuleRef,
RuntimeArgs, RuntimeValue, Signature, TableDescriptor, TableRef, Trap,
};
use sp_std::{
borrow::ToOwned, collections::btree_map::BTreeMap, fmt, marker::PhantomData, prelude::*,
};
use crate::{Error, HostError, HostFuncType, ReturnValue, Value, TARGET};
/// The linear memory used by the sandbox.
#[derive(Clone)]
pub struct Memory {
memref: MemoryRef,
}
impl super::SandboxMemory for Memory {
fn new(initial: u32, maximum: Option<u32>) -> Result<Memory, Error> {
Ok(Memory {
memref: MemoryInstance::alloc(
Pages(initial as usize),
maximum.map(|m| Pages(m as usize)),
)
.map_err(|_| Error::Module)?,
})
}
fn get(&self, ptr: u32, buf: &mut [u8]) -> Result<(), Error> {
self.memref.get_into(ptr, buf).map_err(|_| Error::OutOfBounds)?;
Ok(())
}
fn set(&self, ptr: u32, value: &[u8]) -> Result<(), Error> {
self.memref.set(ptr, value).map_err(|_| Error::OutOfBounds)?;
Ok(())
}
}
struct HostFuncIndex(usize);
struct DefinedHostFunctions<T> {
funcs: Vec<HostFuncType<T>>,
}
impl<T> Clone for DefinedHostFunctions<T> {
fn clone(&self) -> DefinedHostFunctions<T> {
DefinedHostFunctions { funcs: self.funcs.clone() }
}
}
impl<T> DefinedHostFunctions<T> {
fn new() -> DefinedHostFunctions<T> {
DefinedHostFunctions { funcs: Vec::new() }
}
fn define(&mut self, f: HostFuncType<T>) -> HostFuncIndex {
let idx = self.funcs.len();
self.funcs.push(f);
HostFuncIndex(idx)
}
}
#[derive(Debug)]
struct DummyHostError;
impl fmt::Display for DummyHostError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "DummyHostError")
}
}
impl wasmi::HostError for DummyHostError {}
struct GuestExternals<'a, T: 'a> {
state: &'a mut T,
defined_host_functions: &'a DefinedHostFunctions<T>,
}
impl<'a, T> Externals for GuestExternals<'a, T> {
fn invoke_index(
&mut self,
index: usize,
args: RuntimeArgs,
) -> Result<Option<RuntimeValue>, Trap> {
let args = args.as_ref().iter().cloned().map(to_interface).collect::<Vec<_>>();
let result = (self.defined_host_functions.funcs[index])(self.state, &args);
match result {
Ok(value) => Ok(match value {
ReturnValue::Value(v) => Some(to_wasmi(v)),
ReturnValue::Unit => None,
}),
Err(HostError) => Err(Trap::host(DummyHostError)),
}
}
}
enum ExternVal {
HostFunc(HostFuncIndex),
Memory(Memory),
}
/// A builder for the environment of the sandboxed WASM module.
pub struct EnvironmentDefinitionBuilder<T> {
map: BTreeMap<(Vec<u8>, Vec<u8>), ExternVal>,
defined_host_functions: DefinedHostFunctions<T>,
}
impl<T> super::SandboxEnvironmentBuilder<T, Memory> for EnvironmentDefinitionBuilder<T> {
fn new() -> EnvironmentDefinitionBuilder<T> {
EnvironmentDefinitionBuilder {
map: BTreeMap::new(),
defined_host_functions: DefinedHostFunctions::new(),
}
}
fn add_host_func<N1, N2>(&mut self, module: N1, field: N2, f: HostFuncType<T>)
where
N1: Into<Vec<u8>>,
N2: Into<Vec<u8>>,
{
let idx = self.defined_host_functions.define(f);
self.map.insert((module.into(), field.into()), ExternVal::HostFunc(idx));
}
fn add_memory<N1, N2>(&mut self, module: N1, field: N2, mem: Memory)
where
N1: Into<Vec<u8>>,
N2: Into<Vec<u8>>,
{
self.map.insert((module.into(), field.into()), ExternVal::Memory(mem));
}
}
impl<T> ImportResolver for EnvironmentDefinitionBuilder<T> {
fn resolve_func(
&self,
module_name: &str,
field_name: &str,
signature: &Signature,
) -> Result<FuncRef, wasmi::Error> {
let key = (module_name.as_bytes().to_owned(), field_name.as_bytes().to_owned());
let externval = self.map.get(&key).ok_or_else(|| {
log::debug!(target: TARGET, "Export {}:{} not found", module_name, field_name);
wasmi::Error::Instantiation(String::new())
})?;
let host_func_idx = match *externval {
ExternVal::HostFunc(ref idx) => idx,
_ => {
log::debug!(
target: TARGET,
"Export {}:{} is not a host func",
module_name,
field_name,
);
return Err(wasmi::Error::Instantiation(String::new()))
},
};
Ok(FuncInstance::alloc_host(signature.clone(), host_func_idx.0))
}
fn resolve_global(
&self,
_module_name: &str,
_field_name: &str,
_global_type: &GlobalDescriptor,
) -> Result<GlobalRef, wasmi::Error> {
log::debug!(target: TARGET, "Importing globals is not supported yet");
Err(wasmi::Error::Instantiation(String::new()))
}
fn resolve_memory(
&self,
module_name: &str,
field_name: &str,
_memory_type: &MemoryDescriptor,
) -> Result<MemoryRef, wasmi::Error> {
let key = (module_name.as_bytes().to_owned(), field_name.as_bytes().to_owned());
let externval = self.map.get(&key).ok_or_else(|| {
log::debug!(target: TARGET, "Export {}:{} not found", module_name, field_name);
wasmi::Error::Instantiation(String::new())
})?;
let memory = match *externval {
ExternVal::Memory(ref m) => m,
_ => {
log::debug!(
target: TARGET,
"Export {}:{} is not a memory",
module_name,
field_name,
);
return Err(wasmi::Error::Instantiation(String::new()))
},
};
Ok(memory.memref.clone())
}
fn resolve_table(
&self,
_module_name: &str,
_field_name: &str,
_table_type: &TableDescriptor,
) -> Result<TableRef, wasmi::Error> {
log::debug!("Importing tables is not supported yet");
Err(wasmi::Error::Instantiation(String::new()))
}
}
/// Sandboxed instance of a WASM module.
pub struct Instance<T> {
instance: ModuleRef,
defined_host_functions: DefinedHostFunctions<T>,
_marker: PhantomData<T>,
}
impl<T> super::SandboxInstance<T> for Instance<T> {
type Memory = Memory;
type EnvironmentBuilder = EnvironmentDefinitionBuilder<T>;
fn new(
code: &[u8],
env_def_builder: &EnvironmentDefinitionBuilder<T>,
state: &mut T,
) -> Result<Instance<T>, Error> {
let module = Module::from_buffer(code).map_err(|_| Error::Module)?;
let not_started_instance =
ModuleInstance::new(&module, env_def_builder).map_err(|_| Error::Module)?;
let defined_host_functions = env_def_builder.defined_host_functions.clone();
let instance = {
let mut externals =
GuestExternals { state, defined_host_functions: &defined_host_functions };
let instance =
not_started_instance.run_start(&mut externals).map_err(|_| Error::Execution)?;
instance
};
Ok(Instance { instance, defined_host_functions, _marker: PhantomData::<T> })
}
fn invoke(&mut self, name: &str, args: &[Value], state: &mut T) -> Result<ReturnValue, Error> {
let args = args.iter().cloned().map(to_wasmi).collect::<Vec<_>>();
let mut externals =
GuestExternals { state, defined_host_functions: &self.defined_host_functions };
let result = self.instance.invoke_export(name, &args, &mut externals);
match result {
Ok(None) => Ok(ReturnValue::Unit),
Ok(Some(val)) => Ok(ReturnValue::Value(to_interface(val))),
Err(_err) => Err(Error::Execution),
}
}
fn get_global_val(&self, name: &str) -> Option<Value> {
let global = self.instance.export_by_name(name)?.as_global()?.get();
Some(to_interface(global))
}
}
/// Convert the substrate value type to the wasmi value type.
fn to_wasmi(value: Value) -> RuntimeValue {
match value {
Value::I32(val) => RuntimeValue::I32(val),
Value::I64(val) => RuntimeValue::I64(val),
Value::F32(val) => RuntimeValue::F32(val.into()),
Value::F64(val) => RuntimeValue::F64(val.into()),
}
}
/// Convert the wasmi value type to the substrate value type.
fn to_interface(value: RuntimeValue) -> Value {
match value {
RuntimeValue::I32(val) => Value::I32(val),
RuntimeValue::I64(val) => Value::I64(val),
RuntimeValue::F32(val) => Value::F32(val.into()),
RuntimeValue::F64(val) => Value::F64(val.into()),
}
}
#[cfg(test)]
mod tests {
use super::{EnvironmentDefinitionBuilder, Instance};
use crate::{Error, HostError, ReturnValue, SandboxEnvironmentBuilder, SandboxInstance, Value};
use assert_matches::assert_matches;
fn execute_sandboxed(code: &[u8], args: &[Value]) -> Result<ReturnValue, HostError> {
struct State {
counter: u32,
}
fn env_assert(_e: &mut State, args: &[Value]) -> Result<ReturnValue, HostError> {
if args.len() != 1 {
return Err(HostError)
}
let condition = args[0].as_i32().ok_or_else(|| HostError)?;
if condition != 0 {
Ok(ReturnValue::Unit)
} else {
Err(HostError)
}
}
fn env_inc_counter(e: &mut State, args: &[Value]) -> Result<ReturnValue, HostError> {
if args.len() != 1 {
return Err(HostError)
}
let inc_by = args[0].as_i32().ok_or_else(|| HostError)?;
e.counter += inc_by as u32;
Ok(ReturnValue::Value(Value::I32(e.counter as i32)))
}
/// Function that takes one argument of any type and returns that value.
fn env_polymorphic_id(_e: &mut State, args: &[Value]) -> Result<ReturnValue, HostError> {
if args.len() != 1 {
return Err(HostError)
}
Ok(ReturnValue::Value(args[0]))
}
let mut state = State { counter: 0 };
let mut env_builder = EnvironmentDefinitionBuilder::new();
env_builder.add_host_func("env", "assert", env_assert);
env_builder.add_host_func("env", "inc_counter", env_inc_counter);
env_builder.add_host_func("env", "polymorphic_id", env_polymorphic_id);
let mut instance = Instance::new(code, &env_builder, &mut state)?;
let result = instance.invoke("call", args, &mut state);
result.map_err(|_| HostError)
}
#[test]
fn invoke_args() {
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();
let result =
execute_sandboxed(&code, &[Value::I32(0x12345678), Value::I64(0x1234567887654321)]);
assert!(result.is_ok());
}
#[test]
fn return_value() {
let code = wat::parse_str(
r#"
(module
(func (export "call") (param $x i32) (result i32)
(i32.add
(get_local $x)
(i32.const 1)
)
)
)
"#,
)
.unwrap();
let return_val = execute_sandboxed(&code, &[Value::I32(0x1336)]).unwrap();
assert_eq!(return_val, ReturnValue::Value(Value::I32(0x1337)));
}
#[test]
fn signatures_dont_matter() {
let code = wat::parse_str(
r#"
(module
(import "env" "polymorphic_id" (func $id_i32 (param i32) (result i32)))
(import "env" "polymorphic_id" (func $id_i64 (param i64) (result i64)))
(import "env" "assert" (func $assert (param i32)))
(func (export "call")
;; assert that we can actually call the "same" function with different
;; signatures.
(call $assert
(i32.eq
(call $id_i32
(i32.const 0x012345678)
)
(i32.const 0x012345678)
)
)
(call $assert
(i64.eq
(call $id_i64
(i64.const 0x0123456789abcdef)
)
(i64.const 0x0123456789abcdef)
)
)
)
)
"#,
)
.unwrap();
let return_val = execute_sandboxed(&code, &[]).unwrap();
assert_eq!(return_val, ReturnValue::Unit);
}
#[test]
fn cant_return_unmatching_type() {
fn env_returns_i32(_e: &mut (), _args: &[Value]) -> Result<ReturnValue, HostError> {
Ok(ReturnValue::Value(Value::I32(42)))
}
let mut env_builder = EnvironmentDefinitionBuilder::new();
env_builder.add_host_func("env", "returns_i32", env_returns_i32);
let code = wat::parse_str(
r#"
(module
;; It's actually returns i32, but imported as if it returned i64
(import "env" "returns_i32" (func $returns_i32 (result i64)))
(func (export "call")
(drop
(call $returns_i32)
)
)
)
"#,
)
.unwrap();
// It succeeds since we are able to import functions with types we want.
let mut instance = Instance::new(&code, &env_builder, &mut ()).unwrap();
// But this fails since we imported a function that returns i32 as if it returned i64.
assert_matches!(instance.invoke("call", &[], &mut ()), Err(Error::Execution));
}
}
-120
View File
@@ -1,120 +0,0 @@
// This file is part of Substrate.
// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Definition of a sandbox environment.
use codec::{Decode, Encode};
use sp_core::RuntimeDebug;
use sp_std::vec::Vec;
/// Error error that can be returned from host function.
#[derive(Encode, Decode, RuntimeDebug)]
pub struct HostError;
/// Describes an entity to define or import into the environment.
#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug)]
pub enum ExternEntity {
/// Function that is specified by an index in a default table of
/// a module that creates the sandbox.
#[codec(index = 1)]
Function(u32),
/// Linear memory that is specified by some identifier returned by sandbox
/// module upon creation new sandboxed memory.
#[codec(index = 2)]
Memory(u32),
}
/// An entry in a environment definition table.
///
/// Each entry has a two-level name and description of an entity
/// being defined.
#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug)]
pub struct Entry {
/// Module name of which corresponding entity being defined.
pub module_name: Vec<u8>,
/// Field name in which corresponding entity being defined.
pub field_name: Vec<u8>,
/// External entity being defined.
pub entity: ExternEntity,
}
/// Definition of runtime that could be used by sandboxed code.
#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug)]
pub struct EnvironmentDefinition {
/// Vector of all entries in the environment definition.
pub entries: Vec<Entry>,
}
/// Constant for specifying no limit when creating a sandboxed
/// memory instance. For FFI purposes.
pub const MEM_UNLIMITED: u32 = -1i32 as u32;
/// No error happened.
///
/// For FFI purposes.
pub const ERR_OK: u32 = 0;
/// Validation or instantiation error occurred when creating new
/// sandboxed module instance.
///
/// For FFI purposes.
pub const ERR_MODULE: u32 = -1i32 as u32;
/// Out-of-bounds access attempted with memory or table.
///
/// For FFI purposes.
pub const ERR_OUT_OF_BOUNDS: u32 = -2i32 as u32;
/// Execution error occurred (typically trap).
///
/// For FFI purposes.
pub const ERR_EXECUTION: u32 = -3i32 as u32;
#[cfg(test)]
mod tests {
use super::*;
use codec::Codec;
use std::fmt;
fn roundtrip<S: Codec + PartialEq + fmt::Debug>(s: S) {
let encoded = s.encode();
assert_eq!(S::decode(&mut &encoded[..]).unwrap(), s);
}
#[test]
fn env_def_roundtrip() {
roundtrip(EnvironmentDefinition { entries: vec![] });
roundtrip(EnvironmentDefinition {
entries: vec![Entry {
module_name: b"kernel"[..].into(),
field_name: b"memory"[..].into(),
entity: ExternEntity::Memory(1337),
}],
});
roundtrip(EnvironmentDefinition {
entries: vec![Entry {
module_name: b"env"[..].into(),
field_name: b"abort"[..].into(),
entity: ExternEntity::Function(228),
}],
});
}
}
@@ -1,274 +0,0 @@
// This file is part of Substrate.
// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! A WASM executor utilizing the sandbox runtime interface of the host.
use codec::{Decode, Encode};
use sp_io::sandbox;
use sp_std::{marker, mem, prelude::*, rc::Rc, slice, vec};
use crate::{env, Error, HostFuncType, ReturnValue, Value};
mod ffi {
use super::HostFuncType;
use sp_std::mem;
/// Index into the default table that points to a `HostFuncType`.
pub type HostFuncIndex = usize;
/// Coerce `HostFuncIndex` to a callable host function pointer.
///
/// # Safety
///
/// This function should be only called with a `HostFuncIndex` that was previously registered
/// in the environment definition. Typically this should only
/// be called with an argument received in `dispatch_thunk`.
pub unsafe fn coerce_host_index_to_func<T>(idx: HostFuncIndex) -> HostFuncType<T> {
// We need to ensure that sizes of a callable function pointer and host function index is
// indeed equal.
// We can't use `static_assertions` create because it makes compiler panic, fallback to
// runtime assert. const_assert!(mem::size_of::<HostFuncIndex>() ==
// mem::size_of::<HostFuncType<T>>());
assert!(mem::size_of::<HostFuncIndex>() == mem::size_of::<HostFuncType<T>>());
mem::transmute::<HostFuncIndex, HostFuncType<T>>(idx)
}
}
struct MemoryHandle {
memory_idx: u32,
}
impl Drop for MemoryHandle {
fn drop(&mut self) {
sandbox::memory_teardown(self.memory_idx);
}
}
/// The linear memory used by the sandbox.
#[derive(Clone)]
pub struct Memory {
// Handle to memory instance is wrapped to add reference-counting semantics
// to `Memory`.
handle: Rc<MemoryHandle>,
}
impl super::SandboxMemory for Memory {
fn new(initial: u32, maximum: Option<u32>) -> Result<Memory, Error> {
let maximum = if let Some(maximum) = maximum { maximum } else { env::MEM_UNLIMITED };
match sandbox::memory_new(initial, maximum) {
env::ERR_MODULE => Err(Error::Module),
memory_idx => Ok(Memory { handle: Rc::new(MemoryHandle { memory_idx }) }),
}
}
fn get(&self, offset: u32, buf: &mut [u8]) -> Result<(), Error> {
let result =
sandbox::memory_get(self.handle.memory_idx, offset, buf.as_mut_ptr(), buf.len() as u32);
match result {
env::ERR_OK => Ok(()),
env::ERR_OUT_OF_BOUNDS => Err(Error::OutOfBounds),
_ => unreachable!(),
}
}
fn set(&self, offset: u32, val: &[u8]) -> Result<(), Error> {
let result = sandbox::memory_set(
self.handle.memory_idx,
offset,
val.as_ptr() as _,
val.len() as u32,
);
match result {
env::ERR_OK => Ok(()),
env::ERR_OUT_OF_BOUNDS => Err(Error::OutOfBounds),
_ => unreachable!(),
}
}
}
/// A builder for the environment of the sandboxed WASM module.
pub struct EnvironmentDefinitionBuilder<T> {
env_def: env::EnvironmentDefinition,
retained_memories: Vec<Memory>,
_marker: marker::PhantomData<T>,
}
impl<T> EnvironmentDefinitionBuilder<T> {
fn add_entry<N1, N2>(&mut self, module: N1, field: N2, extern_entity: env::ExternEntity)
where
N1: Into<Vec<u8>>,
N2: Into<Vec<u8>>,
{
let entry = env::Entry {
module_name: module.into(),
field_name: field.into(),
entity: extern_entity,
};
self.env_def.entries.push(entry);
}
}
impl<T> super::SandboxEnvironmentBuilder<T, Memory> for EnvironmentDefinitionBuilder<T> {
fn new() -> EnvironmentDefinitionBuilder<T> {
EnvironmentDefinitionBuilder {
env_def: env::EnvironmentDefinition { entries: Vec::new() },
retained_memories: Vec::new(),
_marker: marker::PhantomData::<T>,
}
}
fn add_host_func<N1, N2>(&mut self, module: N1, field: N2, f: HostFuncType<T>)
where
N1: Into<Vec<u8>>,
N2: Into<Vec<u8>>,
{
let f = env::ExternEntity::Function(f as u32);
self.add_entry(module, field, f);
}
fn add_memory<N1, N2>(&mut self, module: N1, field: N2, mem: Memory)
where
N1: Into<Vec<u8>>,
N2: Into<Vec<u8>>,
{
// We need to retain memory to keep it alive while the EnvironmentDefinitionBuilder alive.
self.retained_memories.push(mem.clone());
let mem = env::ExternEntity::Memory(mem.handle.memory_idx as u32);
self.add_entry(module, field, mem);
}
}
/// Sandboxed instance of a WASM module.
pub struct Instance<T> {
instance_idx: u32,
_retained_memories: Vec<Memory>,
_marker: marker::PhantomData<T>,
}
/// The primary responsibility of this thunk is to deserialize arguments and
/// call the original function, specified by the index.
extern "C" fn dispatch_thunk<T>(
serialized_args_ptr: *const u8,
serialized_args_len: usize,
state: usize,
f: ffi::HostFuncIndex,
) -> u64 {
let serialized_args = unsafe {
if serialized_args_len == 0 {
&[]
} else {
slice::from_raw_parts(serialized_args_ptr, serialized_args_len)
}
};
let args = Vec::<Value>::decode(&mut &serialized_args[..]).expect(
"serialized args should be provided by the runtime;
correctly serialized data should be deserializable;
qed",
);
unsafe {
// This should be safe since `coerce_host_index_to_func` is called with an argument
// received in an `dispatch_thunk` implementation, so `f` should point
// on a valid host function.
let f = ffi::coerce_host_index_to_func(f);
// This should be safe since mutable reference to T is passed upon the invocation.
let state = &mut *(state as *mut T);
// Pass control flow to the designated function.
let result = f(state, &args).encode();
// Leak the result vector and return the pointer to return data.
let result_ptr = result.as_ptr() as u64;
let result_len = result.len() as u64;
mem::forget(result);
(result_ptr << 32) | result_len
}
}
impl<T> super::SandboxInstance<T> for Instance<T> {
type Memory = Memory;
type EnvironmentBuilder = EnvironmentDefinitionBuilder<T>;
fn new(
code: &[u8],
env_def_builder: &EnvironmentDefinitionBuilder<T>,
state: &mut T,
) -> Result<Instance<T>, Error> {
let serialized_env_def: Vec<u8> = env_def_builder.env_def.encode();
// It's very important to instantiate thunk with the right type.
let dispatch_thunk = dispatch_thunk::<T>;
let result = sandbox::instantiate(
dispatch_thunk as u32,
code,
&serialized_env_def,
state as *const T as _,
);
let instance_idx = match result {
env::ERR_MODULE => return Err(Error::Module),
env::ERR_EXECUTION => return Err(Error::Execution),
instance_idx => instance_idx,
};
// We need to retain memories to keep them alive while the Instance is alive.
let retained_memories = env_def_builder.retained_memories.clone();
Ok(Instance {
instance_idx,
_retained_memories: retained_memories,
_marker: marker::PhantomData::<T>,
})
}
fn invoke(&mut self, name: &str, args: &[Value], state: &mut T) -> Result<ReturnValue, Error> {
let serialized_args = args.to_vec().encode();
let mut return_val = vec![0u8; ReturnValue::ENCODED_MAX_SIZE];
let result = sandbox::invoke(
self.instance_idx,
name,
&serialized_args,
return_val.as_mut_ptr() as _,
return_val.len() as u32,
state as *const T as _,
);
match result {
env::ERR_OK => {
let return_val =
ReturnValue::decode(&mut &return_val[..]).map_err(|_| Error::Execution)?;
Ok(return_val)
},
env::ERR_EXECUTION => Err(Error::Execution),
_ => unreachable!(),
}
}
fn get_global_val(&self, name: &str) -> Option<Value> {
sandbox::get_global_val(self.instance_idx, name)
}
}
impl<T> Drop for Instance<T> {
fn drop(&mut self) {
sandbox::instance_teardown(self.instance_idx);
}
}
-190
View File
@@ -1,190 +0,0 @@
// This file is part of Substrate.
// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! This crate provides means to instantiate and execute wasm modules.
//!
//! It works even when the user of this library executes from
//! inside the wasm VM. In this case the same VM is used for execution
//! of both the sandbox owner and the sandboxed module, without compromising security
//! and without the performance penalty of full wasm emulation inside wasm.
//!
//! This is achieved by using bindings to the wasm VM, which are published by the host API.
//! This API is thin and consists of only a handful functions. It contains functions for
//! instantiating modules and executing them, but doesn't contain functions for inspecting the
//! module structure. The user of this library is supposed to read the wasm module.
//!
//! When this crate is used in the `std` environment all these functions are implemented by directly
//! calling the wasm VM.
//!
//! Examples of possible use-cases for this library are not limited to the following:
//!
//! - implementing smart-contract runtimes that use wasm for contract code
//! - executing a wasm substrate runtime inside of a wasm parachain
#![warn(missing_docs)]
#![cfg_attr(not(feature = "std"), no_std)]
extern crate alloc;
pub mod embedded_executor;
pub mod env;
#[cfg(not(feature = "std"))]
pub mod host_executor;
use sp_core::RuntimeDebug;
use sp_std::prelude::*;
pub use sp_wasm_interface::{ReturnValue, Value};
#[cfg(not(all(feature = "wasmer-sandbox", not(feature = "std"))))]
pub use self::embedded_executor as default_executor;
pub use self::env::HostError;
#[cfg(all(feature = "wasmer-sandbox", not(feature = "std")))]
pub use self::host_executor as default_executor;
/// The target used for logging.
const TARGET: &str = "runtime::sandbox";
/// Error that can occur while using this crate.
#[derive(RuntimeDebug)]
pub enum Error {
/// Module is not valid, couldn't be instantiated.
Module,
/// Access to a memory or table was made with an address or an index which is out of bounds.
///
/// Note that if wasm module makes an out-of-bounds access then trap will occur.
OutOfBounds,
/// Failed to invoke the start function or an exported function for some reason.
Execution,
}
impl From<Error> for HostError {
fn from(_e: Error) -> HostError {
HostError
}
}
/// Function pointer for specifying functions by the
/// supervisor in [`EnvironmentDefinitionBuilder`].
///
/// [`EnvironmentDefinitionBuilder`]: struct.EnvironmentDefinitionBuilder.html
pub type HostFuncType<T> = fn(&mut T, &[Value]) -> Result<ReturnValue, HostError>;
/// Reference to a sandboxed linear memory, that
/// will be used by the guest module.
///
/// The memory can't be directly accessed by supervisor, but only
/// through designated functions [`get`](SandboxMemory::get) and [`set`](SandboxMemory::set).
pub trait SandboxMemory: Sized + Clone {
/// Construct a new linear memory instance.
///
/// The memory allocated with initial number of pages specified by `initial`.
/// Minimal possible value for `initial` is 0 and maximum possible is `65536`.
/// (Since maximum addressable memory is 2<sup>32</sup> = 4GiB = 65536 * 64KiB).
///
/// It is possible to limit maximum number of pages this memory instance can have by specifying
/// `maximum`. If not specified, this memory instance would be able to allocate up to 4GiB.
///
/// Allocated memory is always zeroed.
fn new(initial: u32, maximum: Option<u32>) -> Result<Self, Error>;
/// Read a memory area at the address `ptr` with the size of the provided slice `buf`.
///
/// Returns `Err` if the range is out-of-bounds.
fn get(&self, ptr: u32, buf: &mut [u8]) -> Result<(), Error>;
/// Write a memory area at the address `ptr` with contents of the provided slice `buf`.
///
/// Returns `Err` if the range is out-of-bounds.
fn set(&self, ptr: u32, value: &[u8]) -> Result<(), Error>;
}
/// Struct that can be used for defining an environment for a sandboxed module.
///
/// The sandboxed module can access only the entities which were defined and passed
/// to the module at the instantiation time.
pub trait SandboxEnvironmentBuilder<State, Memory>: Sized {
/// Construct a new `EnvironmentDefinitionBuilder`.
fn new() -> Self;
/// Register a host function in this environment definition.
///
/// NOTE that there is no constraints on type of this function. An instance
/// can import function passed here with any signature it wants. It can even import
/// the same function (i.e. with same `module` and `field`) several times. It's up to
/// the user code to check or constrain the types of signatures.
fn add_host_func<N1, N2>(&mut self, module: N1, field: N2, f: HostFuncType<State>)
where
N1: Into<Vec<u8>>,
N2: Into<Vec<u8>>;
/// Register a memory in this environment definition.
fn add_memory<N1, N2>(&mut self, module: N1, field: N2, mem: Memory)
where
N1: Into<Vec<u8>>,
N2: Into<Vec<u8>>;
}
/// Sandboxed instance of a wasm module.
///
/// This instance can be used for invoking exported functions.
pub trait SandboxInstance<State>: Sized {
/// The memory type used for this sandbox.
type Memory: SandboxMemory;
/// The environment builder used to construct this sandbox.
type EnvironmentBuilder: SandboxEnvironmentBuilder<State, Self::Memory>;
/// Instantiate a module with the given [`EnvironmentDefinitionBuilder`]. It will
/// run the `start` function (if it is present in the module) with the given `state`.
///
/// Returns `Err(Error::Module)` if this module can't be instantiated with the given
/// environment. If execution of `start` function generated a trap, then `Err(Error::Execution)`
/// will be returned.
///
/// [`EnvironmentDefinitionBuilder`]: struct.EnvironmentDefinitionBuilder.html
fn new(
code: &[u8],
env_def_builder: &Self::EnvironmentBuilder,
state: &mut State,
) -> Result<Self, Error>;
/// Invoke an exported function with the given name.
///
/// # Errors
///
/// Returns `Err(Error::Execution)` if:
///
/// - An export function name isn't a proper utf8 byte sequence,
/// - This module doesn't have an exported function with the given name,
/// - If types of the arguments passed to the function doesn't match function signature then
/// trap occurs (as if the exported function was called via call_indirect),
/// - Trap occurred at the execution time.
fn invoke(
&mut self,
name: &str,
args: &[Value],
state: &mut State,
) -> Result<ReturnValue, Error>;
/// Get the value from a global with the given `name`.
///
/// Returns `Some(_)` if the global could be found.
fn get_global_val(&self, name: &str) -> Option<Value>;
}
@@ -303,9 +303,6 @@ pub trait FunctionContext {
fn allocate_memory(&mut self, size: WordSize) -> Result<Pointer<u8>>;
/// Deallocate a given memory instance.
fn deallocate_memory(&mut self, ptr: Pointer<u8>) -> Result<()>;
/// Provides access to the sandbox.
fn sandbox(&mut self) -> &mut dyn Sandbox;
/// Registers a panic error message within the executor.
///
/// This is meant to be used in situations where the runtime
@@ -330,60 +327,6 @@ pub trait FunctionContext {
fn register_panic_error_message(&mut self, message: &str);
}
/// Sandbox memory identifier.
pub type MemoryId = u32;
/// Something that provides access to the sandbox.
pub trait Sandbox {
/// Get sandbox memory from the `memory_id` instance at `offset` into the given buffer.
fn memory_get(
&mut self,
memory_id: MemoryId,
offset: WordSize,
buf_ptr: Pointer<u8>,
buf_len: WordSize,
) -> Result<u32>;
/// Set sandbox memory from the given value.
fn memory_set(
&mut self,
memory_id: MemoryId,
offset: WordSize,
val_ptr: Pointer<u8>,
val_len: WordSize,
) -> Result<u32>;
/// Delete a memory instance.
fn memory_teardown(&mut self, memory_id: MemoryId) -> Result<()>;
/// Create a new memory instance with the given `initial` size and the `maximum` size.
/// The size is given in wasm pages.
fn memory_new(&mut self, initial: u32, maximum: u32) -> Result<MemoryId>;
/// Invoke an exported function by a name.
fn invoke(
&mut self,
instance_id: u32,
export_name: &str,
args: &[u8],
return_val: Pointer<u8>,
return_val_len: WordSize,
state: u32,
) -> Result<u32>;
/// Delete a sandbox instance.
fn instance_teardown(&mut self, instance_id: u32) -> Result<()>;
/// Create a new sandbox instance.
fn instance_new(
&mut self,
dispatch_thunk_id: u32,
wasm: &[u8],
raw_env_def: &[u8],
state: u32,
) -> Result<u32>;
/// Get the value from a global with the given `name`. The sandbox is determined by the
/// given `instance_idx` instance.
///
/// Returns `Some(_)` when the requested global variable could be found.
fn get_global_val(&self, instance_idx: u32, name: &str) -> Result<Option<Value>>;
}
if_wasmtime_is_enabled! {
/// A trait used to statically register host callbacks with the WASM executor,
/// so that they call be called from within the runtime with minimal overhead.