From 488830e81abad4df55de65ab78892b57e2ccd4b7 Mon Sep 17 00:00:00 2001 From: Sergey Pepyakin <s.pepyakin@gmail.com> Date: Wed, 19 Sep 2018 11:01:25 +0300 Subject: [PATCH] Fix error handling in sandboxing/contracts modules (#744) * Fix error handling in sandboxing/contracts modules * Add some docs. * Add some tests. * grammar --- substrate/core/executor/src/sandbox.rs | 115 +++++++++++++++++-- substrate/core/executor/src/wasm_executor.rs | 18 ++- substrate/core/executor/wasm/src/lib.rs | 10 ++ substrate/core/sr-sandbox/src/lib.rs | 7 +- substrate/core/sr-sandbox/with_std.rs | 2 +- substrate/core/sr-sandbox/without_std.rs | 1 + substrate/srml/contract/src/vm/mod.rs | 24 ++-- 7 files changed, 155 insertions(+), 22 deletions(-) diff --git a/substrate/core/executor/src/sandbox.rs b/substrate/core/executor/src/sandbox.rs index 4d5fa7620f9..4d79b373ea0 100644 --- a/substrate/core/executor/src/sandbox.rs +++ b/substrate/core/executor/src/sandbox.rs @@ -335,11 +335,27 @@ impl SandboxInstance { } } +/// Error occured 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. + EnvironmentDefintionCorrupted, + /// 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>], -) -> Result<(Imports, GuestToSupervisorFunctionMapping), UserError> { - let env_def = sandbox_primitives::EnvironmentDefinition::decode(&mut &raw_env_def[..]).ok_or_else(|| UserError("Sandbox error"))?; +) -> Result<(Imports, GuestToSupervisorFunctionMapping), InstantiationError> { + let env_def = sandbox_primitives::EnvironmentDefinition::decode(&mut &raw_env_def[..]) + .ok_or_else(|| InstantiationError::EnvironmentDefintionCorrupted)?; let mut func_map = HashMap::new(); let mut memories_map = HashMap::new(); @@ -359,8 +375,8 @@ fn decode_environment_definition( let memory_ref = memories .get(memory_idx as usize) .cloned() - .ok_or_else(|| UserError("Sandbox error"))? - .ok_or_else(|| UserError("Sandbox error"))?; + .ok_or_else(|| InstantiationError::EnvironmentDefintionCorrupted)? + .ok_or_else(|| InstantiationError::EnvironmentDefintionCorrupted)?; memories_map.insert((module, field), memory_ref); } } @@ -395,12 +411,12 @@ pub fn instantiate<FE: SandboxCapabilities + Externals>( wasm: &[u8], raw_env_def: &[u8], state: u32, -) -> Result<u32, UserError> { +) -> 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(|_| UserError("Sandbox error"))?; - let instance = ModuleInstance::new(&module, &imports).map_err(|_| UserError("Sandbox error"))?; + 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 @@ -418,14 +434,14 @@ pub fn instantiate<FE: SandboxCapabilities + Externals>( |guest_externals| { instance .run_start(guest_externals) - .map_err(|_| UserError("Sandbox error")) + .map_err(|_| InstantiationError::StartTrapped) }, )?; + // At last, register the instance. let instance_idx = supervisor_externals .store_mut() .register_sandbox_instance(sandbox_instance); - Ok(instance_idx) } @@ -674,4 +690,85 @@ mod tests { vec![1], ); } + + #[test] + fn unlinkable_module() { + let mut ext = TestExternalities::default(); + let test_code = include_bytes!("../wasm/target/wasm32-unknown-unknown/release/runtime_test.compact.wasm"); + + let code = wabt::wat2wasm(r#" + (module + (import "env" "non-existent" (func)) + + (func (export "call") + ) + ) + "#).unwrap(); + + assert_eq!( + WasmExecutor::new().call(&mut ext, 8, &test_code[..], "test_sandbox_instantiate", &code).unwrap(), + vec![1], + ); + } + + #[test] + fn corrupted_module() { + let mut ext = TestExternalities::default(); + let test_code = include_bytes!("../wasm/target/wasm32-unknown-unknown/release/runtime_test.compact.wasm"); + + // Corrupted wasm file + let code = &[0, 0, 0, 0, 1, 0, 0, 0]; + + assert_eq!( + WasmExecutor::new().call(&mut ext, 8, &test_code[..], "test_sandbox_instantiate", code).unwrap(), + vec![1], + ); + } + + #[test] + fn start_fn_ok() { + let mut ext = TestExternalities::default(); + let test_code = include_bytes!("../wasm/target/wasm32-unknown-unknown/release/runtime_test.compact.wasm"); + + let code = wabt::wat2wasm(r#" + (module + (func (export "call") + ) + + (func $start + ) + + (start $start) + ) + "#).unwrap(); + + assert_eq!( + WasmExecutor::new().call(&mut ext, 8, &test_code[..], "test_sandbox_instantiate", &code).unwrap(), + vec![0], + ); + } + + #[test] + fn start_fn_traps() { + let mut ext = TestExternalities::default(); + let test_code = include_bytes!("../wasm/target/wasm32-unknown-unknown/release/runtime_test.compact.wasm"); + + let code = wabt::wat2wasm(r#" + (module + (func (export "call") + ) + + (func $start + unreachable + ) + + (start $start) + ) + "#).unwrap(); + + assert_eq!( + WasmExecutor::new().call(&mut ext, 8, &test_code[..], "test_sandbox_instantiate", &code).unwrap(), + vec![2], + ); + } } diff --git a/substrate/core/executor/src/wasm_executor.rs b/substrate/core/executor/src/wasm_executor.rs index 19f680d6635..e0d8da80d7c 100644 --- a/substrate/core/executor/src/wasm_executor.rs +++ b/substrate/core/executor/src/wasm_executor.rs @@ -344,7 +344,14 @@ impl_function_executor!(this: FunctionExecutor<'e, E>, 5 }) }, - ext_sandbox_instantiate(dispatch_thunk_idx: usize, wasm_ptr: *const u8, wasm_len: usize, imports_ptr: *const u8, imports_len: usize, state: usize) -> u32 => { + ext_sandbox_instantiate( + dispatch_thunk_idx: usize, + wasm_ptr: *const u8, + wasm_len: usize, + imports_ptr: *const u8, + imports_len: usize, + state: usize + ) -> u32 => { let wasm = this.memory.get(wasm_ptr, wasm_len as usize).map_err(|_| UserError("Sandbox error"))?; let raw_env_def = this.memory.get(imports_ptr, imports_len as usize).map_err(|_| UserError("Sandbox error"))?; @@ -357,9 +364,14 @@ impl_function_executor!(this: FunctionExecutor<'e, E>, .clone() }; - let instance_idx = sandbox::instantiate(this, dispatch_thunk, &wasm, &raw_env_def, state)?; + let instance_idx_or_err_code = + match sandbox::instantiate(this, 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 as u32) + Ok(instance_idx_or_err_code as u32) }, ext_sandbox_instance_teardown(instance_idx: u32) => { this.sandbox_store.instance_teardown(instance_idx)?; diff --git a/substrate/core/executor/wasm/src/lib.rs b/substrate/core/executor/wasm/src/lib.rs index 3bec626091f..58759b2a469 100644 --- a/substrate/core/executor/wasm/src/lib.rs +++ b/substrate/core/executor/wasm/src/lib.rs @@ -81,6 +81,16 @@ impl_stubs!( ); let ok = if let Ok(sandbox::ReturnValue::Value(sandbox::TypedValue::I32(0x1337))) = result { true } else { false }; [ok as u8].to_vec() + }, + test_sandbox_instantiate NO_DECODE => |code: &[u8]| { + let env_builder = sandbox::EnvironmentDefinitionBuilder::new(); + let code = match sandbox::Instance::new(code, &env_builder, &mut ()) { + Ok(_) => 0, + Err(sandbox::Error::Module) => 1, + Err(sandbox::Error::Execution) => 2, + Err(sandbox::Error::OutOfBounds) => 3, + }; + [code].to_vec() } ); diff --git a/substrate/core/sr-sandbox/src/lib.rs b/substrate/core/sr-sandbox/src/lib.rs index ca94edf4e41..49d18c13c99 100755 --- a/substrate/core/sr-sandbox/src/lib.rs +++ b/substrate/core/sr-sandbox/src/lib.rs @@ -190,7 +190,12 @@ pub struct Instance<T> { } impl<T> Instance<T> { - /// Instantiate a module with the given [`EnvironmentDefinitionBuilder`]. + /// Instantiate a module with the given [`EnvironmentDefinitionBuilder`]. It will + /// run the `start` function with the given `state`. + /// + /// Returns `Err(Error::Module)` if this module can't be instantiated with the given + /// environment. If execution of `start` function generated a trap, then `Err(Error::Execution)` will + /// be returned. /// /// [`EnvironmentDefinitionBuilder`]: struct.EnvironmentDefinitionBuilder.html pub fn new(code: &[u8], env_def_builder: &EnvironmentDefinitionBuilder<T>, state: &mut T) -> Result<Instance<T>, Error> { diff --git a/substrate/core/sr-sandbox/with_std.rs b/substrate/core/sr-sandbox/with_std.rs index e4e4aa131ea..d6a60694894 100755 --- a/substrate/core/sr-sandbox/with_std.rs +++ b/substrate/core/sr-sandbox/with_std.rs @@ -271,7 +271,7 @@ impl<T> Instance<T> { state, defined_host_functions: &defined_host_functions, }; - let instance = not_started_instance.run_start(&mut externals).map_err(|_| Error::Module)?; + let instance = not_started_instance.run_start(&mut externals).map_err(|_| Error::Execution)?; instance }; diff --git a/substrate/core/sr-sandbox/without_std.rs b/substrate/core/sr-sandbox/without_std.rs index f7feb466a09..6a14a198439 100755 --- a/substrate/core/sr-sandbox/without_std.rs +++ b/substrate/core/sr-sandbox/without_std.rs @@ -253,6 +253,7 @@ impl<T> Instance<T> { }; let instance_idx = match result { sandbox_primitives::ERR_MODULE => return Err(Error::Module), + sandbox_primitives::ERR_EXECUTION => return Err(Error::Execution), instance_idx => instance_idx, }; Ok(Instance { diff --git a/substrate/srml/contract/src/vm/mod.rs b/substrate/srml/contract/src/vm/mod.rs index 3bec1b40afa..53d7f82f7af 100644 --- a/substrate/srml/contract/src/vm/mod.rs +++ b/substrate/srml/contract/src/vm/mod.rs @@ -138,11 +138,11 @@ impl<'a, 'data, E: Ext + 'a> Runtime<'a, 'data, E> { fn to_execution_result<E: Ext>( runtime: Runtime<E>, - run_err: Option<sandbox::Error>, + sandbox_err: Option<sandbox::Error>, ) -> Result<(), Error> { // Check the exact type of the error. It could be plain trap or // special runtime trap the we must recognize. - match (run_err, runtime.special_trap) { + match (sandbox_err, runtime.special_trap) { // No traps were generated. Proceed normally. (None, None) => Ok(()), // Special case. The trap was the result of the execution `return` host function. @@ -188,12 +188,20 @@ pub fn execute<'a, E: Ext>( special_trap: None, }; - let mut instance = sandbox::Instance::new(&instrumented_code, &imports, &mut runtime) - .map_err(|_| Error::Instantiate)?; - - let run_result = instance.invoke(b"call", &[], &mut runtime); - - to_execution_result(runtime, run_result.err()) + // Instantiate the instance from the instrumented module code. + let exec_error: Option<sandbox::Error> = + match sandbox::Instance::new(&instrumented_code, &imports, &mut runtime) { + // No errors or traps were generated on instantiation! That + // means we can now invoke the contract entrypoint. + Ok(mut instance) => instance.invoke(b"call", &[], &mut runtime).err(), + // `start` function trapped. + Err(err @ sandbox::Error::Execution) => Some(err), + // Other instantiation errors. + // Return without executing anything. + Err(_) => return Err(Error::Instantiate), + }; + + to_execution_result(runtime, exec_error) } // TODO: Extract it to the root of the crate -- GitLab