// 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 . use sp_allocator::FreeingBumpHeapAllocator; use sc_executor_common::error::{Error, Result}; use sc_executor_common::sandbox::{self, SandboxCapabilities, SupervisorFuncIndex}; use crate::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 `::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, 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, 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 { 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.sandbox_store } fn store_mut(&mut self) -> &mut sandbox::Store { &mut self.sandbox_store } fn allocate(&mut self, len: WordSize) -> Result> { self.heap.allocate(self.memory, len).map_err(Into::into) } fn deallocate(&mut self, ptr: Pointer) -> Result<()> { self.heap.deallocate(self.memory, ptr).map_err(Into::into) } fn write_memory(&mut self, ptr: Pointer, data: &[u8]) -> Result<()> { write_memory_from(self.memory, ptr, data) } fn read_memory(&self, ptr: Pointer, len: WordSize) -> Result> { 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, invoke_args_len: WordSize, state: u32, func_idx: SupervisorFuncIndex, ) -> Result { 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::(); 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(|e| Error::Other(e.to_string()))?; // 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, dest: &mut [u8]) -> WResult<()> { read_memory_into(self.memory, address, dest).map_err(|e| e.to_string()) } fn write_memory(&mut self, address: Pointer, data: &[u8]) -> WResult<()> { write_memory_from(self.memory, address, data).map_err(|e| e.to_string()) } fn allocate_memory(&mut self, size: WordSize) -> WResult> { self.heap.allocate(self.memory, size).map_err(|e| e.to_string()) } fn deallocate_memory(&mut self, ptr: Pointer) -> 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, buf_len: WordSize, ) -> WResult { 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, val_len: WordSize, ) -> WResult { 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 { 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: u32, 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")?; } 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 { // 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) } fn get_global_val( &self, instance_idx: u32, name: &str, ) -> WResult> { self.sandbox_store .instance(instance_idx) .map(|i| i.get_global_val(name)) .map_err(|e| e.to_string()) } } // 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, frontend_config: TargetFrontendConfig, ) -> (ir::Signature, Vec) { // This code is based on the wasmtime_jit::Context::invoke. let param_types = args.iter() .map(|arg| arg.value_type()) .collect::>(); 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) }