Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions crates/fuzzing/src/generators/memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

use anyhow::Result;
use arbitrary::{Arbitrary, Unstructured};
use std::ops::Range;
use wasmtime::{LinearMemory, MemoryCreator, MemoryType};

/// Configuration for linear memories in Wasmtime.
Expand Down Expand Up @@ -86,6 +87,12 @@ unsafe impl LinearMemory for UnalignedMemory {
// of memory is always unaligned.
self.src[1..].as_ptr() as *mut _
}

fn wasm_accessible(&self) -> Range<usize> {
let base = self.as_ptr() as usize;
let len = self.byte_size();
base..base + len
}
}

/// A mechanism to generate [`UnalignedMemory`] at runtime.
Expand Down
30 changes: 29 additions & 1 deletion crates/runtime/src/instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use crate::vmcontext::{
};
use crate::{
ExportFunction, ExportGlobal, ExportMemory, ExportTable, Imports, ModuleRuntimeInfo, Store,
VMFunctionBody, VMSharedSignatureIndex,
VMFunctionBody, VMSharedSignatureIndex, WasmFault,
};
use anyhow::Error;
use anyhow::Result;
Expand Down Expand Up @@ -1046,6 +1046,23 @@ impl Instance {
}
}
}

fn wasm_fault(&self, addr: usize) -> Option<WasmFault> {
let mut fault = None;
for (_, memory) in self.memories.iter() {
let accessible = memory.wasm_accessible();
if accessible.start <= addr && addr < accessible.end {
// All linear memories should be disjoint so assert that no
// prior fault has been found.
assert!(fault.is_none());
fault = Some(WasmFault {
memory_size: memory.byte_size(),
wasm_address: u64::try_from(addr - accessible.start).unwrap(),
});
}
}
fault
}
}

impl Drop for Instance {
Expand Down Expand Up @@ -1231,4 +1248,15 @@ impl InstanceHandle {
pub fn initialize(&mut self, module: &Module, is_bulk_memory: bool) -> Result<()> {
allocator::initialize_instance(self.instance_mut(), module, is_bulk_memory)
}

/// Attempts to convert from the host `addr` specified to a WebAssembly
/// based address recorded in `WasmFault`.
///
/// This method will check all linear memories that this instance contains
/// to see if any of them contain `addr`. If one does then `Some` is
/// returned with metadata about the wasm fault. Otherwise `None` is
/// returned and `addr` doesn't belong to this instance.
pub fn wasm_fault(&self, addr: usize) -> Option<WasmFault> {
self.instance().wasm_fault(addr)
}
}
10 changes: 7 additions & 3 deletions crates/runtime/src/instance/allocator/pooling.rs
Original file line number Diff line number Diff line change
Expand Up @@ -817,9 +817,13 @@ unsafe impl InstanceAllocator for PoolingInstanceAllocator {
// else to come in and map something.
slot.instantiate(initial_size as usize, image, &plan.style)?;

memories.push(Memory::new_static(plan, memory, slot, unsafe {
&mut *req.store.get().unwrap()
})?);
memories.push(Memory::new_static(
plan,
memory,
slot,
self.memories.memory_and_guard_size,
unsafe { &mut *req.store.get().unwrap() },
)?);
}

Ok(())
Expand Down
20 changes: 20 additions & 0 deletions crates/runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
)]

use anyhow::Error;
use std::fmt;
use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
use std::sync::Arc;
use wasmtime_environ::{DefinedFuncIndex, DefinedMemoryIndex, HostPtr, VMOffsets};
Expand Down Expand Up @@ -237,3 +238,22 @@ pub enum WaitResult {
/// original value matched as expected but nothing ever called `notify`.
TimedOut = 2,
}

/// Description about a fault that occurred in WebAssembly.
#[derive(Debug)]
pub struct WasmFault {
/// The size of memory, in bytes, at the time of the fault.
pub memory_size: usize,
/// The WebAssembly address at which the fault occurred.
pub wasm_address: u64,
}

impl fmt::Display for WasmFault {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"memory fault at wasm address 0x{:x} in linear memory of size 0x{:x}",
self.wasm_address, self.memory_size,
)
}
}
40 changes: 39 additions & 1 deletion crates/runtime/src/memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use crate::{MemoryImage, MemoryImageSlot, Store, WaitResult};
use anyhow::Error;
use anyhow::{bail, format_err, Result};
use std::convert::TryFrom;
use std::ops::Range;
use std::sync::atomic::{AtomicU32, AtomicU64, Ordering};
use std::sync::{Arc, RwLock};
use std::time::Instant;
Expand Down Expand Up @@ -152,6 +153,12 @@ pub trait RuntimeLinearMemory: Send + Sync {

/// Used for optional dynamic downcasting.
fn as_any_mut(&mut self) -> &mut dyn std::any::Any;

/// Returns the range of addresses that may be reached by WebAssembly.
///
/// This starts at the base of linear memory and ends at the end of the
/// guard pages, if any.
fn wasm_accessible(&self) -> Range<usize>;
}

/// A linear memory instance.
Expand Down Expand Up @@ -338,6 +345,12 @@ impl RuntimeLinearMemory for MmapMemory {
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
self
}

