From f8fe9990158f89f632b739d7d011d4009c30efe0 Mon Sep 17 00:00:00 2001 From: losfair Date: Sun, 17 Mar 2019 10:27:14 +0800 Subject: [PATCH] Implemented protected call and floating point traps; passing all spectests! --- lib/dynasm-backend/Cargo.toml | 2 + lib/dynasm-backend/src/codegen.rs | 1 + lib/dynasm-backend/src/codegen_x64.rs | 274 +++++++++++++++++++++++-- lib/dynasm-backend/src/lib.rs | 7 + lib/dynasm-backend/src/parse.rs | 27 +++ lib/dynasm-backend/src/protect_unix.rs | 202 ++++++++++++++++++ lib/runtime-core/src/types.rs | 4 +- 7 files changed, 497 insertions(+), 20 deletions(-) create mode 100644 lib/dynasm-backend/src/protect_unix.rs diff --git a/lib/dynasm-backend/Cargo.toml b/lib/dynasm-backend/Cargo.toml index 26ff42a8d..428a90f47 100644 --- a/lib/dynasm-backend/Cargo.toml +++ b/lib/dynasm-backend/Cargo.toml @@ -14,3 +14,5 @@ dynasm = "0.3.0" dynasmrt = "0.3.1" lazy_static = "1.2.0" byteorder = "1" +nix = "0.13.0" +libc = "0.2.49" diff --git a/lib/dynasm-backend/src/codegen.rs b/lib/dynasm-backend/src/codegen.rs index 5bb299cb1..e6b083d05 100644 --- a/lib/dynasm-backend/src/codegen.rs +++ b/lib/dynasm-backend/src/codegen.rs @@ -9,6 +9,7 @@ use wasmer_runtime_core::{ use wasmparser::{Operator, Type as WpType}; pub trait ModuleCodeGenerator { + fn check_precondition(&mut self, module_info: &ModuleInfo) -> Result<(), CodegenError>; fn next_function(&mut self) -> Result<&mut FCG, CodegenError>; fn finalize(self, module_info: &ModuleInfo) -> Result<(PC, FR), CodegenError>; fn feed_signatures( diff --git a/lib/dynasm-backend/src/codegen_x64.rs b/lib/dynasm-backend/src/codegen_x64.rs index ed7cbf0aa..ca3ebfcaa 100644 --- a/lib/dynasm-backend/src/codegen_x64.rs +++ b/lib/dynasm-backend/src/codegen_x64.rs @@ -24,6 +24,7 @@ use wasmer_runtime_core::{ vm::{self, ImportBacking, LocalGlobal, LocalTable, LocalMemory}, }; use wasmparser::{Operator, Type as WpType}; +use crate::protect_unix; thread_local! { static CURRENT_EXECUTION_CONTEXT: RefCell> = RefCell::new(Vec::new()); @@ -384,17 +385,21 @@ impl ProtectedCaller for X64ExecutionContext { CURRENT_EXECUTION_CONTEXT.with(|x| x.borrow_mut().push(self)); let ret = unsafe { - CALL_WASM( - param_buf.as_ptr(), - param_buf.len(), - ptr, - memory_base, - _vmctx, - ) + protect_unix::call_protected(|| { + CALL_WASM( + param_buf.as_ptr(), + param_buf.len(), + ptr, + memory_base, + _vmctx, + ) + }) }; CURRENT_EXECUTION_CONTEXT.with(|x| x.borrow_mut().pop().unwrap()); + let ret = ret?; + Ok(if let Some(ty) = return_ty { vec![match ty { WpType::I32 => Value::I32(ret as i32), @@ -524,6 +529,19 @@ impl X64ModuleCodeGenerator { } impl ModuleCodeGenerator for X64ModuleCodeGenerator { + fn check_precondition(&mut self, module_info: &ModuleInfo) -> Result<(), CodegenError> { + for mem in module_info.memories.iter().map(|(_, v)| v).chain(module_info.imported_memories.iter().map(|(_, v)| &v.1)) { + match mem.memory_type() { + MemoryType::Dynamic => return Err(CodegenError { + message: "dynamic memory isn't supported yet" + }), + _ => {} + } + } + + Ok(()) + } + fn next_function(&mut self) -> Result<&mut X64FunctionCode, CodegenError> { let (mut assembler, mut function_labels, br_table_data) = match self.functions.last_mut() { Some(x) => ( @@ -1428,6 +1446,98 @@ impl X64FunctionCode { Ok(()) } + fn emit_f32_int_conv_check( + assembler: &mut Assembler, + reg: Register, + lower_bound: f32, + upper_bound: f32, + ) { + let lower_bound = f32::to_bits(lower_bound); + let upper_bound = f32::to_bits(upper_bound); + + dynasm!( + assembler + ; movq xmm5, r15 + + // underflow + ; movd xmm1, Rd(reg as u8) + ; mov r15d, lower_bound as i32 + ; movd xmm2, r15d + ; vcmpltss xmm0, xmm1, xmm2 + ; movd r15d, xmm0 + ; cmp r15d, 1 + ; je >trap + + // overflow + ; mov r15d, upper_bound as i32 + ; movd xmm2, r15d + ; vcmpgtss xmm0, xmm1, xmm2 + ; movd r15d, xmm0 + ; cmp r15d, 1 + ; je >trap + + // NaN + ; vcmpeqss xmm0, xmm1, xmm1 + ; movd r15d, xmm0 + ; cmp r15d, 0 + ; je >trap + + ; movq r15, xmm5 + ; jmp >ok + + ; trap: + ; ud2 + + ; ok: + ); + } + + fn emit_f64_int_conv_check( + assembler: &mut Assembler, + reg: Register, + lower_bound: f64, + upper_bound: f64, + ) { + let lower_bound = f64::to_bits(lower_bound); + let upper_bound = f64::to_bits(upper_bound); + + dynasm!( + assembler + ; movq xmm5, r15 + + // underflow + ; movq xmm1, Rq(reg as u8) + ; mov r15, QWORD lower_bound as i64 + ; movq xmm2, r15 + ; vcmpltsd xmm0, xmm1, xmm2 + ; movd r15d, xmm0 + ; cmp r15d, 1 + ; je >trap + + // overflow + ; mov r15, QWORD upper_bound as i64 + ; movq xmm2, r15 + ; vcmpgtsd xmm0, xmm1, xmm2 + ; movd r15d, xmm0 + ; cmp r15d, 1 + ; je >trap + + // NaN + ; vcmpeqsd xmm0, xmm1, xmm1 + ; movd r15d, xmm0 + ; cmp r15d, 0 + ; je >trap + + ; movq r15, xmm5 + ; jmp >ok + + ; trap: + ; ud2 + + ; ok: + ); + } + fn emit_native_call_trampoline( assembler: &mut Assembler, target: unsafe extern "C" fn( @@ -4137,11 +4247,17 @@ impl FunctionCodeGenerator for X64FunctionCode { WpType::F32 )?; } - Operator::I32TruncUF32 | Operator::I32TruncSF32 => { + Operator::I32TruncUF32 => { Self::emit_unop( assembler, &mut self.value_stack, |assembler, value_stack, reg| { + Self::emit_f32_int_conv_check( + assembler, + reg, + -1.0, + 4294967296.0, + ); dynasm!( assembler ; movd xmm1, Rd(reg as u8) @@ -4153,11 +4269,61 @@ impl FunctionCodeGenerator for X64FunctionCode { WpType::I32 )?; } - Operator::I64TruncUF32 | Operator::I64TruncSF32 => { + Operator::I32TruncSF32 => { Self::emit_unop( assembler, &mut self.value_stack, |assembler, value_stack, reg| { + Self::emit_f32_int_conv_check( + assembler, + reg, + -2147483904.0, + 2147483648.0 + ); + dynasm!( + assembler + ; movd xmm1, Rd(reg as u8) + ; roundss xmm1, xmm1, 3 + ; cvtss2si Rd(reg as u8), xmm1 + ); + }, + WpType::F32, + WpType::I32 + )?; + } + Operator::I64TruncUF32 => { + Self::emit_unop( + assembler, + &mut self.value_stack, + |assembler, value_stack, reg| { + Self::emit_f32_int_conv_check( + assembler, + reg, + -1.0, + 18446744073709551616.0, + ); + dynasm!( + assembler + ; movd xmm1, Rd(reg as u8) + ; roundss xmm1, xmm1, 3 + ; cvtss2si Rq(reg as u8), xmm1 + ); + }, + WpType::F32, + WpType::I64 + )?; + } + Operator::I64TruncSF32 => { + Self::emit_unop( + assembler, + &mut self.value_stack, + |assembler, value_stack, reg| { + Self::emit_f32_int_conv_check( + assembler, + reg, + -9223373136366403584.0, + 9223372036854775808.0, + ); dynasm!( assembler ; movd xmm1, Rd(reg as u8) @@ -4514,11 +4680,18 @@ impl FunctionCodeGenerator for X64FunctionCode { WpType::F64 )?; } - Operator::I32TruncUF64 | Operator::I32TruncSF64 => { + Operator::I32TruncUF64 => { Self::emit_unop( assembler, &mut self.value_stack, |assembler, value_stack, reg| { + Self::emit_f64_int_conv_check( + assembler, + reg, + -1.0, + 4294967296.0, + ); + dynasm!( assembler ; movq xmm1, Rq(reg as u8) @@ -4530,11 +4703,64 @@ impl FunctionCodeGenerator for X64FunctionCode { WpType::I32 )?; } - Operator::I64TruncUF64 | Operator::I64TruncSF64 => { + Operator::I32TruncSF64 => { Self::emit_unop( assembler, &mut self.value_stack, |assembler, value_stack, reg| { + Self::emit_f64_int_conv_check( + assembler, + reg, + -2147483649.0, + 2147483648.0, + ); + + dynasm!( + assembler + ; movq xmm1, Rq(reg as u8) + ; roundsd xmm1, xmm1, 3 + ; cvtsd2si Rd(reg as u8), xmm1 + ); + }, + WpType::F64, + WpType::I32 + )?; + } + Operator::I64TruncUF64 => { + Self::emit_unop( + assembler, + &mut self.value_stack, + |assembler, value_stack, reg| { + Self::emit_f64_int_conv_check( + assembler, + reg, + -1.0, + 18446744073709551616.0, + ); + + dynasm!( + assembler + ; movq xmm1, Rq(reg as u8) + ; roundsd xmm1, xmm1, 3 + ; cvtsd2si Rq(reg as u8), xmm1 + ); + }, + WpType::F64, + WpType::I64 + )?; + } + Operator::I64TruncSF64 => { + Self::emit_unop( + assembler, + &mut self.value_stack, + |assembler, value_stack, reg| { + Self::emit_f64_int_conv_check( + assembler, + reg, + -9223372036854777856.0, + 9223372036854775808.0, + ); + dynasm!( assembler ; movq xmm1, Rq(reg as u8) @@ -4553,7 +4779,8 @@ impl FunctionCodeGenerator for X64FunctionCode { LocalOrImport::Local(local_mem_index) => { let mem_desc = &module_info.memories[local_mem_index]; match mem_desc.memory_type() { - MemoryType::Dynamic => self.native_trampolines.memory_size_dynamic_local, + //MemoryType::Dynamic => self.native_trampolines.memory_size_dynamic_local, + MemoryType::Dynamic => unimplemented!(), MemoryType::Static => self.native_trampolines.memory_size_static_local, MemoryType::SharedStatic => self.native_trampolines.memory_size_shared_local, } @@ -4561,7 +4788,8 @@ impl FunctionCodeGenerator for X64FunctionCode { LocalOrImport::Import(import_mem_index) => { let mem_desc = &module_info.imported_memories[import_mem_index].1; match mem_desc.memory_type() { - MemoryType::Dynamic => self.native_trampolines.memory_size_dynamic_import, + //MemoryType::Dynamic => self.native_trampolines.memory_size_dynamic_import, + MemoryType::Dynamic => unimplemented!(), MemoryType::Static => self.native_trampolines.memory_size_static_import, MemoryType::SharedStatic => self.native_trampolines.memory_size_shared_import, } @@ -4581,7 +4809,8 @@ impl FunctionCodeGenerator for X64FunctionCode { LocalOrImport::Local(local_mem_index) => { let mem_desc = &module_info.memories[local_mem_index]; match mem_desc.memory_type() { - MemoryType::Dynamic => self.native_trampolines.memory_grow_dynamic_local, + //MemoryType::Dynamic => self.native_trampolines.memory_grow_dynamic_local, + MemoryType::Dynamic => unimplemented!(), MemoryType::Static => self.native_trampolines.memory_grow_static_local, MemoryType::SharedStatic => self.native_trampolines.memory_grow_shared_local, } @@ -4589,7 +4818,8 @@ impl FunctionCodeGenerator for X64FunctionCode { LocalOrImport::Import(import_mem_index) => { let mem_desc = &module_info.imported_memories[import_mem_index].1; match mem_desc.memory_type() { - MemoryType::Dynamic => self.native_trampolines.memory_grow_dynamic_import, + //MemoryType::Dynamic => self.native_trampolines.memory_grow_dynamic_import, + MemoryType::Dynamic => unimplemented!(), MemoryType::Static => self.native_trampolines.memory_grow_static_import, MemoryType::SharedStatic => self.native_trampolines.memory_grow_shared_import, } @@ -4725,13 +4955,20 @@ unsafe extern "C" fn call_indirect( CallIndirectLocalOrImport::Import => &*(*(*vmctx).imported_tables), } ; if elem_index >= table.count as usize { - panic!("element index out of bounds"); + eprintln!("element index out of bounds"); + unsafe { protect_unix::trigger_trap(); } } let anyfunc = &*(table.base as *mut vm::Anyfunc).offset(elem_index as isize); let ctx: &X64ExecutionContext = &*CURRENT_EXECUTION_CONTEXT.with(|x| *x.borrow().last().unwrap()); - let func_index = anyfunc.func_index.unwrap(); + let func_index = match anyfunc.func_index { + Some(x) => x, + None => { + eprintln!("empty table entry"); + unsafe { protect_unix::trigger_trap(); } + } + }; /*println!( "SIG INDEX = {}, FUNC INDEX = {:?}, ELEM INDEX = {}", @@ -4741,7 +4978,8 @@ unsafe extern "C" fn call_indirect( if ctx.signatures[SigIndex::new(sig_index)] != ctx.signatures[ctx.function_signatures[func_index]] { - panic!("signature mismatch"); + eprintln!("signature mismatch"); + unsafe { protect_unix::trigger_trap(); } } let func = ctx.function_pointers[func_index.index() as usize].0; diff --git a/lib/dynasm-backend/src/lib.rs b/lib/dynasm-backend/src/lib.rs index b6b035404..080cafca7 100644 --- a/lib/dynasm-backend/src/lib.rs +++ b/lib/dynasm-backend/src/lib.rs @@ -1,5 +1,11 @@ #![feature(proc_macro_hygiene)] +#[cfg(not(any( + all(target_os = "macos", target_arch = "x86_64"), + all(target_os = "linux", target_arch = "x86_64"), +)))] +compile_error!("This crate doesn't yet support compiling on operating systems other than linux and macos and architectures other than x86_64"); + #[macro_use] extern crate dynasmrt; @@ -15,6 +21,7 @@ mod codegen; mod codegen_x64; mod parse; mod stack; +mod protect_unix; use crate::codegen::{CodegenError, ModuleCodeGenerator}; use crate::parse::LoadError; diff --git a/lib/dynasm-backend/src/parse.rs b/lib/dynasm-backend/src/parse.rs index 806517d46..c9da4a0cd 100644 --- a/lib/dynasm-backend/src/parse.rs +++ b/lib/dynasm-backend/src/parse.rs @@ -18,6 +18,7 @@ use wasmparser::{ BinaryReaderError, CodeSectionReader, Data, DataKind, Element, ElementKind, Export, ExternalKind, FuncType, Import, ImportSectionEntryType, InitExpr, ModuleReader, Operator, SectionCode, Type as WpType, + WasmDecoder, }; #[derive(Debug)] @@ -38,6 +39,30 @@ impl From for LoadError { } } +fn validate(bytes: &[u8]) -> Result<(), LoadError> { + let mut parser = wasmparser::ValidatingParser::new( + bytes, + Some(wasmparser::ValidatingParserConfig { + operator_config: wasmparser::OperatorValidatorConfig { + enable_threads: false, + enable_reference_types: false, + enable_simd: false, + enable_bulk_memory: false, + }, + mutable_global_imports: false, + }), + ); + + loop { + let state = parser.read(); + match *state { + wasmparser::ParserState::EndWasm => break Ok(()), + wasmparser::ParserState::Error(err) => Err(LoadError::Parse(err))?, + _ => {} + } + } +} + pub fn read_module< MCG: ModuleCodeGenerator, FCG: FunctionCodeGenerator, @@ -48,6 +73,7 @@ pub fn read_module< backend: Backend, mcg: &mut MCG, ) -> Result { + validate(wasm)?; let mut info = ModuleInfo { memories: Map::new(), globals: Map::new(), @@ -279,6 +305,7 @@ pub fn read_module< } .into()); } + mcg.check_precondition(&info)?; for i in 0..code_reader.get_count() { let item = code_reader.read()?; let mut fcg = mcg.next_function()?; diff --git a/lib/dynasm-backend/src/protect_unix.rs b/lib/dynasm-backend/src/protect_unix.rs new file mode 100644 index 000000000..38fa5e954 --- /dev/null +++ b/lib/dynasm-backend/src/protect_unix.rs @@ -0,0 +1,202 @@ +//! Installing signal handlers allows us to handle traps and out-of-bounds memory +//! accesses that occur when runniing webassembly. +//! +//! This code is inspired by: https://github.com/pepyakin/wasmtime/commit/625a2b6c0815b21996e111da51b9664feb174622 +//! +//! When a WebAssembly module triggers any traps, we perform recovery here. +//! +//! This module uses TLS (thread-local storage) to track recovery information. Since the four signals we're handling +//! are very special, the async signal unsafety of Rust's TLS implementation generally does not affect the correctness here +//! unless you have memory unsafety elsewhere in your code. +//! +use libc::{c_int, c_void, siginfo_t}; +use nix::sys::signal::{ + sigaction, SaFlags, SigAction, SigHandler, SigSet, Signal, SIGBUS, SIGFPE, SIGILL, SIGSEGV, +}; +use std::cell::{Cell, UnsafeCell}; +use std::ptr; +use std::sync::Once; +use wasmer_runtime_core::error::{RuntimeError, RuntimeResult}; + +extern "C" fn signal_trap_handler( + signum: ::nix::libc::c_int, + siginfo: *mut siginfo_t, + ucontext: *mut c_void, +) { + unsafe { + do_unwind(signum, siginfo as _, ucontext); + } +} + +extern "C" { + pub fn setjmp(env: *mut c_void) -> c_int; + fn longjmp(env: *mut c_void, val: c_int) -> !; +} + +pub unsafe fn install_sighandler() { + let sa = SigAction::new( + SigHandler::SigAction(signal_trap_handler), + SaFlags::SA_ONSTACK, + SigSet::empty(), + ); + sigaction(SIGFPE, &sa).unwrap(); + sigaction(SIGILL, &sa).unwrap(); + sigaction(SIGSEGV, &sa).unwrap(); + sigaction(SIGBUS, &sa).unwrap(); +} + +const SETJMP_BUFFER_LEN: usize = 27; +pub static SIGHANDLER_INIT: Once = Once::new(); + +thread_local! { + pub static SETJMP_BUFFER: UnsafeCell<[c_int; SETJMP_BUFFER_LEN]> = UnsafeCell::new([0; SETJMP_BUFFER_LEN]); + pub static CAUGHT_ADDRESSES: Cell<(*const c_void, *const c_void)> = Cell::new((ptr::null(), ptr::null())); + pub static CURRENT_EXECUTABLE_BUFFER: Cell<*const c_void> = Cell::new(ptr::null()); +} + +pub unsafe fn trigger_trap() -> ! { + let jmp_buf = SETJMP_BUFFER.with(|buf| buf.get()); + + longjmp(jmp_buf as *mut c_void, 0) +} + +pub fn call_protected(f: impl FnOnce() -> T) -> RuntimeResult { + unsafe { + let jmp_buf = SETJMP_BUFFER.with(|buf| buf.get()); + let prev_jmp_buf = *jmp_buf; + + SIGHANDLER_INIT.call_once(|| { + install_sighandler(); + }); + + let signum = setjmp(jmp_buf as *mut _); + if signum != 0 { + *jmp_buf = prev_jmp_buf; + + let (faulting_addr, inst_ptr) = CAUGHT_ADDRESSES.with(|cell| cell.get()); + + let signal = match Signal::from_c_int(signum) { + Ok(SIGFPE) => "floating-point exception", + Ok(SIGILL) => "illegal instruction", + Ok(SIGSEGV) => "segmentation violation", + Ok(SIGBUS) => "bus error", + Err(_) => "error while getting the Signal", + _ => "unkown trapped signal", + }; + // When the trap-handler is fully implemented, this will return more information. + Err(RuntimeError::Trap { + msg: format!("unknown trap at {:p} - {}", faulting_addr, signal).into(), + } + .into()) + } else { + let ret = f(); // TODO: Switch stack? + *jmp_buf = prev_jmp_buf; + Ok(ret) + } + } +} + +/// Unwinds to last protected_call. +pub unsafe fn do_unwind(signum: i32, siginfo: *const c_void, ucontext: *const c_void) -> ! { + // Since do_unwind is only expected to get called from WebAssembly code which doesn't hold any host resources (locks etc.) + // itself, accessing TLS here is safe. In case any other code calls this, it often indicates a memory safety bug and you should + // temporarily disable the signal handlers to debug it. + + let jmp_buf = SETJMP_BUFFER.with(|buf| buf.get()); + if *jmp_buf == [0; SETJMP_BUFFER_LEN] { + ::std::process::abort(); + } + + CAUGHT_ADDRESSES.with(|cell| cell.set(get_faulting_addr_and_ip(siginfo, ucontext))); + + longjmp(jmp_buf as *mut ::nix::libc::c_void, signum) +} + +#[cfg(all(target_os = "linux", target_arch = "x86_64"))] +unsafe fn get_faulting_addr_and_ip( + siginfo: *const c_void, + ucontext: *const c_void, +) -> (*const c_void, *const c_void) { + use libc::{ucontext_t, RIP}; + + #[allow(dead_code)] + #[repr(C)] + struct siginfo_t { + si_signo: i32, + si_errno: i32, + si_code: i32, + si_addr: u64, + // ... + } + + let siginfo = siginfo as *const siginfo_t; + let si_addr = (*siginfo).si_addr; + + let ucontext = ucontext as *const ucontext_t; + let rip = (*ucontext).uc_mcontext.gregs[RIP as usize]; + + (si_addr as _, rip as _) +} + +#[cfg(all(target_os = "macos", target_arch = "x86_64"))] +unsafe fn get_faulting_addr_and_ip( + siginfo: *const c_void, + ucontext: *const c_void, +) -> (*const c_void, *const c_void) { + #[allow(dead_code)] + #[repr(C)] + struct ucontext_t { + uc_onstack: u32, + uc_sigmask: u32, + uc_stack: libc::stack_t, + uc_link: *const ucontext_t, + uc_mcsize: u64, + uc_mcontext: *const mcontext_t, + } + #[repr(C)] + struct exception_state { + trapno: u16, + cpu: u16, + err: u32, + faultvaddr: u64, + } + #[repr(C)] + struct regs { + rax: u64, + rbx: u64, + rcx: u64, + rdx: u64, + rdi: u64, + rsi: u64, + rbp: u64, + rsp: u64, + r8: u64, + r9: u64, + r10: u64, + r11: u64, + r12: u64, + r13: u64, + r14: u64, + r15: u64, + rip: u64, + rflags: u64, + cs: u64, + fs: u64, + gs: u64, + } + #[allow(dead_code)] + #[repr(C)] + struct mcontext_t { + es: exception_state, + ss: regs, + // ... + } + + let siginfo = siginfo as *const siginfo_t; + let si_addr = (*siginfo).si_addr; + + let ucontext = ucontext as *const ucontext_t; + let rip = (*(*ucontext).uc_mcontext).ss.rip; + + (si_addr, rip as _) +} diff --git a/lib/runtime-core/src/types.rs b/lib/runtime-core/src/types.rs index e1967ba68..3b641e043 100644 --- a/lib/runtime-core/src/types.rs +++ b/lib/runtime-core/src/types.rs @@ -251,8 +251,8 @@ impl MemoryDescriptor { pub fn memory_type(self) -> MemoryType { match (self.maximum.is_some(), self.shared) { (true, true) => MemoryType::SharedStatic, - (true, false) => MemoryType::Static, - (false, false) => MemoryType::Dynamic, + (true, false) | (false, false) => MemoryType::Static, + //(false, false) => MemoryType::Dynamic, (false, true) => panic!("shared memory without a max is not allowed"), } }