sc_executor_wasmi/
lib.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
5
6// This program is free software: you can redistribute it and/or modify
7// it under the terms of the GNU General Public License as published by
8// the Free Software Foundation, either version 3 of the License, or
9// (at your option) any later version.
10
11// This program is distributed in the hope that it will be useful,
12// but WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14// GNU General Public License for more details.
15
16// You should have received a copy of the GNU General Public License
17// along with this program. If not, see <https://www.gnu.org/licenses/>.
18
19//! This crate provides an implementation of `WasmModule` that is baked by wasmi.
20
21use std::{cell::RefCell, str, sync::Arc};
22
23use log::{error, trace};
24use wasmi::{
25	memory_units::Pages,
26	FuncInstance, ImportsBuilder, MemoryRef, Module, ModuleInstance, ModuleRef,
27	RuntimeValue::{self, I32, I64},
28	TableRef,
29};
30
31use sc_allocator::{AllocationStats, FreeingBumpHeapAllocator};
32use sc_executor_common::{
33	error::{Error, MessageWithBacktrace, WasmError},
34	runtime_blob::{DataSegmentsSnapshot, RuntimeBlob},
35	wasm_runtime::{HeapAllocStrategy, InvokeMethod, WasmInstance, WasmModule},
36};
37use sp_runtime_interface::unpack_ptr_and_len;
38use sp_wasm_interface::{Function, FunctionContext, Pointer, Result as WResult, WordSize};
39
40/// Wrapper around [`MemorRef`] that implements [`sc_allocator::Memory`].
41struct MemoryWrapper<'a>(&'a MemoryRef);
42
43impl sc_allocator::Memory for MemoryWrapper<'_> {
44	fn with_access_mut<R>(&mut self, run: impl FnOnce(&mut [u8]) -> R) -> R {
45		self.0.with_direct_access_mut(run)
46	}
47
48	fn with_access<R>(&self, run: impl FnOnce(&[u8]) -> R) -> R {
49		self.0.with_direct_access(run)
50	}
51
52	fn pages(&self) -> u32 {
53		self.0.current_size().0 as _
54	}
55
56	fn max_pages(&self) -> Option<u32> {
57		self.0.maximum().map(|p| p.0 as _)
58	}
59
60	fn grow(&mut self, additional: u32) -> Result<(), ()> {
61		self.0
62			.grow(Pages(additional as _))
63			.map_err(|e| {
64				log::error!(
65					target: "wasm-executor",
66					"Failed to grow memory by {} pages: {}",
67					additional,
68					e,
69				)
70			})
71			.map(drop)
72	}
73}
74
75struct FunctionExecutor {
76	heap: RefCell<sc_allocator::FreeingBumpHeapAllocator>,
77	memory: MemoryRef,
78	host_functions: Arc<Vec<&'static dyn Function>>,
79	allow_missing_func_imports: bool,
80	missing_functions: Arc<Vec<String>>,
81	panic_message: Option<String>,
82}
83
84impl FunctionExecutor {
85	fn new(
86		m: MemoryRef,
87		heap_base: u32,
88		host_functions: Arc<Vec<&'static dyn Function>>,
89		allow_missing_func_imports: bool,
90		missing_functions: Arc<Vec<String>>,
91	) -> Result<Self, Error> {
92		Ok(FunctionExecutor {
93			heap: RefCell::new(FreeingBumpHeapAllocator::new(heap_base)),
94			memory: m,
95			host_functions,
96			allow_missing_func_imports,
97			missing_functions,
98			panic_message: None,
99		})
100	}
101}
102
103impl FunctionContext for FunctionExecutor {
104	fn read_memory_into(&self, address: Pointer<u8>, dest: &mut [u8]) -> WResult<()> {
105		self.memory.get_into(address.into(), dest).map_err(|e| e.to_string())
106	}
107
108	fn write_memory(&mut self, address: Pointer<u8>, data: &[u8]) -> WResult<()> {
109		self.memory.set(address.into(), data).map_err(|e| e.to_string())
110	}
111
112	fn allocate_memory(&mut self, size: WordSize) -> WResult<Pointer<u8>> {
113		self.heap
114			.borrow_mut()
115			.allocate(&mut MemoryWrapper(&self.memory), size)
116			.map_err(|e| e.to_string())
117	}
118
119	fn deallocate_memory(&mut self, ptr: Pointer<u8>) -> WResult<()> {
120		self.heap
121			.borrow_mut()
122			.deallocate(&mut MemoryWrapper(&self.memory), ptr)
123			.map_err(|e| e.to_string())
124	}
125
126	fn register_panic_error_message(&mut self, message: &str) {
127		self.panic_message = Some(message.to_owned());
128	}
129}
130
131/// Will be used on initialization of a module to resolve function and memory imports.
132struct Resolver<'a> {
133	/// All the hot functions that we export for the WASM blob.
134	host_functions: &'a [&'static dyn Function],
135	/// Should we allow missing function imports?
136	///
137	/// If `true`, we return a stub that will return an error when being called.
138	allow_missing_func_imports: bool,
139	/// All the names of functions for that we did not provide a host function.
140	missing_functions: RefCell<Vec<String>>,
141}
142
143impl<'a> Resolver<'a> {
144	fn new(
145		host_functions: &'a [&'static dyn Function],
146		allow_missing_func_imports: bool,
147	) -> Resolver<'a> {
148		Resolver {
149			host_functions,
150			allow_missing_func_imports,
151			missing_functions: RefCell::new(Vec::new()),
152		}
153	}
154}
155
156impl<'a> wasmi::ModuleImportResolver for Resolver<'a> {
157	fn resolve_func(
158		&self,
159		name: &str,
160		signature: &wasmi::Signature,
161	) -> std::result::Result<wasmi::FuncRef, wasmi::Error> {
162		let signature = sp_wasm_interface::Signature::from(signature);
163		for (function_index, function) in self.host_functions.iter().enumerate() {
164			if name == function.name() {
165				if signature == function.signature() {
166					return Ok(wasmi::FuncInstance::alloc_host(signature.into(), function_index))
167				} else {
168					return Err(wasmi::Error::Instantiation(format!(
169						"Invalid signature for function `{}` expected `{:?}`, got `{:?}`",
170						function.name(),
171						signature,
172						function.signature(),
173					)))
174				}
175			}
176		}
177
178		if self.allow_missing_func_imports {
179			trace!(
180				target: "wasm-executor",
181				"Could not find function `{}`, a stub will be provided instead.",
182				name,
183			);
184			let id = self.missing_functions.borrow().len() + self.host_functions.len();
185			self.missing_functions.borrow_mut().push(name.to_string());
186
187			Ok(wasmi::FuncInstance::alloc_host(signature.into(), id))
188		} else {
189			Err(wasmi::Error::Instantiation(format!("Export {} not found", name)))
190		}
191	}
192
193	fn resolve_memory(
194		&self,
195		_: &str,
196		_: &wasmi::MemoryDescriptor,
197	) -> Result<MemoryRef, wasmi::Error> {
198		Err(wasmi::Error::Instantiation(
199			"Internal error, wasmi expects that the wasm blob exports memory.".into(),
200		))
201	}
202}
203
204impl wasmi::Externals for FunctionExecutor {
205	fn invoke_index(
206		&mut self,
207		index: usize,
208		args: wasmi::RuntimeArgs,
209	) -> Result<Option<wasmi::RuntimeValue>, wasmi::Trap> {
210		let mut args = args.as_ref().iter().copied().map(Into::into);
211
212		if let Some(function) = self.host_functions.clone().get(index) {
213			function
214				.execute(self, &mut args)
215				.map_err(|msg| Error::FunctionExecution(function.name().to_string(), msg))
216				.map_err(wasmi::Trap::from)
217				.map(|v| v.map(Into::into))
218		} else if self.allow_missing_func_imports &&
219			index >= self.host_functions.len() &&
220			index < self.host_functions.len() + self.missing_functions.len()
221		{
222			Err(Error::from(format!(
223				"Function `{}` is only a stub. Calling a stub is not allowed.",
224				self.missing_functions[index - self.host_functions.len()],
225			))
226			.into())
227		} else {
228			Err(Error::from(format!("Could not find host function with index: {}", index)).into())
229		}
230	}
231}
232
233fn get_mem_instance(module: &ModuleRef) -> Result<MemoryRef, Error> {
234	Ok(module
235		.export_by_name("memory")
236		.ok_or(Error::InvalidMemoryReference)?
237		.as_memory()
238		.ok_or(Error::InvalidMemoryReference)?
239		.clone())
240}
241
242/// Find the global named `__heap_base` in the given wasm module instance and
243/// tries to get its value.
244fn get_heap_base(module: &ModuleRef) -> Result<u32, Error> {
245	let heap_base_val = module
246		.export_by_name("__heap_base")
247		.ok_or(Error::HeapBaseNotFoundOrInvalid)?
248		.as_global()
249		.ok_or(Error::HeapBaseNotFoundOrInvalid)?
250		.get();
251
252	match heap_base_val {
253		wasmi::RuntimeValue::I32(v) => Ok(v as u32),
254		_ => Err(Error::HeapBaseNotFoundOrInvalid),
255	}
256}
257
258/// Call a given method in the given wasm-module runtime.
259fn call_in_wasm_module(
260	module_instance: &ModuleRef,
261	memory: &MemoryRef,
262	method: InvokeMethod,
263	data: &[u8],
264	host_functions: Arc<Vec<&'static dyn Function>>,
265	allow_missing_func_imports: bool,
266	missing_functions: Arc<Vec<String>>,
267	allocation_stats: &mut Option<AllocationStats>,
268) -> Result<Vec<u8>, Error> {
269	// Initialize FunctionExecutor.
270	let table: Option<TableRef> = module_instance
271		.export_by_name("__indirect_function_table")
272		.and_then(|e| e.as_table().cloned());
273	let heap_base = get_heap_base(module_instance)?;
274
275	let mut function_executor = FunctionExecutor::new(
276		memory.clone(),
277		heap_base,
278		host_functions,
279		allow_missing_func_imports,
280		missing_functions,
281	)?;
282
283	// Write the call data
284	let offset = function_executor.allocate_memory(data.len() as u32)?;
285	function_executor.write_memory(offset, data)?;
286
287	fn convert_trap(executor: &mut FunctionExecutor, trap: wasmi::Trap) -> Error {
288		if let Some(message) = executor.panic_message.take() {
289			Error::AbortedDueToPanic(MessageWithBacktrace { message, backtrace: None })
290		} else {
291			Error::AbortedDueToTrap(MessageWithBacktrace {
292				message: trap.to_string(),
293				backtrace: None,
294			})
295		}
296	}
297
298	let result = match method {
299		InvokeMethod::Export(method) => module_instance
300			.invoke_export(
301				method,
302				&[I32(u32::from(offset) as i32), I32(data.len() as i32)],
303				&mut function_executor,
304			)
305			.map_err(|error| {
306				if let wasmi::Error::Trap(trap) = error {
307					convert_trap(&mut function_executor, trap)
308				} else {
309					error.into()
310				}
311			}),
312		InvokeMethod::Table(func_ref) => {
313			let func = table
314				.ok_or(Error::NoTable)?
315				.get(func_ref)?
316				.ok_or(Error::NoTableEntryWithIndex(func_ref))?;
317			FuncInstance::invoke(
318				&func,
319				&[I32(u32::from(offset) as i32), I32(data.len() as i32)],
320				&mut function_executor,
321			)
322			.map_err(|trap| convert_trap(&mut function_executor, trap))
323		},
324		InvokeMethod::TableWithWrapper { dispatcher_ref, func } => {
325			let dispatcher = table
326				.ok_or(Error::NoTable)?
327				.get(dispatcher_ref)?
328				.ok_or(Error::NoTableEntryWithIndex(dispatcher_ref))?;
329
330			FuncInstance::invoke(
331				&dispatcher,
332				&[I32(func as _), I32(u32::from(offset) as i32), I32(data.len() as i32)],
333				&mut function_executor,
334			)
335			.map_err(|trap| convert_trap(&mut function_executor, trap))
336		},
337	};
338
339	*allocation_stats = Some(function_executor.heap.borrow().stats());
340
341	match result {
342		Ok(Some(I64(r))) => {
343			let (ptr, length) = unpack_ptr_and_len(r as u64);
344			#[allow(deprecated)]
345			memory.get(ptr, length as usize).map_err(|_| Error::Runtime)
346		},
347		Err(e) => {
348			trace!(
349				target: "wasm-executor",
350				"Failed to execute code with {} pages",
351				memory.current_size().0,
352			);
353			Err(e)
354		},
355		_ => Err(Error::InvalidReturn),
356	}
357}
358
359/// Prepare module instance
360fn instantiate_module(
361	module: &Module,
362	host_functions: &[&'static dyn Function],
363	allow_missing_func_imports: bool,
364) -> Result<(ModuleRef, Vec<String>, MemoryRef), Error> {
365	let resolver = Resolver::new(host_functions, allow_missing_func_imports);
366	// start module instantiation. Don't run 'start' function yet.
367	let intermediate_instance =
368		ModuleInstance::new(module, &ImportsBuilder::new().with_resolver("env", &resolver))?;
369
370	// Verify that the module has the heap base global variable.
371	let _ = get_heap_base(intermediate_instance.not_started_instance())?;
372
373	// The `module` should export the memory with the correct properties (min, max).
374	//
375	// This is ensured by modifying the `RuntimeBlob` before initializing the `Module`.
376	let memory = get_mem_instance(intermediate_instance.not_started_instance())?;
377
378	if intermediate_instance.has_start() {
379		// Runtime is not allowed to have the `start` function.
380		Err(Error::RuntimeHasStartFn)
381	} else {
382		Ok((
383			intermediate_instance.assert_no_start(),
384			resolver.missing_functions.into_inner(),
385			memory,
386		))
387	}
388}
389
390/// A state snapshot of an instance taken just after instantiation.
391///
392/// It is used for restoring the state of the module after execution.
393#[derive(Clone)]
394struct GlobalValsSnapshot {
395	/// The list of all global mutable variables of the module in their sequential order.
396	global_mut_values: Vec<RuntimeValue>,
397}
398
399impl GlobalValsSnapshot {
400	// Returns `None` if instance is not valid.
401	fn take(module_instance: &ModuleRef) -> Self {
402		// Collect all values of mutable globals.
403		let global_mut_values = module_instance
404			.globals()
405			.iter()
406			.filter(|g| g.is_mutable())
407			.map(|g| g.get())
408			.collect();
409		Self { global_mut_values }
410	}
411
412	/// Reset the runtime instance to the initial version by restoring
413	/// the preserved memory and globals.
414	///
415	/// Returns `Err` if applying the snapshot is failed.
416	fn apply(&self, instance: &ModuleRef) -> Result<(), WasmError> {
417		for (global_ref, global_val) in instance
418			.globals()
419			.iter()
420			.filter(|g| g.is_mutable())
421			.zip(self.global_mut_values.iter())
422		{
423			// the instance should be the same as used for preserving and
424			// we iterate the same way it as we do it for preserving values that means that the
425			// types should be the same and all the values are mutable. So no error is expected/
426			global_ref.set(*global_val).map_err(|_| WasmError::ApplySnapshotFailed)?;
427		}
428		Ok(())
429	}
430}
431
432/// A runtime along with initial copy of data segments.
433pub struct WasmiRuntime {
434	/// A wasm module.
435	module: Module,
436	/// The host functions registered for this instance.
437	host_functions: Arc<Vec<&'static dyn Function>>,
438	/// Enable stub generation for functions that are not available in `host_functions`.
439	/// These stubs will error when the wasm blob tries to call them.
440	allow_missing_func_imports: bool,
441
442	global_vals_snapshot: GlobalValsSnapshot,
443	data_segments_snapshot: DataSegmentsSnapshot,
444}
445
446impl WasmModule for WasmiRuntime {
447	fn new_instance(&self) -> Result<Box<dyn WasmInstance>, Error> {
448		// Instantiate this module.
449		let (instance, missing_functions, memory) =
450			instantiate_module(&self.module, &self.host_functions, self.allow_missing_func_imports)
451				.map_err(|e| WasmError::Instantiation(e.to_string()))?;
452
453		Ok(Box::new(WasmiInstance {
454			instance,
455			memory,
456			global_vals_snapshot: self.global_vals_snapshot.clone(),
457			data_segments_snapshot: self.data_segments_snapshot.clone(),
458			host_functions: self.host_functions.clone(),
459			allow_missing_func_imports: self.allow_missing_func_imports,
460			missing_functions: Arc::new(missing_functions),
461			memory_zeroed: true,
462		}))
463	}
464}
465
466/// Create a new `WasmiRuntime` given the code. This function loads the module and
467/// stores it in the instance.
468pub fn create_runtime(
469	mut blob: RuntimeBlob,
470	heap_alloc_strategy: HeapAllocStrategy,
471	host_functions: Vec<&'static dyn Function>,
472	allow_missing_func_imports: bool,
473) -> Result<WasmiRuntime, WasmError> {
474	let data_segments_snapshot =
475		DataSegmentsSnapshot::take(&blob).map_err(|e| WasmError::Other(e.to_string()))?;
476
477	// Make sure we only have exported memory to simplify the code of the wasmi executor.
478	blob.convert_memory_import_into_export()?;
479	// Ensure that the memory uses the correct heap pages.
480	blob.setup_memory_according_to_heap_alloc_strategy(heap_alloc_strategy)?;
481
482	let module =
483		Module::from_parity_wasm_module(blob.into_inner()).map_err(|_| WasmError::InvalidModule)?;
484
485	let global_vals_snapshot = {
486		let (instance, _, _) =
487			instantiate_module(&module, &host_functions, allow_missing_func_imports)
488				.map_err(|e| WasmError::Instantiation(e.to_string()))?;
489		GlobalValsSnapshot::take(&instance)
490	};
491
492	Ok(WasmiRuntime {
493		module,
494		data_segments_snapshot,
495		global_vals_snapshot,
496		host_functions: Arc::new(host_functions),
497		allow_missing_func_imports,
498	})
499}
500
501/// Wasmi instance wrapper along with the state snapshot.
502pub struct WasmiInstance {
503	/// A wasm module instance.
504	instance: ModuleRef,
505	/// The memory instance of used by the wasm module.
506	memory: MemoryRef,
507	/// Is the memory zeroed?
508	memory_zeroed: bool,
509	/// The snapshot of global variable values just after instantiation.
510	global_vals_snapshot: GlobalValsSnapshot,
511	/// The snapshot of data segments.
512	data_segments_snapshot: DataSegmentsSnapshot,
513	/// The host functions registered for this instance.
514	host_functions: Arc<Vec<&'static dyn Function>>,
515	/// Enable stub generation for functions that are not available in `host_functions`.
516	/// These stubs will error when the wasm blob trie to call them.
517	allow_missing_func_imports: bool,
518	/// List of missing functions detected during function resolution
519	missing_functions: Arc<Vec<String>>,
520}
521
522// This is safe because `WasmiInstance` does not leak any references to `self.memory` and
523// `self.instance`
524unsafe impl Send for WasmiInstance {}
525
526impl WasmiInstance {
527	fn call_impl(
528		&mut self,
529		method: InvokeMethod,
530		data: &[u8],
531		allocation_stats: &mut Option<AllocationStats>,
532	) -> Result<Vec<u8>, Error> {
533		// We reuse a single wasm instance for multiple calls and a previous call (if any)
534		// altered the state. Therefore, we need to restore the instance to original state.
535
536		if !self.memory_zeroed {
537			// First, zero initialize the linear memory.
538			self.memory.erase().map_err(|e| {
539				// Snapshot restoration failed. This is pretty unexpected since this can happen
540				// if some invariant is broken or if the system is under extreme memory pressure
541				// (so erasing fails).
542				error!(target: "wasm-executor", "snapshot restoration failed: {}", e);
543				WasmError::ErasingFailed(e.to_string())
544			})?;
545		}
546
547		// Second, reapply data segments into the linear memory.
548		self.data_segments_snapshot
549			.apply(|offset, contents| self.memory.set(offset, contents))?;
550
551		// Third, restore the global variables to their initial values.
552		self.global_vals_snapshot.apply(&self.instance)?;
553
554		let res = call_in_wasm_module(
555			&self.instance,
556			&self.memory,
557			method,
558			data,
559			self.host_functions.clone(),
560			self.allow_missing_func_imports,
561			self.missing_functions.clone(),
562			allocation_stats,
563		);
564
565		// If we couldn't unmap it, erase the memory.
566		self.memory_zeroed = self.memory.erase().is_ok();
567
568		res
569	}
570}
571
572impl WasmInstance for WasmiInstance {
573	fn call_with_allocation_stats(
574		&mut self,
575		method: InvokeMethod,
576		data: &[u8],
577	) -> (Result<Vec<u8>, Error>, Option<AllocationStats>) {
578		let mut allocation_stats = None;
579		let result = self.call_impl(method, data, &mut allocation_stats);
580		(result, allocation_stats)
581	}
582
583	fn get_global_const(&mut self, name: &str) -> Result<Option<sp_wasm_interface::Value>, Error> {
584		match self.instance.export_by_name(name) {
585			Some(global) => Ok(Some(
586				global
587					.as_global()
588					.ok_or_else(|| format!("`{}` is not a global", name))?
589					.get()
590					.into(),
591			)),
592			None => Ok(None),
593		}
594	}
595
596	fn linear_memory_base_ptr(&self) -> Option<*const u8> {
597		Some(self.memory.direct_access().as_ref().as_ptr())
598	}
599}