diff --git a/lib/api/src/externals/memory.rs b/lib/api/src/externals/memory.rs index 2571207ccaf..3b26871f1cb 100644 --- a/lib/api/src/externals/memory.rs +++ b/lib/api/src/externals/memory.rs @@ -119,6 +119,11 @@ impl Memory { self.0.grow(store, delta) } + /// Makes all the memory inaccessible to any reads or writes + pub fn make_inaccessible(&self, store: &impl AsStoreRef) -> Result<(), MemoryError> { + self.0.make_inaccessible(store) + } + /// Copies the memory to a new store and returns a memory reference to it pub fn copy_to_store( &self, diff --git a/lib/api/src/js/externals/memory.rs b/lib/api/src/js/externals/memory.rs index e5cfdb9654f..eb6d118ef0c 100644 --- a/lib/api/src/js/externals/memory.rs +++ b/lib/api/src/js/externals/memory.rs @@ -179,6 +179,11 @@ impl Memory { pub fn duplicate(&mut self, _store: &impl AsStoreRef) -> Result { self.handle.duplicate() } + + #[allow(unused)] + pub fn make_inaccessible(&self, store: &impl AsStoreRef) -> Result<(), MemoryError> { + Err(MemoryError::NotImplemented) + } } impl std::cmp::PartialEq for Memory { diff --git a/lib/api/src/sys/externals/memory.rs b/lib/api/src/sys/externals/memory.rs index 702d7e44bc8..7c92914b22b 100644 --- a/lib/api/src/sys/externals/memory.rs +++ b/lib/api/src/sys/externals/memory.rs @@ -56,6 +56,13 @@ impl Memory { self.handle.get_mut(store.objects_mut()).grow(delta.into()) } + /// Makes all the memory inaccessible to any reads or writes + pub fn make_inaccessible(&self, store: &impl AsStoreRef) -> Result<(), MemoryError> { + self.handle + .get(store.as_store_ref().objects()) + .make_inaccessible() + } + pub fn copy_to_store( &self, store: &impl AsStoreRef, diff --git a/lib/api/src/sys/tunables.rs b/lib/api/src/sys/tunables.rs index 80111464ff8..cc05f6aec51 100644 --- a/lib/api/src/sys/tunables.rs +++ b/lib/api/src/sys/tunables.rs @@ -133,6 +133,7 @@ mod tests { mem, })) } + /* // this code allow custom memory to be ignoring init_memory use wasmer_vm::Trap; diff --git a/lib/sys-utils/src/memory/fd_memory/fd_mmap.rs b/lib/sys-utils/src/memory/fd_memory/fd_mmap.rs index 4bda17c9862..071137e20eb 100644 --- a/lib/sys-utils/src/memory/fd_memory/fd_mmap.rs +++ b/lib/sys-utils/src/memory/fd_memory/fd_mmap.rs @@ -180,6 +180,28 @@ impl FdMmap { .map_err(|e| e.to_string()) } + /// Make the entire memory inaccessible to both reads and writes. + pub fn make_all_inaccessible(&self) -> Result<(), String> { + self.make_inaccessible(0, self.len) + } + + /// Make the memory starting at `start` and extending for `len` bytes inaccessible + /// to both reads and writes. + /// `start` and `len` must be native page-size multiples and describe a range within + /// `self`'s reserved memory. + pub fn make_inaccessible(&self, start: usize, len: usize) -> Result<(), String> { + let page_size = region::page::size(); + assert_eq!(start & (page_size - 1), 0); + assert_eq!(len & (page_size - 1), 0); + assert!(len <= self.len); + assert!(start <= self.len - len); + + // Commit the accessible size. + let ptr = self.ptr as *const u8; + unsafe { region::protect(ptr.add(start), len, region::Protection::NONE) } + .map_err(|e| e.to_string()) + } + /// Return the allocated memory as a slice of u8. pub fn as_slice(&self) -> &[u8] { unsafe { slice::from_raw_parts(self.ptr as *const u8, self.len) } diff --git a/lib/sys-utils/src/memory/fd_memory/memories.rs b/lib/sys-utils/src/memory/fd_memory/memories.rs index c058f1f681c..1a187e29df9 100644 --- a/lib/sys-utils/src/memory/fd_memory/memories.rs +++ b/lib/sys-utils/src/memory/fd_memory/memories.rs @@ -149,6 +149,13 @@ impl WasmMmap { size: self.size, }) } + + /// Makes all the memory inaccessible to reads and writes + pub fn make_inaccessible(&self) -> Result<(), MemoryError> { + self.alloc + .make_all_inaccessible() + .map_err(MemoryError::Region) + } } /// A linear memory instance. @@ -307,6 +314,11 @@ impl VMOwnedMemory { config: self.config.clone(), }) } + + /// Makes all the memory inaccessible to reads and writes + fn make_inaccessible(&self) -> Result<(), MemoryError> { + self.mmap.make_inaccessible() + } } impl LinearMemory for VMOwnedMemory { @@ -349,6 +361,11 @@ impl LinearMemory for VMOwnedMemory { let forked = Self::duplicate(self)?; Ok(Box::new(forked)) } + + /// Makes all the memory inaccessible to reads and writes + fn make_inaccessible(&self) -> Result<(), MemoryError> { + Self::make_inaccessible(self) + } } /// A shared linear memory instance. @@ -395,6 +412,12 @@ impl VMSharedMemory { config: self.config.clone(), }) } + + /// Makes all the memory inaccessible to reads and writes + pub fn make_inaccessible(&self) -> Result<(), MemoryError> { + let guard = self.mmap.write().unwrap(); + guard.make_inaccessible() + } } impl LinearMemory for VMSharedMemory { @@ -443,6 +466,11 @@ impl LinearMemory for VMSharedMemory { let forked = Self::duplicate(self)?; Ok(Box::new(forked)) } + + /// Makes all the memory inaccessible to reads and writes + fn make_inaccessible(&self) -> Result<(), MemoryError> { + Self::make_inaccessible(self) + } } impl From for VMMemory { @@ -510,6 +538,11 @@ impl LinearMemory for VMMemory { fn duplicate(&mut self) -> Result, MemoryError> { self.0.duplicate() } + + /// Makes all the memory inaccessible to reads and writes + fn make_inaccessible(&self) -> Result<(), MemoryError> { + self.0.make_inaccessible() + } } impl VMMemory { diff --git a/lib/types/src/error.rs b/lib/types/src/error.rs index 66f5ce765cc..40bab705eb6 100644 --- a/lib/types/src/error.rs +++ b/lib/types/src/error.rs @@ -85,6 +85,9 @@ pub enum MemoryError { /// A user defined error value, used for error cases not listed above. #[error("A user-defined error occurred: {0}")] Generic(String), + /// Not implemented + #[error("This operation is not yet implemented")] + NotImplemented, } /// An ImportError. diff --git a/lib/vm/src/memory.rs b/lib/vm/src/memory.rs index a55b242f809..e142fc26d06 100644 --- a/lib/vm/src/memory.rs +++ b/lib/vm/src/memory.rs @@ -140,6 +140,13 @@ impl WasmMmap { size: self.size, }) } + + /// Makes all the memory inaccessible to reads and writes + pub fn make_inaccessible(&self) -> Result<(), MemoryError> { + self.alloc + .make_all_inaccessible() + .map_err(MemoryError::Region) + } } /// A linear memory instance. @@ -299,6 +306,11 @@ impl VMOwnedMemory { config: self.config.clone(), }) } + + /// Makes all the memory inaccessible to reads and writes + pub fn make_inaccessible(&self) -> Result<(), MemoryError> { + self.mmap.make_inaccessible() + } } impl LinearMemory for VMOwnedMemory { @@ -341,6 +353,11 @@ impl LinearMemory for VMOwnedMemory { let forked = Self::duplicate(self)?; Ok(Box::new(forked)) } + + /// Makes all the memory inaccessible to reads and writes + fn make_inaccessible(&self) -> Result<(), MemoryError> { + Self::make_inaccessible(self) + } } /// A shared linear memory instance. @@ -390,6 +407,12 @@ impl VMSharedMemory { conditions: ThreadConditions::new(), }) } + + /// Makes all the memory inaccessible to reads and writes + pub fn make_inaccessible(&self) -> Result<(), MemoryError> { + let guard = self.mmap.write().unwrap(); + guard.make_inaccessible() + } } impl LinearMemory for VMSharedMemory { @@ -452,6 +475,11 @@ impl LinearMemory for VMSharedMemory { fn do_notify(&mut self, dst: NotifyLocation, count: u32) -> u32 { self.conditions.do_notify(dst, count) } + + /// Makes all the memory inaccessible to reads and writes + fn make_inaccessible(&self) -> Result<(), MemoryError> { + Self::make_inaccessible(self) + } } impl From for VMMemory { @@ -533,6 +561,11 @@ impl LinearMemory for VMMemory { fn do_notify(&mut self, dst: NotifyLocation, count: u32) -> u32 { self.0.do_notify(dst, count) } + + /// Makes all the memory inaccessible to reads and writes + fn make_inaccessible(&self) -> Result<(), MemoryError> { + self.0.make_inaccessible() + } } impl VMMemory { @@ -596,6 +629,11 @@ impl VMMemory { pub fn duplicate(&mut self) -> Result, MemoryError> { LinearMemory::duplicate(self) } + + /// Makes all the memory inaccessible to reads and writes + pub fn make_inaccessible(&self) -> Result<(), MemoryError> { + LinearMemory::make_inaccessible(self) + } } #[doc(hidden)] @@ -666,4 +704,9 @@ where fn do_notify(&mut self, _dst: NotifyLocation, _count: u32) -> u32 { 0 } + + /// Makes all the memory inaccessible to reads and writes + fn make_inaccessible(&self) -> Result<(), MemoryError> { + Err(MemoryError::NotImplemented) + } } diff --git a/lib/vm/src/mmap.rs b/lib/vm/src/mmap.rs index abeb21a666b..f23899bea59 100644 --- a/lib/vm/src/mmap.rs +++ b/lib/vm/src/mmap.rs @@ -233,6 +233,28 @@ impl Mmap { Ok(()) } + /// Make the entire memory inaccessible to both reads and writes. + pub fn make_all_inaccessible(&self) -> Result<(), String> { + self.make_inaccessible(0, self.total_size) + } + + /// Make the memory starting at `start` and extending for `len` bytes inaccessible + /// to both reads and writes. + /// `start` and `len` must be native page-size multiples and describe a range within + /// `self`'s reserved memory. + pub fn make_inaccessible(&self, start: usize, len: usize) -> Result<(), String> { + let page_size = region::page::size(); + assert_eq!(start & (page_size - 1), 0); + assert_eq!(len & (page_size - 1), 0); + assert_le!(len, self.total_size); + assert_le!(start, self.total_size - len); + + // Commit the accessible size. + let ptr = self.ptr as *const u8; + unsafe { region::protect(ptr.add(start), len, region::Protection::NONE) } + .map_err(|e| e.to_string()) + } + /// Return the allocated memory as a slice of u8. pub fn as_slice(&self) -> &[u8] { unsafe { slice::from_raw_parts(self.ptr as *const u8, self.total_size) } diff --git a/lib/wasi/src/bin_factory/exec.rs b/lib/wasi/src/bin_factory/exec.rs index 62a4becdacd..62e6970f125 100644 --- a/lib/wasi/src/bin_factory/exec.rs +++ b/lib/wasi/src/bin_factory/exec.rs @@ -40,7 +40,7 @@ pub fn spawn_exec( VirtualBusError::CompileError }); if module.is_err() { - env.blocking_cleanup(Some(Errno::Noexec.into())); + env.blocking_cleanup(&store, Some(Errno::Noexec.into())); } let module = module?; compiled_modules.set_compiled_module(binary.hash().as_str(), compiler, &module); @@ -48,7 +48,7 @@ pub fn spawn_exec( } (None, None) => { error!("package has no entry [{}]", name,); - env.blocking_cleanup(Some(Errno::Noexec.into())); + env.blocking_cleanup(&store, Some(Errno::Noexec.into())); return Err(VirtualBusError::CompileError); } }; @@ -113,7 +113,7 @@ pub fn spawn_exec_module( error!("wasi[{}]::wasm instantiate error ({})", pid, err); wasi_env .data(&store) - .blocking_cleanup(Some(Errno::Noexec.into())); + .blocking_cleanup(&store, Some(Errno::Noexec.into())); return; } }; @@ -127,7 +127,7 @@ pub fn spawn_exec_module( error!("wasi[{}]::wasi initialize error ({})", pid, err); wasi_env .data(&store) - .blocking_cleanup(Some(Errno::Noexec.into())); + .blocking_cleanup(&store, Some(Errno::Noexec.into())); return; } @@ -137,7 +137,7 @@ pub fn spawn_exec_module( thread.thread.set_status_finished(Err(err.into())); wasi_env .data(&store) - .blocking_cleanup(Some(Errno::Noexec.into())); + .blocking_cleanup(&store, Some(Errno::Noexec.into())); return; } } @@ -168,7 +168,7 @@ pub fn spawn_exec_module( }; // Cleanup the environment - wasi_env.data(&store).blocking_cleanup(Some(code)); + wasi_env.data(&store).blocking_cleanup(&store, Some(code)); debug!("wasi[{pid}]::main() has exited with {code}"); thread.thread.set_status_finished(ret.map(|a| a.into())); @@ -200,7 +200,7 @@ impl BinFactory { .await .ok_or(VirtualBusError::NotFound); if binary.is_err() { - env.cleanup(Some(Errno::Noent.into())).await; + env.cleanup(&store, Some(Errno::Noent.into())).await; } let binary = binary?; diff --git a/lib/wasi/src/state/env.rs b/lib/wasi/src/state/env.rs index c48f2b39e75..2cfcb6ce94c 100644 --- a/lib/wasi/src/state/env.rs +++ b/lib/wasi/src/state/env.rs @@ -6,8 +6,8 @@ use tracing::{trace, warn}; use virtual_fs::{FsError, VirtualFile}; use virtual_net::DynVirtualNetworking; use wasmer::{ - AsStoreMut, AsStoreRef, FunctionEnvMut, Global, Instance, Memory, MemoryView, Module, - TypedFunction, + AsStoreMut, AsStoreRef, FunctionEnvMut, Global, Instance, Memory, MemoryError, MemoryView, + Module, TypedFunction, }; use wasmer_wasix_types::{ types::Signal, @@ -466,7 +466,7 @@ impl WasiEnv { tracing::error!("wasi[{}]::wasm instantiate error ({})", pid, err); func_env .data(&store) - .blocking_cleanup(Some(Errno::Noexec.into())); + .blocking_cleanup(&store, Some(Errno::Noexec.into())); return Err(err.into()); } }; @@ -481,7 +481,7 @@ impl WasiEnv { tracing::error!("wasi[{}]::wasi initialize error ({})", pid, err); func_env .data(&store) - .blocking_cleanup(Some(Errno::Noexec.into())); + .blocking_cleanup(&store, Some(Errno::Noexec.into())); return Err(err.into()); } @@ -491,7 +491,7 @@ impl WasiEnv { if let Err(err) = crate::run_wasi_func_start(initialize, &mut store) { func_env .data(&store) - .blocking_cleanup(Some(Errno::Noexec.into())); + .blocking_cleanup(&store, Some(Errno::Noexec.into())); return Err(err); } } @@ -904,9 +904,9 @@ impl WasiEnv { /// Cleans up all the open files (if this is the main thread) #[allow(clippy::await_holding_lock)] - pub fn blocking_cleanup(&self, exit_code: Option) { + pub fn blocking_cleanup(&self, store: &impl AsStoreRef, exit_code: Option) { __asyncify_light(self, None, async { - self.cleanup(exit_code).await; + self.cleanup(store, exit_code).await; Ok(()) }) .ok(); @@ -914,7 +914,7 @@ impl WasiEnv { /// Cleans up all the open files (if this is the main thread) #[allow(clippy::await_holding_lock)] - pub async fn cleanup(&self, exit_code: Option) { + pub async fn cleanup(&self, store: &impl AsStoreRef, exit_code: Option) { const CLEANUP_TIMEOUT: Duration = Duration::from_secs(10); // If this is the main thread then also close all the files @@ -938,6 +938,24 @@ impl WasiEnv { // Terminate the process let exit_code = exit_code.unwrap_or_else(|| Errno::Canceled.into()); self.process.terminate(exit_code); + + // Now we also force all the memory into a protected state which will prevent any reads + // or writes and thus terminate processes that try to use it + match self.memory().make_inaccessible(store) { + Ok(_) => {} + Err(MemoryError::NotImplemented) => { + // we silently ignore memory that does not implement this yet rather + // than failing the cleanup call. the consequences for the runtime + // are that for these types of memories they are still accessible after + // the process is terminated. + } + Err(err) => { + tracing::warn!( + "WasiEnv::cleanup failed to set memory to inaccessible - {}", + err + ); + } + } } } } diff --git a/lib/wasi/src/state/func_env.rs b/lib/wasi/src/state/func_env.rs index 9c784a8c53a..a6333af3c72 100644 --- a/lib/wasi/src/state/func_env.rs +++ b/lib/wasi/src/state/func_env.rs @@ -182,6 +182,6 @@ impl WasiFunctionEnv { } // Cleans up all the open files (if this is the main thread) - self.data(store).blocking_cleanup(exit_code); + self.data(store).blocking_cleanup(store, exit_code); } }