// Copyright 2019-2020 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 .
//! This crate provides an implementation of `WasmModule` that is baked by wasmi.
use std::{str, cell::RefCell, sync::Arc};
use wasmi::{
Module, ModuleInstance, MemoryInstance, MemoryRef, TableRef, ImportsBuilder, ModuleRef,
memory_units::Pages,
RuntimeValue::{I32, I64, self},
};
use codec::{Encode, Decode};
use sp_core::sandbox as sandbox_primitives;
use log::{error, trace, debug};
use sp_wasm_interface::{
FunctionContext, Pointer, WordSize, Sandbox, MemoryId, Result as WResult, Function,
};
use sp_runtime_interface::unpack_ptr_and_len;
use sc_executor_common::wasm_runtime::{WasmModule, WasmInstance};
use sc_executor_common::{
error::{Error, WasmError},
sandbox,
};
use sc_executor_common::util::{DataSegmentsSnapshot, WasmModuleInfo};
struct FunctionExecutor<'a> {
sandbox_store: sandbox::Store,
heap: sp_allocator::FreeingBumpHeapAllocator,
memory: MemoryRef,
table: Option,
host_functions: &'a [&'static dyn Function],
allow_missing_func_imports: bool,
missing_functions: &'a [String],
}
impl<'a> FunctionExecutor<'a> {
fn new(
m: MemoryRef,
heap_base: u32,
t: Option,
host_functions: &'a [&'static dyn Function],
allow_missing_func_imports: bool,
missing_functions: &'a [String],
) -> Result {
Ok(FunctionExecutor {
sandbox_store: sandbox::Store::new(),
heap: sp_allocator::FreeingBumpHeapAllocator::new(heap_base),
memory: m,
table: t,
host_functions,
allow_missing_func_imports,
missing_functions,
})
}
}
impl<'a> sandbox::SandboxCapabilities for FunctionExecutor<'a> {
type SupervisorFuncRef = wasmi::FuncRef;
fn invoke(
&mut self,
dispatch_thunk: &Self::SupervisorFuncRef,
invoke_args_ptr: Pointer,
invoke_args_len: WordSize,
state: u32,
func_idx: sandbox::SupervisorFuncIndex,
) -> Result {
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, dest: &mut [u8]) -> WResult<()> {
self.memory.get_into(address.into(), dest).map_err(|e| e.to_string())
}
fn write_memory(&mut self, address: Pointer, data: &[u8]) -> WResult<()> {
self.memory.set(address.into(), data).map_err(|e| e.to_string())
}
fn allocate_memory(&mut self, size: WordSize) -> WResult> {
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) -> 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,
buf_len: WordSize,
) -> WResult {
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,
val_len: WordSize,
) -> WResult {
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 {
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,
return_val_len: WordSize,
state: u32,
) -> WResult {
trace!(target: "sp-sandbox", "invoke, instance_idx={}", instance_id);
// Deserialize arguments and convert them into wasmi types.
let args = Vec::::decode(&mut &args[..])
.map_err(|_| "Can't decode serialized arguments for the invocation")?
.into_iter()
.map(Into::into)
.collect::>();
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.
sp_wasm_interface::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 {
// 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 guest_env = match sandbox::GuestEnvironment::decode(&self.sandbox_store, raw_env_def) {
Ok(guest_env) => guest_env,
Err(_) => return Ok(sandbox_primitives::ERR_MODULE as u32),
};
let instance_idx_or_err_code =
match sandbox::instantiate(self, dispatch_thunk, wasm, guest_env, state)
.map(|i| i.register(&mut self.sandbox_store))
{
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)
}
fn get_global_val(
&self,
instance_idx: u32,
name: &str,
) -> WResult