fn wasm_accessible(&self) -> Range<usize> {
let base = self.mmap.as_mut_ptr() as usize + self.pre_guard_size;
let end = base + (self.mmap.len() - self.pre_guard_size);
base..end
}
}

/// A "static" memory where the lifetime of the backing memory is managed
Expand All @@ -350,6 +363,10 @@ struct StaticMemory {
/// The current size, in bytes, of this memory.
size: usize,

/// The size, in bytes, of the virtual address allocation starting at `base`
/// and going to the end of the guard pages at the end of the linear memory.
memory_and_guard_size: usize,

/// The image management, if any, for this memory. Owned here and
/// returned to the pooling allocator when termination occurs.
memory_image: MemoryImageSlot,
Expand All @@ -361,6 +378,7 @@ impl StaticMemory {
initial_size: usize,
maximum_size: Option<usize>,
memory_image: MemoryImageSlot,
memory_and_guard_size: usize,
) -> Result<Self> {
if base.len() < initial_size {
bail!(
Expand All @@ -381,6 +399,7 @@ impl StaticMemory {
base,
size: initial_size,
memory_image,
memory_and_guard_size,
})
}
}
Expand Down Expand Up @@ -420,6 +439,12 @@ impl RuntimeLinearMemory for StaticMemory {
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
self
}

fn wasm_accessible(&self) -> Range<usize> {
let base = self.base.as_ptr() as usize;
let end = base + self.memory_and_guard_size;
base..end
}
}

/// For shared memory (and only for shared memory), this lock-version restricts
Expand Down Expand Up @@ -620,6 +645,10 @@ impl RuntimeLinearMemory for SharedMemory {
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
self
}

fn wasm_accessible(&self) -> Range<usize> {
self.0.memory.read().unwrap().wasm_accessible()
}
}

/// Representation of a runtime wasm linear memory.
Expand Down Expand Up @@ -648,10 +677,12 @@ impl Memory {
plan: &MemoryPlan,
base: &'static mut [u8],
memory_image: MemoryImageSlot,
memory_and_guard_size: usize,
store: &mut dyn Store,
) -> Result<Self> {
let (minimum, maximum) = Self::limit_new(plan, Some(store))?;
let pooled_memory = StaticMemory::new(base, minimum, maximum, memory_image)?;
let pooled_memory =
StaticMemory::new(base, minimum, maximum, memory_image, memory_and_guard_size)?;
let allocation = Box::new(pooled_memory);
let allocation: Box<dyn RuntimeLinearMemory> = if plan.memory.shared {
// FIXME: since the pooling allocator owns the memory allocation
Expand Down Expand Up @@ -874,6 +905,13 @@ impl Memory {
}
}
}

/// Returns the range of bytes that WebAssembly should be able to address in
/// this linear memory. Note that this includes guard pages which wasm can
/// hit.
pub fn wasm_accessible(&self) -> Range<usize> {
Comment thread
alexcrichton marked this conversation as resolved.
self.0.wasm_accessible()
}
}

/// In the configurations where bounds checks were elided in JIT code (because
Expand Down
36 changes: 28 additions & 8 deletions crates/runtime/src/traphandlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,9 +147,25 @@ pub enum TrapReason {
needs_backtrace: bool,
},

/// A trap raised from Cranelift-generated code with the pc listed of where
/// the trap came from.
Jit(usize),
/// A trap raised from Cranelift-generated code.
Jit {
/// The program counter where this trap originated.
///
/// This is later used with side tables from compilation to translate
/// the trapping address to a trap code.
pc: usize,

/// If the trap was a memory-related trap such as SIGSEGV then this
/// field will contain the address of the inaccessible data.
///
/// Note that wasm loads/stores are not guaranteed to fill in this
/// information. Dynamically-bounds-checked memories, for example, will
/// not access an invalid address but may instead load from NULL or may
/// explicitly jump to a `ud2` instruction. This is only available for
/// fault-based traps which are one of the main ways, but not the only
/// way, to run wasm.
faulting_addr: Option<usize>,
},

/// A trap raised from a wasm libcall
Wasm(wasmtime_environ::Trap),
Expand All @@ -174,7 +190,7 @@ impl TrapReason {

/// Is this a JIT trap?
pub fn is_jit(&self) -> bool {
matches!(self, TrapReason::Jit(_))
matches!(self, TrapReason::Jit { .. })
}
}

Expand Down Expand Up @@ -470,12 +486,16 @@ impl CallThreadState {
self.jmp_buf.replace(ptr::null())
}

fn set_jit_trap(&self, pc: *const u8, fp: usize) {
fn set_jit_trap(&self, pc: *const u8, fp: usize, faulting_addr: Option<usize>) {
let backtrace = self.capture_backtrace(Some((pc as usize, fp)));
unsafe {
(*self.unwind.get())
.as_mut_ptr()
.write((UnwindReason::Trap(TrapReason::Jit(pc as usize)), backtrace));
(*self.unwind.get()).as_mut_ptr().write((
UnwindReason::Trap(TrapReason::Jit {
pc: pc as usize,
faulting_addr,
}),
backtrace,
));
}
}

Expand Down
Loading