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
-554
View File
@@ -1,554 +0,0 @@
// 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;
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);
}
}
-144
View File
@@ -1,144 +0,0 @@
// 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;
#[cfg(feature = "wasmtime")]
use wasmtime_jit::{ActionError, SetupError};
/// 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),
/// Wasmtime action error
#[cfg(feature = "wasmtime")]
Wasmtime(ActionError),
/// 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),
/// The compiler does not support the host machine as a target.
#[cfg(feature = "wasmtime")]
MissingCompilerSupport(&'static str),
/// Wasmtime setup error.
#[cfg(feature = "wasmtime")]
WasmtimeSetup(SetupError),
}
+2 -6
View File
@@ -31,19 +31,13 @@
#[macro_use]
mod wasm_utils;
mod wasmi_execution;
#[macro_use]
mod native_executor;
mod sandbox;
mod allocator;
pub mod deprecated_host_interface;
mod wasm_runtime;
#[cfg(feature = "wasmtime")]
mod wasmtime;
#[cfg(test)]
mod integration_tests;
pub mod error;
pub use wasmi;
pub use native_executor::{with_native_environment, NativeExecutor, NativeExecutionDispatch};
pub use sp_version::{RuntimeVersion, NativeVersion};
@@ -54,6 +48,8 @@ pub use sp_core::traits::Externalities;
pub use sp_wasm_interface;
pub use wasm_runtime::WasmExecutionMethod;
pub use sc_executor_common::{error, allocator, sandbox};
/// Call the given `function` in the given wasm `code`.
///
/// The signature of `function` needs to follow the default Substrate function signature.
@@ -16,20 +16,15 @@
use crate::{
RuntimeInfo, error::{Error, Result},
wasm_runtime::{RuntimesCache, WasmExecutionMethod, WasmRuntime},
wasm_runtime::{RuntimesCache, WasmExecutionMethod},
};
use sp_version::{NativeVersion, RuntimeVersion};
use codec::{Decode, Encode};
use sp_core::{NativeOrEncoded, traits::{CodeExecutor, Externalities}};
use log::trace;
use std::{result, cell::RefCell, panic::{UnwindSafe, AssertUnwindSafe}};
use sp_wasm_interface::{HostFunctions, Function};
use sc_executor_common::wasm_runtime::WasmRuntime;
thread_local! {
static RUNTIMES_CACHE: RefCell<RuntimesCache> = RefCell::new(RuntimesCache::new());
-584
View File
@@ -1,584 +0,0 @@
// 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/>.
#![warn(missing_docs)]
//! This module implements sandboxing support in the runtime.
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
}
}
+4 -24
View File
@@ -19,36 +19,16 @@
//! The primary means of accessing the runtimes is through a cache which saves the reusable
//! components of the runtime that are expensive to initialize.
use crate::{wasmi_execution, error::{Error, WasmError}};
#[cfg(feature = "wasmtime")]
use crate::wasmtime;
use crate::error::{Error, WasmError};
use log::{trace, warn};
use codec::Decode;
use sp_core::{storage::well_known_keys, traits::Externalities};
use sp_version::RuntimeVersion;
use std::{collections::hash_map::{Entry, HashMap}, panic::AssertUnwindSafe};
use sc_executor_common::wasm_runtime::WasmRuntime;
use sp_wasm_interface::Function;
/// The Substrate Wasm runtime.
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>;
}
/// Specification of different methods of executing the runtime Wasm code.
#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)]
pub enum WasmExecutionMethod {
@@ -214,11 +194,11 @@ pub fn create_wasm_runtime_with_code(
) -> Result<Box<dyn WasmRuntime>, WasmError> {
match wasm_method {
WasmExecutionMethod::Interpreted =>
wasmi_execution::create_instance(code, heap_pages, host_functions)
sc_executor_wasmi::create_instance(code, heap_pages, host_functions)
.map(|runtime| -> Box<dyn WasmRuntime> { Box::new(runtime) }),
#[cfg(feature = "wasmtime")]
WasmExecutionMethod::Compiled =>
wasmtime::create_instance(code, heap_pages, host_functions)
sc_executor_wasmtime::create_instance(code, heap_pages, host_functions)
.map(|runtime| -> Box<dyn WasmRuntime> { Box::new(runtime) }),
}
}
@@ -16,8 +16,6 @@
//! Utilities for defining the wasm host environment.
use sp_wasm_interface::{Pointer, WordSize};
/// Converts arguments into respective WASM types.
#[macro_export]
macro_rules! convert_args {
@@ -173,14 +171,3 @@ macro_rules! impl_wasm_host_interface {
}
);
}
/// Runtime API functions return an i64 which encodes a pointer in the least-significant 32 bits
/// and a length in the most-significant 32 bits. This interprets the returned value as a pointer,
/// length tuple.
pub fn interpret_runtime_api_result(retval: i64) -> (Pointer<u8>, WordSize) {
let ptr = <Pointer<u8>>::new(retval as u32);
// The first cast to u64 is necessary so that the right shift does not sign-extend.
let len = ((retval as u64) >> 32) as WordSize;
(ptr, len)
}
@@ -1,612 +0,0 @@
// 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/>.
//! Implementation of a Wasm runtime using the Wasmi interpreter.
use std::{str, mem};
use wasmi::{
Module, ModuleInstance, MemoryInstance, MemoryRef, TableRef, ImportsBuilder, ModuleRef,
memory_units::Pages, RuntimeValue::{I32, I64, self},
};
use crate::error::{Error, WasmError};
use codec::{Encode, Decode};
use sp_core::{sandbox as sandbox_primitives, traits::Externalities};
use crate::sandbox;
use crate::allocator;
use crate::wasm_utils::interpret_runtime_api_result;
use crate::wasm_runtime::WasmRuntime;
use log::{error, trace};
use parity_wasm::elements::{deserialize_buffer, DataSegment, Instruction, Module as RawModule};
use sp_wasm_interface::{
FunctionContext, Pointer, WordSize, Sandbox, MemoryId, Result as WResult, Function,
};
struct FunctionExecutor<'a> {
sandbox_store: sandbox::Store<wasmi::FuncRef>,
heap: allocator::FreeingBumpHeapAllocator,
memory: MemoryRef,
table: Option<TableRef>,
host_functions: &'a [&'static dyn Function],
}
impl<'a> FunctionExecutor<'a> {
fn new(
m: MemoryRef,
heap_base: u32,
t: Option<TableRef>,
host_functions: &'a [&'static dyn Function],
) -> Result<Self, Error> {
Ok(FunctionExecutor {
sandbox_store: sandbox::Store::new(),
heap: allocator::FreeingBumpHeapAllocator::new(heap_base),
memory: m,
table: t,
host_functions,
})
}
}
impl<'a> sandbox::SandboxCapabilities for FunctionExecutor<'a> {
type SupervisorFuncRef = wasmi::FuncRef;
fn store(&self) -> &sandbox::Store<Self::SupervisorFuncRef> {
&self.sandbox_store
}
fn store_mut(&mut self) -> &mut sandbox::Store<Self::SupervisorFuncRef> {
&mut self.sandbox_store
}
fn allocate(&mut self, len: WordSize) -> Result<Pointer<u8>, Error> {
let heap = &mut self.heap;
self.memory.with_direct_access_mut(|mem| {
heap.allocate(mem, len)
})
}
fn deallocate(&mut self, ptr: Pointer<u8>) -> Result<(), Error> {
let heap = &mut self.heap;
self.memory.with_direct_access_mut(|mem| {
heap.deallocate(mem, ptr)
})
}
fn write_memory(&mut self, ptr: Pointer<u8>, data: &[u8]) -> Result<(), Error> {
self.memory.set(ptr.into(), data).map_err(Into::into)
}
fn read_memory(&self, ptr: Pointer<u8>, len: WordSize) -> Result<Vec<u8>, Error> {
self.memory.get(ptr.into(), len as usize).map_err(Into::into)
}
fn invoke(
&mut self,
dispatch_thunk: &Self::SupervisorFuncRef,
invoke_args_ptr: Pointer<u8>,
invoke_args_len: WordSize,
state: u32,
func_idx: sandbox::SupervisorFuncIndex,
) -> Result<i64, Error>
{
let result = wasmi::FuncInstance::invoke(
dispatch_thunk,
&[
RuntimeValue::I32(u32::from(invoke_args_ptr) as i32),
RuntimeValue::I32(invoke_args_len as i32),
RuntimeValue::I32(state as i32),
RuntimeValue::I32(usize::from(func_idx) as i32),
],
self,
);
match result {
Ok(Some(RuntimeValue::I64(val))) => Ok(val),
Ok(_) => return Err("Supervisor function returned unexpected result!".into()),
Err(err) => Err(Error::Trap(err)),
}
}
}
impl<'a> FunctionContext for FunctionExecutor<'a> {
fn read_memory_into(&self, address: Pointer<u8>, dest: &mut [u8]) -> WResult<()> {
self.memory.get_into(address.into(), dest).map_err(|e| e.to_string())
}
fn write_memory(&mut self, address: Pointer<u8>, data: &[u8]) -> WResult<()> {
self.memory.set(address.into(), data).map_err(|e| e.to_string())
}
fn allocate_memory(&mut self, size: WordSize) -> WResult<Pointer<u8>> {
let heap = &mut self.heap;
self.memory.with_direct_access_mut(|mem| {
heap.allocate(mem, size).map_err(|e| e.to_string())
})
}
fn deallocate_memory(&mut self, ptr: Pointer<u8>) -> WResult<()> {
let heap = &mut self.heap;
self.memory.with_direct_access_mut(|mem| {
heap.deallocate(mem, ptr).map_err(|e| e.to_string())
})
}
fn sandbox(&mut self) -> &mut dyn Sandbox {
self
}
}
impl<'a> Sandbox for FunctionExecutor<'a> {
fn memory_get(
&mut self,
memory_id: MemoryId,
offset: WordSize,
buf_ptr: Pointer<u8>,
buf_len: WordSize,
) -> WResult<u32> {
let sandboxed_memory = self.sandbox_store.memory(memory_id).map_err(|e| e.to_string())?;
match MemoryInstance::transfer(
&sandboxed_memory,
offset as usize,
&self.memory,
buf_ptr.into(),
buf_len as usize,
) {
Ok(()) => Ok(sandbox_primitives::ERR_OK),
Err(_) => Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS),
}
}
fn memory_set(
&mut self,
memory_id: MemoryId,
offset: WordSize,
val_ptr: Pointer<u8>,
val_len: WordSize,
) -> WResult<u32> {
let sandboxed_memory = self.sandbox_store.memory(memory_id).map_err(|e| e.to_string())?;
match MemoryInstance::transfer(
&self.memory,
val_ptr.into(),
&sandboxed_memory,
offset as usize,
val_len as usize,
) {
Ok(()) => Ok(sandbox_primitives::ERR_OK),
Err(_) => Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS),
}
}
fn memory_teardown(&mut self, memory_id: MemoryId) -> WResult<()> {
self.sandbox_store.memory_teardown(memory_id).map_err(|e| e.to_string())
}
fn memory_new(
&mut self,
initial: u32,
maximum: u32,
) -> WResult<MemoryId> {
self.sandbox_store.new_memory(initial, maximum).map_err(|e| e.to_string())
}
fn invoke(
&mut self,
instance_id: u32,
export_name: &str,
args: &[u8],
return_val: Pointer<u8>,
return_val_len: WordSize,
state: u32,
) -> WResult<u32> {
trace!(target: "sp-sandbox", "invoke, instance_idx={}", instance_id);
// Deserialize arguments and convert them into wasmi types.
let args = Vec::<sandbox_primitives::TypedValue>::decode(&mut &args[..])
.map_err(|_| "Can't decode serialized arguments for the invocation")?
.into_iter()
.map(Into::into)
.collect::<Vec<_>>();
let instance = self.sandbox_store.instance(instance_id).map_err(|e| e.to_string())?;
let result = instance.invoke(export_name, &args, self, state);
match result {
Ok(None) => Ok(sandbox_primitives::ERR_OK),
Ok(Some(val)) => {
// Serialize return value and write it back into the memory.
sandbox_primitives::ReturnValue::Value(val.into()).using_encoded(|val| {
if val.len() > return_val_len as usize {
Err("Return value buffer is too small")?;
}
self.write_memory(return_val, val).map_err(|_| "Return value buffer is OOB")?;
Ok(sandbox_primitives::ERR_OK)
})
}
Err(_) => Ok(sandbox_primitives::ERR_EXECUTION),
}
}
fn instance_teardown(&mut self, instance_id: u32) -> WResult<()> {
self.sandbox_store.instance_teardown(instance_id).map_err(|e| e.to_string())
}
fn instance_new(
&mut self,
dispatch_thunk_id: u32,
wasm: &[u8],
raw_env_def: &[u8],
state: u32,
) -> WResult<u32> {
// Extract a dispatch thunk from instance's table by the specified index.
let dispatch_thunk = {
let table = self.table.as_ref()
.ok_or_else(|| "Runtime doesn't have a table; sandbox is unavailable")?;
table.get(dispatch_thunk_id)
.map_err(|_| "dispatch_thunk_idx is out of the table bounds")?
.ok_or_else(|| "dispatch_thunk_idx points on an empty table entry")?
.clone()
};
let instance_idx_or_err_code =
match sandbox::instantiate(self, dispatch_thunk, wasm, raw_env_def, state) {
Ok(instance_idx) => instance_idx,
Err(sandbox::InstantiationError::StartTrapped) =>
sandbox_primitives::ERR_EXECUTION,
Err(_) => sandbox_primitives::ERR_MODULE,
};
Ok(instance_idx_or_err_code as u32)
}
}
struct Resolver<'a>(&'a[&'static dyn Function]);
impl<'a> wasmi::ModuleImportResolver for Resolver<'a> {
fn resolve_func(&self, name: &str, signature: &wasmi::Signature)
-> std::result::Result<wasmi::FuncRef, wasmi::Error>
{
let signature = sp_wasm_interface::Signature::from(signature);
for (function_index, function) in self.0.iter().enumerate() {
if name == function.name() {
if signature == function.signature() {
return Ok(
wasmi::FuncInstance::alloc_host(signature.into(), function_index),
)
} else {
return Err(wasmi::Error::Instantiation(
format!(
"Invalid signature for function `{}` expected `{:?}`, got `{:?}`",
function.name(),
signature,
function.signature(),
),
))
}
}
}
Err(wasmi::Error::Instantiation(
format!("Export {} not found", name),
))
}
}
impl<'a> wasmi::Externals for FunctionExecutor<'a> {
fn invoke_index(&mut self, index: usize, args: wasmi::RuntimeArgs)
-> Result<Option<wasmi::RuntimeValue>, wasmi::Trap>
{
let mut args = args.as_ref().iter().copied().map(Into::into);
let function = self.host_functions.get(index).ok_or_else(||
Error::from(
format!("Could not find host function with index: {}", index),
)
)?;
function.execute(self, &mut args)
.map_err(|msg| Error::FunctionExecution(function.name().to_string(), msg))
.map_err(wasmi::Trap::from)
.map(|v| v.map(Into::into))
}
}
fn get_mem_instance(module: &ModuleRef) -> Result<MemoryRef, Error> {
Ok(module
.export_by_name("memory")
.ok_or_else(|| Error::InvalidMemoryReference)?
.as_memory()
.ok_or_else(|| Error::InvalidMemoryReference)?
.clone())
}
/// Find the global named `__heap_base` in the given wasm module instance and
/// tries to get its value.
fn get_heap_base(module: &ModuleRef) -> Result<u32, Error> {
let heap_base_val = module
.export_by_name("__heap_base")
.ok_or_else(|| Error::HeapBaseNotFoundOrInvalid)?
.as_global()
.ok_or_else(|| Error::HeapBaseNotFoundOrInvalid)?
.get();
match heap_base_val {
wasmi::RuntimeValue::I32(v) => Ok(v as u32),
_ => Err(Error::HeapBaseNotFoundOrInvalid),
}
}
/// Call a given method in the given wasm-module runtime.
fn call_in_wasm_module(
ext: &mut dyn Externalities,
module_instance: &ModuleRef,
method: &str,
data: &[u8],
host_functions: &[&'static dyn Function],
) -> Result<Vec<u8>, Error> {
// extract a reference to a linear memory, optional reference to a table
// and then initialize FunctionExecutor.
let memory = get_mem_instance(module_instance)?;
let table: Option<TableRef> = module_instance
.export_by_name("__indirect_function_table")
.and_then(|e| e.as_table().cloned());
let heap_base = get_heap_base(module_instance)?;
let mut fec = FunctionExecutor::new(memory.clone(), heap_base, table, host_functions)?;
// Write the call data
let offset = fec.allocate_memory(data.len() as u32)?;
fec.write_memory(offset, data)?;
let result = sp_externalities::set_and_run_with_externalities(
ext,
|| module_instance.invoke_export(
method,
&[I32(u32::from(offset) as i32), I32(data.len() as i32)],
&mut fec,
),
);
match result {
Ok(Some(I64(r))) => {
let (ptr, length) = interpret_runtime_api_result(r);
memory.get(ptr.into(), length as usize).map_err(|_| Error::Runtime)
},
Err(e) => {
trace!(
target: "wasm-executor",
"Failed to execute code with {} pages",
memory.current_size().0
);
Err(e.into())
},
_ => Err(Error::InvalidReturn),
}
}
/// Prepare module instance
fn instantiate_module(
heap_pages: usize,
module: &Module,
host_functions: &[&'static dyn Function],
) -> Result<ModuleRef, Error> {
let resolver = Resolver(host_functions);
// start module instantiation. Don't run 'start' function yet.
let intermediate_instance = ModuleInstance::new(
module,
&ImportsBuilder::new().with_resolver("env", &resolver),
)?;
// Verify that the module has the heap base global variable.
let _ = get_heap_base(intermediate_instance.not_started_instance())?;
// Extract a reference to a linear memory.
let memory = get_mem_instance(intermediate_instance.not_started_instance())?;
memory.grow(Pages(heap_pages)).map_err(|_| Error::Runtime)?;
if intermediate_instance.has_start() {
// Runtime is not allowed to have the `start` function.
Err(Error::RuntimeHasStartFn)
} else {
Ok(intermediate_instance.assert_no_start())
}
}
/// A state snapshot of an instance taken just after instantiation.
///
/// It is used for restoring the state of the module after execution.
#[derive(Clone)]
struct StateSnapshot {
/// The offset and the content of the memory segments that should be used to restore the snapshot
data_segments: Vec<(u32, Vec<u8>)>,
/// The list of all global mutable variables of the module in their sequential order.
global_mut_values: Vec<RuntimeValue>,
heap_pages: u64,
}
impl StateSnapshot {
// Returns `None` if instance is not valid.
fn take(
module_instance: &ModuleRef,
data_segments: Vec<DataSegment>,
heap_pages: u64,
) -> Option<Self> {
let prepared_segments = data_segments
.into_iter()
.map(|mut segment| {
// Just replace contents of the segment since the segments will be discarded later
// anyway.
let contents = mem::replace(segment.value_mut(), vec![]);
let init_expr = match segment.offset() {
Some(offset) => offset.code(),
// Return if the segment is passive
None => return None
};
// [op, End]
if init_expr.len() != 2 {
return None;
}
let offset = match init_expr[0] {
Instruction::I32Const(v) => v as u32,
Instruction::GetGlobal(idx) => {
let global_val = module_instance.globals().get(idx as usize)?.get();
match global_val {
RuntimeValue::I32(v) => v as u32,
_ => return None,
}
}
_ => return None,
};
Some((offset, contents))
})
.collect::<Option<Vec<_>>>()?;
// Collect all values of mutable globals.
let global_mut_values = module_instance
.globals()
.iter()
.filter(|g| g.is_mutable())
.map(|g| g.get())
.collect();
Some(Self {
data_segments: prepared_segments,
global_mut_values,
heap_pages,
})
}
/// Reset the runtime instance to the initial version by restoring
/// the preserved memory and globals.
///
/// Returns `Err` if applying the snapshot is failed.
fn apply(&self, instance: &ModuleRef) -> Result<(), WasmError> {
let memory = instance
.export_by_name("memory")
.ok_or(WasmError::ApplySnapshotFailed)?
.as_memory()
.cloned()
.ok_or(WasmError::ApplySnapshotFailed)?;
// First, erase the memory and copy the data segments into it.
memory
.erase()
.map_err(|e| WasmError::ErasingFailed(e.to_string()))?;
for (offset, contents) in &self.data_segments {
memory
.set(*offset, contents)
.map_err(|_| WasmError::ApplySnapshotFailed)?;
}
// Second, restore the values of mutable globals.
for (global_ref, global_val) in instance
.globals()
.iter()
.filter(|g| g.is_mutable())
.zip(self.global_mut_values.iter())
{
// the instance should be the same as used for preserving and
// we iterate the same way it as we do it for preserving values that means that the
// types should be the same and all the values are mutable. So no error is expected/
global_ref
.set(*global_val)
.map_err(|_| WasmError::ApplySnapshotFailed)?;
}
Ok(())
}
}
/// A runtime along with its initial state snapshot.
#[derive(Clone)]
pub struct WasmiRuntime {
/// A wasm module instance.
instance: ModuleRef,
/// The snapshot of the instance's state taken just after the instantiation.
state_snapshot: StateSnapshot,
/// The host functions registered for this instance.
host_functions: Vec<&'static dyn Function>,
}
impl WasmRuntime for WasmiRuntime {
fn update_heap_pages(&mut self, heap_pages: u64) -> bool {
self.state_snapshot.heap_pages == heap_pages
}
fn host_functions(&self) -> &[&'static dyn Function] {
&self.host_functions
}
fn call(
&mut self,
ext: &mut dyn Externalities,
method: &str,
data: &[u8],
) -> Result<Vec<u8>, Error> {
self.state_snapshot.apply(&self.instance)
.map_err(|e| {
// Snapshot restoration failed. This is pretty unexpected since this can happen
// if some invariant is broken or if the system is under extreme memory pressure
// (so erasing fails).
error!(target: "wasm-executor", "snapshot restoration failed: {}", e);
e
})?;
call_in_wasm_module(ext, &self.instance, method, data, &self.host_functions)
}
}
pub fn create_instance(
code: &[u8],
heap_pages: u64,
host_functions: Vec<&'static dyn Function>,
) -> Result<WasmiRuntime, WasmError> {
let module = Module::from_buffer(&code).map_err(|_| WasmError::InvalidModule)?;
// Extract the data segments from the wasm code.
//
// A return of this error actually indicates that there is a problem in logic, since
// we just loaded and validated the `module` above.
let data_segments = extract_data_segments(&code)?;
// Instantiate this module.
let instance = instantiate_module(heap_pages as usize, &module, &host_functions)
.map_err(|e| WasmError::Instantiation(e.to_string()))?;
// Take state snapshot before executing anything.
let state_snapshot = StateSnapshot::take(&instance, data_segments, heap_pages)
.expect(
"`take` returns `Err` if the module is not valid;
we already loaded module above, thus the `Module` is proven to be valid at this point;
qed
",
);
Ok(WasmiRuntime {
instance,
state_snapshot,
host_functions,
})
}
/// Extract the data segments from the given wasm code.
///
/// Returns `Err` if the given wasm code cannot be deserialized.
fn extract_data_segments(wasm_code: &[u8]) -> Result<Vec<DataSegment>, WasmError> {
let raw_module: RawModule = deserialize_buffer(wasm_code)
.map_err(|_| WasmError::CantDeserializeWasm)?;
let segments = raw_module
.data_section()
.map(|ds| ds.entries())
.unwrap_or(&[])
.to_vec();
Ok(segments)
}
@@ -1,387 +0,0 @@
// 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/>.
use crate::allocator::FreeingBumpHeapAllocator;
use crate::error::{Error, Result};
use crate::sandbox::{self, SandboxCapabilities, SupervisorFuncIndex};
use crate::wasmtime::util::{
checked_range, cranelift_ir_signature, read_memory_into, write_memory_from,
};
use codec::{Decode, Encode};
use cranelift_codegen::ir;
use cranelift_codegen::isa::TargetFrontendConfig;
use log::trace;
use sp_core::sandbox as sandbox_primitives;
use std::{cmp, mem, ptr};
use wasmtime_environ::translate_signature;
use wasmtime_jit::{ActionError, Compiler};
use wasmtime_runtime::{Export, VMCallerCheckedAnyfunc, VMContext, wasmtime_call_trampoline};
use sp_wasm_interface::{
FunctionContext, MemoryId, Pointer, Result as WResult, Sandbox, Signature, Value, ValueType,
WordSize,
};
/// Wrapper type for pointer to a Wasm table entry.
///
/// The wrapper type is used to ensure that the function reference is valid as it must be unsafely
/// dereferenced from within the safe method `<FunctionExecutor as SandboxCapabilities>::invoke`.
#[derive(Clone, Copy)]
pub struct SupervisorFuncRef(*const VMCallerCheckedAnyfunc);
/// The state required to construct a FunctionExecutor context. The context only lasts for one host
/// call, whereas the state is maintained for the duration of a Wasm runtime call, which may make
/// many different host calls that must share state.
///
/// This is stored as part of the host state of the "env" Wasmtime instance.
pub struct FunctionExecutorState {
sandbox_store: sandbox::Store<SupervisorFuncRef>,
heap: FreeingBumpHeapAllocator,
}
impl FunctionExecutorState {
/// Constructs a new `FunctionExecutorState`.
pub fn new(heap_base: u32) -> Self {
FunctionExecutorState {
sandbox_store: sandbox::Store::new(),
heap: FreeingBumpHeapAllocator::new(heap_base),
}
}
/// Returns a mutable reference to the heap allocator.
pub fn heap(&mut self) -> &mut FreeingBumpHeapAllocator {
&mut self.heap
}
}
/// A `FunctionExecutor` implements `FunctionContext` for making host calls from a Wasmtime
/// runtime. The `FunctionExecutor` exists only for the lifetime of the call and borrows state from
/// a longer-living `FunctionExecutorState`.
pub struct FunctionExecutor<'a> {
compiler: &'a mut Compiler,
sandbox_store: &'a mut sandbox::Store<SupervisorFuncRef>,
heap: &'a mut FreeingBumpHeapAllocator,
memory: &'a mut [u8],
table: Option<&'a [VMCallerCheckedAnyfunc]>,
}
impl<'a> FunctionExecutor<'a> {
/// Construct a new `FunctionExecutor`.
///
/// The vmctx MUST come from a call to a function in the "env" module.
/// The state MUST be looked up from the host state of the "env" module.
pub unsafe fn new(
vmctx: *mut VMContext,
compiler: &'a mut Compiler,
state: &'a mut FunctionExecutorState,
) -> Result<Self>
{
let memory = match (*vmctx).lookup_global_export("memory") {
Some(Export::Memory { definition, vmctx: _, memory: _ }) =>
std::slice::from_raw_parts_mut(
(*definition).base,
(*definition).current_length,
),
_ => return Err(Error::InvalidMemoryReference),
};
let table = match (*vmctx).lookup_global_export("__indirect_function_table") {
Some(Export::Table { definition, vmctx: _, table: _ }) =>
Some(std::slice::from_raw_parts(
(*definition).base as *const VMCallerCheckedAnyfunc,
(*definition).current_elements as usize,
)),
_ => None,
};
Ok(FunctionExecutor {
compiler,
sandbox_store: &mut state.sandbox_store,
heap: &mut state.heap,
memory,
table,
})
}
}
impl<'a> SandboxCapabilities for FunctionExecutor<'a> {
type SupervisorFuncRef = SupervisorFuncRef;
fn store(&self) -> &sandbox::Store<Self::SupervisorFuncRef> {
&self.sandbox_store
}
fn store_mut(&mut self) -> &mut sandbox::Store<Self::SupervisorFuncRef> {
&mut self.sandbox_store
}
fn allocate(&mut self, len: WordSize) -> Result<Pointer<u8>> {
self.heap.allocate(self.memory, len)
}
fn deallocate(&mut self, ptr: Pointer<u8>) -> Result<()> {
self.heap.deallocate(self.memory, ptr)
}
fn write_memory(&mut self, ptr: Pointer<u8>, data: &[u8]) -> Result<()> {
write_memory_from(self.memory, ptr, data)
}
fn read_memory(&self, ptr: Pointer<u8>, len: WordSize) -> Result<Vec<u8>> {
let mut output = vec![0; len as usize];
read_memory_into(self.memory, ptr, output.as_mut())?;
Ok(output)
}
fn invoke(
&mut self,
dispatch_thunk: &Self::SupervisorFuncRef,
invoke_args_ptr: Pointer<u8>,
invoke_args_len: WordSize,
state: u32,
func_idx: SupervisorFuncIndex,
) -> Result<i64>
{
let func_ptr = unsafe { (*dispatch_thunk.0).func_ptr };
let vmctx = unsafe { (*dispatch_thunk.0).vmctx };
// The following code is based on the wasmtime_jit::Context::invoke.
let value_size = mem::size_of::<VMInvokeArgument>();
let (signature, mut values_vec) = generate_signature_and_args(
&[
Value::I32(u32::from(invoke_args_ptr) as i32),
Value::I32(invoke_args_len as i32),
Value::I32(state as i32),
Value::I32(usize::from(func_idx) as i32),
],
Some(ValueType::I64),
self.compiler.frontend_config(),
);
// Get the trampoline to call for this function.
let exec_code_buf = self.compiler
.get_published_trampoline(func_ptr, &signature, value_size)
.map_err(ActionError::Setup)
.map_err(Error::Wasmtime)?;
// Call the trampoline.
if let Err(message) = unsafe {
wasmtime_call_trampoline(
vmctx,
exec_code_buf,
values_vec.as_mut_ptr() as *mut u8,
)
} {
return Err(Error::Other(message));
}
// Load the return value out of `values_vec`.
Ok(unsafe { ptr::read(values_vec.as_ptr() as *const i64) })
}
}
impl<'a> FunctionContext for FunctionExecutor<'a> {
fn read_memory_into(&self, address: Pointer<u8>, dest: &mut [u8]) -> WResult<()> {
read_memory_into(self.memory, address, dest).map_err(|e| e.to_string())
}
fn write_memory(&mut self, address: Pointer<u8>, data: &[u8]) -> WResult<()> {
write_memory_from(self.memory, address, data).map_err(|e| e.to_string())
}
fn allocate_memory(&mut self, size: WordSize) -> WResult<Pointer<u8>> {
self.heap.allocate(self.memory, size).map_err(|e| e.to_string())
}
fn deallocate_memory(&mut self, ptr: Pointer<u8>) -> WResult<()> {
self.heap.deallocate(self.memory, ptr).map_err(|e| e.to_string())
}
fn sandbox(&mut self) -> &mut dyn Sandbox {
self
}
}
impl<'a> Sandbox for FunctionExecutor<'a> {
fn memory_get(
&mut self,
memory_id: MemoryId,
offset: WordSize,
buf_ptr: Pointer<u8>,
buf_len: WordSize,
) -> WResult<u32>
{
let sandboxed_memory = self.sandbox_store.memory(memory_id)
.map_err(|e| e.to_string())?;
sandboxed_memory.with_direct_access(|memory| {
let len = buf_len as usize;
let src_range = match checked_range(offset as usize, len, memory.len()) {
Some(range) => range,
None => return Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS),
};
let dst_range = match checked_range(buf_ptr.into(), len, self.memory.len()) {
Some(range) => range,
None => return Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS),
};
&mut self.memory[dst_range].copy_from_slice(&memory[src_range]);
Ok(sandbox_primitives::ERR_OK)
})
}
fn memory_set(
&mut self,
memory_id: MemoryId,
offset: WordSize,
val_ptr: Pointer<u8>,
val_len: WordSize,
) -> WResult<u32>
{
let sandboxed_memory = self.sandbox_store.memory(memory_id)
.map_err(|e| e.to_string())?;
sandboxed_memory.with_direct_access_mut(|memory| {
let len = val_len as usize;
let src_range = match checked_range(val_ptr.into(), len, self.memory.len()) {
Some(range) => range,
None => return Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS),
};
let dst_range = match checked_range(offset as usize, len, memory.len()) {
Some(range) => range,
None => return Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS),
};
&mut memory[dst_range].copy_from_slice(&self.memory[src_range]);
Ok(sandbox_primitives::ERR_OK)
})
}
fn memory_teardown(&mut self, memory_id: MemoryId)
-> WResult<()>
{
self.sandbox_store.memory_teardown(memory_id).map_err(|e| e.to_string())
}
fn memory_new(&mut self, initial: u32, maximum: MemoryId) -> WResult<u32> {
self.sandbox_store.new_memory(initial, maximum).map_err(|e| e.to_string())
}
fn invoke(
&mut self,
instance_id: u32,
export_name: &str,
args: &[u8],
return_val: Pointer<u8>,
return_val_len: u32,
state: u32,
) -> WResult<u32> {
trace!(target: "sp-sandbox", "invoke, instance_idx={}", instance_id);
// Deserialize arguments and convert them into wasmi types.
let args = Vec::<sandbox_primitives::TypedValue>::decode(&mut &args[..])
.map_err(|_| "Can't decode serialized arguments for the invocation")?
.into_iter()
.map(Into::into)
.collect::<Vec<_>>();
let instance = self.sandbox_store.instance(instance_id).map_err(|e| e.to_string())?;
let result = instance.invoke(export_name, &args, self, state);
match result {
Ok(None) => Ok(sandbox_primitives::ERR_OK),
Ok(Some(val)) => {
// Serialize return value and write it back into the memory.
sandbox_primitives::ReturnValue::Value(val.into()).using_encoded(|val| {
if val.len() > return_val_len as usize {
Err("Return value buffer is too small")?;
}
FunctionContext::write_memory(self, return_val, val)?;
Ok(sandbox_primitives::ERR_OK)
})
}
Err(_) => Ok(sandbox_primitives::ERR_EXECUTION),
}
}
fn instance_teardown(&mut self, instance_id: u32) -> WResult<()> {
self.sandbox_store.instance_teardown(instance_id).map_err(|e| e.to_string())
}
fn instance_new(&mut self, dispatch_thunk_id: u32, wasm: &[u8], raw_env_def: &[u8], state: u32)
-> WResult<u32>
{
// Extract a dispatch thunk from instance's table by the specified index.
let dispatch_thunk = {
let table = self.table.as_ref()
.ok_or_else(|| "Runtime doesn't have a table; sandbox is unavailable")?;
let func_ref = table.get(dispatch_thunk_id as usize)
.ok_or_else(|| "dispatch_thunk_idx is out of the table bounds")?;
SupervisorFuncRef(func_ref)
};
let instance_idx_or_err_code =
match sandbox::instantiate(self, dispatch_thunk, wasm, raw_env_def, state) {
Ok(instance_idx) => instance_idx,
Err(sandbox::InstantiationError::StartTrapped) =>
sandbox_primitives::ERR_EXECUTION,
Err(_) => sandbox_primitives::ERR_MODULE,
};
Ok(instance_idx_or_err_code as u32)
}
}
// The storage for a Wasmtime invocation argument.
#[derive(Debug, Default, Copy, Clone)]
#[repr(C, align(8))]
struct VMInvokeArgument([u8; 8]);
fn generate_signature_and_args(
args: &[Value],
result_type: Option<ValueType>,
frontend_config: TargetFrontendConfig,
) -> (ir::Signature, Vec<VMInvokeArgument>)
{
// This code is based on the wasmtime_jit::Context::invoke.
let param_types = args.iter()
.map(|arg| arg.value_type())
.collect::<Vec<_>>();
let signature = translate_signature(
cranelift_ir_signature(
Signature::new(param_types, result_type),
&frontend_config.default_call_conv
),
frontend_config.pointer_type()
);
let mut values_vec = vec![
VMInvokeArgument::default();
cmp::max(args.len(), result_type.iter().len())
];
// Store the argument values into `values_vec`.
for (index, arg) in args.iter().enumerate() {
unsafe {
let ptr = values_vec.as_mut_ptr().add(index);
match arg {
Value::I32(x) => ptr::write(ptr as *mut i32, *x),
Value::I64(x) => ptr::write(ptr as *mut i64, *x),
Value::F32(x) => ptr::write(ptr as *mut u32, *x),
Value::F64(x) => ptr::write(ptr as *mut u64, *x),
}
}
}
(signature, values_vec)
}
@@ -1,24 +0,0 @@
// 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/>.
///! Defines a `WasmRuntime` that uses the Wasmtime JIT to execute.
mod function_executor;
mod runtime;
mod trampoline;
mod util;
pub use runtime::create_instance;
@@ -1,372 +0,0 @@
// 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/>.
//! Defines the compiled Wasm runtime that uses Wasmtime internally.
use crate::error::{Error, Result, WasmError};
use crate::wasm_runtime::WasmRuntime;
use crate::wasm_utils::interpret_runtime_api_result;
use crate::wasmtime::function_executor::FunctionExecutorState;
use crate::wasmtime::trampoline::{EnvState, make_trampoline};
use crate::wasmtime::util::{cranelift_ir_signature, read_memory_into, write_memory_from};
use crate::Externalities;
use cranelift_codegen::ir;
use cranelift_codegen::isa::TargetIsa;
use cranelift_entity::{EntityRef, PrimaryMap};
use cranelift_frontend::FunctionBuilderContext;
use cranelift_wasm::DefinedFuncIndex;
use std::cell::RefCell;
use std::collections::HashMap;
use std::convert::TryFrom;
use std::rc::Rc;
use sp_wasm_interface::{Pointer, WordSize, Function};
use wasmtime_environ::{Module, translate_signature};
use wasmtime_jit::{
ActionOutcome, ActionError, CodeMemory, CompilationStrategy, CompiledModule, Compiler, Context,
SetupError, RuntimeValue,
};
use wasmtime_runtime::{Export, Imports, InstanceHandle, VMFunctionBody};
/// A `WasmRuntime` implementation using the Wasmtime JIT to compile the runtime module to native
/// and execute the compiled code.
pub struct WasmtimeRuntime {
module: CompiledModule,
context: Context,
max_heap_pages: Option<u32>,
heap_pages: u32,
/// The host functions registered for this instance.
host_functions: Vec<&'static dyn Function>,
}
impl WasmRuntime for WasmtimeRuntime {
fn update_heap_pages(&mut self, heap_pages: u64) -> bool {
match heap_pages_valid(heap_pages, self.max_heap_pages) {
Some(heap_pages) => {
self.heap_pages = heap_pages;
true
}
None => false,
}
}
fn host_functions(&self) -> &[&'static dyn Function] {
&self.host_functions
}
fn call(&mut self, ext: &mut dyn Externalities, method: &str, data: &[u8]) -> Result<Vec<u8>> {
call_method(
&mut self.context,
&mut self.module,
ext,
method,
data,
self.heap_pages,
)
}
}
/// Create a new `WasmtimeRuntime` given the code. This function performs translation from Wasm to
/// machine code, which can be computationally heavy.
pub fn create_instance(
code: &[u8],
heap_pages: u64,
host_functions: Vec<&'static dyn Function>,
) -> std::result::Result<WasmtimeRuntime, WasmError> {
let (compiled_module, context) = create_compiled_unit(code, &host_functions)?;
// Inspect the module for the min and max memory sizes.
let (min_memory_size, max_memory_size) = {
let module = compiled_module.module_ref();
let memory_index = match module.exports.get("memory") {
Some(wasmtime_environ::Export::Memory(memory_index)) => *memory_index,
_ => return Err(WasmError::InvalidMemory),
};
let memory_plan = module.memory_plans.get(memory_index)
.expect("memory_index is retrieved from the module's exports map; qed");
(memory_plan.memory.minimum, memory_plan.memory.maximum)
};
// Check that heap_pages is within the allowed range.
let max_heap_pages = max_memory_size.map(|max| max.saturating_sub(min_memory_size));
let heap_pages = heap_pages_valid(heap_pages, max_heap_pages)
.ok_or_else(|| WasmError::InvalidHeapPages)?;
Ok(WasmtimeRuntime {
module: compiled_module,
context,
max_heap_pages,
heap_pages,
host_functions,
})
}
fn create_compiled_unit(
code: &[u8],
host_functions: &[&'static dyn Function],
) -> std::result::Result<(CompiledModule, Context), WasmError> {
let compilation_strategy = CompilationStrategy::Cranelift;
let compiler = new_compiler(compilation_strategy)?;
let mut context = Context::new(Box::new(compiler));
// Enable/disable producing of debug info.
context.set_debug_info(false);
// Instantiate and link the env module.
let global_exports = context.get_global_exports();
let compiler = new_compiler(compilation_strategy)?;
let env_module = instantiate_env_module(global_exports, compiler, host_functions)?;
context.name_instance("env".to_owned(), env_module);
// Compile the wasm module.
let module = context.compile_module(&code)
.map_err(WasmError::WasmtimeSetup)?;
Ok((module, context))
}
/// Call a function inside a precompiled Wasm module.
fn call_method(
context: &mut Context,
module: &mut CompiledModule,
ext: &mut dyn Externalities,
method: &str,
data: &[u8],
heap_pages: u32,
) -> Result<Vec<u8>> {
// Old exports get clobbered in `InstanceHandle::new` if we don't explicitly remove them first.
//
// The global exports mechanism is temporary in Wasmtime and expected to be removed.
// https://github.com/CraneStation/wasmtime/issues/332
clear_globals(&mut *context.get_global_exports().borrow_mut());
let mut instance = module.instantiate()
.map_err(SetupError::Instantiate)
.map_err(ActionError::Setup)
.map_err(Error::Wasmtime)?;
// Ideally there would be a way to set the heap pages during instantiation rather than
// growing the memory after the fact. Currently this may require an additional mmap and copy.
// However, the wasmtime API doesn't support modifying the size of memory on instantiation
// at this time.
grow_memory(&mut instance, heap_pages)?;
// Initialize the function executor state.
let heap_base = get_heap_base(&instance)?;
let executor_state = FunctionExecutorState::new(heap_base);
reset_env_state_and_take_trap(context, Some(executor_state))?;
// Write the input data into guest memory.
let (data_ptr, data_len) = inject_input_data(context, &mut instance, data)?;
let args = [RuntimeValue::I32(u32::from(data_ptr) as i32), RuntimeValue::I32(data_len as i32)];
// Invoke the function in the runtime.
let outcome = sp_externalities::set_and_run_with_externalities(ext, || {
context
.invoke(&mut instance, method, &args[..])
.map_err(Error::Wasmtime)
})?;
let trap_error = reset_env_state_and_take_trap(context, None)?;
let (output_ptr, output_len) = match outcome {
ActionOutcome::Returned { values } => match values.as_slice() {
[RuntimeValue::I64(retval)] => interpret_runtime_api_result(*retval),
_ => return Err(Error::InvalidReturn),
}
ActionOutcome::Trapped { message } => return Err(trap_error.unwrap_or_else(
|| format!("Wasm execution trapped: {}", message).into()
)),
};
// Read the output data from guest memory.
let mut output = vec![0; output_len as usize];
let memory = get_memory_mut(&mut instance)?;
read_memory_into(memory, output_ptr, &mut output)?;
Ok(output)
}
/// The implementation is based on wasmtime_wasi::instantiate_wasi.
fn instantiate_env_module(
global_exports: Rc<RefCell<HashMap<String, Option<Export>>>>,
compiler: Compiler,
host_functions: &[&'static dyn Function],
) -> std::result::Result<InstanceHandle, WasmError>
{
let isa = target_isa()?;
let pointer_type = isa.pointer_type();
let call_conv = isa.default_call_conv();
let mut fn_builder_ctx = FunctionBuilderContext::new();
let mut module = Module::new();
let mut finished_functions = <PrimaryMap<DefinedFuncIndex, *const VMFunctionBody>>::new();
let mut code_memory = CodeMemory::new();
for function in host_functions {
let sig = translate_signature(
cranelift_ir_signature(function.signature(), &call_conv),
pointer_type
);
let sig_id = module.signatures.push(sig.clone());
let func_id = module.functions.push(sig_id);
module
.exports
.insert(function.name().to_string(), wasmtime_environ::Export::Function(func_id));
let trampoline = make_trampoline(
isa.as_ref(),
&mut code_memory,
&mut fn_builder_ctx,
func_id.index() as u32,
&sig,
)?;
finished_functions.push(trampoline);
}
code_memory.publish();
let imports = Imports::none();
let data_initializers = Vec::new();
let signatures = PrimaryMap::new();
let env_state = EnvState::new(code_memory, compiler, host_functions);
let result = InstanceHandle::new(
Rc::new(module),
global_exports,
finished_functions.into_boxed_slice(),
imports,
&data_initializers,
signatures.into_boxed_slice(),
None,
Box::new(env_state),
);
result.map_err(|e| WasmError::WasmtimeSetup(SetupError::Instantiate(e)))
}
/// Build a new TargetIsa for the host machine.
fn target_isa() -> std::result::Result<Box<dyn TargetIsa>, WasmError> {
let isa_builder = cranelift_native::builder()
.map_err(WasmError::MissingCompilerSupport)?;
let flag_builder = cranelift_codegen::settings::builder();
Ok(isa_builder.finish(cranelift_codegen::settings::Flags::new(flag_builder)))
}
fn new_compiler(strategy: CompilationStrategy) -> std::result::Result<Compiler, WasmError> {
let isa = target_isa()?;
Ok(Compiler::new(isa, strategy))
}
fn clear_globals(global_exports: &mut HashMap<String, Option<Export>>) {
global_exports.remove("memory");
global_exports.remove("__heap_base");
global_exports.remove("__indirect_function_table");
}
fn grow_memory(instance: &mut InstanceHandle, pages: u32) -> Result<()> {
// This is safe to wrap in an unsafe block as:
// - The result of the `lookup_immutable` call is not mutated
// - The definition pointer is returned by a lookup on a valid instance
let memory_index = unsafe {
match instance.lookup_immutable("memory") {
Some(Export::Memory { definition, vmctx: _, memory: _ }) =>
instance.memory_index(&*definition),
_ => return Err(Error::InvalidMemoryReference),
}
};
instance.memory_grow(memory_index, pages)
.map(|_| ())
.ok_or_else(|| "requested heap_pages would exceed maximum memory size".into())
}
fn get_env_state(context: &mut Context) -> Result<&mut EnvState> {
let env_instance = context.get_instance("env")
.map_err(|err| format!("cannot find \"env\" module: {}", err))?;
env_instance
.host_state()
.downcast_mut::<EnvState>()
.ok_or_else(|| "cannot get \"env\" module host state".into())
}
fn reset_env_state_and_take_trap(
context: &mut Context,
executor_state: Option<FunctionExecutorState>,
) -> Result<Option<Error>>
{
let env_state = get_env_state(context)?;
env_state.executor_state = executor_state;
Ok(env_state.take_trap())
}
fn inject_input_data(
context: &mut Context,
instance: &mut InstanceHandle,
data: &[u8],
) -> Result<(Pointer<u8>, WordSize)> {
let env_state = get_env_state(context)?;
let executor_state = env_state.executor_state
.as_mut()
.ok_or_else(|| "cannot get \"env\" module executor state")?;
let memory = get_memory_mut(instance)?;
let data_len = data.len() as WordSize;
let data_ptr = executor_state.heap().allocate(memory, data_len)?;
write_memory_from(memory, data_ptr, data)?;
Ok((data_ptr, data_len))
}
fn get_memory_mut(instance: &mut InstanceHandle) -> Result<&mut [u8]> {
match instance.lookup("memory") {
// This is safe to wrap in an unsafe block as:
// - The definition pointer is returned by a lookup on a valid instance and thus points to
// a valid memory definition
Some(Export::Memory { definition, vmctx: _, memory: _ }) => unsafe {
Ok(std::slice::from_raw_parts_mut(
(*definition).base,
(*definition).current_length,
))
},
_ => Err(Error::InvalidMemoryReference),
}
}
fn get_heap_base(instance: &InstanceHandle) -> Result<u32> {
// This is safe to wrap in an unsafe block as:
// - The result of the `lookup_immutable` call is not mutated
// - The definition pointer is returned by a lookup on a valid instance
// - The defined value is checked to be an I32, which can be read safely as a u32
unsafe {
match instance.lookup_immutable("__heap_base") {
Some(Export::Global { definition, vmctx: _, global })
if global.ty == ir::types::I32 =>
Ok(*(*definition).as_u32()),
_ => return Err(Error::HeapBaseNotFoundOrInvalid),
}
}
}
/// Checks whether the heap_pages parameter is within the valid range and converts it to a u32.
/// Returns None if heaps_pages in not in range.
fn heap_pages_valid(heap_pages: u64, max_heap_pages: Option<u32>)
-> Option<u32>
{
let heap_pages = u32::try_from(heap_pages).ok()?;
if let Some(max_heap_pages) = max_heap_pages {
if heap_pages > max_heap_pages {
return None;
}
}
Some(heap_pages)
}
@@ -1,361 +0,0 @@
// 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/>.
//! The trampoline is the dynamically generated entry point to a runtime host call.
//!
//! This code is based on and large parts are copied from wasmtime's
//! wasmtime-api/src/trampoline/func.rs.
use cranelift_codegen::{Context, binemit, ir, isa};
use cranelift_codegen::ir::{InstBuilder, StackSlotData, StackSlotKind, TrapCode};
use cranelift_frontend::{FunctionBuilder, FunctionBuilderContext};
use cranelift_codegen::print_errors::pretty_error;
use wasmtime_jit::{CodeMemory, Compiler};
use wasmtime_environ::CompiledFunction;
use wasmtime_runtime::{VMContext, VMFunctionBody};
use sp_wasm_interface::{Function, Value, ValueType};
use std::{cmp, panic::{self, AssertUnwindSafe}, ptr};
use crate::error::{Error, WasmError};
use crate::wasmtime::function_executor::{FunctionExecutorState, FunctionExecutor};
const CALL_SUCCESS: u32 = 0;
const CALL_FAILED_WITH_ERROR: u32 = 1;
const CALL_WITH_BAD_HOST_STATE: u32 = 2;
/// A code to trap with that indicates a host call error.
const TRAP_USER_CODE: u16 = 0;
/// The only Wasm types allowed in host function signatures (I32, I64, F32, F64) are all
/// represented in at most 8 bytes.
const MAX_WASM_TYPE_SIZE: usize = 8;
/// The top-level host state of the "env" module. This state is used by the trampoline function to
/// construct a `FunctionExecutor` which can execute the host call.
pub struct EnvState {
host_functions: Vec<&'static dyn Function>,
compiler: Compiler,
// The code memory must be kept around on the state to prevent it from being dropped.
#[allow(dead_code)]
code_memory: CodeMemory,
trap: Option<Error>,
/// The executor state stored across host calls during a single Wasm runtime call.
/// During a runtime call, this MUST be `Some`.
pub executor_state: Option<FunctionExecutorState>,
}
impl EnvState {
/// Construct a new `EnvState` which owns the given code memory.
pub fn new(
code_memory: CodeMemory,
compiler: Compiler,
host_functions: &[&'static dyn Function],
) -> Self {
EnvState {
trap: None,
compiler,
code_memory,
executor_state: None,
host_functions: host_functions.to_vec(),
}
}
/// Resets the trap error to None and returns the current value.
pub fn take_trap(&mut self) -> Option<Error> {
self.trap.take()
}
}
/// This is called by the dynamically generated trampoline taking the function index and reference
/// to the call arguments on the stack as arguments. Returns zero on success and a non-zero value
/// on failure.
unsafe extern "C" fn stub_fn(vmctx: *mut VMContext, func_index: u32, values_vec: *mut i64) -> u32 {
if let Some(state) = (*vmctx).host_state().downcast_mut::<EnvState>() {
match stub_fn_inner(
vmctx,
&state.host_functions,
&mut state.compiler,
state.executor_state.as_mut(),
func_index,
values_vec,
) {
Ok(()) => CALL_SUCCESS,
Err(err) => {
state.trap = Some(err);
CALL_FAILED_WITH_ERROR
}
}
} else {
// Well, we can't even set a trap message, so we'll just exit without one.
CALL_WITH_BAD_HOST_STATE
}
}
/// Implements most of the logic in `stub_fn` but returning a `Result` instead of an integer error
/// for the sake of readability.
unsafe fn stub_fn_inner(
vmctx: *mut VMContext,
externals: &[&dyn Function],
compiler: &mut Compiler,
executor_state: Option<&mut FunctionExecutorState>,
func_index: u32,
values_vec: *mut i64,
) -> Result<(), Error> {
let func = externals.get(func_index as usize)
.ok_or_else(|| format!("call to undefined external function with index {}", func_index))?;
let executor_state = executor_state
.ok_or_else(|| "executor state is None during call to external function")?;
// Build the external function context.
let mut context = FunctionExecutor::new(vmctx, compiler, executor_state)?;
let mut context = AssertUnwindSafe(&mut context);
// Execute and write output back to the stack.
let return_val = panic::catch_unwind(move || {
let signature = func.signature();
// Read the arguments from the stack.
let mut args = signature.args.iter()
.enumerate()
.map(|(i, &param_type)| read_value_from(values_vec.offset(i as isize), param_type));
func.execute(&mut **context, &mut args)
});
match return_val {
Ok(ret_val) => {
if let Some(val) = ret_val
.map_err(|e| Error::FunctionExecution(func.name().to_string(), e))? {
write_value_to(values_vec, val);
}
Ok(())
},
Err(e) => {
let message = if let Some(err) = e.downcast_ref::<String>() {
err.to_string()
} else if let Some(err) = e.downcast_ref::<&str>() {
err.to_string()
} else {
"Panicked without any further information!".into()
};
Err(Error::FunctionExecution(func.name().to_string(), message))
}
}
}
/// Create a trampoline for invoking a host function.
///
/// The trampoline is a dynamically generated entry point to a runtime host call. The function is
/// generated by manually constructing Cranelift IR and using the Cranelift compiler. The
/// trampoline embeds the function index as a constant and delegates to a stub function in Rust,
/// which takes the function index and a memory reference to the stack arguments and return value
/// slots.
///
/// This code is of modified copy of wasmtime's wasmtime-api/src/trampoline/func.rs.
pub fn make_trampoline(
isa: &dyn isa::TargetIsa,
code_memory: &mut CodeMemory,
fn_builder_ctx: &mut FunctionBuilderContext,
func_index: u32,
signature: &ir::Signature,
) -> Result<*const VMFunctionBody, WasmError> {
// Mostly reverse copy of the similar method from wasmtime's
// wasmtime-jit/src/compiler.rs.
let pointer_type = isa.pointer_type();
let mut stub_sig = ir::Signature::new(isa.frontend_config().default_call_conv);
// Ensure that the first parameter of the generated function is the `VMContext` pointer.
assert_eq!(
signature.params[0],
ir::AbiParam::special(pointer_type, ir::ArgumentPurpose::VMContext)
);
// Add the `vmctx` parameter.
stub_sig.params.push(ir::AbiParam::special(
pointer_type,
ir::ArgumentPurpose::VMContext,
));
// Add the `func_index` parameter.
stub_sig.params.push(ir::AbiParam::new(ir::types::I32));
// Add the `values_vec` parameter.
stub_sig.params.push(ir::AbiParam::new(pointer_type));
// Add error/trap return.
stub_sig.returns.push(ir::AbiParam::new(ir::types::I32));
// Each parameter and return value gets a 64-bit (8-byte) wide slot on the stack, as that is
// large enough to fit all Wasm primitive types that can be used in host function signatures.
// The `VMContext` pointer, which is a parameter of the function signature, is excluded as it
// is passed directly to the stub function rather than being looked up on the caller stack from
// the `values_vec` pointer.
let values_vec_len = cmp::max(signature.params.len() - 1, signature.returns.len());
let values_vec_size = (MAX_WASM_TYPE_SIZE * values_vec_len) as u32;
let mut context = Context::new();
context.func =
ir::Function::with_name_signature(ir::ExternalName::user(0, 0), signature.clone());
let ss = context.func.create_stack_slot(StackSlotData::new(
StackSlotKind::ExplicitSlot,
values_vec_size,
));
{
let mut builder = FunctionBuilder::new(&mut context.func, fn_builder_ctx);
let block0 = builder.create_ebb();
builder.append_ebb_params_for_function_params(block0);
builder.switch_to_block(block0);
builder.seal_block(block0);
let values_vec_ptr_val = builder.ins().stack_addr(pointer_type, ss, 0);
let mflags = ir::MemFlags::trusted();
for i in 1..signature.params.len() {
let val = builder.func.dfg.ebb_params(block0)[i];
builder.ins().store(
mflags,
val,
values_vec_ptr_val,
((i - 1) * MAX_WASM_TYPE_SIZE) as i32,
);
}
let vmctx_ptr_val = builder.func.dfg.ebb_params(block0)[0];
let func_index_val = builder.ins().iconst(ir::types::I32, func_index as i64);
let callee_args = vec![vmctx_ptr_val, func_index_val, values_vec_ptr_val];
let new_sig = builder.import_signature(stub_sig.clone());
let callee_value = builder
.ins()
.iconst(pointer_type, stub_fn as *const VMFunctionBody as i64);
let call = builder
.ins()
.call_indirect(new_sig, callee_value, &callee_args);
let call_result = builder.func.dfg.inst_results(call)[0];
builder.ins().trapnz(call_result, TrapCode::User(TRAP_USER_CODE));
let mflags = ir::MemFlags::trusted();
let mut results = Vec::new();
for (i, r) in signature.returns.iter().enumerate() {
let load = builder.ins().load(
r.value_type,
mflags,
values_vec_ptr_val,
(i * MAX_WASM_TYPE_SIZE) as i32,
);
results.push(load);
}
builder.ins().return_(&results);
builder.finalize()
}
let mut code_buf: Vec<u8> = Vec::new();
let mut reloc_sink = RelocSink;
let mut trap_sink = binemit::NullTrapSink {};
let mut stackmap_sink = binemit::NullStackmapSink {};
context
.compile_and_emit(
isa,
&mut code_buf,
&mut reloc_sink,
&mut trap_sink,
&mut stackmap_sink,
)
.map_err(|e| {
WasmError::Instantiation(format!(
"failed to compile trampoline: {}",
pretty_error(&context.func, Some(isa), e)
))
})?;
let mut unwind_info = Vec::new();
context.emit_unwind_info(isa, &mut unwind_info);
let func_ref = code_memory
.allocate_for_function(&CompiledFunction {
body: code_buf,
jt_offsets: context.func.jt_offsets,
unwind_info,
})
.map_err(|e| WasmError::Instantiation(format!("failed to allocate code memory: {}", e)))?;
Ok(func_ref.as_ptr())
}
/// We don't expect trampoline compilation to produce any relocations, so
/// this `RelocSink` just asserts that it doesn't recieve any.
struct RelocSink;
impl binemit::RelocSink for RelocSink {
fn reloc_ebb(
&mut self,
_offset: binemit::CodeOffset,
_reloc: binemit::Reloc,
_ebb_offset: binemit::CodeOffset,
) {
panic!("trampoline compilation should not produce ebb relocs");
}
fn reloc_external(
&mut self,
_offset: binemit::CodeOffset,
_reloc: binemit::Reloc,
_name: &ir::ExternalName,
_addend: binemit::Addend,
) {
panic!("trampoline compilation should not produce external symbol relocs");
}
fn reloc_constant(
&mut self,
_code_offset: binemit::CodeOffset,
_reloc: binemit::Reloc,
_constant_offset: ir::ConstantOffset,
) {
panic!("trampoline compilation should not produce constant relocs");
}
fn reloc_jt(
&mut self,
_offset: binemit::CodeOffset,
_reloc: binemit::Reloc,
_jt: ir::JumpTable,
) {
panic!("trampoline compilation should not produce jump table relocs");
}
}
unsafe fn write_value_to(p: *mut i64, val: Value) {
match val {
Value::I32(i) => ptr::write(p as *mut i32, i),
Value::I64(i) => ptr::write(p as *mut i64, i),
Value::F32(u) => ptr::write(p as *mut u32, u),
Value::F64(u) => ptr::write(p as *mut u64, u),
}
}
unsafe fn read_value_from(p: *const i64, ty: ValueType) -> Value {
match ty {
ValueType::I32 => Value::I32(ptr::read(p as *const i32)),
ValueType::I64 => Value::I64(ptr::read(p as *const i64)),
ValueType::F32 => Value::F32(ptr::read(p as *const u32)),
ValueType::F64 => Value::F64(ptr::read(p as *const u64)),
}
}
@@ -1,113 +0,0 @@
// 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/>.
use crate::error::{Error, Result};
use cranelift_codegen::{ir, isa};
use std::ops::Range;
use sp_wasm_interface::{Pointer, Signature, ValueType};
/// Read data from a slice of memory into a destination buffer.
///
/// Returns an error if the read would go out of the memory bounds.
pub fn read_memory_into(memory: &[u8], address: Pointer<u8>, dest: &mut [u8]) -> Result<()> {
let range = checked_range(address.into(), dest.len(), memory.len())
.ok_or_else(|| Error::Other("memory read is out of bounds".into()))?;
dest.copy_from_slice(&memory[range]);
Ok(())
}
/// Write data to a slice of memory.
///
/// Returns an error if the write would go out of the memory bounds.
pub fn write_memory_from(memory: &mut [u8], address: Pointer<u8>, data: &[u8]) -> Result<()> {
let range = checked_range(address.into(), data.len(), memory.len())
.ok_or_else(|| Error::Other("memory write is out of bounds".into()))?;
&mut memory[range].copy_from_slice(data);
Ok(())
}
/// Construct a range from an offset to a data length after the offset.
/// Returns None if the end of the range would exceed some maximum offset.
pub fn checked_range(offset: usize, len: usize, max: usize) -> Option<Range<usize>> {
let end = offset.checked_add(len)?;
if end <= max {
Some(offset..end)
} else {
None
}
}
/// Convert a wasm_interface Signature into a cranelift_codegen Signature.
pub fn cranelift_ir_signature(signature: Signature, call_conv: &isa::CallConv) -> ir::Signature {
ir::Signature {
params: signature.args.iter()
.map(cranelift_ir_type)
.map(ir::AbiParam::new)
.collect(),
returns: signature.return_value.iter()
.map(cranelift_ir_type)
.map(ir::AbiParam::new)
.collect(),
call_conv: call_conv.clone(),
}
}
/// Convert a wasm_interface ValueType into a cranelift_codegen Type.
pub fn cranelift_ir_type(value_type: &ValueType) -> ir::types::Type {
match value_type {
ValueType::I32 => ir::types::I32,
ValueType::I64 => ir::types::I64,
ValueType::F32 => ir::types::F32,
ValueType::F64 => ir::types::F64,
}
}
#[cfg(test)]
mod tests {
use super::*;
use assert_matches::assert_matches;
#[test]
fn test_read_memory_into() {
let mut memory = [0; 20];
let mut dest = [0; 5];
&mut memory[15..20].copy_from_slice(b"hello");
read_memory_into(&memory[..], Pointer::new(15), &mut dest[..]).unwrap();
// Test that out of bounds read fails.
assert_matches!(
read_memory_into(&memory[..], Pointer::new(16), &mut dest[..]),
Err(Error::Other(_))
)
}
#[test]
fn test_write_memory_from() {
let mut memory = [0; 20];
let data = b"hello";
write_memory_from(&mut memory[..], Pointer::new(15), data).unwrap();
// Test that out of bounds write fails.
assert_matches!(
write_memory_from(&mut memory[..], Pointer::new(16), data),
Err(Error::Other(_))
)
}
}