Reputation: 21
I have a WASM module as follows
#[no_mangle]
pub extern "C" fn process_bytes(ptr: *const u8, len: usize) -> u8 {
if len > 0 {
let bytes = unsafe { std::slice::from_raw_parts(ptr, len) };
let first = bytes[0];
first
} else {
0
}
}
It accepts a byte array, returning the first byte.
This module is compiled with cargo build --target wasm32-unknown-unknown --release
then called from another crate that uses wasmer
pub mod get_tx_data;
use wasmer::{imports, Cranelift, Instance, Module, Store, Value};
fn main() {
// Read the Wasm file
let wasm_bytes = include_bytes!("banana_swap.wasm");
// Create a store
let mut store = Store::default();
// let compiler = Cranelift::default();
// let mut store = Store::new(compiler);
// Compile the module
let module = Module::new(&store, wasm_bytes).unwrap();
// Create an import object with the host function
let import_object = imports! {};
let instance = Instance::new(&mut store, &module, &import_object).unwrap();
let function = instance.exports.get_function("process_bytes").unwrap();
// Example byte array
let byte_array: Vec<u8> = vec![0x48, 0x65, 0x6C, 0x6C, 0x6F];
// Call the exported function with the byte array
let result = function.call(&mut store, &[
Value::I32(byte_array.as_ptr() as i32),
Value::I32(byte_array.len() as i32)
]).unwrap();
// Check the result
println!("Result: {:?}", result);
}
This gives error signal_trap: Some(HeapAccessOutOfBounds) }
. The array length is being read properly, I'm able to return it from the function. What went wrong?
thread 'main' panicked at indexer-core/src/main.rs:31:8:
called `Result::unwrap()` on an `Err` value: RuntimeError { source: Wasm { pc: 127833057173522, backtrace: 0: <unknown>
1: <unknown>
2: <unknown>
3: <unknown>
4: <unknown>
5: <unknown>
6: <unknown>
, signal_trap: Some(HeapAccessOutOfBounds) }, wasm_trace: [FrameInfo { module_name: "<module>", func_index: 0, function_name: Some("process_bytes"), func_start: SourceLoc(119), instr: SourceLoc(137) }] }
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
https://github.com/icedancer-io/indexer-core/tree/stackoverflow
Use the stackoverflow branch. How to run-
cd banana-swap && cargo build --target wasm32-unknown-unknown --release && cd .. && cp ./target/wasm32-unknown-unknown/release/banana_swap.wasm indexer-core/src/banana_swap.wasm && cargo run --bin indexer-core
Upvotes: 1
Views: 421
Reputation: 8504
You passed a pointer to host memory to process_bytes
. The WASM module instance gets its entirely separate block of memory, so when it tries to access that pointer, it's a pointer to nowhere. You'll have to copy the byte array to the instance's memory.
Sorry, this is only a sketch of how to do that:
#[no_mangle]
pub extern "C" fn alloc(len: u32) -> u32 {
let layout = std::alloc::Layout::array::<u8>(len.try_into().unwrap()).unwrap();
unsafe {
(std::alloc::alloc(layout) as usize).try_into().unwrap()
}
}
memory.view(&store).write(pointer_from_alloc, &byte_array);
function.call(&mut store, &[Value::I32(pointer_from_allc), Value::I32(byte_array.len() as i32)])
Of course, there's other ways of getting valid writable pointers, for example Memory::grow
, call a host import from wasm with a pointer (that'd be what wasi's fd_write
does), which can then call process_bytes
, or just reserving a static array at a specific location.
Upvotes: 0
Reputation: 70990
WASM cannot access regions outside of its allocated memory (of the host). That would defeat the entire point of sandboxing using WASM.
You need to copy the data into WASM memory, then pass the address from there.
use wasmer::{imports, Instance, Module, Store, Value};
fn main() {
// Read the Wasm file
let wasm_bytes = include_bytes!("../../target/wasm32-unknown-unknown/release/guest.wasm");
// Create a store
let mut store = Store::default();
// let compiler = Cranelift::default();
// let mut store = Store::new(compiler);
// Compile the module
let module = Module::new(&store, wasm_bytes).unwrap();
// Create an import object with the host function
let import_object = imports! {};
let instance = Instance::new(&mut store, &module, &import_object).unwrap();
let function = instance.exports.get_function("process_bytes").unwrap();
// Example byte array
let byte_array: Vec<u8> = vec![0x48, 0x65, 0x6C, 0x6C, 0x6F];
let memory = instance.exports.get_memory("memory").unwrap();
let view = memory.view(&store);
// Cannot be 0 because null is disallowed in Rust references.
view.write(1, &byte_array).unwrap();
// Call the exported function with the byte array
let result = function
.call(
&mut store,
&[Value::I32(1), Value::I32(byte_array.len() as i32)],
)
.unwrap();
// Check the result
println!("Result: {:?}", result);
}
If you don't know a free place in the memory, you can for example ask the guest to allocate X bytes using its allocator and return their address, then write there.
Upvotes: 1