mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-07-18 08:15:41 +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
@@ -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,
|
||||
|
||||
@@ -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 = []
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user