diff --git a/lib/clif-backend/src/call/mod.rs b/lib/clif-backend/src/call/mod.rs index ef1776ad7..47e1ce555 100644 --- a/lib/clif-backend/src/call/mod.rs +++ b/lib/clif-backend/src/call/mod.rs @@ -1,6 +1,8 @@ mod recovery; mod sighandler; +pub use self::recovery::HandlerData; + use crate::call::recovery::call_protected; use hashbrown::HashSet; use libffi::high::{arg as libffi_arg, call as libffi_call, CodePtr}; @@ -16,10 +18,11 @@ use wasmer_runtime::{ pub struct Caller { func_export_set: HashSet, + handler_data: HandlerData, } impl Caller { - pub fn new(module: &ModuleInner) -> Self { + pub fn new(module: &ModuleInner, handler_data: HandlerData) -> Self { let mut func_export_set = HashSet::new(); for export_index in module.exports.values() { if let ExportIndex::Func(func_index) = export_index { @@ -30,7 +33,10 @@ impl Caller { func_export_set.insert(start_func_index); } - Self { func_export_set } + Self { + func_export_set, + handler_data, + } } } @@ -74,7 +80,7 @@ impl ProtectedCaller for Caller { let code_ptr = CodePtr::from_ptr(func_ptr as _); - call_protected(|| { + call_protected(&self.handler_data, || { // Only supports zero or one return values for now. // To support multiple returns, we will have to // generate trampolines instead of using libffi. diff --git a/lib/clif-backend/src/call/recovery.rs b/lib/clif-backend/src/call/recovery.rs index e186dfe6f..f96badf56 100644 --- a/lib/clif-backend/src/call/recovery.rs +++ b/lib/clif-backend/src/call/recovery.rs @@ -5,11 +5,18 @@ //! unless you have memory unsafety elsewhere in your code. use crate::call::sighandler::install_sighandler; -use nix::libc::siginfo_t; +use crate::relocation::{TrapData, TrapSink}; +use cranelift_codegen::ir::TrapCode; +use nix::libc::{c_void, siginfo_t}; use nix::sys::signal::{Signal, SIGBUS, SIGFPE, SIGILL, SIGSEGV}; use std::cell::{Cell, UnsafeCell}; +use std::ptr; use std::sync::Once; -use wasmer_runtime::error::{RuntimeError, RuntimeResult}; +use wasmer_runtime::{ + error::{RuntimeError, RuntimeResult}, + structures::TypedIndex, + types::{MemoryIndex, TableIndex}, +}; extern "C" { pub fn setjmp(env: *mut ::nix::libc::c_void) -> ::nix::libc::c_int; @@ -21,10 +28,39 @@ pub static SIGHANDLER_INIT: Once = Once::new(); thread_local! { pub static SETJMP_BUFFER: UnsafeCell<[::nix::libc::c_int; SETJMP_BUFFER_LEN]> = UnsafeCell::new([0; SETJMP_BUFFER_LEN]); - pub static CAUGHT_ADDRESS: Cell = Cell::new(0); + 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 fn call_protected(f: impl FnOnce() -> T) -> RuntimeResult { +pub struct HandlerData { + trap_data: TrapSink, + buffer_ptr: *const c_void, + buffer_size: usize, +} + +impl HandlerData { + pub fn new(trap_data: TrapSink, buffer_ptr: *const c_void, buffer_size: usize) -> Self { + Self { + trap_data, + buffer_ptr, + buffer_size, + } + } + + pub fn lookup(&self, ip: *const c_void) -> Option { + let ip = ip as usize; + let buffer_ptr = self.buffer_ptr as usize; + + if buffer_ptr <= ip && ip < buffer_ptr + self.buffer_size { + let offset = ip - buffer_ptr; + self.trap_data.lookup(offset) + } else { + None + } + } +} + +pub fn call_protected(handler_data: &HandlerData, f: impl FnOnce() -> T) -> RuntimeResult { unsafe { let jmp_buf = SETJMP_BUFFER.with(|buf| buf.get()); let prev_jmp_buf = *jmp_buf; @@ -36,21 +72,59 @@ pub fn call_protected(f: impl FnOnce() -> T) -> RuntimeResult { let signum = setjmp(jmp_buf as *mut ::nix::libc::c_void); if signum != 0 { *jmp_buf = prev_jmp_buf; - let addr = CAUGHT_ADDRESS.with(|cell| cell.get()); + let (faulting_addr, _) = 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::Unknown { - msg: format!("trap at {:#x} - {}", addr, signal), + if let Some(TrapData { + trapcode, + srcloc: _, + }) = handler_data.lookup(faulting_addr) + { + Err(match Signal::from_c_int(signum) { + Ok(SIGILL) => match trapcode { + TrapCode::BadSignature => RuntimeError::IndirectCallSignature { + table: TableIndex::new(0), + }, + TrapCode::IndirectCallToNull => RuntimeError::IndirectCallToNull { + table: TableIndex::new(0), + }, + TrapCode::HeapOutOfBounds => RuntimeError::OutOfBoundsAccess { + memory: MemoryIndex::new(0), + addr: 0, + }, + TrapCode::TableOutOfBounds => RuntimeError::TableOutOfBounds { + table: TableIndex::new(0), + }, + _ => RuntimeError::Unknown { + msg: "unknown trap".to_string(), + }, + }, + Ok(SIGSEGV) | Ok(SIGBUS) => { + // I'm too lazy right now to actually check if the address is within one of the memories, + // so just say that it's a memory-out-of-bounds access for now + RuntimeError::OutOfBoundsAccess { + memory: MemoryIndex::new(0), + addr: 0, + } + } + Ok(SIGFPE) => RuntimeError::IllegalArithmeticOperation, + _ => unimplemented!(), + } + .into()) + } else { + 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::Unknown { + msg: format!("trap at {:p} - {}", faulting_addr, signal), + } + .into()) } - .into()) } else { let ret = f(); // TODO: Switch stack? *jmp_buf = prev_jmp_buf; @@ -60,7 +134,7 @@ pub fn call_protected(f: impl FnOnce() -> T) -> RuntimeResult { } /// Unwinds to last protected_call. -pub unsafe fn do_unwind(signum: i32, siginfo: *mut siginfo_t) -> ! { +pub unsafe fn do_unwind(signum: i32, siginfo: *mut siginfo_t, 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. @@ -69,9 +143,30 @@ pub unsafe fn do_unwind(signum: i32, siginfo: *mut siginfo_t) -> ! { if *jmp_buf == [0; SETJMP_BUFFER_LEN] { ::std::process::abort(); } - // We only target macos at the moment as other ones might not have si_addr field - #[cfg(target_os = "macos")] - CAUGHT_ADDRESS.with(|cell| cell.set((*siginfo).si_addr as _)); + + 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: *mut siginfo_t, + _ucontext: *const c_void, +) -> (*const c_void, *const c_void) { + (ptr::null(), ptr::null()) +} + +#[cfg(all(target_os = "macos", target_arch = "x86_64"))] +unsafe fn get_faulting_addr_and_ip( + siginfo: *mut siginfo_t, + _ucontext: *const c_void, +) -> (*const c_void, *const c_void) { + ((*siginfo).si_addr, ptr::null()) +} + +#[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"); diff --git a/lib/clif-backend/src/call/sighandler.rs b/lib/clif-backend/src/call/sighandler.rs index e216bd6d7..b3455202e 100644 --- a/lib/clif-backend/src/call/sighandler.rs +++ b/lib/clif-backend/src/call/sighandler.rs @@ -1,8 +1,6 @@ -//! We install signal handlers to handle WebAssembly traps within -//! our Rust code. Otherwise we will have errors that stop the Rust process -//! such as `process didn't exit successfully: ... (signal: 8, SIGFPE: erroneous arithmetic operation)` +//! Installing signal handlers allows us to handle traps and out-of-bounds memory +//! accesses that occur when runniing webassembly. //! -//! Please read more about this here: https://github.com/CraneStation/wasmtime/issues/15 //! This code is inspired by: https://github.com/pepyakin/wasmtime/commit/625a2b6c0815b21996e111da51b9664feb174622 use crate::call::recovery; use nix::libc::{c_void, siginfo_t}; @@ -25,9 +23,9 @@ pub unsafe fn install_sighandler() { extern "C" fn signal_trap_handler( signum: ::nix::libc::c_int, siginfo: *mut siginfo_t, - _ucontext: *mut c_void, + ucontext: *mut c_void, ) { unsafe { - recovery::do_unwind(signum, siginfo); + recovery::do_unwind(signum, siginfo, ucontext); } } diff --git a/lib/clif-backend/src/module.rs b/lib/clif-backend/src/module.rs index 886da3f76..665a98d90 100644 --- a/lib/clif-backend/src/module.rs +++ b/lib/clif-backend/src/module.rs @@ -94,10 +94,10 @@ impl Module { *sig_index = sig_registry.lookup_deduplicated_sigindex(*sig_index); }); - let func_resolver_builder = FuncResolverBuilder::new(isa, functions)?; + let (func_resolver_builder, handler_data) = FuncResolverBuilder::new(isa, functions)?; self.module.func_resolver = Box::new(func_resolver_builder.finalize()?); - self.module.protected_caller = Box::new(Caller::new(&self.module)); + self.module.protected_caller = Box::new(Caller::new(&self.module, handler_data)); Ok(self.module) } diff --git a/lib/clif-backend/src/relocation.rs b/lib/clif-backend/src/relocation.rs index 8c5c59304..5da1440a6 100644 --- a/lib/clif-backend/src/relocation.rs +++ b/lib/clif-backend/src/relocation.rs @@ -3,10 +3,10 @@ //! any other calls that this function is doing, so we can "patch" the //! function addrs in runtime with the functions we need. use cranelift_codegen::binemit; -use cranelift_codegen::ir::{self, ExternalName, LibCall, SourceLoc, TrapCode}; -use wasmer_runtime::{structures::TypedIndex, types::LocalFuncIndex}; - pub use cranelift_codegen::binemit::Reloc; +use cranelift_codegen::ir::{self, ExternalName, LibCall, SourceLoc, TrapCode}; +use hashbrown::HashMap; +use wasmer_runtime::{structures::TypedIndex, types::LocalFuncIndex}; #[derive(Debug, Clone)] pub struct Relocation { @@ -133,30 +133,50 @@ impl RelocSink { } } +#[derive(Debug, Clone, Copy)] pub struct TrapData { - pub offset: usize, - pub code: TrapCode, + pub trapcode: TrapCode, + pub srcloc: SourceLoc, } /// Simple implementation of a TrapSink /// that saves the info for later. pub struct TrapSink { - trap_datas: Vec, + trap_datas: HashMap, } impl TrapSink { pub fn new() -> TrapSink { TrapSink { - trap_datas: Vec::new(), + trap_datas: HashMap::new(), } } -} -impl binemit::TrapSink for TrapSink { - fn trap(&mut self, offset: u32, _: SourceLoc, code: TrapCode) { - self.trap_datas.push(TrapData { - offset: offset as usize, - code, + pub fn lookup(&self, offset: usize) -> Option { + self.trap_datas.get(&offset).cloned() + } + + pub fn drain_local(&mut self, current_func_offset: usize, local: &mut LocalTrapSink) { + local.trap_datas.drain(..).for_each(|(offset, trap_data)| { + self.trap_datas + .insert(current_func_offset + offset, trap_data); }); } } + +pub struct LocalTrapSink { + trap_datas: Vec<(usize, TrapData)>, +} + +impl LocalTrapSink { + pub fn new() -> Self { + LocalTrapSink { trap_datas: vec![] } + } +} + +impl binemit::TrapSink for LocalTrapSink { + fn trap(&mut self, offset: u32, srcloc: SourceLoc, trapcode: TrapCode) { + self.trap_datas + .push((offset as usize, TrapData { trapcode, srcloc })); + } +} diff --git a/lib/clif-backend/src/resolver.rs b/lib/clif-backend/src/resolver.rs index 6cc783be6..a1ff7f3aa 100644 --- a/lib/clif-backend/src/resolver.rs +++ b/lib/clif-backend/src/resolver.rs @@ -1,5 +1,8 @@ +use crate::call::HandlerData; use crate::libcalls; -use crate::relocation::{Reloc, RelocSink, Relocation, RelocationType, TrapSink, VmCall}; +use crate::relocation::{ + LocalTrapSink, Reloc, RelocSink, Relocation, RelocationType, TrapSink, VmCall, +}; use byteorder::{ByteOrder, LittleEndian}; use cranelift_codegen::{ir, isa, Context}; use std::mem; @@ -17,17 +20,18 @@ use wasmer_runtime::{ pub struct FuncResolverBuilder { resolver: FuncResolver, relocations: Map>, - trap_sinks: Map, } impl FuncResolverBuilder { pub fn new( isa: &isa::TargetIsa, function_bodies: Map, - ) -> CompileResult { + ) -> CompileResult<(Self, HandlerData)> { let mut compiled_functions: Vec> = Vec::with_capacity(function_bodies.len()); let mut relocations = Map::with_capacity(function_bodies.len()); - let mut trap_sinks = Map::with_capacity(function_bodies.len()); + + let mut trap_sink = TrapSink::new(); + let mut local_trap_sink = LocalTrapSink::new(); let mut ctx = Context::new(); let mut total_size = 0; @@ -36,17 +40,20 @@ impl FuncResolverBuilder { ctx.func = func; let mut code_buf = Vec::new(); let mut reloc_sink = RelocSink::new(); - let mut trap_sink = TrapSink::new(); - ctx.compile_and_emit(isa, &mut code_buf, &mut reloc_sink, &mut trap_sink) + ctx.compile_and_emit(isa, &mut code_buf, &mut reloc_sink, &mut local_trap_sink) .map_err(|e| CompileError::InternalError { msg: e.to_string() })?; ctx.clear(); + + // Clear the local trap sink and consolidate all trap info + // into a single location. + trap_sink.drain_local(total_size, &mut local_trap_sink); + // Round up each function's size to pointer alignment. total_size += round_up(code_buf.len(), mem::size_of::()); compiled_functions.push(code_buf); relocations.push(reloc_sink.func_relocs); - trap_sinks.push(trap_sink); } let mut memory = Mmap::with_size(total_size) @@ -84,11 +91,15 @@ impl FuncResolverBuilder { previous_end = new_end; } - Ok(Self { - resolver: FuncResolver { map, memory }, - relocations, - trap_sinks, - }) + let handler_data = HandlerData::new(trap_sink, memory.as_ptr() as _, memory.size()); + + Ok(( + Self { + resolver: FuncResolver { map, memory }, + relocations, + }, + handler_data, + )) } pub fn finalize(mut self) -> CompileResult { diff --git a/lib/runtime/examples/test.rs b/lib/runtime/examples/test.rs index a20965615..cde7aa04d 100644 --- a/lib/runtime/examples/test.rs +++ b/lib/runtime/examples/test.rs @@ -4,7 +4,7 @@ use wasmer_runtime::{import::Imports, Instance}; fn main() { let mut instance = create_module_1(); - let result = instance.call("get-0", &[]); + let result = instance.call("call-overwritten", &[]); println!("result: {:?}", result); } @@ -19,15 +19,15 @@ fn main() { fn create_module_1() -> Instance { let module_str = r#"(module - (type (;0;) (func (result i32))) - (import "spectest" "global_i32" (global (;0;) i32)) - (func (;0;) (type 0) (result i32) - get_global 0) - (func (;1;) (type 0) (result i32) - get_global 1) - (global (;1;) i32 (get_global 0)) - (export "get-0" (func 0)) - (export "get-0-ref" (func 1))) + (type (;0;) (func (result i32))) + (type (;1;) (func)) + (table 10 anyfunc) + (elem (i32.const 0) 0) + (func (;0;) (type 0) (i32.const 65)) + (func (;1;) (type 1)) + (func (export "call-overwritten") (type 0) + (call_indirect (type 0) (i32.const 0)) + )) "#; let wasm_binary = wat2wasm(module_str.as_bytes()).expect("WAST not valid or malformed"); let module = wasmer_runtime::compile(&wasm_binary[..], &CraneliftCompiler::new()) diff --git a/lib/runtime/src/error.rs b/lib/runtime/src/error.rs index 3a350748b..df9d42943 100644 --- a/lib/runtime/src/error.rs +++ b/lib/runtime/src/error.rs @@ -80,8 +80,10 @@ impl PartialEq for LinkError { #[derive(Debug, Clone)] pub enum RuntimeError { OutOfBoundsAccess { memory: MemoryIndex, addr: u32 }, + TableOutOfBounds { table: TableIndex }, IndirectCallSignature { table: TableIndex }, IndirectCallToNull { table: TableIndex }, + IllegalArithmeticOperation, Unknown { msg: String }, }