tc_executor_common/
sandbox.rs

1// This file is part of Tetcore.
2
3// Copyright (C) 2018-2021 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 module implements sandboxing support in the runtime.
20//!
21//! Sandboxing is baked by twasmi at the moment. In future, however, we would like to add/switch to
22//! a compiled execution engine.
23
24use crate::error::{Result, Error};
25use std::{collections::HashMap, rc::Rc};
26use codec::{Decode, Encode};
27use tet_core::sandbox as sandbox_primitives;
28use twasmi::{
29	Externals, ImportResolver, MemoryInstance, MemoryRef, Module, ModuleInstance,
30	ModuleRef, RuntimeArgs, RuntimeValue, Trap, TrapKind, memory_units::Pages,
31};
32use tetcore_wasm_interface::{FunctionContext, Pointer, WordSize};
33
34/// Index of a function inside the supervisor.
35///
36/// This is a typically an index in the default table of the supervisor, however
37/// the exact meaning of this index is depends on the implementation of dispatch function.
38#[derive(Copy, Clone, Debug, PartialEq)]
39pub struct SupervisorFuncIndex(usize);
40
41impl From<SupervisorFuncIndex> for usize {
42	fn from(index: SupervisorFuncIndex) -> Self {
43		index.0
44	}
45}
46
47/// Index of a function within guest index space.
48///
49/// This index is supposed to be used with as index for `Externals`.
50#[derive(Copy, Clone, Debug, PartialEq)]
51struct GuestFuncIndex(usize);
52
53/// This struct holds a mapping from guest index space to supervisor.
54struct GuestToSupervisorFunctionMapping {
55	funcs: Vec<SupervisorFuncIndex>,
56}
57
58impl GuestToSupervisorFunctionMapping {
59	fn new() -> GuestToSupervisorFunctionMapping {
60		GuestToSupervisorFunctionMapping { funcs: Vec::new() }
61	}
62
63	fn define(&mut self, supervisor_func: SupervisorFuncIndex) -> GuestFuncIndex {
64		let idx = self.funcs.len();
65		self.funcs.push(supervisor_func);
66		GuestFuncIndex(idx)
67	}
68
69	fn func_by_guest_index(&self, guest_func_idx: GuestFuncIndex) -> Option<SupervisorFuncIndex> {
70		self.funcs.get(guest_func_idx.0).cloned()
71	}
72}
73
74struct Imports {
75	func_map: HashMap<(Vec<u8>, Vec<u8>), GuestFuncIndex>,
76	memories_map: HashMap<(Vec<u8>, Vec<u8>), MemoryRef>,
77}
78
79impl ImportResolver for Imports {
80	fn resolve_func(
81		&self,
82		module_name: &str,
83		field_name: &str,
84		signature: &::twasmi::Signature,
85	) -> std::result::Result<twasmi::FuncRef, twasmi::Error> {
86		let key = (
87			module_name.as_bytes().to_owned(),
88			field_name.as_bytes().to_owned(),
89		);
90		let idx = *self.func_map.get(&key).ok_or_else(|| {
91			twasmi::Error::Instantiation(format!(
92				"Export {}:{} not found",
93				module_name, field_name
94			))
95		})?;
96		Ok(twasmi::FuncInstance::alloc_host(signature.clone(), idx.0))
97	}
98
99	fn resolve_memory(
100		&self,
101		module_name: &str,
102		field_name: &str,
103		_memory_type: &::twasmi::MemoryDescriptor,
104	) -> std::result::Result<MemoryRef, twasmi::Error> {
105		let key = (
106			module_name.as_bytes().to_vec(),
107			field_name.as_bytes().to_vec(),
108		);
109		let mem = self.memories_map
110			.get(&key)
111			.ok_or_else(|| {
112				twasmi::Error::Instantiation(format!(
113					"Export {}:{} not found",
114					module_name, field_name
115				))
116			})?
117			.clone();
118		Ok(mem)
119	}
120
121	fn resolve_global(
122		&self,
123		module_name: &str,
124		field_name: &str,
125		_global_type: &::twasmi::GlobalDescriptor,
126	) -> std::result::Result<twasmi::GlobalRef, twasmi::Error> {
127		Err(twasmi::Error::Instantiation(format!(
128			"Export {}:{} not found",
129			module_name, field_name
130		)))
131	}
132
133	fn resolve_table(
134		&self,
135		module_name: &str,
136		field_name: &str,
137		_table_type: &::twasmi::TableDescriptor,
138	) -> std::result::Result<twasmi::TableRef, twasmi::Error> {
139		Err(twasmi::Error::Instantiation(format!(
140			"Export {}:{} not found",
141			module_name, field_name
142		)))
143	}
144}
145
146/// This trait encapsulates sandboxing capabilities.
147///
148/// Note that this functions are only called in the `supervisor` context.
149pub trait SandboxCapabilities: FunctionContext {
150	/// Represents a function reference into the supervisor environment.
151	type SupervisorFuncRef;
152
153	/// Invoke a function in the supervisor environment.
154	///
155	/// This first invokes the dispatch_thunk function, passing in the function index of the
156	/// desired function to call and serialized arguments. The thunk calls the desired function
157	/// with the deserialized arguments, then serializes the result into memory and returns
158	/// reference. The pointer to and length of the result in linear memory is encoded into an i64,
159	/// with the upper 32 bits representing the pointer and the lower 32 bits representing the
160	/// length.
161	///
162	/// # Errors
163	///
164	/// Returns `Err` if the dispatch_thunk function has an incorrect signature or traps during
165	/// execution.
166	fn invoke(
167		&mut self,
168		dispatch_thunk: &Self::SupervisorFuncRef,
169		invoke_args_ptr: Pointer<u8>,
170		invoke_args_len: WordSize,
171		state: u32,
172		func_idx: SupervisorFuncIndex,
173	) -> Result<i64>;
174}
175
176/// Implementation of [`Externals`] that allows execution of guest module with
177/// [externals][`Externals`] that might refer functions defined by supervisor.
178///
179/// [`Externals`]: ../twasmi/trait.Externals.html
180pub struct GuestExternals<'a, FE: SandboxCapabilities + 'a> {
181	supervisor_externals: &'a mut FE,
182	sandbox_instance: &'a SandboxInstance<FE::SupervisorFuncRef>,
183	state: u32,
184}
185
186fn trap(msg: &'static str) -> Trap {
187	TrapKind::Host(Box::new(Error::Other(msg.into()))).into()
188}
189
190fn deserialize_result(serialized_result: &[u8]) -> std::result::Result<Option<RuntimeValue>, Trap> {
191	use self::sandbox_primitives::HostError;
192	use tetcore_wasm_interface::ReturnValue;
193	let result_val = std::result::Result::<ReturnValue, HostError>::decode(&mut &serialized_result[..])
194		.map_err(|_| trap("Decoding Result<ReturnValue, HostError> failed!"))?;
195
196	match result_val {
197		Ok(return_value) => Ok(match return_value {
198			ReturnValue::Unit => None,
199			ReturnValue::Value(typed_value) => Some(RuntimeValue::from(typed_value)),
200		}),
201		Err(HostError) => Err(trap("Supervisor function returned sandbox::HostError")),
202	}
203}
204
205impl<'a, FE: SandboxCapabilities + 'a> Externals for GuestExternals<'a, FE> {
206	fn invoke_index(
207		&mut self,
208		index: usize,
209		args: RuntimeArgs,
210	) -> std::result::Result<Option<RuntimeValue>, Trap> {
211		// Make `index` typesafe again.
212		let index = GuestFuncIndex(index);
213
214		let func_idx = self.sandbox_instance
215			.guest_to_supervisor_mapping
216			.func_by_guest_index(index)
217			.expect(
218				"`invoke_index` is called with indexes registered via `FuncInstance::alloc_host`;
219					`FuncInstance::alloc_host` is called with indexes that was obtained from `guest_to_supervisor_mapping`;
220					`func_by_guest_index` called with `index` can't return `None`;
221					qed"
222			);
223
224		// Serialize arguments into a byte vector.
225		let invoke_args_data: Vec<u8> = args.as_ref()
226			.iter()
227			.cloned()
228			.map(tetcore_wasm_interface::Value::from)
229			.collect::<Vec<_>>()
230			.encode();
231
232		let state = self.state;
233
234		// Move serialized arguments inside the memory and invoke dispatch thunk and
235		// then free allocated memory.
236		let invoke_args_len = invoke_args_data.len() as WordSize;
237		let invoke_args_ptr = self
238			.supervisor_externals
239			.allocate_memory(invoke_args_len)
240			.map_err(|_| trap("Can't allocate memory in supervisor for the arguments"))?;
241
242		let deallocate = |this: &mut GuestExternals<FE>, ptr, fail_msg| {
243			this
244				.supervisor_externals
245				.deallocate_memory(ptr)
246				.map_err(|_| trap(fail_msg))
247		};
248
249		if self
250			.supervisor_externals
251			.write_memory(invoke_args_ptr, &invoke_args_data)
252			.is_err()
253		{
254			deallocate(self, invoke_args_ptr, "Failed dealloction after failed write of invoke arguments")?;
255			return Err(trap("Can't write invoke args into memory"));
256		}
257
258		let result = self.supervisor_externals.invoke(
259			&self.sandbox_instance.dispatch_thunk,
260			invoke_args_ptr,
261			invoke_args_len,
262			state,
263			func_idx,
264		);
265
266		deallocate(self, invoke_args_ptr, "Can't deallocate memory for dispatch thunk's invoke arguments")?;
267		let result = result?;
268
269		// dispatch_thunk returns pointer to serialized arguments.
270		// Unpack pointer and len of the serialized result data.
271		let (serialized_result_val_ptr, serialized_result_val_len) = {
272			// Cast to u64 to use zero-extension.
273			let v = result as u64;
274			let ptr = (v as u64 >> 32) as u32;
275			let len = (v & 0xFFFFFFFF) as u32;
276			(Pointer::new(ptr), len)
277		};
278
279		let serialized_result_val = self.supervisor_externals
280			.read_memory(serialized_result_val_ptr, serialized_result_val_len)
281			.map_err(|_| trap("Can't read the serialized result from dispatch thunk"));
282
283		deallocate(self, serialized_result_val_ptr, "Can't deallocate memory for dispatch thunk's result")
284			.and_then(|_| serialized_result_val)
285			.and_then(|serialized_result_val| deserialize_result(&serialized_result_val))
286	}
287}
288
289fn with_guest_externals<FE, R, F>(
290	supervisor_externals: &mut FE,
291	sandbox_instance: &SandboxInstance<FE::SupervisorFuncRef>,
292	state: u32,
293	f: F,
294) -> R
295where
296	FE: SandboxCapabilities,
297	F: FnOnce(&mut GuestExternals<FE>) -> R,
298{
299	let mut guest_externals = GuestExternals {
300		supervisor_externals,
301		sandbox_instance,
302		state,
303	};
304	f(&mut guest_externals)
305}
306
307/// Sandboxed instance of a wasm module.
308///
309/// It's primary purpose is to [`invoke`] exported functions on it.
310///
311/// All imports of this instance are specified at the creation time and
312/// imports are implemented by the supervisor.
313///
314/// Hence, in order to invoke an exported function on a sandboxed module instance,
315/// it's required to provide supervisor externals: it will be used to execute
316/// code in the supervisor context.
317///
318/// This is generic over a supervisor function reference type.
319///
320/// [`invoke`]: #method.invoke
321pub struct SandboxInstance<FR> {
322	instance: ModuleRef,
323	dispatch_thunk: FR,
324	guest_to_supervisor_mapping: GuestToSupervisorFunctionMapping,
325}
326
327impl<FR> SandboxInstance<FR> {
328	/// Invoke an exported function by a name.
329	///
330	/// `supervisor_externals` is required to execute the implementations
331	/// of the syscalls that published to a sandboxed module instance.
332	///
333	/// The `state` parameter can be used to provide custom data for
334	/// these syscall implementations.
335	pub fn invoke<FE: SandboxCapabilities<SupervisorFuncRef=FR>>(
336		&self,
337		export_name: &str,
338		args: &[RuntimeValue],
339		supervisor_externals: &mut FE,
340		state: u32,
341	) -> std::result::Result<Option<twasmi::RuntimeValue>, twasmi::Error> {
342		with_guest_externals(
343			supervisor_externals,
344			self,
345			state,
346			|guest_externals| {
347				self.instance
348					.invoke_export(export_name, args, guest_externals)
349			},
350		)
351	}
352
353	/// Get the value from a global with the given `name`.
354	///
355	/// Returns `Some(_)` if the global could be found.
356	pub fn get_global_val(&self, name: &str) -> Option<tetcore_wasm_interface::Value> {
357		let global = self.instance
358			.export_by_name(name)?
359			.as_global()?
360			.get();
361
362		Some(global.into())
363	}
364}
365
366/// Error occurred during instantiation of a sandboxed module.
367pub enum InstantiationError {
368	/// Something wrong with the environment definition. It either can't
369	/// be decoded, have a reference to a non-existent or torn down memory instance.
370	EnvironmentDefinitionCorrupted,
371	/// Provided module isn't recognized as a valid webassembly binary.
372	ModuleDecoding,
373	/// Module is a well-formed webassembly binary but could not be instantiated. This could
374	/// happen because, e.g. the module imports entries not provided by the environment.
375	Instantiation,
376	/// Module is well-formed, instantiated and linked, but while executing the start function
377	/// a trap was generated.
378	StartTrapped,
379}
380
381fn decode_environment_definition(
382	raw_env_def: &[u8],
383	memories: &[Option<MemoryRef>],
384) -> std::result::Result<(Imports, GuestToSupervisorFunctionMapping), InstantiationError> {
385	let env_def = sandbox_primitives::EnvironmentDefinition::decode(&mut &raw_env_def[..])
386		.map_err(|_| InstantiationError::EnvironmentDefinitionCorrupted)?;
387
388	let mut func_map = HashMap::new();
389	let mut memories_map = HashMap::new();
390	let mut guest_to_supervisor_mapping = GuestToSupervisorFunctionMapping::new();
391
392	for entry in &env_def.entries {
393		let module = entry.module_name.clone();
394		let field = entry.field_name.clone();
395
396		match entry.entity {
397			sandbox_primitives::ExternEntity::Function(func_idx) => {
398				let externals_idx =
399					guest_to_supervisor_mapping.define(SupervisorFuncIndex(func_idx as usize));
400				func_map.insert((module, field), externals_idx);
401			}
402			sandbox_primitives::ExternEntity::Memory(memory_idx) => {
403				let memory_ref = memories
404					.get(memory_idx as usize)
405					.cloned()
406					.ok_or_else(|| InstantiationError::EnvironmentDefinitionCorrupted)?
407					.ok_or_else(|| InstantiationError::EnvironmentDefinitionCorrupted)?;
408				memories_map.insert((module, field), memory_ref);
409			}
410		}
411	}
412
413	Ok((
414		Imports {
415			func_map,
416			memories_map,
417		},
418		guest_to_supervisor_mapping,
419	))
420}
421
422/// An environment in which the guest module is instantiated.
423pub struct GuestEnvironment {
424	imports: Imports,
425	guest_to_supervisor_mapping: GuestToSupervisorFunctionMapping,
426}
427
428impl GuestEnvironment {
429	/// Decodes an environment definition from the given raw bytes.
430	///
431	/// Returns `Err` if the definition cannot be decoded.
432	pub fn decode<FR>(
433		store: &Store<FR>,
434		raw_env_def: &[u8],
435	) -> std::result::Result<Self, InstantiationError> {
436		let (imports, guest_to_supervisor_mapping) =
437			decode_environment_definition(raw_env_def, &store.memories)?;
438		Ok(Self {
439			imports,
440			guest_to_supervisor_mapping,
441		})
442	}
443}
444
445/// An unregistered sandboxed instance.
446///
447/// To finish off the instantiation the user must call `register`.
448#[must_use]
449pub struct UnregisteredInstance<FR> {
450	sandbox_instance: Rc<SandboxInstance<FR>>,
451}
452
453impl<FR> UnregisteredInstance<FR> {
454	/// Finalizes instantiation of this module.
455	pub fn register(self, store: &mut Store<FR>) -> u32 {
456		// At last, register the instance.
457		let instance_idx = store.register_sandbox_instance(self.sandbox_instance);
458		instance_idx
459	}
460}
461
462/// Instantiate a guest module and return it's index in the store.
463///
464/// The guest module's code is specified in `wasm`. Environment that will be available to
465/// guest module is specified in `raw_env_def` (serialized version of [`EnvironmentDefinition`]).
466/// `dispatch_thunk` is used as function that handle calls from guests.
467///
468/// # Errors
469///
470/// Returns `Err` if any of the following conditions happens:
471///
472/// - `raw_env_def` can't be deserialized as a [`EnvironmentDefinition`].
473/// - Module in `wasm` is invalid or couldn't be instantiated.
474///
475/// [`EnvironmentDefinition`]: ../sandbox/struct.EnvironmentDefinition.html
476pub fn instantiate<'a, FE: SandboxCapabilities>(
477	supervisor_externals: &mut FE,
478	dispatch_thunk: FE::SupervisorFuncRef,
479	wasm: &[u8],
480	host_env: GuestEnvironment,
481	state: u32,
482) -> std::result::Result<UnregisteredInstance<FE::SupervisorFuncRef>, InstantiationError> {
483	let module = Module::from_buffer(wasm).map_err(|_| InstantiationError::ModuleDecoding)?;
484	let instance = ModuleInstance::new(&module, &host_env.imports)
485		.map_err(|_| InstantiationError::Instantiation)?;
486
487	let sandbox_instance = Rc::new(SandboxInstance {
488		// In general, it's not a very good idea to use `.not_started_instance()` for anything
489		// but for extracting memory and tables. But in this particular case, we are extracting
490		// for the purpose of running `start` function which should be ok.
491		instance: instance.not_started_instance().clone(),
492		dispatch_thunk,
493		guest_to_supervisor_mapping: host_env.guest_to_supervisor_mapping,
494	});
495
496	with_guest_externals(
497		supervisor_externals,
498		&sandbox_instance,
499		state,
500		|guest_externals| {
501			instance
502				.run_start(guest_externals)
503				.map_err(|_| InstantiationError::StartTrapped)
504		},
505	)?;
506
507	Ok(UnregisteredInstance { sandbox_instance })
508}
509
510/// This struct keeps track of all sandboxed components.
511///
512/// This is generic over a supervisor function reference type.
513pub struct Store<FR> {
514	// Memories and instances are `Some` until torn down.
515	instances: Vec<Option<Rc<SandboxInstance<FR>>>>,
516	memories: Vec<Option<MemoryRef>>,
517}
518
519impl<FR> Store<FR> {
520	/// Create a new empty sandbox store.
521	pub fn new() -> Self {
522		Store {
523			instances: Vec::new(),
524			memories: Vec::new(),
525		}
526	}
527
528	/// Create a new memory instance and return it's index.
529	///
530	/// # Errors
531	///
532	/// Returns `Err` if the memory couldn't be created.
533	/// Typically happens if `initial` is more than `maximum`.
534	pub fn new_memory(&mut self, initial: u32, maximum: u32) -> Result<u32> {
535		let maximum = match maximum {
536			sandbox_primitives::MEM_UNLIMITED => None,
537			specified_limit => Some(Pages(specified_limit as usize)),
538		};
539
540		let mem =
541			MemoryInstance::alloc(
542				Pages(initial as usize),
543				maximum,
544			)?;
545
546		let mem_idx = self.memories.len();
547		self.memories.push(Some(mem));
548		Ok(mem_idx as u32)
549	}
550
551	/// Returns `SandboxInstance` by `instance_idx`.
552	///
553	/// # Errors
554	///
555	/// Returns `Err` If `instance_idx` isn't a valid index of an instance or
556	/// instance is already torndown.
557	pub fn instance(&self, instance_idx: u32) -> Result<Rc<SandboxInstance<FR>>> {
558		self.instances
559			.get(instance_idx as usize)
560			.cloned()
561			.ok_or_else(|| "Trying to access a non-existent instance")?
562			.ok_or_else(|| "Trying to access a torndown instance".into())
563	}
564
565	/// Returns reference to a memory instance by `memory_idx`.
566	///
567	/// # Errors
568	///
569	/// Returns `Err` If `memory_idx` isn't a valid index of an memory or
570	/// if memory has been torn down.
571	pub fn memory(&self, memory_idx: u32) -> Result<MemoryRef> {
572		self.memories
573			.get(memory_idx as usize)
574			.cloned()
575			.ok_or_else(|| "Trying to access a non-existent sandboxed memory")?
576			.ok_or_else(|| "Trying to access a torndown sandboxed memory".into())
577	}
578
579	/// Tear down the memory at the specified index.
580	///
581	/// # Errors
582	///
583	/// Returns `Err` if `memory_idx` isn't a valid index of an memory or
584	/// if it has been torn down.
585	pub fn memory_teardown(&mut self, memory_idx: u32) -> Result<()> {
586		match self.memories.get_mut(memory_idx as usize) {
587			None => Err("Trying to teardown a non-existent sandboxed memory".into()),
588			Some(None) => Err("Double teardown of a sandboxed memory".into()),
589			Some(memory) => {
590				*memory = None;
591				Ok(())
592			}
593		}
594	}
595
596	/// Tear down the instance at the specified index.
597	///
598	/// # Errors
599	///
600	/// Returns `Err` if `instance_idx` isn't a valid index of an instance or
601	/// if it has been torn down.
602	pub fn instance_teardown(&mut self, instance_idx: u32) -> Result<()> {
603		match self.instances.get_mut(instance_idx as usize) {
604			None => Err("Trying to teardown a non-existent instance".into()),
605			Some(None) => Err("Double teardown of an instance".into()),
606			Some(instance) => {
607				*instance = None;
608				Ok(())
609			}
610		}
611	}
612
613	fn register_sandbox_instance(&mut self, sandbox_instance: Rc<SandboxInstance<FR>>) -> u32 {
614		let instance_idx = self.instances.len();
615		self.instances.push(Some(sandbox_instance));
616		instance_idx as u32
617	}
618}