Extract execution engines definitions into their own crates (#4489)

* Clean imports in wasmi_execution

* Replace `interpret_runtime_api_result` with `pointer_and_len_from_u64`.

* Extract sc-executor-common crate

* Extract `sc-executor-wasmi` into its own crate

* Extract `sc-executor-wasmtime` into its own crate.

* Add missing headers.

* Clean and docs

* Docs for sc-executor-wasmi

* Expand a comment about sandboxing

* Fix assert_matches

* Rename (un)pack_ptr_and_len and move them into util module

* Remove wasmtime errors in sc-executor-common
This commit is contained in:
Sergei Pepyakin
2019-12-24 13:17:41 +01:00
committed by GitHub
parent b214b3f3e9
commit 1782fbbbba
25 changed files with 326 additions and 161 deletions
@@ -0,0 +1,557 @@
// Copyright 2017-2019 Parity Technologies (UK) Ltd.
// This file is part of Substrate.
// Substrate 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.
// Substrate 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 Substrate. If not, see <http://www.gnu.org/licenses/>.
//! This module implements a freeing-bump allocator.
//!
//! The algorithm is as follows:
//! We store `N` linked list heads, where `N` is the total number of sizes
//! of allocations to support. A simple set is powers of two from 8 bytes
//! to 16,777,216 bytes (2^3 - 2^24 inclusive), resulting in `N = 22`:
//!
//! ```ignore
//! let mut heads [u64; N] = [0; N];
//! fn size(n: u64) -> u64 { 8 << n }
//! let mut bumper = 0;
//! fn bump(n: u64) -> u64 { let res = bumper; bumper += n; res }
//! ```
//!
//! We assume there is a slab of heap to be allocated:
//!
//! ```ignore
//! let mut heap = [0u8; HEAP_SIZE];
//! ```
//!
//! Whenever we allocate, we select the lowest linked list item size that
//! will fit the allocation (i.e. the next highest power of two).
//! We then check to see if the linked list is empty. If empty, we use
//! the bump allocator to get the allocation with an extra 8 bytes
//! preceding it. We initialise those preceding 8 bytes to identify the
//! list to which it belongs. If it is not empty, we unlink the first item from
//! the linked list and then reset the 8 preceding bytes so they now record
//! the identity of the linked list.
//!
//! To deallocate we use the preceding 8 bytes of the allocation to knit
//! back the allocation into the linked list from the head.
use crate::error::{Error, Result};
use log::trace;
use std::convert::{TryFrom, TryInto};
use std::ops::Range;
use sp_wasm_interface::{Pointer, WordSize};
// The pointers need to be aligned to 8 bytes. This is because the
// maximum value type handled by wasm32 is u64.
const ALIGNMENT: u32 = 8;
// The pointer returned by `allocate()` needs to fulfill the alignment
// requirement. In our case a pointer will always be a multiple of
// 8, as long as the first pointer is aligned to 8 bytes.
// This is because all pointers will contain a 8 byte prefix (the list
// index) and then a subsequent item of 2^x bytes, where x = [3..24].
const N: usize = 22;
const MAX_POSSIBLE_ALLOCATION: u32 = 16777216; // 2^24 bytes
const MIN_POSSIBLE_ALLOCATION: u32 = 8;
// Each pointer is prefixed with 8 bytes, which identify the list index
// to which it belongs.
const PREFIX_SIZE: u32 = 8;
/// An implementation of freeing bump allocator.
///
/// Refer to the module-level documentation for further details.
pub struct FreeingBumpHeapAllocator {
bumper: u32,
heads: [u32; N],
ptr_offset: u32,
total_size: u32,
}
/// Create an allocator error.
fn error(msg: &'static str) -> Error {
Error::Allocator(msg)
}
impl FreeingBumpHeapAllocator {
/// Creates a new allocation heap which follows a freeing-bump strategy.
/// The maximum size which can be allocated at once is 16 MiB.
///
/// # Arguments
///
/// - `heap_base` - the offset from the beginning of the linear memory where the heap starts.
pub fn new(heap_base: u32) -> Self {
// ptr_offset is the next alignment boundary on or after heap_base.
let ptr_offset = (heap_base + ALIGNMENT - 1) / ALIGNMENT * ALIGNMENT;
FreeingBumpHeapAllocator {
bumper: 0,
heads: [0; N],
ptr_offset,
total_size: 0,
}
}
/// Gets requested number of bytes to allocate and returns a pointer.
/// The maximum size which can be allocated at once is 16 MiB.
/// There is no minimum size, but whatever size is passed into
/// this function is rounded to the next power of two. If the requested
/// size is below 8 bytes it will be rounded up to 8 bytes.
///
/// # Arguments
///
/// - `mem` - a slice representing the linear memory on which this allocator operates.
/// - `size` - size in bytes of the allocation request
pub fn allocate(&mut self, mem: &mut [u8], size: WordSize) -> Result<Pointer<u8>> {
let mem_size = u32::try_from(mem.len())
.expect("size of Wasm linear memory is <2^32");
let max_heap_size = mem_size - self.ptr_offset;
if size > MAX_POSSIBLE_ALLOCATION {
return Err(Error::RequestedAllocationTooLarge);
}
let size = size.max(MIN_POSSIBLE_ALLOCATION);
let item_size = size.next_power_of_two();
if item_size + PREFIX_SIZE + self.total_size > max_heap_size {
return Err(Error::AllocatorOutOfSpace);
}
let list_index = (item_size.trailing_zeros() - 3) as usize;
let ptr: u32 = if self.heads[list_index] != 0 {
// Something from the free list
let item = self.heads[list_index];
let ptr = item + PREFIX_SIZE;
assert!(
ptr + item_size <= max_heap_size,
"Pointer is looked up in list of free entries, into which
only valid values are inserted; qed"
);
self.heads[list_index] = self.get_heap_u64(mem, item)?
.try_into()
.map_err(|_| error("read invalid free list pointer"))?;
ptr
} else {
// Nothing to be freed. Bump.
self.bump(item_size, max_heap_size)? + PREFIX_SIZE
};
self.set_heap_u64(mem, ptr - PREFIX_SIZE, list_index as u64)?;
self.total_size = self.total_size + item_size + PREFIX_SIZE;
trace!(target: "wasm-heap", "Heap size is {} bytes after allocation", self.total_size);
Ok(Pointer::new(self.ptr_offset + ptr))
}
/// Deallocates the space which was allocated for a pointer.
///
/// # Arguments
///
/// - `mem` - a slice representing the linear memory on which this allocator operates.
/// - `ptr` - pointer to the allocated chunk
pub fn deallocate(&mut self, mem: &mut [u8], ptr: Pointer<u8>) -> Result<()> {
let ptr = u32::from(ptr) - self.ptr_offset;
if ptr < PREFIX_SIZE {
return Err(error("Invalid pointer for deallocation"));
}
let list_index: usize = self.get_heap_u64(mem, ptr - PREFIX_SIZE)?
.try_into()
.map_err(|_| error("read invalid list index"))?;
if list_index > self.heads.len() {
return Err(error("read invalid list index"));
}
self.set_heap_u64(mem, ptr - PREFIX_SIZE, self.heads[list_index] as u64)?;
self.heads[list_index] = ptr - PREFIX_SIZE;
let item_size = Self::get_item_size_from_index(list_index);
self.total_size = self.total_size.checked_sub(item_size as u32 + PREFIX_SIZE)
.ok_or_else(|| error("Unable to subtract from total heap size without overflow"))?;
trace!(target: "wasm-heap", "Heap size is {} bytes after deallocation", self.total_size);
Ok(())
}
/// Increases the `bumper` by `item_size + PREFIX_SIZE`.
///
/// Returns the `bumper` from before the increase.
/// Returns an `Error::AllocatorOutOfSpace` if the operation
/// would exhaust the heap.
fn bump(&mut self, item_size: u32, max_heap_size: u32) -> Result<u32> {
if self.bumper + PREFIX_SIZE + item_size > max_heap_size {
return Err(Error::AllocatorOutOfSpace);
}
let res = self.bumper;
self.bumper += item_size + PREFIX_SIZE;
Ok(res)
}
fn get_item_size_from_index(index: usize) -> usize {
// we shift 1 by three places, since the first possible item size is 8
1 << 3 << index
}
// Read a u64 from the heap in LE form. Used to read heap allocation prefixes.
fn get_heap_u64(&self, heap: &[u8], offset: u32) -> Result<u64> {
let range = self.heap_range(offset, 8, heap.len())
.ok_or_else(|| error("read out of heap bounds"))?;
let bytes = heap[range].try_into()
.expect("[u8] slice of length 8 must be convertible to [u8; 8]");
Ok(u64::from_le_bytes(bytes))
}
// Write a u64 to the heap in LE form. Used to write heap allocation prefixes.
fn set_heap_u64(&self, heap: &mut [u8], offset: u32, val: u64) -> Result<()> {
let range = self.heap_range(offset, 8, heap.len())
.ok_or_else(|| error("write out of heap bounds"))?;
let bytes = val.to_le_bytes();
&mut heap[range].copy_from_slice(&bytes[..]);
Ok(())
}
fn heap_range(&self, offset: u32, length: u32, heap_len: usize) -> Option<Range<usize>> {
let start = offset
.checked_add(self.ptr_offset)?
as usize;
let end = offset
.checked_add(self.ptr_offset)?
.checked_add(length)?
as usize;
if end <= heap_len {
Some(start..end)
} else {
None
}
}
}
#[cfg(test)]
mod tests {
use super::*;
const PAGE_SIZE: u32 = 65536;
/// Makes a pointer out of the given address.
fn to_pointer(address: u32) -> Pointer<u8> {
Pointer::new(address)
}
#[test]
fn should_allocate_properly() {
// given
let mut mem = [0u8; PAGE_SIZE as usize];
let mut heap = FreeingBumpHeapAllocator::new(0);
// when
let ptr = heap.allocate(&mut mem[..], 1).unwrap();
// then
// returned pointer must start right after `PREFIX_SIZE`
assert_eq!(ptr, to_pointer(PREFIX_SIZE));
}
#[test]
fn should_always_align_pointers_to_multiples_of_8() {
// given
let mut mem = [0u8; PAGE_SIZE as usize];
let mut heap = FreeingBumpHeapAllocator::new(13);
// when
let ptr = heap.allocate(&mut mem[..], 1).unwrap();
// then
// the pointer must start at the next multiple of 8 from 13
// + the prefix of 8 bytes.
assert_eq!(ptr, to_pointer(24));
}
#[test]
fn should_increment_pointers_properly() {
// given
let mut mem = [0u8; PAGE_SIZE as usize];
let mut heap = FreeingBumpHeapAllocator::new(0);
// when
let ptr1 = heap.allocate(&mut mem[..], 1).unwrap();
let ptr2 = heap.allocate(&mut mem[..], 9).unwrap();
let ptr3 = heap.allocate(&mut mem[..], 1).unwrap();
// then
// a prefix of 8 bytes is prepended to each pointer
assert_eq!(ptr1, to_pointer(PREFIX_SIZE));
// the prefix of 8 bytes + the content of ptr1 padded to the lowest possible
// item size of 8 bytes + the prefix of ptr1
assert_eq!(ptr2, to_pointer(24));
// ptr2 + its content of 16 bytes + the prefix of 8 bytes
assert_eq!(ptr3, to_pointer(24 + 16 + PREFIX_SIZE));
}
#[test]
fn should_free_properly() {
// given
let mut mem = [0u8; PAGE_SIZE as usize];
let mut heap = FreeingBumpHeapAllocator::new(0);
let ptr1 = heap.allocate(&mut mem[..], 1).unwrap();
// the prefix of 8 bytes is prepended to the pointer
assert_eq!(ptr1, to_pointer(PREFIX_SIZE));
let ptr2 = heap.allocate(&mut mem[..], 1).unwrap();
// the prefix of 8 bytes + the content of ptr 1 is prepended to the pointer
assert_eq!(ptr2, to_pointer(24));
// when
heap.deallocate(&mut mem[..], ptr2).unwrap();
// then
// then the heads table should contain a pointer to the
// prefix of ptr2 in the leftmost entry
assert_eq!(heap.heads[0], u32::from(ptr2) - PREFIX_SIZE);
}
#[test]
fn should_deallocate_and_reallocate_properly() {
// given
let mut mem = [0u8; PAGE_SIZE as usize];
let padded_offset = 16;
let mut heap = FreeingBumpHeapAllocator::new(13);
let ptr1 = heap.allocate(&mut mem[..], 1).unwrap();
// the prefix of 8 bytes is prepended to the pointer
assert_eq!(ptr1, to_pointer(padded_offset + PREFIX_SIZE));
let ptr2 = heap.allocate(&mut mem[..], 9).unwrap();
// the padded_offset + the previously allocated ptr (8 bytes prefix +
// 8 bytes content) + the prefix of 8 bytes which is prepended to the
// current pointer
assert_eq!(ptr2, to_pointer(padded_offset + 16 + PREFIX_SIZE));
// when
heap.deallocate(&mut mem[..], ptr2).unwrap();
let ptr3 = heap.allocate(&mut mem[..], 9).unwrap();
// then
// should have re-allocated
assert_eq!(ptr3, to_pointer(padded_offset + 16 + PREFIX_SIZE));
assert_eq!(heap.heads, [0; N]);
}
#[test]
fn should_build_linked_list_of_free_areas_properly() {
// given
let mut mem = [0u8; PAGE_SIZE as usize];
let mut heap = FreeingBumpHeapAllocator::new(0);
let ptr1 = heap.allocate(&mut mem[..], 8).unwrap();
let ptr2 = heap.allocate(&mut mem[..], 8).unwrap();
let ptr3 = heap.allocate(&mut mem[..], 8).unwrap();
// when
heap.deallocate(&mut mem[..], ptr1).unwrap();
heap.deallocate(&mut mem[..], ptr2).unwrap();
heap.deallocate(&mut mem[..], ptr3).unwrap();
// then
assert_eq!(heap.heads[0], u32::from(ptr3) - PREFIX_SIZE);
let ptr4 = heap.allocate(&mut mem[..], 8).unwrap();
assert_eq!(ptr4, ptr3);
assert_eq!(heap.heads[0], u32::from(ptr2) - PREFIX_SIZE);
}
#[test]
fn should_not_allocate_if_too_large() {
// given
let mut mem = [0u8; PAGE_SIZE as usize];
let mut heap = FreeingBumpHeapAllocator::new(13);
// when
let ptr = heap.allocate(&mut mem[..], PAGE_SIZE - 13);
// then
match ptr.unwrap_err() {
Error::AllocatorOutOfSpace => {},
e => panic!("Expected allocator out of space error, got: {:?}", e),
}
}
#[test]
fn should_not_allocate_if_full() {
// given
let mut mem = [0u8; PAGE_SIZE as usize];
let mut heap = FreeingBumpHeapAllocator::new(0);
let ptr1 = heap.allocate(&mut mem[..], (PAGE_SIZE / 2) - PREFIX_SIZE).unwrap();
assert_eq!(ptr1, to_pointer(PREFIX_SIZE));
// when
let ptr2 = heap.allocate(&mut mem[..], PAGE_SIZE / 2);
// then
// there is no room for another half page incl. its 8 byte prefix
match ptr2.unwrap_err() {
Error::AllocatorOutOfSpace => {},
e => panic!("Expected allocator out of space error, got: {:?}", e),
}
}
#[test]
fn should_allocate_max_possible_allocation_size() {
// given
let mut mem = vec![0u8; (MAX_POSSIBLE_ALLOCATION + PAGE_SIZE) as usize];
let mut heap = FreeingBumpHeapAllocator::new(0);
// when
let ptr = heap.allocate(&mut mem[..], MAX_POSSIBLE_ALLOCATION).unwrap();
// then
assert_eq!(ptr, to_pointer(PREFIX_SIZE));
}
#[test]
fn should_not_allocate_if_requested_size_too_large() {
// given
let mut mem = [0u8; PAGE_SIZE as usize];
let mut heap = FreeingBumpHeapAllocator::new(0);
// when
let ptr = heap.allocate(&mut mem[..], MAX_POSSIBLE_ALLOCATION + 1);
// then
match ptr.unwrap_err() {
Error::RequestedAllocationTooLarge => {},
e => panic!("Expected allocation size too large error, got: {:?}", e),
}
}
#[test]
fn should_return_error_when_bumper_greater_than_heap_size() {
// given
let mut mem = [0u8; 64];
let mut heap = FreeingBumpHeapAllocator::new(0);
let ptr1 = heap.allocate(&mut mem[..], 32).unwrap();
assert_eq!(ptr1, to_pointer(PREFIX_SIZE));
heap.deallocate(&mut mem[..], ptr1).expect("failed freeing ptr1");
assert_eq!(heap.total_size, 0);
assert_eq!(heap.bumper, 40);
let ptr2 = heap.allocate(&mut mem[..], 16).unwrap();
assert_eq!(ptr2, to_pointer(48));
heap.deallocate(&mut mem[..], ptr2).expect("failed freeing ptr2");
assert_eq!(heap.total_size, 0);
assert_eq!(heap.bumper, 64);
// when
// the `bumper` value is equal to `max_heap_size` here and any
// further allocation which would increment the bumper must fail.
// we try to allocate 8 bytes here, which will increment the
// bumper since no 8 byte item has been allocated+freed before.
let ptr = heap.allocate(&mut mem[..], 8);
// then
match ptr.unwrap_err() {
Error::AllocatorOutOfSpace => {},
e => panic!("Expected allocator out of space error, got: {:?}", e),
}
}
#[test]
fn should_include_prefixes_in_total_heap_size() {
// given
let mut mem = [0u8; PAGE_SIZE as usize];
let mut heap = FreeingBumpHeapAllocator::new(1);
// when
// an item size of 16 must be used then
heap.allocate(&mut mem[..], 9).unwrap();
// then
assert_eq!(heap.total_size, PREFIX_SIZE + 16);
}
#[test]
fn should_calculate_total_heap_size_to_zero() {
// given
let mut mem = [0u8; PAGE_SIZE as usize];
let mut heap = FreeingBumpHeapAllocator::new(13);
// when
let ptr = heap.allocate(&mut mem[..], 42).unwrap();
assert_eq!(ptr, to_pointer(16 + PREFIX_SIZE));
heap.deallocate(&mut mem[..], ptr).unwrap();
// then
assert_eq!(heap.total_size, 0);
}
#[test]
fn should_calculate_total_size_of_zero() {
// given
let mut mem = [0u8; PAGE_SIZE as usize];
let mut heap = FreeingBumpHeapAllocator::new(19);
// when
for _ in 1..10 {
let ptr = heap.allocate(&mut mem[..], 42).unwrap();
heap.deallocate(&mut mem[..], ptr).unwrap();
}
// then
assert_eq!(heap.total_size, 0);
}
#[test]
fn should_read_and_write_u64_correctly() {
// given
let mut mem = [0u8; PAGE_SIZE as usize];
let heap = FreeingBumpHeapAllocator::new(16);
// when
heap.set_heap_u64(&mut mem[..], 40, 4480113).unwrap();
// then
let value = heap.get_heap_u64(&mut mem[..], 40).unwrap();
assert_eq!(value, 4480113);
}
#[test]
fn should_get_item_size_from_index() {
// given
let index = 0;
// when
let item_size = FreeingBumpHeapAllocator::get_item_size_from_index(index);
// then
assert_eq!(item_size, 8);
}
#[test]
fn should_get_max_item_size_from_index() {
// given
let index = 21;
// when
let item_size = FreeingBumpHeapAllocator::get_item_size_from_index(index);
// then
assert_eq!(item_size as u32, MAX_POSSIBLE_ALLOCATION);
}
}
@@ -0,0 +1,135 @@
// Copyright 2017-2019 Parity Technologies (UK) Ltd.
// This file is part of Substrate.
// Substrate 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.
// Substrate 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 Substrate. If not, see <http://www.gnu.org/licenses/>.
//! Rust executor possible errors.
use sp_serializer;
use wasmi;
/// Result type alias.
pub type Result<T> = std::result::Result<T, Error>;
/// Error type.
#[derive(Debug, derive_more::Display, derive_more::From)]
pub enum Error {
/// Unserializable Data
InvalidData(sp_serializer::Error),
/// Trap occured during execution
Trap(wasmi::Trap),
/// Wasmi loading/instantiating error
Wasmi(wasmi::Error),
/// Error in the API. Parameter is an error message.
#[from(ignore)]
ApiError(String),
/// Method is not found
#[display(fmt="Method not found: '{}'", _0)]
#[from(ignore)]
MethodNotFound(String),
/// Code is invalid (expected single byte)
#[display(fmt="Invalid Code: {}", _0)]
#[from(ignore)]
InvalidCode(String),
/// Could not get runtime version.
#[display(fmt="On-chain runtime does not specify version")]
VersionInvalid,
/// Externalities have failed.
#[display(fmt="Externalities error")]
Externalities,
/// Invalid index.
#[display(fmt="Invalid index provided")]
InvalidIndex,
/// Invalid return type.
#[display(fmt="Invalid type returned (should be u64)")]
InvalidReturn,
/// Runtime failed.
#[display(fmt="Runtime error")]
Runtime,
/// Invalid memory reference.
#[display(fmt="Invalid memory reference")]
InvalidMemoryReference,
/// The runtime must provide a global named `__heap_base` of type i32 for specifying where the
/// allocator is allowed to place its data.
#[display(fmt="The runtime doesn't provide a global named `__heap_base`")]
HeapBaseNotFoundOrInvalid,
/// The runtime WebAssembly module is not allowed to have the `start` function.
#[display(fmt="The runtime has the `start` function")]
RuntimeHasStartFn,
/// Some other error occurred
#[from(ignore)]
Other(String),
/// Some error occurred in the allocator
#[display(fmt="Error in allocator: {}", _0)]
Allocator(&'static str),
/// The allocator ran out of space.
#[display(fmt="Allocator ran out of space")]
AllocatorOutOfSpace,
/// Someone tried to allocate more memory than the allowed maximum per allocation.
#[display(fmt="Requested allocation size is too large")]
RequestedAllocationTooLarge,
/// Execution of a host function failed.
#[display(fmt="Host function {} execution failed with: {}", _0, _1)]
FunctionExecution(String, String),
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Error::InvalidData(ref err) => Some(err),
Error::Trap(ref err) => Some(err),
Error::Wasmi(ref err) => Some(err),
_ => None,
}
}
}
impl wasmi::HostError for Error {}
impl From<String> for Error {
fn from(err: String) -> Error {
Error::Other(err)
}
}
impl From<WasmError> for Error {
fn from(err: WasmError) -> Error {
Error::Other(err.to_string())
}
}
/// Type for errors occurring during Wasm runtime construction.
#[derive(Debug, derive_more::Display)]
pub enum WasmError {
/// Code could not be read from the state.
CodeNotFound,
/// Failure to reinitialize runtime instance from snapshot.
ApplySnapshotFailed,
/// Failure to erase the wasm memory.
///
/// Depending on the implementation might mean failure of allocating memory.
ErasingFailed(String),
/// Wasm code failed validation.
InvalidModule,
/// Wasm code could not be deserialized.
CantDeserializeWasm,
/// The module does not export a linear memory named `memory`.
InvalidMemory,
/// The number of heap pages requested is disallowed by the module.
InvalidHeapPages,
/// Instantiation error.
Instantiation(String),
/// Other error happenend.
Other(String),
}
@@ -0,0 +1,24 @@
// Copyright 2019 Parity Technologies (UK) Ltd.
// This file is part of Substrate.
// Substrate 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.
// Substrate 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 Substrate. If not, see <http://www.gnu.org/licenses/>.
//! A set of common definitions that are needed for defining execution engines.
#![warn(missing_docs)]
pub mod sandbox;
pub mod allocator;
pub mod error;
pub mod wasm_runtime;
@@ -0,0 +1,585 @@
// Copyright 2018-2019 Parity Technologies (UK) Ltd.
// This file is part of Substrate.
// Substrate 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.
// Substrate 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 Substrate. If not, see <http://www.gnu.org/licenses/>.
//! This module implements sandboxing support in the runtime.
//!
//! Sandboxing is baked by wasmi at the moment. In future, however, we would like to add/switch to
//! a compiled execution engine.
use crate::error::{Result, Error};
use std::{collections::HashMap, rc::Rc};
use codec::{Decode, Encode};
use sp_core::sandbox as sandbox_primitives;
use wasmi::{
Externals, ImportResolver, MemoryInstance, MemoryRef, Module, ModuleInstance,
ModuleRef, RuntimeArgs, RuntimeValue, Trap, TrapKind, memory_units::Pages,
};
use sp_wasm_interface::{Pointer, WordSize};
/// 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 with 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 {
funcs: Vec<SupervisorFuncIndex>,
}
impl GuestToSupervisorFunctionMapping {
fn new() -> GuestToSupervisorFunctionMapping {
GuestToSupervisorFunctionMapping { funcs: Vec::new() }
}
fn define(&mut self, supervisor_func: SupervisorFuncIndex) -> GuestFuncIndex {
let idx = self.funcs.len();
self.funcs.push(supervisor_func);
GuestFuncIndex(idx)
}
fn func_by_guest_index(&self, guest_func_idx: GuestFuncIndex) -> Option<SupervisorFuncIndex> {
self.funcs.get(guest_func_idx.0).cloned()
}
}
struct Imports {
func_map: HashMap<(Vec<u8>, Vec<u8>), GuestFuncIndex>,
memories_map: HashMap<(Vec<u8>, Vec<u8>), MemoryRef>,
}
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 key = (
module_name.as_bytes().to_owned(),
field_name.as_bytes().to_owned(),
);
let idx = *self.func_map.get(&key).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<MemoryRef, wasmi::Error> {
let key = (
module_name.as_bytes().to_vec(),
field_name.as_bytes().to_vec(),
);
let mem = self.memories_map
.get(&key)
.ok_or_else(|| {
wasmi::Error::Instantiation(format!(
"Export {}:{} not found",
module_name, field_name
))
})?
.clone();
Ok(mem)
}
fn resolve_global(
&self,
module_name: &str,
field_name: &str,
_global_type: &::wasmi::GlobalDescriptor,
) -> std::result::Result<wasmi::GlobalRef, wasmi::Error> {
Err(wasmi::Error::Instantiation(format!(
"Export {}:{} not found",
module_name, field_name
)))
}
fn resolve_table(
&self,
module_name: &str,
field_name: &str,
_table_type: &::wasmi::TableDescriptor,
) -> std::result::Result<wasmi::TableRef, wasmi::Error> {
Err(wasmi::Error::Instantiation(format!(
"Export {}:{} not found",
module_name, field_name
)))
}
}
/// This trait encapsulates sandboxing capabilities.
///
/// Note that this functions are only called in the `supervisor` context.
pub trait SandboxCapabilities {
/// Represents a function reference into the supervisor environment.
type SupervisorFuncRef;
/// Returns a reference to an associated sandbox `Store`.
fn store(&self) -> &Store<Self::SupervisorFuncRef>;
/// Returns a mutable reference to an associated sandbox `Store`.
fn store_mut(&mut self) -> &mut Store<Self::SupervisorFuncRef>;
/// Allocate space of the specified length in the supervisor memory.
///
/// # Errors
///
/// Returns `Err` if allocation not possible or errors during heap management.
///
/// Returns pointer to the allocated block.
fn allocate(&mut self, len: WordSize) -> Result<Pointer<u8>>;
/// Deallocate space specified by the pointer that was previously returned by [`allocate`].
///
/// # Errors
///
/// Returns `Err` if deallocation not possible or because of errors in heap management.
///
/// [`allocate`]: #tymethod.allocate
fn deallocate(&mut self, ptr: Pointer<u8>) -> Result<()>;
/// Write `data` into the supervisor memory at offset specified by `ptr`.
///
/// # Errors
///
/// Returns `Err` if `ptr + data.len()` is out of bounds.
fn write_memory(&mut self, ptr: Pointer<u8>, data: &[u8]) -> Result<()>;
/// Read `len` bytes from the supervisor memory.
///
/// # Errors
///
/// Returns `Err` if `ptr + len` is out of bounds.
fn read_memory(&self, ptr: Pointer<u8>, len: WordSize) -> Result<Vec<u8>>;
/// 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,
dispatch_thunk: &Self::SupervisorFuncRef,
invoke_args_ptr: Pointer<u8>,
invoke_args_len: WordSize,
state: u32,
func_idx: SupervisorFuncIndex,
) -> Result<i64>;
}
/// 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, FE: SandboxCapabilities + 'a> {
supervisor_externals: &'a mut FE,
sandbox_instance: &'a SandboxInstance<FE::SupervisorFuncRef>,
state: u32,
}
fn trap(msg: &'static str) -> Trap {
TrapKind::Host(Box::new(Error::Other(msg.into()))).into()
}
fn deserialize_result(serialized_result: &[u8]) -> std::result::Result<Option<RuntimeValue>, Trap> {
use self::sandbox_primitives::{HostError, ReturnValue};
let result_val = std::result::Result::<ReturnValue, HostError>::decode(&mut &serialized_result[..])
.map_err(|_| trap("Decoding Result<ReturnValue, HostError> failed!"))?;
match result_val {
Ok(return_value) => Ok(match return_value {
ReturnValue::Unit => None,
ReturnValue::Value(typed_value) => Some(RuntimeValue::from(typed_value)),
}),
Err(HostError) => Err(trap("Supervisor function returned sandbox::HostError")),
}
}
impl<'a, FE: SandboxCapabilities + 'a> Externals for GuestExternals<'a, FE> {
fn invoke_index(
&mut self,
index: usize,
args: RuntimeArgs,
) -> std::result::Result<Option<RuntimeValue>, Trap> {
// Make `index` typesafe again.
let index = GuestFuncIndex(index);
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 was 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(sandbox_primitives::TypedValue::from)
.collect::<Vec<_>>()
.encode();
let state = self.state;
// Move serialized arguments inside the memory and invoke dispatch thunk and
// then free allocated memory.
let invoke_args_len = invoke_args_data.len() as WordSize;
let invoke_args_ptr = self.supervisor_externals.allocate(invoke_args_len)?;
self.supervisor_externals.write_memory(invoke_args_ptr, &invoke_args_data)?;
let result = self.supervisor_externals.invoke(
&self.sandbox_instance.dispatch_thunk,
invoke_args_ptr,
invoke_args_len,
state,
func_idx,
)?;
self.supervisor_externals.deallocate(invoke_args_ptr)?;
// 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 = self.supervisor_externals
.read_memory(serialized_result_val_ptr, serialized_result_val_len)?;
self.supervisor_externals
.deallocate(serialized_result_val_ptr)?;
deserialize_result(&serialized_result_val)
}
}
fn with_guest_externals<FE, R, F>(
supervisor_externals: &mut FE,
sandbox_instance: &SandboxInstance<FE::SupervisorFuncRef>,
state: u32,
f: F,
) -> R
where
FE: SandboxCapabilities,
F: FnOnce(&mut GuestExternals<FE>) -> R,
{
let mut guest_externals = GuestExternals {
supervisor_externals,
sandbox_instance,
state,
};
f(&mut guest_externals)
}
/// 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<FR> {
instance: ModuleRef,
dispatch_thunk: FR,
guest_to_supervisor_mapping: GuestToSupervisorFunctionMapping,
}
impl<FR> SandboxInstance<FR> {
/// 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<FE: SandboxCapabilities<SupervisorFuncRef=FR>>(
&self,
export_name: &str,
args: &[RuntimeValue],
supervisor_externals: &mut FE,
state: u32,
) -> std::result::Result<Option<wasmi::RuntimeValue>, wasmi::Error> {
with_guest_externals(
supervisor_externals,
self,
state,
|guest_externals| {
self.instance
.invoke_export(export_name, args, guest_externals)
},
)
}
}
/// 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,
}
fn decode_environment_definition(
raw_env_def: &[u8],
memories: &[Option<MemoryRef>],
) -> std::result::Result<(Imports, GuestToSupervisorFunctionMapping), InstantiationError> {
let env_def = sandbox_primitives::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_primitives::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_primitives::ExternEntity::Memory(memory_idx) => {
let memory_ref = memories
.get(memory_idx as usize)
.cloned()
.ok_or_else(|| InstantiationError::EnvironmentDefinitionCorrupted)?
.ok_or_else(|| InstantiationError::EnvironmentDefinitionCorrupted)?;
memories_map.insert((module, field), memory_ref);
}
}
}
Ok((
Imports {
func_map,
memories_map,
},
guest_to_supervisor_mapping,
))
}
/// 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 `raw_env_def` (serialized version of [`EnvironmentDefinition`]).
/// `dispatch_thunk` is used as function that handle calls from guests.
///
/// # Errors
///
/// Returns `Err` if any of the following conditions happens:
///
/// - `raw_env_def` can't be deserialized as a [`EnvironmentDefinition`].
/// - Module in `wasm` is invalid or couldn't be instantiated.
///
/// [`EnvironmentDefinition`]: ../sandbox/struct.EnvironmentDefinition.html
pub fn instantiate<FE: SandboxCapabilities>(
supervisor_externals: &mut FE,
dispatch_thunk: FE::SupervisorFuncRef,
wasm: &[u8],
raw_env_def: &[u8],
state: u32,
) -> std::result::Result<u32, InstantiationError> {
let (imports, guest_to_supervisor_mapping) =
decode_environment_definition(raw_env_def, &supervisor_externals.store().memories)?;
let module = Module::from_buffer(wasm).map_err(|_| InstantiationError::ModuleDecoding)?;
let instance = ModuleInstance::new(&module, &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.
instance: instance.not_started_instance().clone(),
dispatch_thunk,
guest_to_supervisor_mapping,
});
with_guest_externals(
supervisor_externals,
&sandbox_instance,
state,
|guest_externals| {
instance
.run_start(guest_externals)
.map_err(|_| InstantiationError::StartTrapped)
},
)?;
// At last, register the instance.
let instance_idx = supervisor_externals
.store_mut()
.register_sandbox_instance(sandbox_instance);
Ok(instance_idx)
}
/// This struct keeps track of all sandboxed components.
///
/// This is generic over a supervisor function reference type.
pub struct Store<FR> {
// Memories and instances are `Some` untill torndown.
instances: Vec<Option<Rc<SandboxInstance<FR>>>>,
memories: Vec<Option<MemoryRef>>,
}
impl<FR> Store<FR> {
/// Create a new empty sandbox store.
pub fn new() -> Self {
Store {
instances: Vec::new(),
memories: Vec::new(),
}
}
/// 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 maximum = match maximum {
sandbox_primitives::MEM_UNLIMITED => None,
specified_limit => Some(Pages(specified_limit as usize)),
};
let mem =
MemoryInstance::alloc(
Pages(initial as usize),
maximum,
)?;
let mem_idx = self.memories.len();
self.memories.push(Some(mem));
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<FR>>> {
self.instances
.get(instance_idx as usize)
.cloned()
.ok_or_else(|| "Trying to access a non-existent instance")?
.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<MemoryRef> {
self.memories
.get(memory_idx as usize)
.cloned()
.ok_or_else(|| "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(())
}
}
}
fn register_sandbox_instance(&mut self, sandbox_instance: Rc<SandboxInstance<FR>>) -> u32 {
let instance_idx = self.instances.len();
self.instances.push(Some(sandbox_instance));
instance_idx as u32
}
}
@@ -0,0 +1,39 @@
// Copyright 2019 Parity Technologies (UK) Ltd.
// This file is part of Substrate.
// Substrate 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.
// Substrate 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 Substrate. If not, see <http://www.gnu.org/licenses/>.
//! Definitions for a wasm runtime.
use crate::error::Error;
use sp_core::traits::Externalities;
use sp_wasm_interface::Function;
/// A trait that defines an abstract wasm runtime.
///
/// This can be implemented by an execution engine.
pub trait WasmRuntime {
/// Attempt to update the number of heap pages available during execution.
///
/// Returns false if the update cannot be applied. The function is guaranteed to return true if
/// the heap pages would not change from its current value.
fn update_heap_pages(&mut self, heap_pages: u64) -> bool;
/// Return the host functions that are registered for this Wasm runtime.
fn host_functions(&self) -> &[&'static dyn Function];
/// Call a method in the Substrate runtime by name. Returns the encoded result on success.
fn call(&mut self, ext: &mut dyn Externalities, method: &str, data: &[u8])
-> Result<Vec<u8>, Error>;
}