Search code examples
rustwebassembly

How to pass a byte array to a WASM module from wasmer in Rust?


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

Source code for reproduction

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

Solution

  • 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.