diff --git a/lib/vm/src/backends/singlepass.rs b/lib/vm/src/backends/singlepass.rs index b350c5f02d..00903c3d17 100644 --- a/lib/vm/src/backends/singlepass.rs +++ b/lib/vm/src/backends/singlepass.rs @@ -5,6 +5,7 @@ use wasmer_runtime_core::codegen::{MiddlewareChain, StreamingCompiler}; use wasmer_singlepass_backend::ModuleCodeGenerator as SinglePassMCG; use crate::errors::{CompileErr, Error}; +use crate::middleware::DeterministicMiddleware; use snafu::ResultExt; static GAS_LIMIT: u64 = 10_000_000_000; @@ -12,6 +13,7 @@ static GAS_LIMIT: u64 = 10_000_000_000; pub fn compile(code: &[u8]) -> Result { let c: StreamingCompiler = StreamingCompiler::new(move || { let mut chain = MiddlewareChain::new(); + chain.push(DeterministicMiddleware::new()); chain.push(metering::Metering::new(GAS_LIMIT)); chain }); diff --git a/lib/vm/src/errors.rs b/lib/vm/src/errors.rs index b03cfec3d9..599c5791bf 100644 --- a/lib/vm/src/errors.rs +++ b/lib/vm/src/errors.rs @@ -14,7 +14,7 @@ pub enum Error { #[cfg(feature = "backtraces")] backtrace: snafu::Backtrace, }, - #[snafu(display("Compilin wasm: {}", source))] + #[snafu(display("Compiling wasm: {}", source))] CompileErr { source: core_error::CompileError, #[cfg(feature = "backtraces")] @@ -55,6 +55,12 @@ pub enum Error { #[cfg(feature = "backtraces")] backtrace: snafu::Backtrace, }, + #[snafu(display("Validating Wasm: {}", msg))] + ValidationErr { + msg: &'static str, + #[cfg(feature = "backtraces")] + backtrace: snafu::Backtrace, + }, #[snafu(display("Wasmer error: {}", source))] WasmerErr { source: core_error::Error, diff --git a/lib/vm/src/lib.rs b/lib/vm/src/lib.rs index 78c298668e..d06b02f279 100644 --- a/lib/vm/src/lib.rs +++ b/lib/vm/src/lib.rs @@ -5,6 +5,7 @@ mod context; pub mod errors; mod instance; mod memory; +mod middleware; mod modules; pub mod testing; mod wasm_store; diff --git a/lib/vm/src/middleware/deterministic.rs b/lib/vm/src/middleware/deterministic.rs new file mode 100644 index 0000000000..45bdc27701 --- /dev/null +++ b/lib/vm/src/middleware/deterministic.rs @@ -0,0 +1,215 @@ +/** +This code is a slightly modified version of ValidationMiddleware taken from spacemesh vm, +under the MIT license. + +Original source: https://github.com/spacemeshos/svm/blob/5df80288c8b9a5ab3665297251c283cb614ebb81/crates/svm-compiler/src/middleware/validation.rs +*/ + +use wasmer_runtime_core::{ + codegen::{Event, EventSink, FunctionMiddleware}, + error::CompileError, + module::ModuleInfo, + wasmparser::Operator, +}; + +/// The `DeterministicMiddleware` has one main objective: +/// * validation - make sure the wasm is valid and doesn't contain any non-deterministic opcodes (for example: floats) +pub struct DeterministicMiddleware; + +impl DeterministicMiddleware { + pub fn new() -> Self { + Self {} + } +} + +impl FunctionMiddleware for DeterministicMiddleware { + type Error = CompileError; + + fn feed_event<'a, 'b: 'a>( + &mut self, + event: Event<'a, 'b>, + _module_info: &ModuleInfo, + sink: &mut EventSink<'a, 'b>, + ) -> Result<(), Self::Error> { + match event { + Event::Wasm(op) => parse_wasm_opcode(op)?, + Event::WasmOwned(ref op) => parse_wasm_opcode(op)?, + _ => (), + }; + + sink.push(event); + Ok(()) + } +} + +/// we explicitly whitelist the supported opcodes +fn parse_wasm_opcode(opcode: &Operator) -> Result<(), CompileError> { + match opcode { + Operator::Unreachable + | Operator::Nop + | Operator::Block { .. } + | Operator::Loop { .. } + | Operator::If { .. } + | Operator::Else + | Operator::End + | Operator::Br { .. } + | Operator::BrIf { .. } + | Operator::BrTable { .. } + | Operator::Return + | Operator::Call { .. } + | Operator::CallIndirect { .. } + | Operator::Drop + | Operator::Select + | Operator::GetLocal { .. } + | Operator::SetLocal { .. } + | Operator::TeeLocal { .. } + | Operator::GetGlobal { .. } + | Operator::SetGlobal { .. } + | Operator::I32Load { .. } + | Operator::I64Load { .. } + | Operator::I32Load8S { .. } + | Operator::I32Load8U { .. } + | Operator::I32Load16S { .. } + | Operator::I32Load16U { .. } + | Operator::I64Load8S { .. } + | Operator::I64Load8U { .. } + | Operator::I64Load16S { .. } + | Operator::I64Load16U { .. } + | Operator::I64Load32S { .. } + | Operator::I64Load32U { .. } + | Operator::I32Store { .. } + | Operator::I64Store { .. } + | Operator::I32Store8 { .. } + | Operator::I32Store16 { .. } + | Operator::I64Store8 { .. } + | Operator::I64Store16 { .. } + | Operator::I64Store32 { .. } + | Operator::MemorySize { .. } + | Operator::MemoryGrow { .. } + | Operator::I32Const { .. } + | Operator::I64Const { .. } + | Operator::I32Eqz + | Operator::I32Eq + | Operator::I32Ne + | Operator::I32LtS + | Operator::I32LtU + | Operator::I32GtS + | Operator::I32GtU + | Operator::I32LeS + | Operator::I32LeU + | Operator::I32GeS + | Operator::I32GeU + | Operator::I64Eqz + | Operator::I64Eq + | Operator::I64Ne + | Operator::I64LtS + | Operator::I64LtU + | Operator::I64GtS + | Operator::I64GtU + | Operator::I64LeS + | Operator::I64LeU + | Operator::I64GeS + | Operator::I64GeU + | Operator::I32Clz + | Operator::I32Ctz + | Operator::I32Popcnt + | Operator::I32Add + | Operator::I32Sub + | Operator::I32Mul + | Operator::I32DivS + | Operator::I32DivU + | Operator::I32RemS + | Operator::I32RemU + | Operator::I32And + | Operator::I32Or + | Operator::I32Xor + | Operator::I32Shl + | Operator::I32ShrS + | Operator::I32ShrU + | Operator::I32Rotl + | Operator::I32Rotr + | Operator::I64Clz + | Operator::I64Ctz + | Operator::I64Popcnt + | Operator::I64Add + | Operator::I64Sub + | Operator::I64Mul + | Operator::I64DivS + | Operator::I64DivU + | Operator::I64RemS + | Operator::I64RemU + | Operator::I64And + | Operator::I64Or + | Operator::I64Xor + | Operator::I64Shl + | Operator::I64ShrS + | Operator::I64ShrU + | Operator::I64Rotl + | Operator::I64Rotr + | Operator::I32WrapI64 + | Operator::I64ExtendSI32 + | Operator::I64ExtendUI32 + | Operator::I32Extend8S + | Operator::I32Extend16S + | Operator::I64Extend8S + | Operator::I64Extend16S + | Operator::I64Extend32S => Ok(()), + _ => Err(CompileError::ValidationError{msg: "non-deterministic opcode".to_string()}), + } +} + +#[cfg(test)] +mod tests { + use super::*; + use wasmer_runtime::{imports, Func}; + + use crate::backends::compile; + use crate::errors::{Error}; + + #[test] + #[cfg(feature = "default-singlepass")] + fn valid_wasm_instance_sanity() { + let input = r#" + (module + (func (export "sum") (param i32 i32) (result i32) + get_local 0 + get_local 1 + i32.add + )) + "#; + let wasm = wabt::wat2wasm(input).unwrap(); + let module = compile(&wasm).unwrap(); + let instance = module.instantiate(&imports! {}).unwrap(); + + let func: Func<(i32, i32), i32> = instance.func("sum").unwrap(); + let res = func.call(10, 20); + assert!(res.is_ok()); + assert_eq!(30, res.unwrap()); + } + + #[test] + #[cfg(feature = "default-singlepass")] + fn parser_floats_are_not_supported() { + let input = r#" + (module + (func $to_float (param i32) (result f32) + get_local 0 + f32.convert_u/i32 + )) + "#; + + let wasm = wabt::wat2wasm(input).unwrap(); + let res = compile(&wasm); + + let failure = res.err().expect("compile should have failed"); + + if let Error::CompileErr { source } = &failure { + if let CompileError::InternalError { msg } = source { + assert_eq!("Codegen(\"ValidationError { msg: \\\"non-deterministic opcode\\\" }\")", msg.as_str()); + return; + } + } + + panic!("unexpected error: {:?}", failure) + } +} diff --git a/lib/vm/src/middleware/mod.rs b/lib/vm/src/middleware/mod.rs new file mode 100644 index 0000000000..6b0a735943 --- /dev/null +++ b/lib/vm/src/middleware/mod.rs @@ -0,0 +1,3 @@ +mod deterministic; + +pub use deterministic::DeterministicMiddleware; \ No newline at end of file