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
68 changes: 66 additions & 2 deletions crates/wasmtime/src/externals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -605,6 +605,22 @@ impl Table {
}
}

/// Error for out of bounds [`Memory`] access.
#[derive(Debug)]
#[non_exhaustive]
pub struct MemoryAccessError {
// Keep struct internals private for future extensibility.
_private: (),
}

impl std::fmt::Display for MemoryAccessError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "out of bounds memory access")
}
}

impl std::error::Error for MemoryAccessError {}

/// A WebAssembly linear memory.
///
/// WebAssembly memories represent a contiguous array of bytes that have a size
Expand Down Expand Up @@ -661,9 +677,24 @@ impl Table {
/// Let's run through a few safe examples first of how you can use a `Memory`.
///
/// ```rust
/// use wasmtime::Memory;
/// use wasmtime::{Memory, MemoryAccessError};
///
/// // Memory can be read and written safely with the `Memory::read` and
/// // `Memory::write` methods.
/// // An error is returned if the copy did not succeed.
/// fn safe_examples(mem: &Memory) -> Result<(), MemoryAccessError> {
/// let offset = 5;
/// mem.write(offset, b"hello")?;
/// let mut buffer = [0u8; 5];
/// mem.read(offset, &mut buffer)?;
/// assert_eq!(b"hello", &buffer);
/// Ok(())
/// }
///
/// // You can also get direct, unsafe access to the memory, but must manually
/// // ensure that safety invariants are upheld.
///
/// fn safe_examples(mem: &Memory) {
/// fn correct_unsafe_examples(mem: &Memory) {
/// // Just like wasm, it's safe to read memory almost at any time. The
/// // gotcha here is that we need to be sure to load from the correct base
/// // pointer and perform the bounds check correctly. So long as this is
Expand Down Expand Up @@ -871,6 +902,39 @@ impl Memory {
MemoryType::from_wasmtime_memory(&self.wasmtime_export.memory.memory)
}

/// Safely reads memory contents at the given offset into a buffer.
///
/// The entire buffer will be filled.
///
/// If offset + buffer length exceed the current memory capacity,
/// a [`MemoryAccessError`] is returned.
pub fn read(&self, offset: usize, buffer: &mut [u8]) -> Result<(), MemoryAccessError> {
unsafe {
let slice = self
.data_unchecked()
.get(offset..)
.and_then(|s| s.get(..buffer.len()))
.ok_or(MemoryAccessError { _private: () })?;
buffer.copy_from_slice(slice);
Ok(())
}
}

/// Safely writes contents of a buffer to this memory at the given offset.
///
/// If the offset + buffer length exceed current memory capacity, a
/// [`MemoryAccessError`] is returned.
pub fn write(&self, offset: usize, buffer: &[u8]) -> Result<(), MemoryAccessError> {
unsafe {
self.data_unchecked_mut()
.get_mut(offset..)
.and_then(|s| s.get_mut(..buffer.len()))
.ok_or(MemoryAccessError { _private: () })?
.copy_from_slice(buffer);
Ok(())
}
}

/// Returns this memory as a slice view that can be read natively in Rust.
///
/// # Safety
Expand Down
37 changes: 37 additions & 0 deletions tests/all/externals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -340,3 +340,40 @@ fn grow_externref_tables_via_api() -> anyhow::Result<()> {

Ok(())
}

#[test]
fn read_write_memory_via_api() {
let cfg = Config::new();
let store = Store::new(&Engine::new(&cfg));
let ty = MemoryType::new(Limits::new(1, None));
let mem = Memory::new(&store, ty);
mem.grow(1).unwrap();

let value = b"hello wasm";
mem.write(mem.data_size() - value.len(), value).unwrap();

let mut buffer = [0u8; 10];
mem.read(mem.data_size() - buffer.len(), &mut buffer)
.unwrap();
assert_eq!(value, &buffer);

// Error conditions.

// Out of bounds write.

let res = mem.write(mem.data_size() - value.len() + 1, value);
assert!(res.is_err());

// Out of bounds read.

let res = mem.read(mem.data_size() - buffer.len() + 1, &mut buffer);
assert!(res.is_err());

// Read offset overflow.
let res = mem.read(usize::MAX, &mut buffer);
assert!(res.is_err());

// Write offset overflow.
let res = mem.write(usize::MAX, &mut buffer);
assert!(res.is_err());
}