diff --git a/src/wasmv1_execution/abi/handler.rs b/src/wasmv1_execution/abi/handler.rs index c2b66426..44f0e228 100644 --- a/src/wasmv1_execution/abi/handler.rs +++ b/src/wasmv1_execution/abi/handler.rs @@ -103,19 +103,19 @@ pub struct ABIHandler<'a, 'b> { impl<'a, 'b> ABIHandler<'a, 'b> { /// Read argument - pub fn read_arg(&self, arg_offset: i32) -> Result + pub fn read_arg(&mut self, arg_offset: i32) -> Result where M: prost::Message + Default, { let byte_vec = self .exec_env - .read_buffer(&self.store_env, arg_offset) + .take_buffer(&mut self.store_env, arg_offset) .map_err(|err| { - WasmV1Error::RuntimeError(format!( - "Could not read ABI argument: {}", - err - )) - })?; + WasmV1Error::RuntimeError(format!( + "Could not read ABI argument: {}", + err + )) + })?; M::decode(&mut Cursor::new(&byte_vec)).map_err(|err| { WasmV1Error::RuntimeError(format!( "Could not deserialize ABI argument: {}", @@ -127,18 +127,18 @@ impl<'a, 'b> ABIHandler<'a, 'b> { /// Read argument raw /// For use with abort and other function that cannot use protobuf pub fn read_arg_raw( - &self, + &mut self, arg_offset: i32, ) -> Result, WasmV1Error> { let byte_vec = self .exec_env - .read_buffer(&self.store_env, arg_offset) + .take_buffer(&mut self.store_env, arg_offset) .map_err(|err| { - WasmV1Error::RuntimeError(format!( - "Could not read ABI argument: {}", - err - )) - })?; + WasmV1Error::RuntimeError(format!( + "Could not read ABI argument: {}", + err + )) + })?; Ok(byte_vec) } @@ -156,7 +156,7 @@ impl<'a, 'b> ABIHandler<'a, 'b> { )) })?; self.exec_env - .write_buffer(&mut self.store_env, &buf) + .create_buffer(&mut self.store_env, &buf) .map_err(|err| { WasmV1Error::RuntimeError(format!( "Could not write ABI return value: {}", @@ -171,7 +171,7 @@ impl<'a, 'b> ABIHandler<'a, 'b> { value: &[u8], ) -> Result { self.exec_env - .write_buffer(&mut self.store_env, value) + .create_buffer(&mut self.store_env, value) .map_err(|err| { WasmV1Error::RuntimeError(format!( "Could not write ABI return value: {}", diff --git a/src/wasmv1_execution/env.rs b/src/wasmv1_execution/env.rs index 73123511..4d40a823 100644 --- a/src/wasmv1_execution/env.rs +++ b/src/wasmv1_execution/env.rs @@ -185,22 +185,24 @@ impl ExecutionEnv { } } - /// Read buffer from memory. - pub fn read_buffer( + /// Read buffer from guest memory, + /// try to deallocate it. + pub fn take_buffer( &self, - store: &impl AsStoreRef, + store: &mut impl AsStoreMut, offset: i32, ) -> Result, WasmV1Error> { - self.ffi.read_buffer(store, offset) + self.ffi.take_buffer(store, offset) } - /// Write buffer to memory. - pub fn write_buffer( + /// Allocate a buffer into guest memory, + /// write data into it. + pub fn create_buffer( &self, store: &mut impl AsStoreMut, data: &[u8], ) -> Result { - self.ffi.write_buffer(store, data) + self.ffi.create_buffer(store, data) } /// Get gas costs. diff --git a/src/wasmv1_execution/ffi.rs b/src/wasmv1_execution/ffi.rs index 1694df26..dc6c183c 100644 --- a/src/wasmv1_execution/ffi.rs +++ b/src/wasmv1_execution/ffi.rs @@ -5,6 +5,7 @@ use super::WasmV1Error; #[derive(Clone)] pub struct Ffi { guest_alloc_func: TypedFunction, + guest_dealloc_func: Option>, guest_memory: Memory, } @@ -22,6 +23,22 @@ impl Ffi { err )) })?; + let guest_dealloc_func = match instance + .exports + .get_typed_function::(store, "__dealloc") + { + // deallocator was successfully loaded + Ok(func) => Some(func), + // deallocator is absent + Err(wasmer::ExportError::Missing(_)) => None, + // deallocator is present but has the wrong signature + Err(err) => { + return Err(WasmV1Error::RuntimeError(format!( + "Invalid __dealloc function signature exported by guest instance: {}", + err + ))); + } + }; let guest_memory = instance .exports .get_memory("memory") @@ -34,6 +51,7 @@ impl Ffi { .clone(); Ok(Self { guest_alloc_func, + guest_dealloc_func, guest_memory, }) } @@ -43,19 +61,20 @@ impl Ffi { self.guest_memory.view(store).data_size() } + /// Reads a buffer and tries to deallocate it guest-side. /// Assumes memory layout is: [len: i32 little-endian][data: u8*] - pub fn read_buffer( + pub fn take_buffer( &self, - store: &impl AsStoreRef, + store: &mut impl AsStoreMut, offset: i32, ) -> Result, WasmV1Error> { - let Ok(offset): Result = offset.try_into() else { + let Ok(offset_u64): Result = offset.try_into() else { return Err(WasmV1Error::RuntimeError(format!("Invalid memory read offset: {}", offset))); }; let view = self.guest_memory.view(store); let mut len_buffer = [0u8; 4]; - view.read(offset, &mut len_buffer).map_err(|err| { + view.read(offset_u64, &mut len_buffer).map_err(|err| { WasmV1Error::RuntimeError(format!( "Could not read length prefix from guest memory: {}", err @@ -79,21 +98,30 @@ impl Ffi { "Buffer too large to be addressed on this system using usize" ) ]; - let Some(offset) = offset.checked_add(len_buffer.len() as u64) else { + let Some(data_offset) = offset_u64.checked_add(len_buffer.len() as u64) else { return Err(WasmV1Error::RuntimeError("Offset overflow".into())); }; - view.read(offset, &mut buffer).map_err(|err| { + view.read(data_offset, &mut buffer).map_err(|err| { WasmV1Error::RuntimeError(format!( "Could not read guest memory: {}", err )) })?; + + // Deallocate the buffer if there is a dealloc guest function + if let Some(guest_dealloc_func) = &self.guest_dealloc_func { + guest_dealloc_func.call(store, offset).map_err(|err| { + WasmV1Error::RuntimeError(format!( + "__dealloc function call failed: {}", + err + )) + })?; + } Ok(buffer) } - /// Does not assume anything on memory layout (managed by the guest on - /// allocation) - pub fn write_buffer( + /// Allocates and creates a buffer. + pub fn create_buffer( &self, store: &mut impl AsStoreMut, buffer: &[u8], diff --git a/src/wasmv1_execution/mod.rs b/src/wasmv1_execution/mod.rs index d82d00f6..22ce798c 100644 --- a/src/wasmv1_execution/mod.rs +++ b/src/wasmv1_execution/mod.rs @@ -263,10 +263,10 @@ pub(crate) fn exec_wasmv1_module( } })?; - // Write function argument to guest memory + // Allocate and write function argument to guest memory let param_offset = execution_env - .write_buffer(&mut store, param) + .create_buffer(&mut store, param) .map_err(|err| VMError::ExecutionError { error: format!( "Could not write argument for guest call {}: {}", @@ -299,17 +299,16 @@ pub(crate) fn exec_wasmv1_module( .take() .expect("Execution environment unavailable after execution"); - // Read returned value - let ret = - execution_env - .read_buffer(&store, returned_offset) - .map_err(|err| VMError::ExecutionError { - error: format!( - "Could not read return value from guest call {}: {}", - function, err - ), - init_gas_cost, - })?; + // Read returned value from guest memory and deallocate it + let ret = execution_env + .take_buffer(&mut store, returned_offset) + .map_err(|err| VMError::ExecutionError { + error: format!( + "Could not read return value from guest call {}: {}", + function, err + ), + init_gas_cost, + })?; // Get remaining gas let remaining_gas = execution_env.get_remaining_gas(&mut store);