From 6c9449cf6285a9e47068b4dfcb7901bb5cdc9f67 Mon Sep 17 00:00:00 2001 From: Roman Maslennikov Date: Sun, 29 Sep 2024 18:20:30 +0400 Subject: [PATCH 01/25] Update lazy-pages-fuzzer wasmi version --- Cargo.lock | 70 +++++- utils/lazy-pages-fuzzer/Cargo.toml | 3 +- utils/lazy-pages-fuzzer/src/wasmi_backend.rs | 203 ++++++++++-------- .../src/wasmi_backend/error.rs | 2 +- 4 files changed, 185 insertions(+), 93 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0d39ea13a98..930eb320c5d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9017,7 +9017,7 @@ dependencies = [ "log", "region", "wasmer", - "wasmi 0.13.2", + "wasmi 0.36.1", "wasmprinter", "wat", ] @@ -10497,6 +10497,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7843ec2de400bcbc6a6328c958dc38e5359da6e93e72e37bc5246bf1ae776389" +[[package]] +name = "multi-stash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "685a9ac4b61f4e728e1d2c6a7844609c16527aeb5e6c865915c08e619c16410f" + [[package]] name = "multiaddr" version = "0.17.1" @@ -10934,6 +10940,17 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.71", +] + [[package]] name = "num-format" version = "0.4.4" @@ -17339,6 +17356,17 @@ version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e75b72ee54e2f93c3ea1354066162be893ee5e25773ab743de3e088cecbb4f31" +[[package]] +name = "string-interner" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c6a0d765f5807e98a091107bae0a56ea3799f66a5de47b2c84c94a39c09974e" +dependencies = [ + "cfg-if", + "hashbrown 0.14.5", + "serde", +] + [[package]] name = "string_cache" version = "0.8.7" @@ -19503,6 +19531,23 @@ dependencies = [ "wasmparser-nostd", ] +[[package]] +name = "wasmi" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81eacbefcfb4fc0d0af5424752e10895a131e1d0edb4b87554aac024bd294bdd" +dependencies = [ + "arrayvec 0.7.4", + "multi-stash", + "num-derive", + "num-traits", + "smallvec", + "spin 0.9.8", + "wasmi_collections", + "wasmi_core 0.36.1", + "wasmparser-nostd", +] + [[package]] name = "wasmi-validation" version = "0.5.0" @@ -19522,6 +19567,17 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "104a7f73be44570cac297b3035d76b169d6599637631cf37a1703326a0727073" +[[package]] +name = "wasmi_collections" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d1ff23df2c456c8b5d9a0ae7eed03a40f0c4520466b4aa87135c5fc557476e8" +dependencies = [ + "ahash 0.8.11", + "hashbrown 0.14.5", + "string-interner", +] + [[package]] name = "wasmi_core" version = "0.2.1" @@ -19559,6 +19615,18 @@ dependencies = [ "paste", ] +[[package]] +name = "wasmi_core" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac1b21ded145eb313d44a5895442c28e18904fb95718dc83893779f55945d342" +dependencies = [ + "downcast-rs", + "libm", + "num-traits", + "paste", +] + [[package]] name = "wasmparser" version = "0.102.0" diff --git a/utils/lazy-pages-fuzzer/Cargo.toml b/utils/lazy-pages-fuzzer/Cargo.toml index ced04890460..9138785aea4 100644 --- a/utils/lazy-pages-fuzzer/Cargo.toml +++ b/utils/lazy-pages-fuzzer/Cargo.toml @@ -15,6 +15,7 @@ gear-lazy-pages-common.workspace = true log.workspace = true region.workspace = true wasmer = { workspace = true, features = ["singlepass"] } -sandbox-wasmi.workspace = true +#sandbox-wasmi.workspace = true +sandbox-wasmi = { package = "wasmi", version = "0.36.1"} wasmprinter.workspace = true wat.workspace = true diff --git a/utils/lazy-pages-fuzzer/src/wasmi_backend.rs b/utils/lazy-pages-fuzzer/src/wasmi_backend.rs index c03403fa270..6d4069f78ea 100644 --- a/utils/lazy-pages-fuzzer/src/wasmi_backend.rs +++ b/utils/lazy-pages-fuzzer/src/wasmi_backend.rs @@ -16,14 +16,16 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use anyhow::{bail, Context}; +use std::slice; + +use anyhow::{anyhow, bail, Context}; use gear_wasm_gen::SyscallName; use gear_wasm_instrument::{parity_wasm::elements::Module, GLOBAL_NAME_GAS}; +use region::{Allocation, Protection}; use sandbox_wasmi::{ - memory_units::Pages, ExternVal, FuncInstance, FuncRef, ImportsBuilder, MemoryInstance, - MemoryRef, Module as WasmiModule, ModuleImportResolver, ModuleInstance, ModuleRef, - RuntimeValue, Trap, TrapCode, ValueType, + core::UntypedVal, Caller, Config, Engine, Error, Instance, Linker, Memory, MemoryType, + Module as WasmiModule, StackLimits, Store, Val, }; use crate::{ @@ -35,127 +37,148 @@ use crate::{ use error::CustomHostError; mod error; -struct Resolver { - memory: MemoryRef, +#[derive(Clone)] +struct InstanceBundle { + instance: Instance, + store: *mut Store<()>, } -impl ModuleImportResolver for Resolver { - fn resolve_func( - &self, - field_name: &str, - _signature: &sandbox_wasmi::Signature, - ) -> Result { - if field_name == SyscallName::SystemBreak.to_str() { - Ok(FuncInstance::alloc_host( - sandbox_wasmi::Signature::new([ValueType::I32].as_slice(), None), - 0, - )) - } else { - Err(sandbox_wasmi::Error::Instantiation(format!( - "Export '{field_name}' not found" - ))) - } - } - - fn resolve_memory( - &self, - _field_name: &str, - _memory_type: &sandbox_wasmi::MemoryDescriptor, - ) -> Result { - Ok(self.memory.clone()) +impl InstanceAccessGlobal for InstanceBundle { + fn set_global(&self, name: &str, value: i64) -> anyhow::Result<()> { + let global = self + .instance + .get_global(unsafe { &*self.store }, name) + .ok_or_else(|| anyhow!("failed to get global {name}"))?; + global.set(unsafe { &mut *self.store }, Val::I64(value))?; + Ok(()) } -} -struct Externals { - gr_system_break_idx: usize, -} + fn get_global(&self, name: &str) -> anyhow::Result { + let global = self + .instance + .get_global(unsafe { &*self.store }, name) + .ok_or_else(|| anyhow!("failed to get global {name}"))?; + let Val::I64(v) = global.get(unsafe { &mut *self.store }) else { + bail!("global {name} is not an i64") + }; -impl sandbox_wasmi::Externals for Externals { - fn invoke_index( - &mut self, - index: usize, - _args: sandbox_wasmi::RuntimeArgs, - ) -> Result, sandbox_wasmi::Trap> { - Err(if index == self.gr_system_break_idx { - sandbox_wasmi::Trap::host(CustomHostError::from("out of gas")) - } else { - TrapCode::Unreachable.into() - }) + Ok(v) } } -impl InstanceAccessGlobal for ModuleRef { - fn set_global(&self, name: &str, value: i64) -> anyhow::Result<()> { - let Some(ExternVal::Global(global)) = self.export_by_name(name) else { - bail!("global '{name}' not found"); - }; +fn config() -> Config { + let register_len = size_of::(); - Ok(global.set(RuntimeValue::I64(value))?) - } + const DEFAULT_MIN_VALUE_STACK_HEIGHT: usize = 1024; + const DEFAULT_MAX_VALUE_STACK_HEIGHT: usize = 1024 * DEFAULT_MIN_VALUE_STACK_HEIGHT; + const DEFAULT_MAX_RECURSION_DEPTH: usize = 16384; - fn get_global(&self, name: &str) -> anyhow::Result { - let Some(ExternVal::Global(global)) = self.export_by_name(name) else { - bail!("global '{name}' not found"); - }; + let mut config = Config::default(); + config.set_stack_limits( + StackLimits::new( + DEFAULT_MIN_VALUE_STACK_HEIGHT / register_len, + DEFAULT_MAX_VALUE_STACK_HEIGHT / register_len, + DEFAULT_MAX_RECURSION_DEPTH, + ) + .expect("infallible"), + ); - let RuntimeValue::I64(v) = global.get() else { - bail!("global is not an i64"); - }; + config +} - Ok(v) - } +fn memory(store: &mut Store<()>) -> anyhow::Result<(Memory, Allocation)> { + let mut alloc = region::alloc(u32::MAX as usize, Protection::READ_WRITE) + .unwrap_or_else(|err| unreachable!("Failed to allocate memory: {err}")); + // # Safety: + // + // `wasmi::Memory::new_static()` requires static lifetime so we convert our buffer to it + // but actual lifetime of the buffer is lifetime of `Store` itself, + // so memory will be deallocated when `Store` is dropped. + // + // Also, according to Rust drop order semantics, `wasmi::Store` will be dropped first and + // only then our allocated memories will be freed to ensure they are not used anymore. + let memref = + unsafe { slice::from_raw_parts_mut::<'static, u8>(alloc.as_mut_ptr(), alloc.len()) }; + let ty = MemoryType::new(INITIAL_PAGES, None).context("failed to create memory type")?; + let memref = Memory::new_static(store, ty, memref).context("failed to create memory")?; + + Ok((memref, alloc)) } pub struct WasmiRunner; impl Runner for WasmiRunner { fn run(module: &Module) -> anyhow::Result { - let wasmi_module = - WasmiModule::from_buffer(module.clone().into_bytes().map_err(anyhow::Error::msg)?) - .context("failed to load wasm")?; - - let memory = MemoryInstance::alloc(Pages(INITIAL_PAGES as usize), None) - .context("failed to allocate memory")?; - - let mem_ptr = memory.direct_access().as_ref().as_ptr() as usize; - let mem_size = memory.direct_access().as_ref().len(); - - let resolver = Resolver { memory }; - let imports = ImportsBuilder::new().with_resolver(MODULE_ENV, &resolver); - - let instance = ModuleInstance::new(&wasmi_module, &imports) + let engine = Engine::new(&config()); + + let wasmi_module = WasmiModule::new( + &engine, + &module.clone().into_bytes().map_err(anyhow::Error::msg)?, + ) + .context("failed to load wasm")?; + + let mut store = Store::new(&engine, ()); + + // NOTE: alloc should be dropped after exit of this function's scope + let (memory, _alloc) = memory(&mut store)?; + let mem_ptr = memory.data_ptr(&store) as usize; + let mem_size = memory.data_size(&store); + + let mut linker: Linker<()> = >::new(&engine); + linker + .func_wrap( + MODULE_ENV, + SyscallName::SystemBreak.to_str(), + |_caller: Caller<()>, _param: i32| -> Result<(), Error> { + Err(Error::host(CustomHostError::from("out of gas"))) + }, + ) + .context("failed to define host function")?; + + linker + .define(MODULE_ENV, "memory", memory) + .context("failed to define memory")?; + + let instance = linker + .instantiate(&mut store, &wasmi_module) .context("failed to instantiate wasm module")? - .assert_no_start(); + .ensure_no_start(&mut store) + .context("failed to ensure no start")?; - instance - .set_global(GLOBAL_NAME_GAS, PROGRAM_GAS) + let gas = instance + .get_global(&store, GLOBAL_NAME_GAS) + .context("failed to get gas")?; + gas.set(&mut store, Val::I64(PROGRAM_GAS)) .context("failed to set gas")?; + let global_accessor = InstanceBundle { + instance, + store: &mut store, + }; + + let init_fn = instance + .get_func(&store, "init") + .context("failed to get export fn")?; + lazy_pages::init_fuzzer_lazy_pages(FuzzerLazyPagesContext { - instance: Box::new(instance.clone()), + instance: Box::new(global_accessor.clone()), memory_range: mem_ptr..(mem_ptr + mem_size), pages: Default::default(), globals_list: globals_list(module), }); - if let Err(error) = instance.invoke_export( - "init", - &[], - &mut Externals { - gr_system_break_idx: 0, - }, - ) { - if let sandbox_wasmi::Error::Trap(Trap::Host(_)) = error { - log::info!("out of gas"); + if let Err(error) = init_fn.call(&mut store, &[], &mut []) { + if let Some(custom_error) = error.downcast_ref::() { + log::info!("{custom_error}"); } else { Err(error)?; } } let result = RunResult { - gas_global: instance.get_global(GLOBAL_NAME_GAS)?, + gas_global: gas.get(&store).i64().context("failed to get gas global")?, pages: lazy_pages::get_touched_pages(), - globals: get_globals(&instance, module).context("failed to get globals")?, + globals: get_globals(&global_accessor, module).context("failed to get globals")?, }; Ok(result) diff --git a/utils/lazy-pages-fuzzer/src/wasmi_backend/error.rs b/utils/lazy-pages-fuzzer/src/wasmi_backend/error.rs index 1369aecaea0..9724dc9aab1 100644 --- a/utils/lazy-pages-fuzzer/src/wasmi_backend/error.rs +++ b/utils/lazy-pages-fuzzer/src/wasmi_backend/error.rs @@ -17,7 +17,7 @@ // along with this program. If not, see . use derive_more::Display; -use sandbox_wasmi::HostError; +use sandbox_wasmi::core::HostError; #[derive(Debug, Display)] #[display(fmt = "{message}")] From 733e6b04742b00910ae730959375d54e2f716229 Mon Sep 17 00:00:00 2001 From: Roman Maslennikov Date: Mon, 30 Sep 2024 16:10:28 +0400 Subject: [PATCH 02/25] Update tests --- .../lazy-pages-fuzzer/src/generate/globals.rs | 34 ++++++------- .../src/generate/mem_accesses.rs | 49 ++++++++----------- 2 files changed, 36 insertions(+), 47 deletions(-) diff --git a/utils/lazy-pages-fuzzer/src/generate/globals.rs b/utils/lazy-pages-fuzzer/src/generate/globals.rs index bd71156ca9e..7eb338c037d 100644 --- a/utils/lazy-pages-fuzzer/src/generate/globals.rs +++ b/utils/lazy-pages-fuzzer/src/generate/globals.rs @@ -182,34 +182,30 @@ mod tests { let module = Module::from_bytes(wasm).unwrap(); let (module, _) = globals.inject(module).unwrap(); - let module = sandbox_wasmi::Module::from_buffer(module.into_bytes().unwrap()).unwrap(); - let instance = - sandbox_wasmi::ModuleInstance::new(&module, &sandbox_wasmi::ImportsBuilder::default()) - .unwrap() - .assert_no_start(); + let engine = sandbox_wasmi::Engine::default(); + let mut store = sandbox_wasmi::Store::new(&engine, ()); - let gear_fuzz_a: i64 = instance - .export_by_name("gear_fuzz_a") - .unwrap() - .as_global() + let module = sandbox_wasmi::Module::new(&engine, &module.into_bytes().unwrap()).unwrap(); + let instance = sandbox_wasmi::Instance::new(&mut store, &module, &[]).unwrap(); + + let gear_fuzz_a = instance + .get_global(&store, "gear_fuzz_a") .unwrap() - .get() - .try_into() + .get(&store) + .i64() .unwrap(); assert_eq!(gear_fuzz_a, INITIAL_GLOBAL_VALUE); - let _ = instance - .invoke_export("main", &[], &mut sandbox_wasmi::NopExternals) + let func = instance.get_func(&store, "main").unwrap(); + func.call(&mut store, &[], &mut [sandbox_wasmi::Val::I64(0)]) .unwrap(); // Assert that global was modified (initially 0) - let gear_fuzz_a: i64 = instance - .export_by_name("gear_fuzz_a") - .unwrap() - .as_global() + let gear_fuzz_a = instance + .get_global(&store, "gear_fuzz_a") .unwrap() - .get() - .try_into() + .get(&store) + .i64() .unwrap(); assert_eq!(gear_fuzz_a, EXPECTED_GLOBAL_VALUE); } diff --git a/utils/lazy-pages-fuzzer/src/generate/mem_accesses.rs b/utils/lazy-pages-fuzzer/src/generate/mem_accesses.rs index aafef772fd2..078138b4417 100644 --- a/utils/lazy-pages-fuzzer/src/generate/mem_accesses.rs +++ b/utils/lazy-pages-fuzzer/src/generate/mem_accesses.rs @@ -151,6 +151,8 @@ impl<'u> InjectMemoryAccesses<'u> { mod tests { use std::hash::{DefaultHasher, Hash, Hasher}; + use crate::MODULE_ENV; + use super::*; const TEST_PROGRAM_WAT: &str = r#" @@ -162,20 +164,6 @@ mod tests { ) "#; - struct Resolver { - memory: sandbox_wasmi::MemoryRef, - } - - impl sandbox_wasmi::ModuleImportResolver for Resolver { - fn resolve_memory( - &self, - _field_name: &str, - _memory_type: &sandbox_wasmi::MemoryDescriptor, - ) -> Result { - Ok(self.memory.clone()) - } - } - fn calculate_slice_hash(slice: &[u8]) -> u64 { let mut s = DefaultHasher::new(); for b in slice { @@ -198,29 +186,34 @@ mod tests { .inject(module) .unwrap(); - let memory = - sandbox_wasmi::MemoryInstance::alloc(sandbox_wasmi::memory_units::Pages(1), None) - .unwrap(); + let engine = sandbox_wasmi::Engine::default(); + let mut store = sandbox_wasmi::Store::new(&engine, ()); + let module = sandbox_wasmi::Module::new(&engine, &module.into_bytes().unwrap()).unwrap(); + + let ty = sandbox_wasmi::MemoryType::new(1, None).unwrap(); + let memory = sandbox_wasmi::Memory::new(&mut store, ty).unwrap(); let original_mem_hash = { - let mem_slice = memory.direct_access(); - calculate_slice_hash(mem_slice.as_ref()) + let mem_slice = memory.data(&store); + calculate_slice_hash(mem_slice) }; - let resolver = Resolver { memory }; - let imports = sandbox_wasmi::ImportsBuilder::new().with_resolver("env", &resolver); + let mut linker = >::new(&engine); + linker.define(MODULE_ENV, "memory", memory).unwrap(); - let module = sandbox_wasmi::Module::from_buffer(module.into_bytes().unwrap()).unwrap(); - let instance = sandbox_wasmi::ModuleInstance::new(&module, &imports) + let instance = linker + .instantiate(&mut store, &module) .unwrap() - .assert_no_start(); - let _ = instance - .invoke_export("main", &[], &mut sandbox_wasmi::NopExternals) + .ensure_no_start(&mut store) + .unwrap(); + let func = instance.get_func(&store, "main").unwrap(); + + func.call(&mut store, &[], &mut [sandbox_wasmi::Val::I32(0)]) .unwrap(); let mem_hash = { - let mem_slice = resolver.memory.direct_access(); - calculate_slice_hash(mem_slice.as_ref()) + let mem_slice = memory.data(&store); + calculate_slice_hash(mem_slice) }; assert_ne!(original_mem_hash, mem_hash); From 4192e81ca71bd0418a40fc84463b0b580d189a1b Mon Sep 17 00:00:00 2001 From: Roman Maslennikov Date: Mon, 30 Sep 2024 18:50:46 +0400 Subject: [PATCH 03/25] Generic refcellstore --- sandbox/host/src/sandbox/wasmer_backend.rs | 2 +- .../sandbox/wasmer_backend/store_refcell.rs | 87 ++++++++++--------- 2 files changed, 47 insertions(+), 42 deletions(-) diff --git a/sandbox/host/src/sandbox/wasmer_backend.rs b/sandbox/host/src/sandbox/wasmer_backend.rs index a4431947832..cffbb936f69 100644 --- a/sandbox/host/src/sandbox/wasmer_backend.rs +++ b/sandbox/host/src/sandbox/wasmer_backend.rs @@ -35,7 +35,7 @@ use crate::{ util::MemoryTransfer, }; -pub use store_refcell::StoreRefCell; +pub type StoreRefCell = store_refcell::StoreRefCell; mod store_refcell; #[cfg(feature = "wasmer-cache")] diff --git a/sandbox/host/src/sandbox/wasmer_backend/store_refcell.rs b/sandbox/host/src/sandbox/wasmer_backend/store_refcell.rs index 548ec2de725..1f2f86abc4b 100644 --- a/sandbox/host/src/sandbox/wasmer_backend/store_refcell.rs +++ b/sandbox/host/src/sandbox/wasmer_backend/store_refcell.rs @@ -92,7 +92,7 @@ use std::{ }; use defer::defer; -use wasmer::{AsStoreMut, AsStoreRef, Store, StoreRef}; +use wasmer::StoreMut; #[derive(Debug, Clone, Copy)] enum BorrowState { @@ -104,17 +104,21 @@ enum BorrowState { /// Custom implementation of `RefCell` which allows to safely borrow store /// mutably/immutably second time inside the scope. #[derive(Debug)] -pub struct StoreRefCell { - store: UnsafeCell, +pub struct StoreRefCell { + store: UnsafeCell, state: Cell, } +trait GenericAsStoreMut {} + +impl<'r, 's> GenericAsStoreMut for &'r mut StoreMut<'s> {} + #[derive(Debug)] pub struct BorrowScopeError; -impl StoreRefCell { +impl StoreRefCell { /// Create new `StoreRefCell` with provided `Store` - pub fn new(store: Store) -> Self { + pub fn new(store: S) -> Self { Self { store: UnsafeCell::new(store), state: Cell::new(BorrowState::NonShared), @@ -123,7 +127,7 @@ impl StoreRefCell { /// Borrow store immutably, same semantics as `RefCell::borrow` #[track_caller] - pub fn borrow(&self) -> Ref<'_> { + pub fn borrow(&self) -> Ref<'_, S> { match self.state.get() { BorrowState::Shared(n) => { self.state.set(BorrowState::Shared( @@ -148,7 +152,7 @@ impl StoreRefCell { /// Borrow store mutably, same semantics as `RefCell::borrow_mut` #[track_caller] - pub fn borrow_mut(&self) -> RefMut<'_> { + pub fn borrow_mut(&self) -> RefMut<'_, S> { match self.state.get() { BorrowState::NonShared => { self.state.set(BorrowState::Mutable); @@ -165,16 +169,17 @@ impl StoreRefCell { } /// Provide borrow scope where store can be borrowed mutably second time safely (or borrowed immutably multiple times). + #[allow(private_bounds)] pub fn borrow_scope R>( &self, - store: impl AsStoreMut, + store: impl GenericAsStoreMut, f: F, ) -> Result { // We expect the same store - debug_assert!( - self.compare_stores(store.as_store_ref()), - "stores are different" - ); + //debug_assert!( + // self.compare_stores(store.as_store_ref()), + // "stores are different" + //); // Caller just returned borrowed mutably reference to the store, now we can safely borrow it mutably again let _store = store; @@ -197,29 +202,29 @@ impl StoreRefCell { Ok(result) } - #[allow(unused)] - fn compare_stores(&self, returned_store: StoreRef) -> bool { - // SAFETY: - // Verified with Miri, it seems safe. - // Carefully compare the stores while don't using/holding mutable references to them in the same time. - let orig_store_ref: StoreRef = unsafe { &*self.store.get() }.as_store_ref(); + //#[allow(unused)] + //fn compare_stores(&self, returned_store: StoreRef) -> bool { + // // SAFETY: + // // Verified with Miri, it seems safe. + // // Carefully compare the stores while don't using/holding mutable references to them in the same time. + // let orig_store_ref: StoreRef = unsafe { &*self.store.get() }.as_store_ref(); - StoreRef::same(&orig_store_ref, &returned_store) - } + // StoreRef::same(&orig_store_ref, &returned_store) + //} /// Returns store ptr, same semantics as `RefCell::as_ptr` - pub unsafe fn as_ptr(&self) -> *mut Store { + pub unsafe fn as_ptr(&self) -> *mut S { self.store.get() } } -pub struct Ref<'b> { - store: NonNull, +pub struct Ref<'b, S> { + store: NonNull, state: &'b Cell, } -impl Deref for Ref<'_> { - type Target = Store; +impl Deref for Ref<'_, S> { + type Target = S; #[inline] fn deref(&self) -> &Self::Target { @@ -228,7 +233,7 @@ impl Deref for Ref<'_> { } } -impl Drop for Ref<'_> { +impl Drop for Ref<'_, S> { fn drop(&mut self) { match self.state.get() { BorrowState::Shared(n) if n.get() == 1 => { @@ -244,13 +249,13 @@ impl Drop for Ref<'_> { } } -pub struct RefMut<'b> { - store: NonNull, +pub struct RefMut<'b, S> { + store: NonNull, state: &'b Cell, } -impl<'a> Deref for RefMut<'a> { - type Target = Store; +impl<'a, S> Deref for RefMut<'a, S> { + type Target = S; #[inline] fn deref(&self) -> &Self::Target { @@ -259,7 +264,7 @@ impl<'a> Deref for RefMut<'a> { } } -impl DerefMut for RefMut<'_> { +impl DerefMut for RefMut<'_, S> { #[inline] fn deref_mut(&mut self) -> &mut Self::Target { // SAFETY: we ensure that store isn't borrowed before @@ -267,7 +272,7 @@ impl DerefMut for RefMut<'_> { } } -impl Drop for RefMut<'_> { +impl Drop for RefMut<'_, S> { fn drop(&mut self) { match self.state.get() { BorrowState::Mutable => { @@ -280,15 +285,15 @@ impl Drop for RefMut<'_> { #[cfg(test)] mod tests { + use super::*; use std::rc::Rc; - use wasmer::StoreMut; - - use super::*; + struct Store; + impl<'r> GenericAsStoreMut for &'r mut Store {} #[test] fn test_store_refcell_borrow() { - let store = Store::default(); + let store = Store; let store_refcell = StoreRefCell::new(store); { @@ -307,19 +312,19 @@ mod tests { #[test] fn test_store_refcell_borrow_scope() { struct Env { - store: Rc, + store: Rc>, } - let store = Store::default(); + let store = Store; let rc = Rc::new(StoreRefCell::new(store)); let env = Env { store: rc.clone() }; - let callback = |env: Env, mut storemut: StoreMut| { + let callback = |env: Env, storemut: &mut Store| { // do something with `storemut` // .. let rc = rc.clone(); - let _ = env.store.borrow_scope(&mut storemut, move || { + let _ = env.store.borrow_scope(storemut, move || { // Callback is called and it allowed to borrow store mutably/immutably { let _borrow = rc.borrow_mut(); @@ -339,6 +344,6 @@ mod tests { }; let mut borrow = rc.borrow_mut(); - callback(env, borrow.as_store_mut()) + callback(env, &mut borrow) } } From d94a1bc9600779426a72272430ca5d3fe2ac8ff5 Mon Sep 17 00:00:00 2001 From: Roman Maslennikov Date: Tue, 1 Oct 2024 18:32:28 +0400 Subject: [PATCH 04/25] WIP --- Cargo.lock | 3 +- sandbox/host/Cargo.toml | 3 +- sandbox/host/src/error.rs | 2 +- sandbox/host/src/lib.rs | 1 + sandbox/host/src/sandbox.rs | 50 +- sandbox/host/src/sandbox/wasmer_backend.rs | 2 +- sandbox/host/src/sandbox/wasmi_backend.rs | 452 ++++++------------ sandbox/host/src/sandbox/wasmi_backend_old.rs | 410 ++++++++++++++++ sandbox/host/src/store_refcell.rs | 348 ++++++++++++++ 9 files changed, 953 insertions(+), 318 deletions(-) create mode 100644 sandbox/host/src/sandbox/wasmi_backend_old.rs create mode 100644 sandbox/host/src/store_refcell.rs diff --git a/Cargo.lock b/Cargo.lock index 930eb320c5d..3d836535967 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6886,6 +6886,7 @@ dependencies = [ "gear-sandbox-env", "log", "parity-scale-codec", + "region", "sp-allocator", "sp-wasm-interface-common", "tempfile", @@ -6894,7 +6895,7 @@ dependencies = [ "wasmer", "wasmer-cache", "wasmer-types", - "wasmi 0.13.2", + "wasmi 0.36.2", ] [[package]] diff --git a/sandbox/host/Cargo.toml b/sandbox/host/Cargo.toml index 842303c3704..207a5b4970d 100644 --- a/sandbox/host/Cargo.toml +++ b/sandbox/host/Cargo.toml @@ -22,13 +22,14 @@ thiserror.workspace = true log = { workspace = true, features = ["std"] } wasmer = { workspace = true, features = ["singlepass"] } wasmer-types.workspace = true -sandbox-wasmi.workspace = true +sandbox-wasmi = { package = "wasmi", version = "0.36"} sp-allocator = { workspace = true, features = ["std"] } sp-wasm-interface-common = { workspace = true, features = ["std"] } gear-sandbox-env = { workspace = true, features = ["std"] } wasmer-cache = { workspace = true, optional = true } tempfile.workspace = true uluru = { workspace = true, optional = true } +region.workspace = true [features] default = ["wasmer-cache", "uluru"] diff --git a/sandbox/host/src/error.rs b/sandbox/host/src/error.rs index 28d1945e88e..f80ac87355d 100644 --- a/sandbox/host/src/error.rs +++ b/sandbox/host/src/error.rs @@ -107,7 +107,7 @@ pub enum Error { AbortedDueToTrap(MessageWithBacktrace), } -impl sandbox_wasmi::HostError for Error {} +impl sandbox_wasmi::core::HostError for Error {} impl From<&'static str> for Error { fn from(err: &'static str) -> Error { diff --git a/sandbox/host/src/lib.rs b/sandbox/host/src/lib.rs index 24997b5e6f2..b0388817754 100644 --- a/sandbox/host/src/lib.rs +++ b/sandbox/host/src/lib.rs @@ -23,6 +23,7 @@ pub mod error; pub mod sandbox; +pub(crate) mod store_refcell; pub mod util; use log as _; diff --git a/sandbox/host/src/sandbox.rs b/sandbox/host/src/sandbox.rs index 2efdc0093de..217e0ce16ae 100644 --- a/sandbox/host/src/sandbox.rs +++ b/sandbox/host/src/sandbox.rs @@ -44,8 +44,8 @@ use self::{ }, wasmi_backend::{ get_global as wasmi_get_global, instantiate as wasmi_instantiate, invoke as wasmi_invoke, - new_memory as wasmi_new_memory, set_global as wasmi_set_global, - MemoryWrapper as WasmiMemoryWrapper, + new_memory as wasmi_new_memory, set_global as wasmi_set_global, Backend as WasmiBackend, + MemoryWrapper as WasmiMemoryWrapper, StoreRefCell as WasmiStoreRefCell, }, }; @@ -177,7 +177,12 @@ pub struct GuestExternals<'a> { /// Module instance in terms of selected backend enum BackendInstanceBundle { /// Wasmi module instance - Wasmi(sandbox_wasmi::ModuleRef), + Wasmi { + /// Wasmer module instance + instance: sandbox_wasmi::Instance, + /// Wasmer store + store: Rc, + }, /// Wasmer module instance and store Wasmer { @@ -219,8 +224,8 @@ impl SandboxInstance { supervisor_context: &mut dyn SupervisorContext, ) -> std::result::Result, error::Error> { match &self.backend_instance { - BackendInstanceBundle::Wasmi(wasmi_instance) => { - wasmi_invoke(self, wasmi_instance, export_name, args, supervisor_context) + BackendInstanceBundle::Wasmi { instance, store } => { + wasmi_invoke(instance, store, export_name, args, supervisor_context) } BackendInstanceBundle::Wasmer { instance, store } => { @@ -234,7 +239,9 @@ impl SandboxInstance { /// Returns `Some(_)` if the global could be found. pub fn get_global_val(&self, name: &str) -> Option { match &self.backend_instance { - BackendInstanceBundle::Wasmi(wasmi_instance) => wasmi_get_global(wasmi_instance, name), + BackendInstanceBundle::Wasmi { instance, store } => { + wasmi_get_global(instance, &store.borrow(), name) + } BackendInstanceBundle::Wasmer { instance, store } => { wasmer_get_global(instance, &mut store.borrow_mut(), name) @@ -251,8 +258,8 @@ impl SandboxInstance { value: Value, ) -> std::result::Result, error::Error> { match &self.backend_instance { - BackendInstanceBundle::Wasmi(wasmi_instance) => { - wasmi_set_global(wasmi_instance, name, value) + BackendInstanceBundle::Wasmi { instance, store } => { + wasmi_set_global(instance, &mut store.borrow_mut(), name, value) } BackendInstanceBundle::Wasmer { instance, store } => { @@ -270,7 +277,9 @@ impl SandboxInstance { /// Expected to be called only from signal handler. pub unsafe fn signal_handler_get_global_val(&self, name: &str) -> Option { match &self.backend_instance { - BackendInstanceBundle::Wasmi(wasmi_instance) => wasmi_get_global(wasmi_instance, name), + BackendInstanceBundle::Wasmi { instance, store } => unsafe { + wasmi_get_global(instance, &*store.as_ptr(), name) + }, BackendInstanceBundle::Wasmer { instance, store } => unsafe { // We cannot use `store.borrow_mut()` in signal handler context because it's already borrowed during `invoke` call. @@ -292,9 +301,10 @@ impl SandboxInstance { value: Value, ) -> Result> { match &self.backend_instance { - BackendInstanceBundle::Wasmi(wasmi_instance) => { - wasmi_set_global(wasmi_instance, name, value) - } + BackendInstanceBundle::Wasmi { instance, store } => unsafe { + // We cannot use `store.borrow_mut()` in signal handler context because it's already borrowed during `invoke` call. + wasmi_set_global(instance, &mut *store.as_ptr(), name, value) + }, BackendInstanceBundle::Wasmer { instance, store } => unsafe { // We cannot use `store.borrow_mut()` in signal handler context because it's already borrowed during `invoke` call. @@ -497,7 +507,7 @@ impl util::MemoryTransfer for Memory { /// Information specific to a particular execution backend enum BackendContext { /// Wasmi specific context - Wasmi, + Wasmi(WasmiBackend), /// Wasmer specific context Wasmer(WasmerBackend), @@ -506,7 +516,7 @@ enum BackendContext { impl BackendContext { pub fn new(backend: SandboxBackend) -> BackendContext { match backend { - SandboxBackend::Wasmi => BackendContext::Wasmi, + SandboxBackend::Wasmi => BackendContext::Wasmi(WasmiBackend::new()), SandboxBackend::Wasmer => BackendContext::Wasmer(WasmerBackend::new()), } @@ -550,7 +560,9 @@ impl SandboxComponents
{ self.memories.clear(); match self.backend_context { - BackendContext::Wasmi => (), + BackendContext::Wasmi(_) => { + self.backend_context = BackendContext::Wasmi(WasmiBackend::new()); + } BackendContext::Wasmer(_) => { self.backend_context = BackendContext::Wasmer(WasmerBackend::new()); } @@ -573,7 +585,9 @@ impl SandboxComponents
{ }; let memory = match &backend_context { - BackendContext::Wasmi => wasmi_new_memory(initial, maximum)?, + BackendContext::Wasmi(backend) => { + wasmi_new_memory(backend.store().clone(), initial, maximum)? + } BackendContext::Wasmer(backend) => { wasmer_new_memory(backend.store().clone(), initial, maximum)? @@ -684,7 +698,9 @@ impl SandboxComponents
{ supervisor_context: &mut dyn SupervisorContext, ) -> std::result::Result { let sandbox_instance = match self.backend_context { - BackendContext::Wasmi => wasmi_instantiate(wasm, guest_env, supervisor_context)?, + BackendContext::Wasmi(ref context) => { + wasmi_instantiate(version, context, wasm, guest_env, supervisor_context)? + } BackendContext::Wasmer(ref context) => { wasmer_instantiate(version, context, wasm, guest_env, supervisor_context)? diff --git a/sandbox/host/src/sandbox/wasmer_backend.rs b/sandbox/host/src/sandbox/wasmer_backend.rs index cffbb936f69..ee49ddffc50 100644 --- a/sandbox/host/src/sandbox/wasmer_backend.rs +++ b/sandbox/host/src/sandbox/wasmer_backend.rs @@ -32,11 +32,11 @@ use crate::{ BackendInstanceBundle, GuestEnvironment, InstantiationError, Memory, SandboxInstance, SupervisorContext, SupervisorFuncIndex, }, + store_refcell, util::MemoryTransfer, }; pub type StoreRefCell = store_refcell::StoreRefCell; -mod store_refcell; #[cfg(feature = "wasmer-cache")] mod cache; diff --git a/sandbox/host/src/sandbox/wasmi_backend.rs b/sandbox/host/src/sandbox/wasmi_backend.rs index eccab3efef2..aabdd0bfc3e 100644 --- a/sandbox/host/src/sandbox/wasmi_backend.rs +++ b/sandbox/host/src/sandbox/wasmi_backend.rs @@ -18,14 +18,17 @@ //! Wasmi specific impls for sandbox -use std::fmt; +use std::{fmt, rc::Rc, slice}; use codec::{Decode, Encode}; -use gear_sandbox_env::HostError; +use gear_sandbox_env::{HostError, Instantiate}; +use region::{Allocation, Protection}; use sandbox_wasmi::{ - memory_units::Pages, ImportResolver, MemoryInstance, Module, ModuleInstance, RuntimeArgs, - RuntimeValue, Trap, TrapCode, + core::{Pages, Trap, UntypedVal}, + Config, Engine, ExternType, Linker, MemoryType, Module, StackLimits, StoreContext, + StoreContextMut, }; + use sp_wasm_interface_common::{util, Pointer, ReturnValue, Value, WordSize}; use crate::{ @@ -34,9 +37,13 @@ use crate::{ BackendInstanceBundle, GuestEnvironment, GuestExternals, GuestFuncIndex, Imports, InstantiationError, Memory, SandboxInstance, SupervisorContext, }, + store_refcell, util::MemoryTransfer, }; +type Store = sandbox_wasmi::Store<()>; +pub type StoreRefCell = store_refcell::StoreRefCell; + environmental::environmental!(SupervisorContextStore: trait SupervisorContext); #[derive(Debug)] @@ -48,363 +55,214 @@ impl fmt::Display for CustomHostError { } } -impl sandbox_wasmi::HostError for CustomHostError {} +impl sandbox_wasmi::core::HostError for CustomHostError {} /// Construct trap error from specified message -fn trap(msg: &'static str) -> Trap { - Trap::host(CustomHostError(msg.into())) +//fn trap(msg: &'static str) -> Trap { +// Trap::host(CustomHostError(msg.into())) +//} + +fn into_wasmi_val(value: Value) -> sandbox_wasmi::Val { + match value { + Value::I32(val) => sandbox_wasmi::Val::I32(val), + Value::I64(val) => sandbox_wasmi::Val::I64(val), + Value::F32(val) => sandbox_wasmi::Val::F32(val.into()), + Value::F64(val) => sandbox_wasmi::Val::F64(val.into()), + } } -impl ImportResolver for Imports { - fn resolve_func( - &self, - module_name: &str, - field_name: &str, - signature: &sandbox_wasmi::Signature, - ) -> std::result::Result { - let idx = self.func_by_name(module_name, field_name).ok_or_else(|| { - sandbox_wasmi::Error::Instantiation(format!( - "Export {}:{} not found", - module_name, field_name - )) - })?; - - Ok(sandbox_wasmi::FuncInstance::alloc_host( - signature.clone(), - idx.0, - )) +fn into_wasmi_result(value: ReturnValue) -> Vec { + match value { + ReturnValue::Value(v) => vec![into_wasmi_val(v)], + ReturnValue::Unit => vec![], } +} - fn resolve_memory( - &self, - module_name: &str, - field_name: &str, - _memory_type: &sandbox_wasmi::MemoryDescriptor, - ) -> std::result::Result { - let mem = self - .memory_by_name(module_name, field_name) - .ok_or_else(|| { - sandbox_wasmi::Error::Instantiation(format!( - "Export {}:{} not found", - module_name, field_name - )) - })?; - - let wrapper = mem.as_wasmi().ok_or_else(|| { - sandbox_wasmi::Error::Instantiation(format!( - "Unsupported non-wasmi export {}:{}", - module_name, field_name - )) - })?; - - // Here we use inner memory reference only to resolve the imports - // without accessing the memory contents. All subsequent memory accesses - // should happen through the wrapper, that enforces the memory access protocol. - let mem = wrapper.0; - - Ok(mem) +fn into_value(value: sandbox_wasmi::Val) -> Option { + match value { + sandbox_wasmi::Val::I32(val) => Some(Value::I32(val)), + sandbox_wasmi::Val::I64(val) => Some(Value::I64(val)), + sandbox_wasmi::Val::F32(val) => Some(Value::F32(val.into())), + sandbox_wasmi::Val::F64(val) => Some(Value::F64(val.into())), + _ => None, } +} + +/// Wasmi specific context +pub struct Backend { + store: Rc, +} - fn resolve_global( - &self, - module_name: &str, - field_name: &str, - _global_type: &sandbox_wasmi::GlobalDescriptor, - ) -> std::result::Result { - Err(sandbox_wasmi::Error::Instantiation(format!( - "Export {}:{} not found", - module_name, field_name - ))) +impl Default for Backend { + fn default() -> Self { + Self::new() + } +} + +impl Backend { + pub fn new() -> Self { + let register_len = size_of::(); + + const DEFAULT_MIN_VALUE_STACK_HEIGHT: usize = 1024; + const DEFAULT_MAX_VALUE_STACK_HEIGHT: usize = 1024 * DEFAULT_MIN_VALUE_STACK_HEIGHT; + const DEFAULT_MAX_RECURSION_DEPTH: usize = 16384; + + let mut config = Config::default(); + config.set_stack_limits( + StackLimits::new( + DEFAULT_MIN_VALUE_STACK_HEIGHT / register_len, + DEFAULT_MAX_VALUE_STACK_HEIGHT / register_len, + DEFAULT_MAX_RECURSION_DEPTH, + ) + .expect("infallible"), + ); + + let engine = Engine::new(&config); + let store = Store::new(&engine, ()); + Backend { + store: Rc::new(StoreRefCell::new(Store::new(&engine, ()))), + } } - fn resolve_table( - &self, - module_name: &str, - field_name: &str, - _table_type: &sandbox_wasmi::TableDescriptor, - ) -> std::result::Result { - Err(sandbox_wasmi::Error::Instantiation(format!( - "Export {}:{} not found", - module_name, field_name - ))) + pub fn store(&self) -> &Rc { + &self.store } } /// Allocate new memory region -pub fn new_memory(initial: u32, maximum: Option) -> crate::error::Result { - let memory = Memory::Wasmi(MemoryWrapper::new( - MemoryInstance::alloc(Pages(initial as usize), maximum.map(|m| Pages(m as usize))) - .map_err(|error| Error::Sandbox(error.to_string()))?, - )); - - Ok(memory) +pub fn new_memory( + store: Rc, + initial: u32, + maximum: Option, +) -> crate::error::Result { + let ty = + MemoryType::new(initial, maximum).map_err(|error| Error::Sandbox(error.to_string()))?; + let mut alloc = region::alloc(u32::MAX as usize, Protection::READ_WRITE) + .unwrap_or_else(|err| unreachable!("Failed to allocate memory: {err}")); + // # Safety: + // + // `wasmi::Memory::new_static()` requires static lifetime so we convert our buffer to it + // but actual lifetime of the buffer is lifetime of `Store` itself, + // so memory will be deallocated when `Store` is dropped. + // + // Also, according to Rust drop order semantics, `wasmi::Store` will be dropped first and + // only then our allocated memories will be freed to ensure they are not used anymore. + let raw = unsafe { slice::from_raw_parts_mut::<'static, u8>(alloc.as_mut_ptr(), alloc.len()) }; + let memory = sandbox_wasmi::Memory::new_static(&mut *store.borrow_mut(), ty, raw) + .map_err(|error| Error::Sandbox(error.to_string()))?; + + Ok(Memory::Wasmi(MemoryWrapper::new(memory, store, alloc))) } /// Wasmi provides direct access to its memory using slices. /// /// This wrapper limits the scope where the slice can be taken to #[derive(Debug, Clone)] -pub struct MemoryWrapper(sandbox_wasmi::MemoryRef); +pub struct MemoryWrapper { + memory: sandbox_wasmi::Memory, + store: Rc, + //alloc: Allocation, +} impl MemoryWrapper { /// Take ownership of the memory region and return a wrapper object - fn new(memory: sandbox_wasmi::MemoryRef) -> Self { - Self(memory) + fn new(memory: sandbox_wasmi::Memory, store: Rc, alloc: Allocation) -> Self { + Self { + memory, + store, + //alloc, + } } } impl MemoryTransfer for MemoryWrapper { fn read(&self, source_addr: Pointer, size: usize) -> error::Result> { - self.0.with_direct_access(|source| { - let range = util::checked_range(source_addr.into(), size, source.len()) - .ok_or_else(|| error::Error::Other("memory read is out of bounds".into()))?; + let mut buffer = Vec::with_capacity(size); + let ctx = self.store.borrow(); + self.memory + .read(&*ctx, source_addr.into(), &mut buffer) + .map_err(|_| error::Error::Other("memory read is out of bounds".into())); - Ok(Vec::from(&source[range])) - }) + Ok(buffer) } fn read_into(&self, source_addr: Pointer, destination: &mut [u8]) -> error::Result<()> { - self.0.with_direct_access(|source| { - let range = util::checked_range(source_addr.into(), destination.len(), source.len()) - .ok_or_else(|| error::Error::Other("memory read is out of bounds".into()))?; + let ctx = self.store.borrow(); + self.memory + .read(&*ctx, source_addr.into(), destination) + .map_err(|_| error::Error::Other("memory read is out of bounds".into())); - destination.copy_from_slice(&source[range]); - Ok(()) - }) + Ok(()) } fn write_from(&self, dest_addr: Pointer, source: &[u8]) -> error::Result<()> { - self.0.with_direct_access_mut(|destination| { - let range = util::checked_range(dest_addr.into(), source.len(), destination.len()) - .ok_or_else(|| error::Error::Other("memory write is out of bounds".into()))?; + let mut ctx = self.store.borrow_mut(); + self.memory + .write(&mut *ctx, dest_addr.into(), source) + .map_err(|_| error::Error::Other("memory write is out of bounds".into())); - destination[range].copy_from_slice(source); - Ok(()) - }) + Ok(()) } fn memory_grow(&mut self, pages: u32) -> error::Result { - self.0 - .grow(Pages(pages as usize)) - .map_err(|e| { - Error::Sandbox(format!( - "Cannot grow memory in masmi sandbox executor: {}", - e - )) - }) - .map(|p| p.0 as u32) + let mut ctx = self.store.borrow_mut(); + self.memory.grow(&mut *ctx, pages).map_err(|e| { + Error::Sandbox(format!("Cannot grow memory in wasmi sandbox executor: {e}",)) + }) } fn memory_size(&mut self) -> u32 { - self.0.current_size().0 as u32 + let ctx = self.store.borrow(); + self.memory.size(&*ctx) } fn get_buff(&mut self) -> *mut u8 { - self.0.direct_access_mut().as_mut().as_mut_ptr() + let ctx = self.store.borrow_mut(); + self.memory.data_ptr(&*ctx) } } -impl<'a> sandbox_wasmi::Externals for GuestExternals<'a> { - fn invoke_index( - &mut self, - index: usize, - args: RuntimeArgs, - ) -> std::result::Result, Trap> { - SupervisorContextStore::with(|supervisor_context| { - // Make `index` typesafe again. - let index = GuestFuncIndex(index); - - // Convert function index from guest to supervisor space - let func_idx = self.sandbox_instance - .guest_to_supervisor_mapping - .func_by_guest_index(index) - .expect( - "`invoke_index` is called with indexes registered via `FuncInstance::alloc_host`; - `FuncInstance::alloc_host` is called with indexes that were obtained from `guest_to_supervisor_mapping`; - `func_by_guest_index` called with `index` can't return `None`; - qed" - ); - - // Serialize arguments into a byte vector. - let invoke_args_data: Vec = args - .as_ref() - .iter() - .cloned() - .map(Value::from) - .collect::>() - .encode(); - - // Move serialized arguments inside the memory, invoke dispatch thunk and - // then free allocated memory. - let invoke_args_len = invoke_args_data.len() as WordSize; - let invoke_args_ptr = supervisor_context - .allocate_memory(invoke_args_len) - .map_err(|_| trap("Can't allocate memory in supervisor for the arguments"))?; - - let deallocate = |supervisor_context: &mut dyn SupervisorContext, ptr, fail_msg| { - supervisor_context.deallocate_memory(ptr).map_err(|_| trap(fail_msg)) - }; - - if supervisor_context - .write_memory(invoke_args_ptr, &invoke_args_data) - .is_err() - { - deallocate( - supervisor_context, - invoke_args_ptr, - "Failed deallocation after failed write of invoke arguments", - )?; - return Err(trap("Can't write invoke args into memory")) - } - - let result = supervisor_context.invoke( - invoke_args_ptr, - invoke_args_len, - func_idx, - ); - - deallocate( - supervisor_context, - invoke_args_ptr, - "Can't deallocate memory for dispatch thunk's invoke arguments", - )?; - let result = result?; - - // dispatch_thunk returns pointer to serialized arguments. - // Unpack pointer and len of the serialized result data. - let (serialized_result_val_ptr, serialized_result_val_len) = { - // Cast to u64 to use zero-extension. - let v = result as u64; - let ptr = (v >> 32) as u32; - let len = (v & 0xFFFFFFFF) as u32; - (Pointer::new(ptr), len) - }; - - let serialized_result_val = supervisor_context - .read_memory(serialized_result_val_ptr, serialized_result_val_len) - .map_err(|_| trap("Can't read the serialized result from dispatch thunk")); - - deallocate( - supervisor_context, - serialized_result_val_ptr, - "Can't deallocate memory for dispatch thunk's result", - ) - .and(serialized_result_val) - .and_then(|serialized_result_val| { - let result_val = std::result::Result::::decode(&mut serialized_result_val.as_slice()) - .map_err(|_| trap("Decoding Result failed!"))?; - - match result_val { - Ok(return_value) => Ok(match return_value { - ReturnValue::Unit => None, - ReturnValue::Value(typed_value) => Some(From::from(typed_value)), - }), - Err(HostError) => Err(trap("Supervisor function returned sandbox::HostError")), - } - }) - }).expect("SandboxContextStore is set when invoking sandboxed functions; qed") - } +/// Get global value by name +pub fn get_global(instance: &sandbox_wasmi::Instance, store: &Store, name: &str) -> Option { + into_value(instance.get_global(store, name)?.get(store)) } -fn with_guest_externals(sandbox_instance: &SandboxInstance, f: F) -> R -where - F: FnOnce(&mut GuestExternals) -> R, -{ - f(&mut GuestExternals { sandbox_instance }) +/// Set global value by name +pub fn set_global( + instance: &sandbox_wasmi::Instance, + store: &mut Store, + name: &str, + value: Value, +) -> std::result::Result, error::Error> { + let global = match instance.get_global(&*store, name) { + Some(e) => e, + None => return Ok(None), + }; + + global + .set(store, into_wasmi_val(value)) + .map(Some) + .map_err(|e| Error::Sandbox(e.to_string())) } /// Instantiate a module within a sandbox context pub fn instantiate( + version: Instantiate, + context: &Backend, wasm: &[u8], guest_env: GuestEnvironment, supervisor_context: &mut dyn SupervisorContext, ) -> std::result::Result { - let wasmi_module = Module::from_buffer(wasm).map_err(|_| InstantiationError::ModuleDecoding)?; - let wasmi_instance = ModuleInstance::new(&wasmi_module, &guest_env.imports) - .map_err(|_| InstantiationError::Instantiation)?; - - let sandbox_instance = SandboxInstance { - // In general, it's not a very good idea to use `.not_started_instance()` for - // anything but for extracting memory and tables. But in this particular case, we - // are extracting for the purpose of running `start` function which should be ok. - backend_instance: BackendInstanceBundle::Wasmi( - wasmi_instance.not_started_instance().clone(), - ), - guest_to_supervisor_mapping: guest_env.guest_to_supervisor_mapping, - }; - - with_guest_externals(&sandbox_instance, |guest_externals| { - SupervisorContextStore::using(supervisor_context, || { - wasmi_instance - .run_start(guest_externals) - .map_err(|_| InstantiationError::StartTrapped) - }) - })?; - - Ok(sandbox_instance) + todo!() } /// Invoke a function within a sandboxed module pub fn invoke( - instance: &SandboxInstance, - module: &sandbox_wasmi::ModuleRef, + instance: &sandbox_wasmi::Instance, + store: &Rc, export_name: &str, args: &[Value], supervisor_context: &mut dyn SupervisorContext, -) -> std::result::Result, error::Error> { - with_guest_externals(instance, |guest_externals| { - SupervisorContextStore::using(supervisor_context, || { - let args = args.iter().cloned().map(From::from).collect::>(); - - module - .invoke_export(export_name, &args, guest_externals) - .map(|result| result.map(Into::into)) - .map_err(|error| { - if matches!(error, sandbox_wasmi::Error::Trap(Trap::Code(TrapCode::StackOverflow))) { - // Panic stops process queue execution in that case. - // This allows to avoid error lead to consensus failures, that must be handled - // in node binaries forever. If this panic occur, then we must increase stack memory size, - // or tune stack limit injection. - // see also https://github.com/wasmerio/wasmer/issues/4181 - let err_msg = format!( - "invoke: Suppose that this can not happen, because we have a stack limit instrumentation in programs. \ - Export name - {export_name}, args - {args:?}", - ); - - log::error!("{err_msg}"); - unreachable!("{err_msg}") - } - error::Error::Sandbox(error.to_string()) - }) - }) - }) -} - -/// Get global value by name -pub fn get_global(instance: &sandbox_wasmi::ModuleRef, name: &str) -> Option { - Some(Into::into( - instance.export_by_name(name)?.as_global()?.get(), - )) -} - -/// Set global value by name -pub fn set_global( - instance: &sandbox_wasmi::ModuleRef, - name: &str, - value: Value, -) -> std::result::Result, error::Error> { - let export = match instance.export_by_name(name) { - Some(e) => e, - None => return Ok(None), - }; - - let global = match export.as_global() { - Some(g) => g, - None => return Ok(None), - }; - - global - .set(From::from(value)) - .map(|_| Some(())) - .map_err(error::Error::Wasmi) +) -> std::result::Result, Error> { + todo!() } diff --git a/sandbox/host/src/sandbox/wasmi_backend_old.rs b/sandbox/host/src/sandbox/wasmi_backend_old.rs new file mode 100644 index 00000000000..eccab3efef2 --- /dev/null +++ b/sandbox/host/src/sandbox/wasmi_backend_old.rs @@ -0,0 +1,410 @@ +// This file is part of Gear. + +// Copyright (C) Gear Technologies Inc. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! Wasmi specific impls for sandbox + +use std::fmt; + +use codec::{Decode, Encode}; +use gear_sandbox_env::HostError; +use sandbox_wasmi::{ + memory_units::Pages, ImportResolver, MemoryInstance, Module, ModuleInstance, RuntimeArgs, + RuntimeValue, Trap, TrapCode, +}; +use sp_wasm_interface_common::{util, Pointer, ReturnValue, Value, WordSize}; + +use crate::{ + error::{self, Error}, + sandbox::{ + BackendInstanceBundle, GuestEnvironment, GuestExternals, GuestFuncIndex, Imports, + InstantiationError, Memory, SandboxInstance, SupervisorContext, + }, + util::MemoryTransfer, +}; + +environmental::environmental!(SupervisorContextStore: trait SupervisorContext); + +#[derive(Debug)] +struct CustomHostError(String); + +impl fmt::Display for CustomHostError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "HostError: {}", self.0) + } +} + +impl sandbox_wasmi::HostError for CustomHostError {} + +/// Construct trap error from specified message +fn trap(msg: &'static str) -> Trap { + Trap::host(CustomHostError(msg.into())) +} + +impl ImportResolver for Imports { + fn resolve_func( + &self, + module_name: &str, + field_name: &str, + signature: &sandbox_wasmi::Signature, + ) -> std::result::Result { + let idx = self.func_by_name(module_name, field_name).ok_or_else(|| { + sandbox_wasmi::Error::Instantiation(format!( + "Export {}:{} not found", + module_name, field_name + )) + })?; + + Ok(sandbox_wasmi::FuncInstance::alloc_host( + signature.clone(), + idx.0, + )) + } + + fn resolve_memory( + &self, + module_name: &str, + field_name: &str, + _memory_type: &sandbox_wasmi::MemoryDescriptor, + ) -> std::result::Result { + let mem = self + .memory_by_name(module_name, field_name) + .ok_or_else(|| { + sandbox_wasmi::Error::Instantiation(format!( + "Export {}:{} not found", + module_name, field_name + )) + })?; + + let wrapper = mem.as_wasmi().ok_or_else(|| { + sandbox_wasmi::Error::Instantiation(format!( + "Unsupported non-wasmi export {}:{}", + module_name, field_name + )) + })?; + + // Here we use inner memory reference only to resolve the imports + // without accessing the memory contents. All subsequent memory accesses + // should happen through the wrapper, that enforces the memory access protocol. + let mem = wrapper.0; + + Ok(mem) + } + + fn resolve_global( + &self, + module_name: &str, + field_name: &str, + _global_type: &sandbox_wasmi::GlobalDescriptor, + ) -> std::result::Result { + Err(sandbox_wasmi::Error::Instantiation(format!( + "Export {}:{} not found", + module_name, field_name + ))) + } + + fn resolve_table( + &self, + module_name: &str, + field_name: &str, + _table_type: &sandbox_wasmi::TableDescriptor, + ) -> std::result::Result { + Err(sandbox_wasmi::Error::Instantiation(format!( + "Export {}:{} not found", + module_name, field_name + ))) + } +} + +/// Allocate new memory region +pub fn new_memory(initial: u32, maximum: Option) -> crate::error::Result { + let memory = Memory::Wasmi(MemoryWrapper::new( + MemoryInstance::alloc(Pages(initial as usize), maximum.map(|m| Pages(m as usize))) + .map_err(|error| Error::Sandbox(error.to_string()))?, + )); + + Ok(memory) +} + +/// Wasmi provides direct access to its memory using slices. +/// +/// This wrapper limits the scope where the slice can be taken to +#[derive(Debug, Clone)] +pub struct MemoryWrapper(sandbox_wasmi::MemoryRef); + +impl MemoryWrapper { + /// Take ownership of the memory region and return a wrapper object + fn new(memory: sandbox_wasmi::MemoryRef) -> Self { + Self(memory) + } +} + +impl MemoryTransfer for MemoryWrapper { + fn read(&self, source_addr: Pointer, size: usize) -> error::Result> { + self.0.with_direct_access(|source| { + let range = util::checked_range(source_addr.into(), size, source.len()) + .ok_or_else(|| error::Error::Other("memory read is out of bounds".into()))?; + + Ok(Vec::from(&source[range])) + }) + } + + fn read_into(&self, source_addr: Pointer, destination: &mut [u8]) -> error::Result<()> { + self.0.with_direct_access(|source| { + let range = util::checked_range(source_addr.into(), destination.len(), source.len()) + .ok_or_else(|| error::Error::Other("memory read is out of bounds".into()))?; + + destination.copy_from_slice(&source[range]); + Ok(()) + }) + } + + fn write_from(&self, dest_addr: Pointer, source: &[u8]) -> error::Result<()> { + self.0.with_direct_access_mut(|destination| { + let range = util::checked_range(dest_addr.into(), source.len(), destination.len()) + .ok_or_else(|| error::Error::Other("memory write is out of bounds".into()))?; + + destination[range].copy_from_slice(source); + Ok(()) + }) + } + + fn memory_grow(&mut self, pages: u32) -> error::Result { + self.0 + .grow(Pages(pages as usize)) + .map_err(|e| { + Error::Sandbox(format!( + "Cannot grow memory in masmi sandbox executor: {}", + e + )) + }) + .map(|p| p.0 as u32) + } + + fn memory_size(&mut self) -> u32 { + self.0.current_size().0 as u32 + } + + fn get_buff(&mut self) -> *mut u8 { + self.0.direct_access_mut().as_mut().as_mut_ptr() + } +} + +impl<'a> sandbox_wasmi::Externals for GuestExternals<'a> { + fn invoke_index( + &mut self, + index: usize, + args: RuntimeArgs, + ) -> std::result::Result, Trap> { + SupervisorContextStore::with(|supervisor_context| { + // Make `index` typesafe again. + let index = GuestFuncIndex(index); + + // Convert function index from guest to supervisor space + let func_idx = self.sandbox_instance + .guest_to_supervisor_mapping + .func_by_guest_index(index) + .expect( + "`invoke_index` is called with indexes registered via `FuncInstance::alloc_host`; + `FuncInstance::alloc_host` is called with indexes that were obtained from `guest_to_supervisor_mapping`; + `func_by_guest_index` called with `index` can't return `None`; + qed" + ); + + // Serialize arguments into a byte vector. + let invoke_args_data: Vec = args + .as_ref() + .iter() + .cloned() + .map(Value::from) + .collect::>() + .encode(); + + // Move serialized arguments inside the memory, invoke dispatch thunk and + // then free allocated memory. + let invoke_args_len = invoke_args_data.len() as WordSize; + let invoke_args_ptr = supervisor_context + .allocate_memory(invoke_args_len) + .map_err(|_| trap("Can't allocate memory in supervisor for the arguments"))?; + + let deallocate = |supervisor_context: &mut dyn SupervisorContext, ptr, fail_msg| { + supervisor_context.deallocate_memory(ptr).map_err(|_| trap(fail_msg)) + }; + + if supervisor_context + .write_memory(invoke_args_ptr, &invoke_args_data) + .is_err() + { + deallocate( + supervisor_context, + invoke_args_ptr, + "Failed deallocation after failed write of invoke arguments", + )?; + return Err(trap("Can't write invoke args into memory")) + } + + let result = supervisor_context.invoke( + invoke_args_ptr, + invoke_args_len, + func_idx, + ); + + deallocate( + supervisor_context, + invoke_args_ptr, + "Can't deallocate memory for dispatch thunk's invoke arguments", + )?; + let result = result?; + + // dispatch_thunk returns pointer to serialized arguments. + // Unpack pointer and len of the serialized result data. + let (serialized_result_val_ptr, serialized_result_val_len) = { + // Cast to u64 to use zero-extension. + let v = result as u64; + let ptr = (v >> 32) as u32; + let len = (v & 0xFFFFFFFF) as u32; + (Pointer::new(ptr), len) + }; + + let serialized_result_val = supervisor_context + .read_memory(serialized_result_val_ptr, serialized_result_val_len) + .map_err(|_| trap("Can't read the serialized result from dispatch thunk")); + + deallocate( + supervisor_context, + serialized_result_val_ptr, + "Can't deallocate memory for dispatch thunk's result", + ) + .and(serialized_result_val) + .and_then(|serialized_result_val| { + let result_val = std::result::Result::::decode(&mut serialized_result_val.as_slice()) + .map_err(|_| trap("Decoding Result failed!"))?; + + match result_val { + Ok(return_value) => Ok(match return_value { + ReturnValue::Unit => None, + ReturnValue::Value(typed_value) => Some(From::from(typed_value)), + }), + Err(HostError) => Err(trap("Supervisor function returned sandbox::HostError")), + } + }) + }).expect("SandboxContextStore is set when invoking sandboxed functions; qed") + } +} + +fn with_guest_externals(sandbox_instance: &SandboxInstance, f: F) -> R +where + F: FnOnce(&mut GuestExternals) -> R, +{ + f(&mut GuestExternals { sandbox_instance }) +} + +/// Instantiate a module within a sandbox context +pub fn instantiate( + wasm: &[u8], + guest_env: GuestEnvironment, + supervisor_context: &mut dyn SupervisorContext, +) -> std::result::Result { + let wasmi_module = Module::from_buffer(wasm).map_err(|_| InstantiationError::ModuleDecoding)?; + let wasmi_instance = ModuleInstance::new(&wasmi_module, &guest_env.imports) + .map_err(|_| InstantiationError::Instantiation)?; + + let sandbox_instance = SandboxInstance { + // In general, it's not a very good idea to use `.not_started_instance()` for + // anything but for extracting memory and tables. But in this particular case, we + // are extracting for the purpose of running `start` function which should be ok. + backend_instance: BackendInstanceBundle::Wasmi( + wasmi_instance.not_started_instance().clone(), + ), + guest_to_supervisor_mapping: guest_env.guest_to_supervisor_mapping, + }; + + with_guest_externals(&sandbox_instance, |guest_externals| { + SupervisorContextStore::using(supervisor_context, || { + wasmi_instance + .run_start(guest_externals) + .map_err(|_| InstantiationError::StartTrapped) + }) + })?; + + Ok(sandbox_instance) +} + +/// Invoke a function within a sandboxed module +pub fn invoke( + instance: &SandboxInstance, + module: &sandbox_wasmi::ModuleRef, + export_name: &str, + args: &[Value], + supervisor_context: &mut dyn SupervisorContext, +) -> std::result::Result, error::Error> { + with_guest_externals(instance, |guest_externals| { + SupervisorContextStore::using(supervisor_context, || { + let args = args.iter().cloned().map(From::from).collect::>(); + + module + .invoke_export(export_name, &args, guest_externals) + .map(|result| result.map(Into::into)) + .map_err(|error| { + if matches!(error, sandbox_wasmi::Error::Trap(Trap::Code(TrapCode::StackOverflow))) { + // Panic stops process queue execution in that case. + // This allows to avoid error lead to consensus failures, that must be handled + // in node binaries forever. If this panic occur, then we must increase stack memory size, + // or tune stack limit injection. + // see also https://github.com/wasmerio/wasmer/issues/4181 + let err_msg = format!( + "invoke: Suppose that this can not happen, because we have a stack limit instrumentation in programs. \ + Export name - {export_name}, args - {args:?}", + ); + + log::error!("{err_msg}"); + unreachable!("{err_msg}") + } + error::Error::Sandbox(error.to_string()) + }) + }) + }) +} + +/// Get global value by name +pub fn get_global(instance: &sandbox_wasmi::ModuleRef, name: &str) -> Option { + Some(Into::into( + instance.export_by_name(name)?.as_global()?.get(), + )) +} + +/// Set global value by name +pub fn set_global( + instance: &sandbox_wasmi::ModuleRef, + name: &str, + value: Value, +) -> std::result::Result, error::Error> { + let export = match instance.export_by_name(name) { + Some(e) => e, + None => return Ok(None), + }; + + let global = match export.as_global() { + Some(g) => g, + None => return Ok(None), + }; + + global + .set(From::from(value)) + .map(|_| Some(())) + .map_err(error::Error::Wasmi) +} diff --git a/sandbox/host/src/store_refcell.rs b/sandbox/host/src/store_refcell.rs new file mode 100644 index 00000000000..26f0158b141 --- /dev/null +++ b/sandbox/host/src/store_refcell.rs @@ -0,0 +1,348 @@ +// This file is part of Gear. + +// Copyright (C) Gear Technologies Inc. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! # Description +//! +//! Custom implementation of `RefCell` for the `wasmer::Store` type, +//! enabling safe repeated mutable borrowing of `StoreRefCell` higher up the call stack +//! when the mutable borrow of `StoreRefCell` still exists. +//! +//! Example illustrating functionality in terms of `RefCell` from the standard library: +//! +//! At first we borrow store mutably: +//! +//! ```ignore +//! let refcell = RefCell::new(Store::default()); +//! let mut_borrow = refcell.borrow_mut(); +//! +//! func(&refcell, &mut mut_borrow); +//! ``` +//! +//! Now we need to borrow store mutably again inside `func`, +//! but we can't do it because `mut_borrow` still exists. +//! +//! ```ignore +//! fn func(ref_cell: &RefCell, mut_borrow: &mut Store) { +//! ref_cell.borrow_mut(); // This will panic +//! } +//! ``` +//! +//! With `StoreRefCell` we can do it safely: +//! +//! ```ignore +//! fn func(store_refcell: &StoreRefCell, mut_borrow: &mut Store) { +//! store_refcell.borrow_scope(mut_borrow, || { +//! // Now we can borrow store again +//! let second_mut_borrow = store_refcell.borrow_mut(); +//! }); +//! } +//! ``` +//! +//! # Why is this necessary? Can't we do without repeated mutable borrowing? +//! +//! The issue arises because when handling syscalls within an instance of a program running in Wasmer, +//! a runtime interface call occurs, leading to a situation where we have two nested runtime interface calls. +//! The first call `sandbox::invoke` initiates the program execution, the second occurs during the syscall processing. +//! +//! Thus, the call stack at the highest point looks like this: +//! +//! ```text +//! ----------------------------------- +//! | Memory::write | Write sandbox memory (Borrows Store mutably) +//! ---------native boundary----------- +//! | sandbox::memory_set | Runtime on behalf of processing syscall make a call to runtime interface +//! ----------------------------------- +//! | runtime executes syscall | +//! --------runtime boundary----------- +//! | syscall_callback | Wasmer calls syscall callback from inside his VM +//! ----------------------------------- +//! | Wasmer's Func::call | Sandbox starts to executes program function (Borrows Store mutably) +//! -------native boundary----------- | +//! | sandbox::invoke | Runtime interface call +//! ----------------------------------- +//! ``` +//! +//! As we can see, the `sandbox::invoke` function borrows the store mutably, +//! and then the `sandbox::memory_set` runtime interface call borrows the store mutably again. +//! +//! Therefore, since it is not possible to pass a reference to Store through nested runtime interface call +//! or cancel previous mutable borrow, it is necessary to use `StoreRefCell` for safe repeated mutable borrowing of `Store`. +//! + +use std::{ + cell::{Cell, UnsafeCell}, + num::NonZeroUsize, + ops::{Deref, DerefMut}, + ptr::NonNull, +}; + +use defer::defer; +use wasmer::StoreMut; + +#[derive(Debug, Clone, Copy)] +enum BorrowState { + Shared(NonZeroUsize), + Mutable, + NonShared, +} + +/// Custom implementation of `RefCell` which allows to safely borrow store +/// mutably/immutably second time inside the scope. +#[derive(Debug)] +pub struct StoreRefCell { + store: UnsafeCell, + state: Cell, +} + +trait GenericAsStoreMut {} + +impl<'r, 's> GenericAsStoreMut for &'r mut StoreMut<'s> {} + +#[derive(Debug)] +pub struct BorrowScopeError; + +impl StoreRefCell { + /// Create new `StoreRefCell` with provided `Store` + pub fn new(store: S) -> Self { + Self { + store: UnsafeCell::new(store), + state: Cell::new(BorrowState::NonShared), + } + } + + /// Borrow store immutably, same semantics as `RefCell::borrow` + #[track_caller] + pub fn borrow(&self) -> Ref<'_, S> { + match self.state.get() { + BorrowState::Shared(n) => { + self.state.set(BorrowState::Shared( + NonZeroUsize::new(n.get() + 1).expect("non zero"), + )); + } + BorrowState::NonShared => { + self.state + .set(BorrowState::Shared(NonZeroUsize::new(1).expect("non zero"))); + } + BorrowState::Mutable => { + panic!("store already borrowed mutably"); + } + } + + Ref { + store: NonNull::new(self.store.get()).expect("non null"), + state: &self.state, + } + } + + /// Borrow store mutably, same semantics as `RefCell::borrow_mut` + #[track_caller] + pub fn borrow_mut(&self) -> RefMut<'_, S> { + match self.state.get() { + BorrowState::NonShared => { + self.state.set(BorrowState::Mutable); + } + BorrowState::Shared(_) | BorrowState::Mutable => { + panic!("store already borrowed"); + } + } + + RefMut { + store: NonNull::new(self.store.get()).expect("non null"), + state: &self.state, + } + } + + /// Provide borrow scope where store can be borrowed mutably second time safely (or borrowed immutably multiple times). + #[allow(private_bounds)] + pub fn borrow_scope R>( + &self, + store: impl GenericAsStoreMut, + f: F, + ) -> Result { + // We expect the same store + //debug_assert!( + // self.compare_stores(store.as_store_ref()), + // "stores are different" + //); + + // Caller just returned borrowed mutably reference to the store, now we can safely borrow it mutably again + let _store = store; + + // We received a mutable borrow, so other states shouldn't be possible + if let BorrowState::Shared(_) | BorrowState::NonShared = self.state.get() { + return Err(BorrowScopeError); + } + + self.state.set(BorrowState::NonShared); + + let result = f(); + + // We expect that after scope ends, store won't be borrowed + debug_assert!(matches!(self.state.get(), BorrowState::NonShared)); + + // Restore previous state after scope ends + defer!(self.state.set(BorrowState::Mutable)); + + Ok(result) + } + + //#[allow(unused)] + //fn compare_stores(&self, returned_store: StoreRef) -> bool { + // // SAFETY: + // // Verified with Miri, it seems safe. + // // Carefully compare the stores while don't using/holding mutable references to them in the same time. + // let orig_store_ref: StoreRef = unsafe { &*self.store.get() }.as_store_ref(); + + // StoreRef::same(&orig_store_ref, &returned_store) + //} + + /// Returns store ptr, same semantics as `RefCell::as_ptr` + pub unsafe fn as_ptr(&self) -> *mut S { + self.store.get() + } +} + +pub struct Ref<'b, S> { + store: NonNull, + state: &'b Cell, +} + +impl Deref for Ref<'_, S> { + type Target = S; + + #[inline] + fn deref(&self) -> &Self::Target { + // SAFETY: we ensure that store isn't borrowed mutably before + unsafe { self.store.as_ref() } + } +} + +impl Drop for Ref<'_, S> { + fn drop(&mut self) { + match self.state.get() { + BorrowState::Shared(n) if n.get() == 1 => { + self.state.set(BorrowState::NonShared); + } + BorrowState::Shared(n) => { + self.state.set(BorrowState::Shared( + NonZeroUsize::new(n.get() - 1).expect("non zero"), + )); + } + _ => unreachable!(), + } + } +} + +pub struct RefMut<'b, S> { + store: NonNull, + state: &'b Cell, +} + +impl<'a, S> Deref for RefMut<'a, S> { + type Target = S; + + #[inline] + fn deref(&self) -> &Self::Target { + // SAFETY: we ensure that store isn't borrowed before + unsafe { self.store.as_ref() } + } +} + +impl DerefMut for RefMut<'_, S> { + #[inline] + fn deref_mut(&mut self) -> &mut Self::Target { + // SAFETY: we ensure that store isn't borrowed before + unsafe { self.store.as_mut() } + } +} + +impl Drop for RefMut<'_, S> { + fn drop(&mut self) { + match self.state.get() { + BorrowState::Mutable => { + self.state.set(BorrowState::NonShared); + } + _ => unreachable!(), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::rc::Rc; + + struct Store; + impl<'r> GenericAsStoreMut for &'r mut Store {} + + #[test] + fn test_store_refcell_borrow() { + let store = Store; + let store_refcell = StoreRefCell::new(store); + + { + let _borrow = store_refcell.borrow(); + let _borrow = store_refcell.borrow(); + } + { + let _borrow = store_refcell.borrow_mut(); + } + { + let _borrow = store_refcell.borrow(); + let _borrow = store_refcell.borrow(); + } + } + + #[test] + fn test_store_refcell_borrow_scope() { + struct Env { + store: Rc>, + } + + let store = Store; + let rc = Rc::new(StoreRefCell::new(store)); + let env = Env { store: rc.clone() }; + + let callback = |env: Env, storemut: &mut Store| { + // do something with `storemut` + // .. + + let rc = rc.clone(); + let _ = env.store.borrow_scope(storemut, move || { + // Callback is called and it allowed to borrow store mutably/immutably + { + let _borrow = rc.borrow_mut(); + } + { + let _borrow = rc.borrow(); + let _borrow = rc.borrow(); + } + { + let _borrow = rc.borrow_mut(); + } + }); + + // do something with `storemut` + // .. + let _ = storemut; + }; + + let mut borrow = rc.borrow_mut(); + callback(env, &mut borrow) + } +} From 51825c367397b904e43709844a7995f03cb17c1b Mon Sep 17 00:00:00 2001 From: Roman Maslennikov Date: Wed, 2 Oct 2024 14:17:53 +0400 Subject: [PATCH 05/25] WIP2 --- sandbox/host/src/error.rs | 4 +- sandbox/host/src/sandbox/wasmi_backend.rs | 353 ++++++++++++++++++++-- 2 files changed, 333 insertions(+), 24 deletions(-) diff --git a/sandbox/host/src/error.rs b/sandbox/host/src/error.rs index f80ac87355d..a5e32b19b80 100644 --- a/sandbox/host/src/error.rs +++ b/sandbox/host/src/error.rs @@ -109,8 +109,8 @@ pub enum Error { impl sandbox_wasmi::core::HostError for Error {} -impl From<&'static str> for Error { - fn from(err: &'static str) -> Error { +impl From<&'_ str> for Error { + fn from(err: &'_ str) -> Error { Error::Other(err.into()) } } diff --git a/sandbox/host/src/sandbox/wasmi_backend.rs b/sandbox/host/src/sandbox/wasmi_backend.rs index aabdd0bfc3e..6c8393f6c92 100644 --- a/sandbox/host/src/sandbox/wasmi_backend.rs +++ b/sandbox/host/src/sandbox/wasmi_backend.rs @@ -26,7 +26,7 @@ use region::{Allocation, Protection}; use sandbox_wasmi::{ core::{Pages, Trap, UntypedVal}, Config, Engine, ExternType, Linker, MemoryType, Module, StackLimits, StoreContext, - StoreContextMut, + StoreContextMut, Val, }; use sp_wasm_interface_common::{util, Pointer, ReturnValue, Value, WordSize}; @@ -41,26 +41,53 @@ use crate::{ util::MemoryTransfer, }; -type Store = sandbox_wasmi::Store<()>; +use super::SupervisorFuncIndex; + +type Store = sandbox_wasmi::Store>; pub type StoreRefCell = store_refcell::StoreRefCell; -environmental::environmental!(SupervisorContextStore: trait SupervisorContext); +//environmental::environmental!(SupervisorContextStore: trait SupervisorContext); -#[derive(Debug)] -struct CustomHostError(String); +pub struct FuncEnv { + store: Rc, + gas_global: sandbox_wasmi::Global, + supervisor_context: &'static mut dyn SupervisorContext, +} -impl fmt::Display for CustomHostError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "HostError: {}", self.0) +impl FuncEnv { + pub fn new( + store: Rc, + gas_global: sandbox_wasmi::Global, + supervisor_context: &mut dyn SupervisorContext, + ) -> Self { + Self { + store, + gas_global, + supervisor_context: unsafe { + std::mem::transmute::<&mut dyn SupervisorContext, &'static mut dyn SupervisorContext>( + supervisor_context, + ) + }, + } } } -impl sandbox_wasmi::core::HostError for CustomHostError {} +//#[derive(Debug)] +//struct CustomHostError(String); +// +//impl fmt::Display for CustomHostError { +// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { +// write!(f, "HostError: {}", self.0) +// } +//} +// +//impl sandbox_wasmi::core::HostError for CustomHostError {} /// Construct trap error from specified message -//fn trap(msg: &'static str) -> Trap { -// Trap::host(CustomHostError(msg.into())) -//} +fn host_trap(msg: impl Into) -> sandbox_wasmi::Error { + //Trap::host(CustomHostError(msg.into())) + sandbox_wasmi::Error::host(msg.into()) +} fn into_wasmi_val(value: Value) -> sandbox_wasmi::Val { match value { @@ -78,12 +105,12 @@ fn into_wasmi_result(value: ReturnValue) -> Vec { } } -fn into_value(value: sandbox_wasmi::Val) -> Option { +fn into_value(value: &sandbox_wasmi::Val) -> Option { match value { - sandbox_wasmi::Val::I32(val) => Some(Value::I32(val)), - sandbox_wasmi::Val::I64(val) => Some(Value::I64(val)), - sandbox_wasmi::Val::F32(val) => Some(Value::F32(val.into())), - sandbox_wasmi::Val::F64(val) => Some(Value::F64(val.into())), + sandbox_wasmi::Val::I32(val) => Some(Value::I32(*val)), + sandbox_wasmi::Val::I64(val) => Some(Value::I64(*val)), + sandbox_wasmi::Val::F32(val) => Some(Value::F32((*val).into())), + sandbox_wasmi::Val::F64(val) => Some(Value::F64((*val).into())), _ => None, } } @@ -118,9 +145,9 @@ impl Backend { ); let engine = Engine::new(&config); - let store = Store::new(&engine, ()); + let store = Store::new(&engine, None); Backend { - store: Rc::new(StoreRefCell::new(Store::new(&engine, ()))), + store: Rc::new(StoreRefCell::new(store)), } } @@ -157,13 +184,21 @@ pub fn new_memory( /// Wasmi provides direct access to its memory using slices. /// /// This wrapper limits the scope where the slice can be taken to -#[derive(Debug, Clone)] +#[derive(Clone)] pub struct MemoryWrapper { memory: sandbox_wasmi::Memory, store: Rc, //alloc: Allocation, } +impl std::fmt::Debug for MemoryWrapper { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("MemoryWrapper") + .field("memory", &self.memory) + .finish() + } +} + impl MemoryWrapper { /// Take ownership of the memory region and return a wrapper object fn new(memory: sandbox_wasmi::Memory, store: Rc, alloc: Allocation) -> Self { @@ -224,7 +259,7 @@ impl MemoryTransfer for MemoryWrapper { /// Get global value by name pub fn get_global(instance: &sandbox_wasmi::Instance, store: &Store, name: &str) -> Option { - into_value(instance.get_global(store, name)?.get(store)) + into_value(&instance.get_global(store, name)?.get(store)) } /// Set global value by name @@ -253,7 +288,281 @@ pub fn instantiate( guest_env: GuestEnvironment, supervisor_context: &mut dyn SupervisorContext, ) -> std::result::Result { - todo!() + let mut store = context.store().borrow_mut(); + + let module = + Module::new(store.engine(), wasm).map_err(|_| InstantiationError::ModuleDecoding)?; + let mut linker = Linker::new(store.engine()); + + for import in module.imports() { + let module = import.module().to_string(); + let name = import.name().to_string(); + let key = (module.clone(), name.clone()); + + match import.ty() { + ExternType::Global(_) | ExternType::Table(_) => {} + ExternType::Memory(_mem_ty) => { + let memory = guest_env + .imports + .memory_by_name(&module, &name) + .ok_or(InstantiationError::ModuleDecoding)?; + + let wasmi_memory = memory.as_wasmi().expect( + "memory is created by wasmi; \ + exported by the same module and backend; \ + thus the operation can't fail; \ + qed", + ); + + linker + .define(&module, &name, wasmi_memory.memory) + .map_err(|_| InstantiationError::EnvironmentDefinitionCorrupted)?; + } + ExternType::Func(func_ty) => { + //let func_ptr = env_def_builder + // .map + // .get(&key) + // .cloned() + // .and_then(|val| val.host_func()) + // .ok_or(Error::Module)?; + + //let func = wasmi::Func::new( + // &mut store, + // func_ty.clone(), + // move |caller, params, results| { + // let gas = caller + // .get_export(GLOBAL_NAME_GAS) + // .ok_or_else(|| { + // wasmi::Error::new(format!( + // "failed to find `{GLOBAL_NAME_GAS}` export" + // )) + // })? + // .into_global() + // .ok_or_else(|| { + // wasmi::Error::new(format!("{GLOBAL_NAME_GAS} is not global")) + // })?; + + // let params: Vec<_> = Some(gas.get(&caller)) + // .into_iter() + // .chain(params.iter().cloned()) + // .map(to_interface) + // .map(|val| { + // val.ok_or(wasmi::Error::new( + // "`externref` or `funcref` are not supported", + // )) + // }) + // .collect::>()?; + + // let mut caller = Caller(caller); + // let val = (func_ptr)(&mut caller, ¶ms) + // .map_err(|HostError| wasmi::Error::new("function error"))?; + + // match (val.inner, results) { + // (ReturnValue::Unit, []) => {} + // (ReturnValue::Value(val), [ret]) => { + // let val = to_wasmi(val); + + // if val.ty() != ret.ty() { + // return Err(wasmi::Error::new("mismatching return types")); + // } + + // *ret = val; + // } + // _results => { + // let err_msg = format!( + // "Instance::new: embedded executor doesn't support multi-value return. \ + // Function name - {key:?}, params - {params:?}, results - {_results:?}" + // ); + + // log::error!("{err_msg}"); + // unreachable!("{err_msg}") + // } + // } + + // gas.set(&mut caller.0, RuntimeValue::I64(val.gas)) + // .map_err(|e| { + // wasmi::Error::new(format!( + // "failed to set `{GLOBAL_NAME_GAS}` global: {e}" + // )) + // })?; + + // Ok(()) + // }, + //); + //let func = wasmi::Extern::Func(func); + //linker + // .define(&module, &name, func) + // .map_err(|_| Error::Module)?; + } + } + } + + let instance_pre = linker + .instantiate(&mut *store, &module) + .map_err(|e| InstantiationError::Instantiation)?; + let instance = instance_pre + .start(&mut *store) + .map_err(|e| InstantiationError::StartTrapped)?; + + Ok(SandboxInstance { + backend_instance: BackendInstanceBundle::Wasmi { + instance, + store: context.store().clone(), + }, + guest_to_supervisor_mapping: guest_env.guest_to_supervisor_mapping, + }) +} + +fn dispatch_function( + supervisor_func_index: SupervisorFuncIndex, + store: &mut Store, + func_ty: sandbox_wasmi::FuncType, +) -> sandbox_wasmi::Func { + sandbox_wasmi::Func::new( + store, + func_ty, + move |mut caller, params, results| -> Result<(), sandbox_wasmi::Error> { + let func_env = caller.data_mut().as_mut().expect("func env should be set"); + let supervisor_context = &mut func_env.supervisor_context; + + let invoke_args_data = params + .iter() + .map(|value| { + into_value(value).ok_or_else(|| { + host_trap(format!("Unsupported function argument: {:?}", value)) + }) + }) + .collect::, _>>()? + .encode(); + + let serialized_result_val = + dispatch_common(supervisor_func_index, supervisor_context, invoke_args_data)?; + + let deserialized_result = std::result::Result::::decode( + &mut serialized_result_val.as_slice(), + ) + .map_err(|_| host_trap("Decoding Result failed!"))? + .map_err(|_| host_trap("Supervisor function returned sandbox::HostError"))?; + + for (idx, result_val) in into_wasmi_result(deserialized_result) + .into_iter() + .enumerate() + { + results[idx] = result_val; + } + + Ok(()) + }, + ) +} + +fn dispatch_function_v2( + supervisor_func_index: SupervisorFuncIndex, + store: &mut Store, + func_ty: sandbox_wasmi::FuncType, +) -> sandbox_wasmi::Func { + sandbox_wasmi::Func::new( + store, + func_ty, + move |mut caller, params, results| -> Result<(), sandbox_wasmi::Error> { + let func_env = caller.data_mut().as_mut().expect("func env should be set"); + let supervisor_context = &mut func_env.supervisor_context; + + let invoke_args_data = params + .iter() + .map(|value| { + into_value(value).ok_or_else(|| { + host_trap(format!("Unsupported function argument: {:?}", value)) + }) + }) + .collect::, _>>()? + .encode(); + + let serialized_result_val = + dispatch_common(supervisor_func_index, supervisor_context, invoke_args_data)?; + + let deserialized_result = std::result::Result::::decode( + &mut serialized_result_val.as_slice(), + ) + .map_err(|_| host_trap("Decoding Result failed!"))? + .map_err(|_| host_trap("Supervisor function returned sandbox::HostError"))?; + + for (idx, result_val) in into_wasmi_result(deserialized_result) + .into_iter() + .enumerate() + { + results[idx] = result_val; + } + + Ok(()) + }, + ) +} + +fn dispatch_common( + supervisor_func_index: SupervisorFuncIndex, + supervisor_context: &mut &mut dyn SupervisorContext, + invoke_args_data: Vec, +) -> std::result::Result, sandbox_wasmi::Error> { + // Move serialized arguments inside the memory, invoke dispatch thunk and + // then free allocated memory. + let invoke_args_len = invoke_args_data.len() as WordSize; + let invoke_args_ptr = supervisor_context + .allocate_memory(invoke_args_len) + .map_err(|_| host_trap("Can't allocate memory in supervisor for the arguments"))?; + + let deallocate = |fe: &mut &mut dyn SupervisorContext, ptr, fail_msg| { + fe.deallocate_memory(ptr).map_err(|_| host_trap(fail_msg)) + }; + + if supervisor_context + .write_memory(invoke_args_ptr, &invoke_args_data) + .is_err() + { + deallocate( + supervisor_context, + invoke_args_ptr, + "Failed deallocation after failed write of invoke arguments", + )?; + + return Err(host_trap("Can't write invoke args into memory")); + } + + // Perform the actual call + let serialized_result = supervisor_context + .invoke(invoke_args_ptr, invoke_args_len, supervisor_func_index) + .map_err(|e| host_trap(e.to_string())); + + deallocate( + supervisor_context, + invoke_args_ptr, + "Failed deallocation after invoke", + )?; + + let serialized_result = serialized_result?; + + // TODO #3038 + // dispatch_thunk returns pointer to serialized arguments. + // Unpack pointer and len of the serialized result data. + let (serialized_result_val_ptr, serialized_result_val_len) = { + // Cast to u64 to use zero-extension. + let v = serialized_result as u64; + let ptr = (v >> 32) as u32; + let len = (v & 0xFFFFFFFF) as u32; + (Pointer::new(ptr), len) + }; + + let serialized_result_val = supervisor_context + .read_memory(serialized_result_val_ptr, serialized_result_val_len) + .map_err(|_| host_trap("Can't read the serialized result from dispatch thunk")); + + deallocate( + supervisor_context, + serialized_result_val_ptr, + "Can't deallocate memory for dispatch thunk's result", + )?; + + serialized_result_val } /// Invoke a function within a sandboxed module From 911f3a9b5c0b5a4a1ba451405a4715162c9e9c36 Mon Sep 17 00:00:00 2001 From: Roman Maslennikov Date: Wed, 2 Oct 2024 16:59:51 +0400 Subject: [PATCH 06/25] WIP3 --- sandbox/host/src/sandbox/wasmi_backend.rs | 156 ++++++++++++---------- sandbox/host/src/store_refcell.rs | 1 + 2 files changed, 85 insertions(+), 72 deletions(-) diff --git a/sandbox/host/src/sandbox/wasmi_backend.rs b/sandbox/host/src/sandbox/wasmi_backend.rs index 6c8393f6c92..05878a6181a 100644 --- a/sandbox/host/src/sandbox/wasmi_backend.rs +++ b/sandbox/host/src/sandbox/wasmi_backend.rs @@ -21,12 +21,12 @@ use std::{fmt, rc::Rc, slice}; use codec::{Decode, Encode}; -use gear_sandbox_env::{HostError, Instantiate}; +use gear_sandbox_env::{HostError, Instantiate, WasmReturnValue}; use region::{Allocation, Protection}; use sandbox_wasmi::{ core::{Pages, Trap, UntypedVal}, - Config, Engine, ExternType, Linker, MemoryType, Module, StackLimits, StoreContext, - StoreContextMut, Val, + AsContext, AsContextMut, Config, Engine, ExternType, Linker, MemoryType, Module, StackLimits, + StoreContext, StoreContextMut, Val, }; use sp_wasm_interface_common::{util, Pointer, ReturnValue, Value, WordSize}; @@ -46,12 +46,11 @@ use super::SupervisorFuncIndex; type Store = sandbox_wasmi::Store>; pub type StoreRefCell = store_refcell::StoreRefCell; -//environmental::environmental!(SupervisorContextStore: trait SupervisorContext); +environmental::environmental!(SupervisorContextStore: trait SupervisorContext); pub struct FuncEnv { store: Rc, gas_global: sandbox_wasmi::Global, - supervisor_context: &'static mut dyn SupervisorContext, } impl FuncEnv { @@ -60,15 +59,7 @@ impl FuncEnv { gas_global: sandbox_wasmi::Global, supervisor_context: &mut dyn SupervisorContext, ) -> Self { - Self { - store, - gas_global, - supervisor_context: unsafe { - std::mem::transmute::<&mut dyn SupervisorContext, &'static mut dyn SupervisorContext>( - supervisor_context, - ) - }, - } + Self { store, gas_global } } } @@ -422,36 +413,38 @@ fn dispatch_function( store, func_ty, move |mut caller, params, results| -> Result<(), sandbox_wasmi::Error> { - let func_env = caller.data_mut().as_mut().expect("func env should be set"); - let supervisor_context = &mut func_env.supervisor_context; - - let invoke_args_data = params - .iter() - .map(|value| { - into_value(value).ok_or_else(|| { - host_trap(format!("Unsupported function argument: {:?}", value)) + SupervisorContextStore::with(|supervisor_context| { + let func_env = caller.data_mut().as_mut().expect("func env should be set"); + + let invoke_args_data = params + .iter() + .map(|value| { + into_value(value).ok_or_else(|| { + host_trap(format!("Unsupported function argument: {:?}", value)) + }) }) - }) - .collect::, _>>()? - .encode(); + .collect::, _>>()? + .encode(); - let serialized_result_val = - dispatch_common(supervisor_func_index, supervisor_context, invoke_args_data)?; - - let deserialized_result = std::result::Result::::decode( - &mut serialized_result_val.as_slice(), - ) - .map_err(|_| host_trap("Decoding Result failed!"))? - .map_err(|_| host_trap("Supervisor function returned sandbox::HostError"))?; - - for (idx, result_val) in into_wasmi_result(deserialized_result) - .into_iter() - .enumerate() - { - results[idx] = result_val; - } + let serialized_result_val = + dispatch_common(supervisor_func_index, supervisor_context, invoke_args_data)?; - Ok(()) + let deserialized_result = std::result::Result::::decode( + &mut serialized_result_val.as_slice(), + ) + .map_err(|_| host_trap("Decoding Result failed!"))? + .map_err(|_| host_trap("Supervisor function returned sandbox::HostError"))?; + + for (idx, result_val) in into_wasmi_result(deserialized_result) + .into_iter() + .enumerate() + { + results[idx] = result_val; + } + + Ok(()) + }) + .expect("SupervisorContextStore is set when invoking sandboxed functions; qed") }, ) } @@ -465,43 +458,62 @@ fn dispatch_function_v2( store, func_ty, move |mut caller, params, results| -> Result<(), sandbox_wasmi::Error> { - let func_env = caller.data_mut().as_mut().expect("func env should be set"); - let supervisor_context = &mut func_env.supervisor_context; - - let invoke_args_data = params - .iter() - .map(|value| { - into_value(value).ok_or_else(|| { - host_trap(format!("Unsupported function argument: {:?}", value)) + SupervisorContextStore::with(|supervisor_context| { + let func_env = caller.data().as_ref().expect("func env should be set"); + let store_ref_cell = func_env.store.clone(); + let gas_global = func_env.gas_global; + + let gas = gas_global.get(caller.as_context()); + let store_ctx_mut = caller.as_context_mut(); + + let deserialized_result = store_ref_cell + .borrow_scope(store_ctx_mut, move || { + let invoke_args_data = [gas] + .iter() + .chain(params.iter()) + .map(|value| { + into_value(value).ok_or_else(|| { + host_trap(format!("Unsupported function argument: {:?}", value)) + }) + }) + .collect::, _>>()? + .encode(); + + let serialized_result_val = dispatch_common( + supervisor_func_index, + supervisor_context, + invoke_args_data, + )?; + + std::result::Result::::decode( + &mut serialized_result_val.as_slice(), + ) + .map_err(|_| host_trap("Decoding Result failed!"))? + .map_err(|_| host_trap("Supervisor function returned sandbox::HostError")) }) - }) - .collect::, _>>()? - .encode(); - - let serialized_result_val = - dispatch_common(supervisor_func_index, supervisor_context, invoke_args_data)?; - - let deserialized_result = std::result::Result::::decode( - &mut serialized_result_val.as_slice(), - ) - .map_err(|_| host_trap("Decoding Result failed!"))? - .map_err(|_| host_trap("Supervisor function returned sandbox::HostError"))?; - - for (idx, result_val) in into_wasmi_result(deserialized_result) - .into_iter() - .enumerate() - { - results[idx] = result_val; - } - - Ok(()) + .map_err(|_| host_trap("StoreRefCell borrow scope error"))??; + + for (idx, result_val) in into_wasmi_result(deserialized_result.inner) + .into_iter() + .enumerate() + { + results[idx] = result_val; + } + + gas_global + .set(caller, Val::I64(deserialized_result.gas)) + .map_err(|e| host_trap(format!("Failed to set gas global: {:?}", e)))?; + + Ok(()) + }) + .expect("SandboxContextStore is set when invoking sandboxed functions; qed") }, ) } fn dispatch_common( supervisor_func_index: SupervisorFuncIndex, - supervisor_context: &mut &mut dyn SupervisorContext, + supervisor_context: &mut dyn SupervisorContext, invoke_args_data: Vec, ) -> std::result::Result, sandbox_wasmi::Error> { // Move serialized arguments inside the memory, invoke dispatch thunk and @@ -511,7 +523,7 @@ fn dispatch_common( .allocate_memory(invoke_args_len) .map_err(|_| host_trap("Can't allocate memory in supervisor for the arguments"))?; - let deallocate = |fe: &mut &mut dyn SupervisorContext, ptr, fail_msg| { + let deallocate = |fe: &mut dyn SupervisorContext, ptr, fail_msg| { fe.deallocate_memory(ptr).map_err(|_| host_trap(fail_msg)) }; diff --git a/sandbox/host/src/store_refcell.rs b/sandbox/host/src/store_refcell.rs index 26f0158b141..6951bb19d47 100644 --- a/sandbox/host/src/store_refcell.rs +++ b/sandbox/host/src/store_refcell.rs @@ -112,6 +112,7 @@ pub struct StoreRefCell { trait GenericAsStoreMut {} impl<'r, 's> GenericAsStoreMut for &'r mut StoreMut<'s> {} +impl<'s, T> GenericAsStoreMut for sandbox_wasmi::StoreContextMut<'s, T> {} #[derive(Debug)] pub struct BorrowScopeError; From 2335b3d2edb09a15ecb7d157fcfde5d1160c1081 Mon Sep 17 00:00:00 2001 From: Roman Maslennikov Date: Wed, 2 Oct 2024 17:26:59 +0400 Subject: [PATCH 07/25] WIP4 --- sandbox/host/src/sandbox/wasmi_backend.rs | 141 ++++++++++------------ 1 file changed, 62 insertions(+), 79 deletions(-) diff --git a/sandbox/host/src/sandbox/wasmi_backend.rs b/sandbox/host/src/sandbox/wasmi_backend.rs index 05878a6181a..f7782d13d89 100644 --- a/sandbox/host/src/sandbox/wasmi_backend.rs +++ b/sandbox/host/src/sandbox/wasmi_backend.rs @@ -310,80 +310,38 @@ pub fn instantiate( .map_err(|_| InstantiationError::EnvironmentDefinitionCorrupted)?; } ExternType::Func(func_ty) => { - //let func_ptr = env_def_builder - // .map - // .get(&key) - // .cloned() - // .and_then(|val| val.host_func()) - // .ok_or(Error::Module)?; - - //let func = wasmi::Func::new( - // &mut store, - // func_ty.clone(), - // move |caller, params, results| { - // let gas = caller - // .get_export(GLOBAL_NAME_GAS) - // .ok_or_else(|| { - // wasmi::Error::new(format!( - // "failed to find `{GLOBAL_NAME_GAS}` export" - // )) - // })? - // .into_global() - // .ok_or_else(|| { - // wasmi::Error::new(format!("{GLOBAL_NAME_GAS} is not global")) - // })?; - - // let params: Vec<_> = Some(gas.get(&caller)) - // .into_iter() - // .chain(params.iter().cloned()) - // .map(to_interface) - // .map(|val| { - // val.ok_or(wasmi::Error::new( - // "`externref` or `funcref` are not supported", - // )) - // }) - // .collect::>()?; - - // let mut caller = Caller(caller); - // let val = (func_ptr)(&mut caller, ¶ms) - // .map_err(|HostError| wasmi::Error::new("function error"))?; - - // match (val.inner, results) { - // (ReturnValue::Unit, []) => {} - // (ReturnValue::Value(val), [ret]) => { - // let val = to_wasmi(val); - - // if val.ty() != ret.ty() { - // return Err(wasmi::Error::new("mismatching return types")); - // } - - // *ret = val; - // } - // _results => { - // let err_msg = format!( - // "Instance::new: embedded executor doesn't support multi-value return. \ - // Function name - {key:?}, params - {params:?}, results - {_results:?}" - // ); - - // log::error!("{err_msg}"); - // unreachable!("{err_msg}") - // } - // } - - // gas.set(&mut caller.0, RuntimeValue::I64(val.gas)) - // .map_err(|e| { - // wasmi::Error::new(format!( - // "failed to set `{GLOBAL_NAME_GAS}` global: {e}" - // )) - // })?; - - // Ok(()) - // }, - //); - //let func = wasmi::Extern::Func(func); - //linker - // .define(&module, &name, func) - // .map_err(|_| Error::Module)?; + let guest_func_index = guest_env + .imports + .func_by_name(import.module(), import.name()); + + let guest_func_index = if let Some(index) = guest_func_index { + index + } else { + // Missing import (should we abort here?) + continue; + }; + + let supervisor_func_index = guest_env + .guest_to_supervisor_mapping + .func_by_guest_index(guest_func_index) + .ok_or(InstantiationError::ModuleDecoding)?; + + let function = match version { + Instantiate::Version1 => dispatch_function( + supervisor_func_index, + &mut context.store().borrow_mut(), + func_ty, + ), + Instantiate::Version2 => dispatch_function_v2( + supervisor_func_index, + &mut context.store().borrow_mut(), + func_ty, + ), + }; + + linker + .define(&module, &name, function) + .map_err(|_| InstantiationError::ModuleDecoding)?; } } } @@ -407,11 +365,11 @@ pub fn instantiate( fn dispatch_function( supervisor_func_index: SupervisorFuncIndex, store: &mut Store, - func_ty: sandbox_wasmi::FuncType, + func_ty: &sandbox_wasmi::FuncType, ) -> sandbox_wasmi::Func { sandbox_wasmi::Func::new( store, - func_ty, + func_ty.clone(), move |mut caller, params, results| -> Result<(), sandbox_wasmi::Error> { SupervisorContextStore::with(|supervisor_context| { let func_env = caller.data_mut().as_mut().expect("func env should be set"); @@ -452,11 +410,11 @@ fn dispatch_function( fn dispatch_function_v2( supervisor_func_index: SupervisorFuncIndex, store: &mut Store, - func_ty: sandbox_wasmi::FuncType, + func_ty: &sandbox_wasmi::FuncType, ) -> sandbox_wasmi::Func { sandbox_wasmi::Func::new( store, - func_ty, + func_ty.clone(), move |mut caller, params, results| -> Result<(), sandbox_wasmi::Error> { SupervisorContextStore::with(|supervisor_context| { let func_env = caller.data().as_ref().expect("func env should be set"); @@ -585,5 +543,30 @@ pub fn invoke( args: &[Value], supervisor_context: &mut dyn SupervisorContext, ) -> std::result::Result, Error> { - todo!() + let function = instance + .get_func(&*store.borrow(), export_name) + .ok_or_else(|| Error::Sandbox("function export error".into()))?; + + let args: Vec = args.into_iter().copied().map(into_wasmi_val).collect(); + let func_ty = function.ty(&*store.borrow()); + + let mut outputs = vec![ + sandbox_wasmi::Val::ExternRef(sandbox_wasmi::ExternRef::null()); + func_ty.results().len() + ]; + + function + .call(&mut *store.borrow_mut(), &args, &mut outputs) + .map_err(|error| Error::Sandbox(error.to_string()))?; + + match outputs.as_slice() { + [] => Ok(None), + [val] => match into_value(val) { + None => Err(Error::Sandbox(format!("Unsupported return value: {val:?}"))), + Some(v) => Ok(Some(v)), + }, + _outputs => Err(Error::Sandbox( + "multiple return types are not supported yet".into(), + )), + } } From 67719b6790fa8cf2c6b5057fbcfcdbad36fdcabd Mon Sep 17 00:00:00 2001 From: Roman Maslennikov Date: Wed, 2 Oct 2024 18:52:35 +0400 Subject: [PATCH 08/25] WIP5 --- sandbox/host/src/sandbox/wasmi_backend.rs | 86 +++++++++++------------ 1 file changed, 41 insertions(+), 45 deletions(-) diff --git a/sandbox/host/src/sandbox/wasmi_backend.rs b/sandbox/host/src/sandbox/wasmi_backend.rs index f7782d13d89..e615aa1f1c1 100644 --- a/sandbox/host/src/sandbox/wasmi_backend.rs +++ b/sandbox/host/src/sandbox/wasmi_backend.rs @@ -18,24 +18,23 @@ //! Wasmi specific impls for sandbox -use std::{fmt, rc::Rc, slice}; +use std::{rc::Rc, slice}; use codec::{Decode, Encode}; -use gear_sandbox_env::{HostError, Instantiate, WasmReturnValue}; +use gear_sandbox_env::{HostError, Instantiate, WasmReturnValue, GLOBAL_NAME_GAS}; use region::{Allocation, Protection}; use sandbox_wasmi::{ - core::{Pages, Trap, UntypedVal}, - AsContext, AsContextMut, Config, Engine, ExternType, Linker, MemoryType, Module, StackLimits, - StoreContext, StoreContextMut, Val, + core::UntypedVal, AsContext, AsContextMut, Config, Engine, ExternType, Linker, MemoryType, + Module, StackLimits, Val, }; -use sp_wasm_interface_common::{util, Pointer, ReturnValue, Value, WordSize}; +use sp_wasm_interface_common::{Pointer, ReturnValue, Value, WordSize}; use crate::{ error::{self, Error}, sandbox::{ - BackendInstanceBundle, GuestEnvironment, GuestExternals, GuestFuncIndex, Imports, - InstantiationError, Memory, SandboxInstance, SupervisorContext, + BackendInstanceBundle, GuestEnvironment, InstantiationError, Memory, SandboxInstance, + SupervisorContext, }, store_refcell, util::MemoryTransfer, @@ -54,29 +53,13 @@ pub struct FuncEnv { } impl FuncEnv { - pub fn new( - store: Rc, - gas_global: sandbox_wasmi::Global, - supervisor_context: &mut dyn SupervisorContext, - ) -> Self { + pub fn new(store: Rc, gas_global: sandbox_wasmi::Global) -> Self { Self { store, gas_global } } } -//#[derive(Debug)] -//struct CustomHostError(String); -// -//impl fmt::Display for CustomHostError { -// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { -// write!(f, "HostError: {}", self.0) -// } -//} -// -//impl sandbox_wasmi::core::HostError for CustomHostError {} - /// Construct trap error from specified message fn host_trap(msg: impl Into) -> sandbox_wasmi::Error { - //Trap::host(CustomHostError(msg.into())) sandbox_wasmi::Error::host(msg.into()) } @@ -192,7 +175,7 @@ impl std::fmt::Debug for MemoryWrapper { impl MemoryWrapper { /// Take ownership of the memory region and return a wrapper object - fn new(memory: sandbox_wasmi::Memory, store: Rc, alloc: Allocation) -> Self { + fn new(memory: sandbox_wasmi::Memory, store: Rc, _alloc: Allocation) -> Self { Self { memory, store, @@ -207,7 +190,7 @@ impl MemoryTransfer for MemoryWrapper { let ctx = self.store.borrow(); self.memory .read(&*ctx, source_addr.into(), &mut buffer) - .map_err(|_| error::Error::Other("memory read is out of bounds".into())); + .map_err(|_| error::Error::Other("memory read is out of bounds".into()))?; Ok(buffer) } @@ -216,7 +199,7 @@ impl MemoryTransfer for MemoryWrapper { let ctx = self.store.borrow(); self.memory .read(&*ctx, source_addr.into(), destination) - .map_err(|_| error::Error::Other("memory read is out of bounds".into())); + .map_err(|_| error::Error::Other("memory read is out of bounds".into()))?; Ok(()) } @@ -225,7 +208,7 @@ impl MemoryTransfer for MemoryWrapper { let mut ctx = self.store.borrow_mut(); self.memory .write(&mut *ctx, dest_addr.into(), source) - .map_err(|_| error::Error::Other("memory write is out of bounds".into())); + .map_err(|_| error::Error::Other("memory write is out of bounds".into()))?; Ok(()) } @@ -277,7 +260,7 @@ pub fn instantiate( context: &Backend, wasm: &[u8], guest_env: GuestEnvironment, - supervisor_context: &mut dyn SupervisorContext, + _supervisor_context: &mut dyn SupervisorContext, ) -> std::result::Result { let mut store = context.store().borrow_mut(); @@ -288,7 +271,6 @@ pub fn instantiate( for import in module.imports() { let module = import.module().to_string(); let name = import.name().to_string(); - let key = (module.clone(), name.clone()); match import.ty() { ExternType::Global(_) | ExternType::Table(_) => {} @@ -346,12 +328,14 @@ pub fn instantiate( } } - let instance_pre = linker - .instantiate(&mut *store, &module) - .map_err(|e| InstantiationError::Instantiation)?; - let instance = instance_pre - .start(&mut *store) - .map_err(|e| InstantiationError::StartTrapped)?; + let instance_pre = linker.instantiate(&mut *store, &module).map_err(|error| { + log::trace!("Failed to call wasmi instantiate: {error:?}"); + InstantiationError::Instantiation + })?; + let instance = instance_pre.start(&mut *store).map_err(|error| { + log::trace!("Failed to call wasmi start: {error:?}"); + InstantiationError::StartTrapped + })?; Ok(SandboxInstance { backend_instance: BackendInstanceBundle::Wasmi { @@ -370,10 +354,8 @@ fn dispatch_function( sandbox_wasmi::Func::new( store, func_ty.clone(), - move |mut caller, params, results| -> Result<(), sandbox_wasmi::Error> { + move |_caller, params, results| -> Result<(), sandbox_wasmi::Error> { SupervisorContextStore::with(|supervisor_context| { - let func_env = caller.data_mut().as_mut().expect("func env should be set"); - let invoke_args_data = params .iter() .map(|value| { @@ -545,9 +527,9 @@ pub fn invoke( ) -> std::result::Result, Error> { let function = instance .get_func(&*store.borrow(), export_name) - .ok_or_else(|| Error::Sandbox("function export error".into()))?; + .ok_or_else(|| Error::Sandbox(format!("function {export_name} export error")))?; - let args: Vec = args.into_iter().copied().map(into_wasmi_val).collect(); + let args: Vec = args.iter().copied().map(into_wasmi_val).collect(); let func_ty = function.ty(&*store.borrow()); let mut outputs = vec![ @@ -555,9 +537,23 @@ pub fn invoke( func_ty.results().len() ]; - function - .call(&mut *store.borrow_mut(), &args, &mut outputs) - .map_err(|error| Error::Sandbox(error.to_string()))?; + // Init func env + { + let gas_global = instance + .get_global(&*store.borrow(), GLOBAL_NAME_GAS) + .ok_or_else(|| Error::Sandbox("Failed to get gas global".into()))?; + + store + .borrow_mut() + .data_mut() + .replace(FuncEnv::new(store.clone(), gas_global)); + } + + SupervisorContextStore::using(supervisor_context, || { + function + .call(&mut *store.borrow_mut(), &args, &mut outputs) + .map_err(|error| Error::Sandbox(error.to_string())) + })?; match outputs.as_slice() { [] => Ok(None), From 2869fb8abe201f497e787be8f110e517c264dbf0 Mon Sep 17 00:00:00 2001 From: Roman Maslennikov Date: Wed, 2 Oct 2024 18:57:24 +0400 Subject: [PATCH 09/25] WIP6 --- sandbox/host/src/sandbox.rs | 9 --------- sandbox/host/src/sandbox/wasmi_backend.rs | 6 +++--- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/sandbox/host/src/sandbox.rs b/sandbox/host/src/sandbox.rs index 217e0ce16ae..9df60784f53 100644 --- a/sandbox/host/src/sandbox.rs +++ b/sandbox/host/src/sandbox.rs @@ -165,15 +165,6 @@ pub trait SupervisorContext { fn deallocate_memory(&mut self, ptr: Pointer) -> SandboxResult<()>; } -/// Implementation of [`Externals`] that allows execution of guest module with -/// [externals][`Externals`] that might refer functions defined by supervisor. -/// -/// [`Externals`]: ../wasmi/trait.Externals.html -pub struct GuestExternals<'a> { - /// Instance of sandboxed module to be dispatched - sandbox_instance: &'a SandboxInstance, -} - /// Module instance in terms of selected backend enum BackendInstanceBundle { /// Wasmi module instance diff --git a/sandbox/host/src/sandbox/wasmi_backend.rs b/sandbox/host/src/sandbox/wasmi_backend.rs index e615aa1f1c1..5f2d9bc0af0 100644 --- a/sandbox/host/src/sandbox/wasmi_backend.rs +++ b/sandbox/host/src/sandbox/wasmi_backend.rs @@ -162,7 +162,7 @@ pub fn new_memory( pub struct MemoryWrapper { memory: sandbox_wasmi::Memory, store: Rc, - //alloc: Allocation, + _alloc: Rc, } impl std::fmt::Debug for MemoryWrapper { @@ -175,11 +175,11 @@ impl std::fmt::Debug for MemoryWrapper { impl MemoryWrapper { /// Take ownership of the memory region and return a wrapper object - fn new(memory: sandbox_wasmi::Memory, store: Rc, _alloc: Allocation) -> Self { + fn new(memory: sandbox_wasmi::Memory, store: Rc, alloc: Allocation) -> Self { Self { memory, store, - //alloc, + _alloc: Rc::new(alloc), } } } From b4dcd1caa3b90e032389dadb446bd070ad537317 Mon Sep 17 00:00:00 2001 From: Roman Maslennikov Date: Wed, 2 Oct 2024 19:30:31 +0400 Subject: [PATCH 10/25] Switch sandbox to wasmi --- runtime-interface/sandbox/src/detail.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime-interface/sandbox/src/detail.rs b/runtime-interface/sandbox/src/detail.rs index c4f401d7f6b..3dbc1bca9f8 100644 --- a/runtime-interface/sandbox/src/detail.rs +++ b/runtime-interface/sandbox/src/detail.rs @@ -35,7 +35,7 @@ impl Sandboxes { pub fn new() -> Self { Self { store_data_key: 0, - store: sandbox_env::SandboxComponents::new(sandbox_env::SandboxBackend::Wasmer), + store: sandbox_env::SandboxComponents::new(sandbox_env::SandboxBackend::Wasmi), } } From 25620bdba0b264e323d348dc3544867f093893d2 Mon Sep 17 00:00:00 2001 From: Roman Maslennikov Date: Wed, 9 Oct 2024 21:23:59 +0400 Subject: [PATCH 11/25] Fix double borrow --- sandbox/host/src/sandbox/wasmi_backend.rs | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/sandbox/host/src/sandbox/wasmi_backend.rs b/sandbox/host/src/sandbox/wasmi_backend.rs index 5f2d9bc0af0..762c808bd0a 100644 --- a/sandbox/host/src/sandbox/wasmi_backend.rs +++ b/sandbox/host/src/sandbox/wasmi_backend.rs @@ -309,16 +309,12 @@ pub fn instantiate( .ok_or(InstantiationError::ModuleDecoding)?; let function = match version { - Instantiate::Version1 => dispatch_function( - supervisor_func_index, - &mut context.store().borrow_mut(), - func_ty, - ), - Instantiate::Version2 => dispatch_function_v2( - supervisor_func_index, - &mut context.store().borrow_mut(), - func_ty, - ), + Instantiate::Version1 => { + dispatch_function(supervisor_func_index, &mut store, func_ty) + } + Instantiate::Version2 => { + dispatch_function_v2(supervisor_func_index, &mut store, func_ty) + } }; linker From 4a65c7358860c4e9c2e87f84b71840da4f80f364 Mon Sep 17 00:00:00 2001 From: Roman Maslennikov Date: Thu, 10 Oct 2024 23:30:46 +0400 Subject: [PATCH 12/25] Fix wrong mem buffer size --- sandbox/host/src/sandbox/wasmi_backend.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/sandbox/host/src/sandbox/wasmi_backend.rs b/sandbox/host/src/sandbox/wasmi_backend.rs index 762c808bd0a..b3d1957f1a9 100644 --- a/sandbox/host/src/sandbox/wasmi_backend.rs +++ b/sandbox/host/src/sandbox/wasmi_backend.rs @@ -187,6 +187,7 @@ impl MemoryWrapper { impl MemoryTransfer for MemoryWrapper { fn read(&self, source_addr: Pointer, size: usize) -> error::Result> { let mut buffer = Vec::with_capacity(size); + unsafe { buffer.set_len(size) }; let ctx = self.store.borrow(); self.memory .read(&*ctx, source_addr.into(), &mut buffer) From 4adef59493671ad0498e56f39bcbc64d3246f0c4 Mon Sep 17 00:00:00 2001 From: Roman Maslennikov Date: Mon, 14 Oct 2024 22:19:59 +0400 Subject: [PATCH 13/25] Fix rc cycle --- sandbox/host/src/sandbox.rs | 11 ++++++++- sandbox/host/src/sandbox/wasmi_backend.rs | 29 ++++++++++------------- 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/sandbox/host/src/sandbox.rs b/sandbox/host/src/sandbox.rs index 9df60784f53..4db88ddc503 100644 --- a/sandbox/host/src/sandbox.rs +++ b/sandbox/host/src/sandbox.rs @@ -28,6 +28,7 @@ use std::{collections::HashMap, pin::Pin, rc::Rc}; use codec::Decode; use env::Instantiate; use gear_sandbox_env as sandbox_env; +use region::Allocation; use sp_wasm_interface_common::{Pointer, Value, WordSize}; use crate::{ @@ -524,6 +525,8 @@ pub struct SandboxComponents
{ instances: Vec>, DT)>>, /// Memories are `Some` until torn down. memories: Vec>, + /// Allocation used for the Wasmi memory. + allocations: Vec, backend_context: BackendContext, } @@ -533,6 +536,7 @@ impl SandboxComponents
{ SandboxComponents { instances: Vec::new(), memories: Vec::new(), + allocations: Vec::new(), backend_context: BackendContext::new(backend), } } @@ -558,6 +562,9 @@ impl SandboxComponents
{ self.backend_context = BackendContext::Wasmer(WasmerBackend::new()); } } + + // Clear allocations after store is dropped + self.allocations.clear(); } /// Create a new memory instance and return it's index. @@ -577,7 +584,9 @@ impl SandboxComponents
{ let memory = match &backend_context { BackendContext::Wasmi(backend) => { - wasmi_new_memory(backend.store().clone(), initial, maximum)? + let (memory, alloc) = wasmi_new_memory(backend.store().clone(), initial, maximum)?; + self.allocations.push(alloc); + memory } BackendContext::Wasmer(backend) => { diff --git a/sandbox/host/src/sandbox/wasmi_backend.rs b/sandbox/host/src/sandbox/wasmi_backend.rs index b3d1957f1a9..fafe684c9d6 100644 --- a/sandbox/host/src/sandbox/wasmi_backend.rs +++ b/sandbox/host/src/sandbox/wasmi_backend.rs @@ -18,7 +18,10 @@ //! Wasmi specific impls for sandbox -use std::{rc::Rc, slice}; +use std::{ + rc::{Rc, Weak}, + slice, +}; use codec::{Decode, Encode}; use gear_sandbox_env::{HostError, Instantiate, WasmReturnValue, GLOBAL_NAME_GAS}; @@ -48,12 +51,12 @@ pub type StoreRefCell = store_refcell::StoreRefCell; environmental::environmental!(SupervisorContextStore: trait SupervisorContext); pub struct FuncEnv { - store: Rc, + store: Weak, gas_global: sandbox_wasmi::Global, } impl FuncEnv { - pub fn new(store: Rc, gas_global: sandbox_wasmi::Global) -> Self { + pub fn new(store: Weak, gas_global: sandbox_wasmi::Global) -> Self { Self { store, gas_global } } } @@ -135,7 +138,7 @@ pub fn new_memory( store: Rc, initial: u32, maximum: Option, -) -> crate::error::Result { +) -> crate::error::Result<(Memory, Allocation)> { let ty = MemoryType::new(initial, maximum).map_err(|error| Error::Sandbox(error.to_string()))?; let mut alloc = region::alloc(u32::MAX as usize, Protection::READ_WRITE) @@ -145,14 +148,11 @@ pub fn new_memory( // `wasmi::Memory::new_static()` requires static lifetime so we convert our buffer to it // but actual lifetime of the buffer is lifetime of `Store` itself, // so memory will be deallocated when `Store` is dropped. - // - // Also, according to Rust drop order semantics, `wasmi::Store` will be dropped first and - // only then our allocated memories will be freed to ensure they are not used anymore. let raw = unsafe { slice::from_raw_parts_mut::<'static, u8>(alloc.as_mut_ptr(), alloc.len()) }; let memory = sandbox_wasmi::Memory::new_static(&mut *store.borrow_mut(), ty, raw) .map_err(|error| Error::Sandbox(error.to_string()))?; - Ok(Memory::Wasmi(MemoryWrapper::new(memory, store, alloc))) + Ok((Memory::Wasmi(MemoryWrapper::new(memory, store)), alloc)) } /// Wasmi provides direct access to its memory using slices. @@ -162,7 +162,6 @@ pub fn new_memory( pub struct MemoryWrapper { memory: sandbox_wasmi::Memory, store: Rc, - _alloc: Rc, } impl std::fmt::Debug for MemoryWrapper { @@ -175,12 +174,8 @@ impl std::fmt::Debug for MemoryWrapper { impl MemoryWrapper { /// Take ownership of the memory region and return a wrapper object - fn new(memory: sandbox_wasmi::Memory, store: Rc, alloc: Allocation) -> Self { - Self { - memory, - store, - _alloc: Rc::new(alloc), - } + fn new(memory: sandbox_wasmi::Memory, store: Rc) -> Self { + Self { memory, store } } } @@ -397,7 +392,7 @@ fn dispatch_function_v2( move |mut caller, params, results| -> Result<(), sandbox_wasmi::Error> { SupervisorContextStore::with(|supervisor_context| { let func_env = caller.data().as_ref().expect("func env should be set"); - let store_ref_cell = func_env.store.clone(); + let store_ref_cell = func_env.store.upgrade().expect("store should be alive"); let gas_global = func_env.gas_global; let gas = gas_global.get(caller.as_context()); @@ -543,7 +538,7 @@ pub fn invoke( store .borrow_mut() .data_mut() - .replace(FuncEnv::new(store.clone(), gas_global)); + .replace(FuncEnv::new(Rc::downgrade(store), gas_global)); } SupervisorContextStore::using(supervisor_context, || { From 6975f423361008fdb1f2b6088690b7337180e2c3 Mon Sep 17 00:00:00 2001 From: Roman Maslennikov Date: Mon, 14 Oct 2024 22:24:54 +0400 Subject: [PATCH 14/25] Add cli param --- node/cli/Cargo.toml | 1 + node/cli/src/cli.rs | 27 +++++++++++++++++++++++++ node/cli/src/command.rs | 10 ++++++++- node/cli/src/main.rs | 1 - runtime-interface/sandbox/src/detail.rs | 13 ++++++------ runtime-interface/sandbox/src/lib.rs | 2 +- runtime-interface/src/lib.rs | 4 +++- 7 files changed, 48 insertions(+), 10 deletions(-) diff --git a/node/cli/Cargo.toml b/node/cli/Cargo.toml index 3de1a1494d6..4fc0a0f4ce0 100644 --- a/node/cli/Cargo.toml +++ b/node/cli/Cargo.toml @@ -23,6 +23,7 @@ clap = { workspace = true, features = ["derive"] } mimalloc = { workspace = true, default-features = false } log = { workspace = true, features = ["std"] } futures.workspace = true +derive_more.workspace = true # Gear runtime-primitives.workspace = true diff --git a/node/cli/src/cli.rs b/node/cli/src/cli.rs index e5af588fc9c..0f1b0f40fba 100644 --- a/node/cli/src/cli.rs +++ b/node/cli/src/cli.rs @@ -17,6 +17,29 @@ // along with this program. If not, see . use clap::Parser; +use std::str::FromStr; + +#[allow(missing_docs)] +#[derive(Debug, Clone, Parser, derive_more::Display)] +pub enum SandboxBackend { + #[display(fmt = "wasmer")] + Wasmer, + #[display(fmt = "wasmi")] + Wasmi, +} + +// TODO: use `derive_more::FromStr` when derive_more dependency is updated to 1.0 +impl FromStr for SandboxBackend { + type Err = String; + + fn from_str(s: &str) -> Result { + match s.to_lowercase().as_str() { + "wasmer" => Ok(SandboxBackend::Wasmer), + "wasmi" => Ok(SandboxBackend::Wasmi), + _ => Err(format!("Unknown sandbox executor: {}", s)), + } + } +} #[allow(missing_docs)] #[derive(Debug, Parser)] @@ -26,6 +49,10 @@ pub struct RunCmd { #[command(flatten)] pub base: sc_cli::RunCmd, + /// The Wasm host executor to use in program sandbox. + #[arg(long, default_value_t = SandboxBackend::Wasmer)] + pub sandbox_backend: SandboxBackend, + /// The upper limit for the amount of gas a validator can burn in one block. #[arg(long)] pub max_gas: Option, diff --git a/node/cli/src/command.rs b/node/cli/src/command.rs index 3b1e57182cc..19eb0d854a7 100644 --- a/node/cli/src/command.rs +++ b/node/cli/src/command.rs @@ -16,7 +16,10 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use crate::cli::{Cli, Subcommand}; +use crate::{ + cli::{Cli, Subcommand}, + SandboxBackend, +}; use runtime_primitives::Block; use sc_cli::{ChainSpec, SubstrateCli}; use sc_service::config::BasePath; @@ -130,6 +133,11 @@ macro_rules! unwrap_client { pub fn run() -> sc_cli::Result<()> { let cli = Cli::from_args(); + gear_runtime_interface::sandbox_init(match cli.run.sandbox_backend { + SandboxBackend::Wasmer => gear_runtime_interface::SandboxBackend::Wasmer, + SandboxBackend::Wasmi => gear_runtime_interface::SandboxBackend::Wasmi, + }); + let old_base = BasePath::from_project("", "", "gear-node"); let new_base = BasePath::from_project("", "", &Cli::executable_name()); if old_base.path().exists() && !new_base.path().exists() { diff --git a/node/cli/src/main.rs b/node/cli/src/main.rs index 7830fb5cfbb..93e1c2326e3 100644 --- a/node/cli/src/main.rs +++ b/node/cli/src/main.rs @@ -17,6 +17,5 @@ // along with this program. If not, see . fn main() -> gear_cli::Result<()> { - gear_runtime_interface::sandbox_init(); gear_cli::run() } diff --git a/runtime-interface/sandbox/src/detail.rs b/runtime-interface/sandbox/src/detail.rs index 3dbc1bca9f8..cf530db42bb 100644 --- a/runtime-interface/sandbox/src/detail.rs +++ b/runtime-interface/sandbox/src/detail.rs @@ -32,10 +32,10 @@ struct Sandboxes { } impl Sandboxes { - pub fn new() -> Self { + pub fn new(sandbox_backend: sandbox_env::SandboxBackend) -> Self { Self { store_data_key: 0, - store: sandbox_env::SandboxComponents::new(sandbox_env::SandboxBackend::Wasmi), + store: sandbox_env::SandboxComponents::new(sandbox_backend), } } @@ -62,12 +62,13 @@ impl Sandboxes { } thread_local! { - static SANDBOXES: RefCell = RefCell::new(Sandboxes::new()); + static SANDBOXES: RefCell = panic!("Sandbox not initialized"); } -pub fn init() { - SANDBOXES.with(|sandboxes| { - let _store = sandboxes.borrow_mut().get(0); +pub fn init(sandbox_backend: sandbox_env::SandboxBackend) { + SANDBOXES.set(Sandboxes::new(sandbox_backend)); + SANDBOXES.with_borrow_mut(|sandboxes| { + let _store = sandboxes.get(0); }) } diff --git a/runtime-interface/sandbox/src/lib.rs b/runtime-interface/sandbox/src/lib.rs index 7d15ef14fcd..4830f369266 100644 --- a/runtime-interface/sandbox/src/lib.rs +++ b/runtime-interface/sandbox/src/lib.rs @@ -21,7 +21,7 @@ #![cfg_attr(not(feature = "std"), no_std)] #[cfg(feature = "std")] -pub use gear_sandbox_host::sandbox::env::Instantiate; +pub use gear_sandbox_host::sandbox::{env::Instantiate, SandboxBackend}; use sp_runtime_interface::{runtime_interface, Pointer}; use sp_wasm_interface::HostPointer; diff --git a/runtime-interface/src/lib.rs b/runtime-interface/src/lib.rs index 4c099d6f685..4eb02cc090c 100644 --- a/runtime-interface/src/lib.rs +++ b/runtime-interface/src/lib.rs @@ -50,7 +50,9 @@ use { pub use gear_sandbox_interface::sandbox; #[cfg(feature = "std")] -pub use gear_sandbox_interface::{detail as sandbox_detail, init as sandbox_init, Instantiate}; +pub use gear_sandbox_interface::{ + detail as sandbox_detail, init as sandbox_init, Instantiate, SandboxBackend, +}; const _: () = assert!(size_of::() >= size_of::()); From 9e4ad109718b384e2cc6f898771873e6914d5072 Mon Sep 17 00:00:00 2001 From: Roman Maslennikov Date: Mon, 14 Oct 2024 22:36:53 +0400 Subject: [PATCH 15/25] Fix clippy --- sandbox/host/src/sandbox.rs | 1 - sandbox/host/src/sandbox/wasmer_backend.rs | 1 - .../sandbox/wasmer_backend/store_refcell.rs | 349 --------------- sandbox/host/src/sandbox/wasmi_backend.rs | 7 +- sandbox/host/src/sandbox/wasmi_backend_old.rs | 410 ------------------ 5 files changed, 5 insertions(+), 763 deletions(-) delete mode 100644 sandbox/host/src/sandbox/wasmer_backend/store_refcell.rs delete mode 100644 sandbox/host/src/sandbox/wasmi_backend_old.rs diff --git a/sandbox/host/src/sandbox.rs b/sandbox/host/src/sandbox.rs index 4db88ddc503..3d9090d2be6 100644 --- a/sandbox/host/src/sandbox.rs +++ b/sandbox/host/src/sandbox.rs @@ -201,7 +201,6 @@ enum BackendInstanceBundle { /// [`invoke`]: #method.invoke pub struct SandboxInstance { backend_instance: BackendInstanceBundle, - guest_to_supervisor_mapping: GuestToSupervisorFunctionMapping, } impl SandboxInstance { diff --git a/sandbox/host/src/sandbox/wasmer_backend.rs b/sandbox/host/src/sandbox/wasmer_backend.rs index ee49ddffc50..3887d2d9b6f 100644 --- a/sandbox/host/src/sandbox/wasmer_backend.rs +++ b/sandbox/host/src/sandbox/wasmer_backend.rs @@ -301,7 +301,6 @@ pub fn instantiate( instance, store: context.store().clone(), }, - guest_to_supervisor_mapping: guest_env.guest_to_supervisor_mapping, }) } diff --git a/sandbox/host/src/sandbox/wasmer_backend/store_refcell.rs b/sandbox/host/src/sandbox/wasmer_backend/store_refcell.rs deleted file mode 100644 index 1f2f86abc4b..00000000000 --- a/sandbox/host/src/sandbox/wasmer_backend/store_refcell.rs +++ /dev/null @@ -1,349 +0,0 @@ -// This file is part of Gear. - -// Copyright (C) Gear Technologies Inc. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -//! # Description -//! -//! Custom implementation of `RefCell` for the `wasmer::Store` type, -//! enabling safe repeated mutable borrowing of `StoreRefCell` higher up the call stack -//! when the mutable borrow of `StoreRefCell` still exists. -//! -//! Example illustrating functionality in terms of `RefCell` from the standard library: -//! -//! At first we borrow store mutably: -//! -//! ```ignore -//! let refcell = RefCell::new(Store::default()); -//! let mut_borrow = refcell.borrow_mut(); -//! -//! func(&refcell, &mut mut_borrow); -//! ``` -//! -//! Now we need to borrow store mutably again inside `func`, -//! but we can't do it because `mut_borrow` still exists. -//! -//! ```ignore -//! fn func(ref_cell: &RefCell, mut_borrow: &mut Store) { -//! ref_cell.borrow_mut(); // This will panic -//! } -//! ``` -//! -//! With `StoreRefCell` we can do it safely: -//! -//! ```ignore -//! fn func(store_refcell: &StoreRefCell, mut_borrow: &mut Store) { -//! store_refcell.borrow_scope(mut_borrow, || { -//! // Now we can borrow store again -//! let second_mut_borrow = store_refcell.borrow_mut(); -//! }); -//! } -//! ``` -//! -//! # Why is this necessary? Can't we do without repeated mutable borrowing? -//! -//! The issue arises because when handling syscalls within an instance of a program running in Wasmer, -//! a runtime interface call occurs, leading to a situation where we have two nested runtime interface calls. -//! The first call `sandbox::invoke` initiates the program execution, the second occurs during the syscall processing. -//! -//! Thus, the call stack at the highest point looks like this: -//! -//! ```text -//! ----------------------------------- -//! | Memory::write | Write sandbox memory (Borrows Store mutably) -//! ---------native boundary----------- -//! | sandbox::memory_set | Runtime on behalf of processing syscall make a call to runtime interface -//! ----------------------------------- -//! | runtime executes syscall | -//! --------runtime boundary----------- -//! | syscall_callback | Wasmer calls syscall callback from inside his VM -//! ----------------------------------- -//! | Wasmer's Func::call | Sandbox starts to executes program function (Borrows Store mutably) -//! -------native boundary----------- | -//! | sandbox::invoke | Runtime interface call -//! ----------------------------------- -//! ``` -//! -//! As we can see, the `sandbox::invoke` function borrows the store mutably, -//! and then the `sandbox::memory_set` runtime interface call borrows the store mutably again. -//! -//! Therefore, since it is not possible to pass a reference to Store through nested runtime interface call -//! or cancel previous mutable borrow, it is necessary to use `StoreRefCell` for safe repeated mutable borrowing of `Store`. -//! - -use std::{ - cell::{Cell, UnsafeCell}, - num::NonZero, - ops::{Deref, DerefMut}, - ptr::NonNull, -}; - -use defer::defer; -use wasmer::StoreMut; - -#[derive(Debug, Clone, Copy)] -enum BorrowState { - Shared(NonZero), - Mutable, - NonShared, -} - -/// Custom implementation of `RefCell` which allows to safely borrow store -/// mutably/immutably second time inside the scope. -#[derive(Debug)] -pub struct StoreRefCell { - store: UnsafeCell, - state: Cell, -} - -trait GenericAsStoreMut {} - -impl<'r, 's> GenericAsStoreMut for &'r mut StoreMut<'s> {} - -#[derive(Debug)] -pub struct BorrowScopeError; - -impl StoreRefCell { - /// Create new `StoreRefCell` with provided `Store` - pub fn new(store: S) -> Self { - Self { - store: UnsafeCell::new(store), - state: Cell::new(BorrowState::NonShared), - } - } - - /// Borrow store immutably, same semantics as `RefCell::borrow` - #[track_caller] - pub fn borrow(&self) -> Ref<'_, S> { - match self.state.get() { - BorrowState::Shared(n) => { - self.state.set(BorrowState::Shared( - NonZero::::new(n.get() + 1).expect("non zero"), - )); - } - BorrowState::NonShared => { - self.state.set(BorrowState::Shared( - NonZero::::new(1).expect("non zero"), - )); - } - BorrowState::Mutable => { - panic!("store already borrowed mutably"); - } - } - - Ref { - store: NonNull::new(self.store.get()).expect("non null"), - state: &self.state, - } - } - - /// Borrow store mutably, same semantics as `RefCell::borrow_mut` - #[track_caller] - pub fn borrow_mut(&self) -> RefMut<'_, S> { - match self.state.get() { - BorrowState::NonShared => { - self.state.set(BorrowState::Mutable); - } - BorrowState::Shared(_) | BorrowState::Mutable => { - panic!("store already borrowed"); - } - } - - RefMut { - store: NonNull::new(self.store.get()).expect("non null"), - state: &self.state, - } - } - - /// Provide borrow scope where store can be borrowed mutably second time safely (or borrowed immutably multiple times). - #[allow(private_bounds)] - pub fn borrow_scope R>( - &self, - store: impl GenericAsStoreMut, - f: F, - ) -> Result { - // We expect the same store - //debug_assert!( - // self.compare_stores(store.as_store_ref()), - // "stores are different" - //); - - // Caller just returned borrowed mutably reference to the store, now we can safely borrow it mutably again - let _store = store; - - // We received a mutable borrow, so other states shouldn't be possible - if let BorrowState::Shared(_) | BorrowState::NonShared = self.state.get() { - return Err(BorrowScopeError); - } - - self.state.set(BorrowState::NonShared); - - let result = f(); - - // We expect that after scope ends, store won't be borrowed - debug_assert!(matches!(self.state.get(), BorrowState::NonShared)); - - // Restore previous state after scope ends - defer!(self.state.set(BorrowState::Mutable)); - - Ok(result) - } - - //#[allow(unused)] - //fn compare_stores(&self, returned_store: StoreRef) -> bool { - // // SAFETY: - // // Verified with Miri, it seems safe. - // // Carefully compare the stores while don't using/holding mutable references to them in the same time. - // let orig_store_ref: StoreRef = unsafe { &*self.store.get() }.as_store_ref(); - - // StoreRef::same(&orig_store_ref, &returned_store) - //} - - /// Returns store ptr, same semantics as `RefCell::as_ptr` - pub unsafe fn as_ptr(&self) -> *mut S { - self.store.get() - } -} - -pub struct Ref<'b, S> { - store: NonNull, - state: &'b Cell, -} - -impl Deref for Ref<'_, S> { - type Target = S; - - #[inline] - fn deref(&self) -> &Self::Target { - // SAFETY: we ensure that store isn't borrowed mutably before - unsafe { self.store.as_ref() } - } -} - -impl Drop for Ref<'_, S> { - fn drop(&mut self) { - match self.state.get() { - BorrowState::Shared(n) if n.get() == 1 => { - self.state.set(BorrowState::NonShared); - } - BorrowState::Shared(n) => { - self.state.set(BorrowState::Shared( - NonZero::::new(n.get() - 1).expect("non zero"), - )); - } - _ => unreachable!(), - } - } -} - -pub struct RefMut<'b, S> { - store: NonNull, - state: &'b Cell, -} - -impl<'a, S> Deref for RefMut<'a, S> { - type Target = S; - - #[inline] - fn deref(&self) -> &Self::Target { - // SAFETY: we ensure that store isn't borrowed before - unsafe { self.store.as_ref() } - } -} - -impl DerefMut for RefMut<'_, S> { - #[inline] - fn deref_mut(&mut self) -> &mut Self::Target { - // SAFETY: we ensure that store isn't borrowed before - unsafe { self.store.as_mut() } - } -} - -impl Drop for RefMut<'_, S> { - fn drop(&mut self) { - match self.state.get() { - BorrowState::Mutable => { - self.state.set(BorrowState::NonShared); - } - _ => unreachable!(), - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use std::rc::Rc; - - struct Store; - impl<'r> GenericAsStoreMut for &'r mut Store {} - - #[test] - fn test_store_refcell_borrow() { - let store = Store; - let store_refcell = StoreRefCell::new(store); - - { - let _borrow = store_refcell.borrow(); - let _borrow = store_refcell.borrow(); - } - { - let _borrow = store_refcell.borrow_mut(); - } - { - let _borrow = store_refcell.borrow(); - let _borrow = store_refcell.borrow(); - } - } - - #[test] - fn test_store_refcell_borrow_scope() { - struct Env { - store: Rc>, - } - - let store = Store; - let rc = Rc::new(StoreRefCell::new(store)); - let env = Env { store: rc.clone() }; - - let callback = |env: Env, storemut: &mut Store| { - // do something with `storemut` - // .. - - let rc = rc.clone(); - let _ = env.store.borrow_scope(storemut, move || { - // Callback is called and it allowed to borrow store mutably/immutably - { - let _borrow = rc.borrow_mut(); - } - { - let _borrow = rc.borrow(); - let _borrow = rc.borrow(); - } - { - let _borrow = rc.borrow_mut(); - } - }); - - // do something with `storemut` - // .. - let _ = storemut; - }; - - let mut borrow = rc.borrow_mut(); - callback(env, &mut borrow) - } -} diff --git a/sandbox/host/src/sandbox/wasmi_backend.rs b/sandbox/host/src/sandbox/wasmi_backend.rs index fafe684c9d6..106ad48dd29 100644 --- a/sandbox/host/src/sandbox/wasmi_backend.rs +++ b/sandbox/host/src/sandbox/wasmi_backend.rs @@ -182,7 +182,11 @@ impl MemoryWrapper { impl MemoryTransfer for MemoryWrapper { fn read(&self, source_addr: Pointer, size: usize) -> error::Result> { let mut buffer = Vec::with_capacity(size); - unsafe { buffer.set_len(size) }; + let spare_cap = buffer.spare_capacity_mut().len(); + // # Safety: + // `Vec::set_len` is safe to call because we have just allocated enough space and never read from it. + unsafe { buffer.set_len(spare_cap) }; + let ctx = self.store.borrow(); self.memory .read(&*ctx, source_addr.into(), &mut buffer) @@ -334,7 +338,6 @@ pub fn instantiate( instance, store: context.store().clone(), }, - guest_to_supervisor_mapping: guest_env.guest_to_supervisor_mapping, }) } diff --git a/sandbox/host/src/sandbox/wasmi_backend_old.rs b/sandbox/host/src/sandbox/wasmi_backend_old.rs deleted file mode 100644 index eccab3efef2..00000000000 --- a/sandbox/host/src/sandbox/wasmi_backend_old.rs +++ /dev/null @@ -1,410 +0,0 @@ -// This file is part of Gear. - -// Copyright (C) Gear Technologies Inc. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -//! Wasmi specific impls for sandbox - -use std::fmt; - -use codec::{Decode, Encode}; -use gear_sandbox_env::HostError; -use sandbox_wasmi::{ - memory_units::Pages, ImportResolver, MemoryInstance, Module, ModuleInstance, RuntimeArgs, - RuntimeValue, Trap, TrapCode, -}; -use sp_wasm_interface_common::{util, Pointer, ReturnValue, Value, WordSize}; - -use crate::{ - error::{self, Error}, - sandbox::{ - BackendInstanceBundle, GuestEnvironment, GuestExternals, GuestFuncIndex, Imports, - InstantiationError, Memory, SandboxInstance, SupervisorContext, - }, - util::MemoryTransfer, -}; - -environmental::environmental!(SupervisorContextStore: trait SupervisorContext); - -#[derive(Debug)] -struct CustomHostError(String); - -impl fmt::Display for CustomHostError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "HostError: {}", self.0) - } -} - -impl sandbox_wasmi::HostError for CustomHostError {} - -/// Construct trap error from specified message -fn trap(msg: &'static str) -> Trap { - Trap::host(CustomHostError(msg.into())) -} - -impl ImportResolver for Imports { - fn resolve_func( - &self, - module_name: &str, - field_name: &str, - signature: &sandbox_wasmi::Signature, - ) -> std::result::Result { - let idx = self.func_by_name(module_name, field_name).ok_or_else(|| { - sandbox_wasmi::Error::Instantiation(format!( - "Export {}:{} not found", - module_name, field_name - )) - })?; - - Ok(sandbox_wasmi::FuncInstance::alloc_host( - signature.clone(), - idx.0, - )) - } - - fn resolve_memory( - &self, - module_name: &str, - field_name: &str, - _memory_type: &sandbox_wasmi::MemoryDescriptor, - ) -> std::result::Result { - let mem = self - .memory_by_name(module_name, field_name) - .ok_or_else(|| { - sandbox_wasmi::Error::Instantiation(format!( - "Export {}:{} not found", - module_name, field_name - )) - })?; - - let wrapper = mem.as_wasmi().ok_or_else(|| { - sandbox_wasmi::Error::Instantiation(format!( - "Unsupported non-wasmi export {}:{}", - module_name, field_name - )) - })?; - - // Here we use inner memory reference only to resolve the imports - // without accessing the memory contents. All subsequent memory accesses - // should happen through the wrapper, that enforces the memory access protocol. - let mem = wrapper.0; - - Ok(mem) - } - - fn resolve_global( - &self, - module_name: &str, - field_name: &str, - _global_type: &sandbox_wasmi::GlobalDescriptor, - ) -> std::result::Result { - Err(sandbox_wasmi::Error::Instantiation(format!( - "Export {}:{} not found", - module_name, field_name - ))) - } - - fn resolve_table( - &self, - module_name: &str, - field_name: &str, - _table_type: &sandbox_wasmi::TableDescriptor, - ) -> std::result::Result { - Err(sandbox_wasmi::Error::Instantiation(format!( - "Export {}:{} not found", - module_name, field_name - ))) - } -} - -/// Allocate new memory region -pub fn new_memory(initial: u32, maximum: Option) -> crate::error::Result { - let memory = Memory::Wasmi(MemoryWrapper::new( - MemoryInstance::alloc(Pages(initial as usize), maximum.map(|m| Pages(m as usize))) - .map_err(|error| Error::Sandbox(error.to_string()))?, - )); - - Ok(memory) -} - -/// Wasmi provides direct access to its memory using slices. -/// -/// This wrapper limits the scope where the slice can be taken to -#[derive(Debug, Clone)] -pub struct MemoryWrapper(sandbox_wasmi::MemoryRef); - -impl MemoryWrapper { - /// Take ownership of the memory region and return a wrapper object - fn new(memory: sandbox_wasmi::MemoryRef) -> Self { - Self(memory) - } -} - -impl MemoryTransfer for MemoryWrapper { - fn read(&self, source_addr: Pointer, size: usize) -> error::Result> { - self.0.with_direct_access(|source| { - let range = util::checked_range(source_addr.into(), size, source.len()) - .ok_or_else(|| error::Error::Other("memory read is out of bounds".into()))?; - - Ok(Vec::from(&source[range])) - }) - } - - fn read_into(&self, source_addr: Pointer, destination: &mut [u8]) -> error::Result<()> { - self.0.with_direct_access(|source| { - let range = util::checked_range(source_addr.into(), destination.len(), source.len()) - .ok_or_else(|| error::Error::Other("memory read is out of bounds".into()))?; - - destination.copy_from_slice(&source[range]); - Ok(()) - }) - } - - fn write_from(&self, dest_addr: Pointer, source: &[u8]) -> error::Result<()> { - self.0.with_direct_access_mut(|destination| { - let range = util::checked_range(dest_addr.into(), source.len(), destination.len()) - .ok_or_else(|| error::Error::Other("memory write is out of bounds".into()))?; - - destination[range].copy_from_slice(source); - Ok(()) - }) - } - - fn memory_grow(&mut self, pages: u32) -> error::Result { - self.0 - .grow(Pages(pages as usize)) - .map_err(|e| { - Error::Sandbox(format!( - "Cannot grow memory in masmi sandbox executor: {}", - e - )) - }) - .map(|p| p.0 as u32) - } - - fn memory_size(&mut self) -> u32 { - self.0.current_size().0 as u32 - } - - fn get_buff(&mut self) -> *mut u8 { - self.0.direct_access_mut().as_mut().as_mut_ptr() - } -} - -impl<'a> sandbox_wasmi::Externals for GuestExternals<'a> { - fn invoke_index( - &mut self, - index: usize, - args: RuntimeArgs, - ) -> std::result::Result, Trap> { - SupervisorContextStore::with(|supervisor_context| { - // Make `index` typesafe again. - let index = GuestFuncIndex(index); - - // Convert function index from guest to supervisor space - let func_idx = self.sandbox_instance - .guest_to_supervisor_mapping - .func_by_guest_index(index) - .expect( - "`invoke_index` is called with indexes registered via `FuncInstance::alloc_host`; - `FuncInstance::alloc_host` is called with indexes that were obtained from `guest_to_supervisor_mapping`; - `func_by_guest_index` called with `index` can't return `None`; - qed" - ); - - // Serialize arguments into a byte vector. - let invoke_args_data: Vec = args - .as_ref() - .iter() - .cloned() - .map(Value::from) - .collect::>() - .encode(); - - // Move serialized arguments inside the memory, invoke dispatch thunk and - // then free allocated memory. - let invoke_args_len = invoke_args_data.len() as WordSize; - let invoke_args_ptr = supervisor_context - .allocate_memory(invoke_args_len) - .map_err(|_| trap("Can't allocate memory in supervisor for the arguments"))?; - - let deallocate = |supervisor_context: &mut dyn SupervisorContext, ptr, fail_msg| { - supervisor_context.deallocate_memory(ptr).map_err(|_| trap(fail_msg)) - }; - - if supervisor_context - .write_memory(invoke_args_ptr, &invoke_args_data) - .is_err() - { - deallocate( - supervisor_context, - invoke_args_ptr, - "Failed deallocation after failed write of invoke arguments", - )?; - return Err(trap("Can't write invoke args into memory")) - } - - let result = supervisor_context.invoke( - invoke_args_ptr, - invoke_args_len, - func_idx, - ); - - deallocate( - supervisor_context, - invoke_args_ptr, - "Can't deallocate memory for dispatch thunk's invoke arguments", - )?; - let result = result?; - - // dispatch_thunk returns pointer to serialized arguments. - // Unpack pointer and len of the serialized result data. - let (serialized_result_val_ptr, serialized_result_val_len) = { - // Cast to u64 to use zero-extension. - let v = result as u64; - let ptr = (v >> 32) as u32; - let len = (v & 0xFFFFFFFF) as u32; - (Pointer::new(ptr), len) - }; - - let serialized_result_val = supervisor_context - .read_memory(serialized_result_val_ptr, serialized_result_val_len) - .map_err(|_| trap("Can't read the serialized result from dispatch thunk")); - - deallocate( - supervisor_context, - serialized_result_val_ptr, - "Can't deallocate memory for dispatch thunk's result", - ) - .and(serialized_result_val) - .and_then(|serialized_result_val| { - let result_val = std::result::Result::::decode(&mut serialized_result_val.as_slice()) - .map_err(|_| trap("Decoding Result failed!"))?; - - match result_val { - Ok(return_value) => Ok(match return_value { - ReturnValue::Unit => None, - ReturnValue::Value(typed_value) => Some(From::from(typed_value)), - }), - Err(HostError) => Err(trap("Supervisor function returned sandbox::HostError")), - } - }) - }).expect("SandboxContextStore is set when invoking sandboxed functions; qed") - } -} - -fn with_guest_externals(sandbox_instance: &SandboxInstance, f: F) -> R -where - F: FnOnce(&mut GuestExternals) -> R, -{ - f(&mut GuestExternals { sandbox_instance }) -} - -/// Instantiate a module within a sandbox context -pub fn instantiate( - wasm: &[u8], - guest_env: GuestEnvironment, - supervisor_context: &mut dyn SupervisorContext, -) -> std::result::Result { - let wasmi_module = Module::from_buffer(wasm).map_err(|_| InstantiationError::ModuleDecoding)?; - let wasmi_instance = ModuleInstance::new(&wasmi_module, &guest_env.imports) - .map_err(|_| InstantiationError::Instantiation)?; - - let sandbox_instance = SandboxInstance { - // In general, it's not a very good idea to use `.not_started_instance()` for - // anything but for extracting memory and tables. But in this particular case, we - // are extracting for the purpose of running `start` function which should be ok. - backend_instance: BackendInstanceBundle::Wasmi( - wasmi_instance.not_started_instance().clone(), - ), - guest_to_supervisor_mapping: guest_env.guest_to_supervisor_mapping, - }; - - with_guest_externals(&sandbox_instance, |guest_externals| { - SupervisorContextStore::using(supervisor_context, || { - wasmi_instance - .run_start(guest_externals) - .map_err(|_| InstantiationError::StartTrapped) - }) - })?; - - Ok(sandbox_instance) -} - -/// Invoke a function within a sandboxed module -pub fn invoke( - instance: &SandboxInstance, - module: &sandbox_wasmi::ModuleRef, - export_name: &str, - args: &[Value], - supervisor_context: &mut dyn SupervisorContext, -) -> std::result::Result, error::Error> { - with_guest_externals(instance, |guest_externals| { - SupervisorContextStore::using(supervisor_context, || { - let args = args.iter().cloned().map(From::from).collect::>(); - - module - .invoke_export(export_name, &args, guest_externals) - .map(|result| result.map(Into::into)) - .map_err(|error| { - if matches!(error, sandbox_wasmi::Error::Trap(Trap::Code(TrapCode::StackOverflow))) { - // Panic stops process queue execution in that case. - // This allows to avoid error lead to consensus failures, that must be handled - // in node binaries forever. If this panic occur, then we must increase stack memory size, - // or tune stack limit injection. - // see also https://github.com/wasmerio/wasmer/issues/4181 - let err_msg = format!( - "invoke: Suppose that this can not happen, because we have a stack limit instrumentation in programs. \ - Export name - {export_name}, args - {args:?}", - ); - - log::error!("{err_msg}"); - unreachable!("{err_msg}") - } - error::Error::Sandbox(error.to_string()) - }) - }) - }) -} - -/// Get global value by name -pub fn get_global(instance: &sandbox_wasmi::ModuleRef, name: &str) -> Option { - Some(Into::into( - instance.export_by_name(name)?.as_global()?.get(), - )) -} - -/// Set global value by name -pub fn set_global( - instance: &sandbox_wasmi::ModuleRef, - name: &str, - value: Value, -) -> std::result::Result, error::Error> { - let export = match instance.export_by_name(name) { - Some(e) => e, - None => return Ok(None), - }; - - let global = match export.as_global() { - Some(g) => g, - None => return Ok(None), - }; - - global - .set(From::from(value)) - .map(|_| Some(())) - .map_err(error::Error::Wasmi) -} From 4bd475b56b2932d19d2cec4eb8ae04db172564b8 Mon Sep 17 00:00:00 2001 From: Roman Maslennikov Date: Tue, 15 Oct 2024 11:42:11 +0400 Subject: [PATCH 16/25] Update doc --- sandbox/host/src/store_refcell.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/sandbox/host/src/store_refcell.rs b/sandbox/host/src/store_refcell.rs index 6951bb19d47..a5f20476205 100644 --- a/sandbox/host/src/store_refcell.rs +++ b/sandbox/host/src/store_refcell.rs @@ -18,7 +18,7 @@ //! # Description //! -//! Custom implementation of `RefCell` for the `wasmer::Store` type, +//! Custom implementation of `RefCell` for the `wasmer::Store`/`wasmi::Store` types, //! enabling safe repeated mutable borrowing of `StoreRefCell` higher up the call stack //! when the mutable borrow of `StoreRefCell` still exists. //! @@ -55,7 +55,7 @@ //! //! # Why is this necessary? Can't we do without repeated mutable borrowing? //! -//! The issue arises because when handling syscalls within an instance of a program running in Wasmer, +//! The issue arises because when handling syscalls within an instance of a program running in the sandbox, //! a runtime interface call occurs, leading to a situation where we have two nested runtime interface calls. //! The first call `sandbox::invoke` initiates the program execution, the second occurs during the syscall processing. //! @@ -69,7 +69,7 @@ //! ----------------------------------- //! | runtime executes syscall | //! --------runtime boundary----------- -//! | syscall_callback | Wasmer calls syscall callback from inside his VM +//! | syscall_callback | Wasmer/Wasmi calls syscall callback from inside its VM //! ----------------------------------- //! | Wasmer's Func::call | Sandbox starts to executes program function (Borrows Store mutably) //! -------native boundary----------- | @@ -83,7 +83,6 @@ //! Therefore, since it is not possible to pass a reference to Store through nested runtime interface call //! or cancel previous mutable borrow, it is necessary to use `StoreRefCell` for safe repeated mutable borrowing of `Store`. //! - use std::{ cell::{Cell, UnsafeCell}, num::NonZeroUsize, From f9795f41fd089029bff134a2dfff48af37feb207 Mon Sep 17 00:00:00 2001 From: Roman Maslennikov Date: Tue, 15 Oct 2024 12:09:42 +0400 Subject: [PATCH 17/25] Unify wasmi dep --- Cargo.lock | 19 ++++++++++--------- Cargo.toml | 4 +--- ethexe/processor/src/host/mod.rs | 2 +- node/authorship/src/tests.rs | 2 +- sandbox/host/Cargo.toml | 2 +- utils/gear-replay-cli/src/cmd/mod.rs | 2 +- utils/lazy-pages-fuzzer/Cargo.toml | 3 +-- 7 files changed, 16 insertions(+), 18 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3d836535967..8a65e281343 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6464,6 +6464,7 @@ name = "gear-cli" version = "1.6.2" dependencies = [ "clap 4.5.9", + "derive_more 0.99.18", "frame-benchmarking", "frame-benchmarking-cli", "frame-system", @@ -6895,7 +6896,7 @@ dependencies = [ "wasmer", "wasmer-cache", "wasmer-types", - "wasmi 0.36.2", + "wasmi 0.36.5", ] [[package]] @@ -9018,7 +9019,7 @@ dependencies = [ "log", "region", "wasmer", - "wasmi 0.36.1", + "wasmi 0.36.5", "wasmprinter", "wat", ] @@ -19534,9 +19535,9 @@ dependencies = [ [[package]] name = "wasmi" -version = "0.36.1" +version = "0.36.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81eacbefcfb4fc0d0af5424752e10895a131e1d0edb4b87554aac024bd294bdd" +checksum = "446ddc18185880ff32de907a809864283d18f9de568c23d46464b6231973d595" dependencies = [ "arrayvec 0.7.4", "multi-stash", @@ -19545,7 +19546,7 @@ dependencies = [ "smallvec", "spin 0.9.8", "wasmi_collections", - "wasmi_core 0.36.1", + "wasmi_core 0.36.5", "wasmparser-nostd", ] @@ -19570,9 +19571,9 @@ checksum = "104a7f73be44570cac297b3035d76b169d6599637631cf37a1703326a0727073" [[package]] name = "wasmi_collections" -version = "0.36.1" +version = "0.36.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d1ff23df2c456c8b5d9a0ae7eed03a40f0c4520466b4aa87135c5fc557476e8" +checksum = "8eddc10bfb0069e913399ebd66c5a72c7d9aceabddcaa0296f062a55ab61d404" dependencies = [ "ahash 0.8.11", "hashbrown 0.14.5", @@ -19618,9 +19619,9 @@ dependencies = [ [[package]] name = "wasmi_core" -version = "0.36.1" +version = "0.36.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac1b21ded145eb313d44a5895442c28e18904fb95718dc83893779f55945d342" +checksum = "f08b12621457c17cfd5349cce25029eeac3769b63b1b02bd850d595a00f375ff" dependencies = [ "downcast-rs", "libm", diff --git a/Cargo.toml b/Cargo.toml index c0ce76527e5..e6d42ab03d4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -303,9 +303,7 @@ ethexe-rpc = { path = "ethexe/rpc", default-features = false } ethexe-common = { path = "ethexe/common" } # Common executor between `sandbox-host` and `lazy-pages-fuzzer` -sandbox-wasmi = { package = "wasmi", git = "https://github.com/gear-tech/wasmi", branch = "v0.13.2-sign-ext", features = [ - "virtual_memory", -] } +sandbox-wasmi = { package = "wasmi", version = "0.36"} # Substrate deps binary-merkle-tree = { version = "4.0.0-dev", git = "https://github.com/gear-tech/polkadot-sdk.git", branch = "gear-v1.4.0", default-features = false } diff --git a/ethexe/processor/src/host/mod.rs b/ethexe/processor/src/host/mod.rs index d28d28820c6..30f4e7d45e5 100644 --- a/ethexe/processor/src/host/mod.rs +++ b/ethexe/processor/src/host/mod.rs @@ -54,7 +54,7 @@ pub(crate) struct InstanceCreator { impl InstanceCreator { pub fn new(runtime: Vec) -> Result { - gear_runtime_interface::sandbox_init(); + gear_runtime_interface::sandbox_init(gear_runtime_interface::SandboxBackend::Wasmer); let engine = wasmtime::Engine::default(); diff --git a/node/authorship/src/tests.rs b/node/authorship/src/tests.rs index dd1b69705bb..4a6cb02b3fb 100644 --- a/node/authorship/src/tests.rs +++ b/node/authorship/src/tests.rs @@ -329,7 +329,7 @@ type TestCase = Box; #[test] fn run_all_tests() { init_logger(); - gear_runtime_interface::sandbox_init(); + gear_runtime_interface::sandbox_init(gear_runtime_interface::SandboxBackend::Wasmer); use basic_tests::*; diff --git a/sandbox/host/Cargo.toml b/sandbox/host/Cargo.toml index 207a5b4970d..53649d7643d 100644 --- a/sandbox/host/Cargo.toml +++ b/sandbox/host/Cargo.toml @@ -22,7 +22,7 @@ thiserror.workspace = true log = { workspace = true, features = ["std"] } wasmer = { workspace = true, features = ["singlepass"] } wasmer-types.workspace = true -sandbox-wasmi = { package = "wasmi", version = "0.36"} +sandbox-wasmi.workspace = true sp-allocator = { workspace = true, features = ["std"] } sp-wasm-interface-common = { workspace = true, features = ["std"] } gear-sandbox-env = { workspace = true, features = ["std"] } diff --git a/utils/gear-replay-cli/src/cmd/mod.rs b/utils/gear-replay-cli/src/cmd/mod.rs index ffc93f217f0..89c5c6396b0 100644 --- a/utils/gear-replay-cli/src/cmd/mod.rs +++ b/utils/gear-replay-cli/src/cmd/mod.rs @@ -63,7 +63,7 @@ pub enum Command { impl Command { pub async fn run(&self, shared: &SharedParams) -> sc_cli::Result<()> { - gear_runtime_interface::sandbox_init(); + gear_runtime_interface::sandbox_init(gear_runtime_interface::SandboxBackend::Wasmer); match &self { Command::ReplayBlock(cmd) => { diff --git a/utils/lazy-pages-fuzzer/Cargo.toml b/utils/lazy-pages-fuzzer/Cargo.toml index 9138785aea4..ced04890460 100644 --- a/utils/lazy-pages-fuzzer/Cargo.toml +++ b/utils/lazy-pages-fuzzer/Cargo.toml @@ -15,7 +15,6 @@ gear-lazy-pages-common.workspace = true log.workspace = true region.workspace = true wasmer = { workspace = true, features = ["singlepass"] } -#sandbox-wasmi.workspace = true -sandbox-wasmi = { package = "wasmi", version = "0.36.1"} +sandbox-wasmi.workspace = true wasmprinter.workspace = true wat.workspace = true From 03d93b380dde3793900d48d473ceb95ce8e323a7 Mon Sep 17 00:00:00 2001 From: Roman Maslennikov Date: Tue, 15 Oct 2024 12:31:45 +0400 Subject: [PATCH 18/25] Rename sandbox-wasmi dep --- Cargo.toml | 2 +- sandbox/host/Cargo.toml | 2 +- sandbox/host/src/error.rs | 4 +- sandbox/host/src/sandbox.rs | 2 +- sandbox/host/src/sandbox/wasmi_backend.rs | 72 +++++++++---------- sandbox/host/src/store_refcell.rs | 2 +- utils/lazy-pages-fuzzer/Cargo.toml | 2 +- .../lazy-pages-fuzzer/src/generate/globals.rs | 10 +-- .../src/generate/mem_accesses.rs | 14 ++-- utils/lazy-pages-fuzzer/src/wasmi_backend.rs | 2 +- .../src/wasmi_backend/error.rs | 2 +- 11 files changed, 56 insertions(+), 58 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e6d42ab03d4..9f34f0c3a8b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -303,7 +303,7 @@ ethexe-rpc = { path = "ethexe/rpc", default-features = false } ethexe-common = { path = "ethexe/common" } # Common executor between `sandbox-host` and `lazy-pages-fuzzer` -sandbox-wasmi = { package = "wasmi", version = "0.36"} +wasmi = { package = "wasmi", version = "0.36"} # Substrate deps binary-merkle-tree = { version = "4.0.0-dev", git = "https://github.com/gear-tech/polkadot-sdk.git", branch = "gear-v1.4.0", default-features = false } diff --git a/sandbox/host/Cargo.toml b/sandbox/host/Cargo.toml index 53649d7643d..54b0284695d 100644 --- a/sandbox/host/Cargo.toml +++ b/sandbox/host/Cargo.toml @@ -22,7 +22,7 @@ thiserror.workspace = true log = { workspace = true, features = ["std"] } wasmer = { workspace = true, features = ["singlepass"] } wasmer-types.workspace = true -sandbox-wasmi.workspace = true +wasmi.workspace = true sp-allocator = { workspace = true, features = ["std"] } sp-wasm-interface-common = { workspace = true, features = ["std"] } gear-sandbox-env = { workspace = true, features = ["std"] } diff --git a/sandbox/host/src/error.rs b/sandbox/host/src/error.rs index a5e32b19b80..e39dfac783b 100644 --- a/sandbox/host/src/error.rs +++ b/sandbox/host/src/error.rs @@ -26,7 +26,7 @@ pub type Result = std::result::Result; #[allow(missing_docs)] pub enum Error { #[error(transparent)] - Wasmi(#[from] sandbox_wasmi::Error), + Wasmi(#[from] wasmi::Error), #[error("Sandbox error: {0}")] Sandbox(String), @@ -107,7 +107,7 @@ pub enum Error { AbortedDueToTrap(MessageWithBacktrace), } -impl sandbox_wasmi::core::HostError for Error {} +impl wasmi::core::HostError for Error {} impl From<&'_ str> for Error { fn from(err: &'_ str) -> Error { diff --git a/sandbox/host/src/sandbox.rs b/sandbox/host/src/sandbox.rs index 3d9090d2be6..22cde5fa8c9 100644 --- a/sandbox/host/src/sandbox.rs +++ b/sandbox/host/src/sandbox.rs @@ -171,7 +171,7 @@ enum BackendInstanceBundle { /// Wasmi module instance Wasmi { /// Wasmer module instance - instance: sandbox_wasmi::Instance, + instance: wasmi::Instance, /// Wasmer store store: Rc, }, diff --git a/sandbox/host/src/sandbox/wasmi_backend.rs b/sandbox/host/src/sandbox/wasmi_backend.rs index 106ad48dd29..e56d38ea43a 100644 --- a/sandbox/host/src/sandbox/wasmi_backend.rs +++ b/sandbox/host/src/sandbox/wasmi_backend.rs @@ -26,7 +26,7 @@ use std::{ use codec::{Decode, Encode}; use gear_sandbox_env::{HostError, Instantiate, WasmReturnValue, GLOBAL_NAME_GAS}; use region::{Allocation, Protection}; -use sandbox_wasmi::{ +use wasmi::{ core::UntypedVal, AsContext, AsContextMut, Config, Engine, ExternType, Linker, MemoryType, Module, StackLimits, Val, }; @@ -45,49 +45,49 @@ use crate::{ use super::SupervisorFuncIndex; -type Store = sandbox_wasmi::Store>; +type Store = wasmi::Store>; pub type StoreRefCell = store_refcell::StoreRefCell; environmental::environmental!(SupervisorContextStore: trait SupervisorContext); pub struct FuncEnv { store: Weak, - gas_global: sandbox_wasmi::Global, + gas_global: wasmi::Global, } impl FuncEnv { - pub fn new(store: Weak, gas_global: sandbox_wasmi::Global) -> Self { + pub fn new(store: Weak, gas_global: wasmi::Global) -> Self { Self { store, gas_global } } } /// Construct trap error from specified message -fn host_trap(msg: impl Into) -> sandbox_wasmi::Error { - sandbox_wasmi::Error::host(msg.into()) +fn host_trap(msg: impl Into) -> wasmi::Error { + wasmi::Error::host(msg.into()) } -fn into_wasmi_val(value: Value) -> sandbox_wasmi::Val { +fn into_wasmi_val(value: Value) -> wasmi::Val { match value { - Value::I32(val) => sandbox_wasmi::Val::I32(val), - Value::I64(val) => sandbox_wasmi::Val::I64(val), - Value::F32(val) => sandbox_wasmi::Val::F32(val.into()), - Value::F64(val) => sandbox_wasmi::Val::F64(val.into()), + Value::I32(val) => wasmi::Val::I32(val), + Value::I64(val) => wasmi::Val::I64(val), + Value::F32(val) => wasmi::Val::F32(val.into()), + Value::F64(val) => wasmi::Val::F64(val.into()), } } -fn into_wasmi_result(value: ReturnValue) -> Vec { +fn into_wasmi_result(value: ReturnValue) -> Vec { match value { ReturnValue::Value(v) => vec![into_wasmi_val(v)], ReturnValue::Unit => vec![], } } -fn into_value(value: &sandbox_wasmi::Val) -> Option { +fn into_value(value: &wasmi::Val) -> Option { match value { - sandbox_wasmi::Val::I32(val) => Some(Value::I32(*val)), - sandbox_wasmi::Val::I64(val) => Some(Value::I64(*val)), - sandbox_wasmi::Val::F32(val) => Some(Value::F32((*val).into())), - sandbox_wasmi::Val::F64(val) => Some(Value::F64((*val).into())), + wasmi::Val::I32(val) => Some(Value::I32(*val)), + wasmi::Val::I64(val) => Some(Value::I64(*val)), + wasmi::Val::F32(val) => Some(Value::F32((*val).into())), + wasmi::Val::F64(val) => Some(Value::F64((*val).into())), _ => None, } } @@ -149,7 +149,7 @@ pub fn new_memory( // but actual lifetime of the buffer is lifetime of `Store` itself, // so memory will be deallocated when `Store` is dropped. let raw = unsafe { slice::from_raw_parts_mut::<'static, u8>(alloc.as_mut_ptr(), alloc.len()) }; - let memory = sandbox_wasmi::Memory::new_static(&mut *store.borrow_mut(), ty, raw) + let memory = wasmi::Memory::new_static(&mut *store.borrow_mut(), ty, raw) .map_err(|error| Error::Sandbox(error.to_string()))?; Ok((Memory::Wasmi(MemoryWrapper::new(memory, store)), alloc)) @@ -160,7 +160,7 @@ pub fn new_memory( /// This wrapper limits the scope where the slice can be taken to #[derive(Clone)] pub struct MemoryWrapper { - memory: sandbox_wasmi::Memory, + memory: wasmi::Memory, store: Rc, } @@ -174,7 +174,7 @@ impl std::fmt::Debug for MemoryWrapper { impl MemoryWrapper { /// Take ownership of the memory region and return a wrapper object - fn new(memory: sandbox_wasmi::Memory, store: Rc) -> Self { + fn new(memory: wasmi::Memory, store: Rc) -> Self { Self { memory, store } } } @@ -232,13 +232,13 @@ impl MemoryTransfer for MemoryWrapper { } /// Get global value by name -pub fn get_global(instance: &sandbox_wasmi::Instance, store: &Store, name: &str) -> Option { +pub fn get_global(instance: &wasmi::Instance, store: &Store, name: &str) -> Option { into_value(&instance.get_global(store, name)?.get(store)) } /// Set global value by name pub fn set_global( - instance: &sandbox_wasmi::Instance, + instance: &wasmi::Instance, store: &mut Store, name: &str, value: Value, @@ -344,12 +344,12 @@ pub fn instantiate( fn dispatch_function( supervisor_func_index: SupervisorFuncIndex, store: &mut Store, - func_ty: &sandbox_wasmi::FuncType, -) -> sandbox_wasmi::Func { - sandbox_wasmi::Func::new( + func_ty: &wasmi::FuncType, +) -> wasmi::Func { + wasmi::Func::new( store, func_ty.clone(), - move |_caller, params, results| -> Result<(), sandbox_wasmi::Error> { + move |_caller, params, results| -> Result<(), wasmi::Error> { SupervisorContextStore::with(|supervisor_context| { let invoke_args_data = params .iter() @@ -387,12 +387,12 @@ fn dispatch_function( fn dispatch_function_v2( supervisor_func_index: SupervisorFuncIndex, store: &mut Store, - func_ty: &sandbox_wasmi::FuncType, -) -> sandbox_wasmi::Func { - sandbox_wasmi::Func::new( + func_ty: &wasmi::FuncType, +) -> wasmi::Func { + wasmi::Func::new( store, func_ty.clone(), - move |mut caller, params, results| -> Result<(), sandbox_wasmi::Error> { + move |mut caller, params, results| -> Result<(), wasmi::Error> { SupervisorContextStore::with(|supervisor_context| { let func_env = caller.data().as_ref().expect("func env should be set"); let store_ref_cell = func_env.store.upgrade().expect("store should be alive"); @@ -450,7 +450,7 @@ fn dispatch_common( supervisor_func_index: SupervisorFuncIndex, supervisor_context: &mut dyn SupervisorContext, invoke_args_data: Vec, -) -> std::result::Result, sandbox_wasmi::Error> { +) -> std::result::Result, wasmi::Error> { // Move serialized arguments inside the memory, invoke dispatch thunk and // then free allocated memory. let invoke_args_len = invoke_args_data.len() as WordSize; @@ -514,7 +514,7 @@ fn dispatch_common( /// Invoke a function within a sandboxed module pub fn invoke( - instance: &sandbox_wasmi::Instance, + instance: &wasmi::Instance, store: &Rc, export_name: &str, args: &[Value], @@ -524,13 +524,11 @@ pub fn invoke( .get_func(&*store.borrow(), export_name) .ok_or_else(|| Error::Sandbox(format!("function {export_name} export error")))?; - let args: Vec = args.iter().copied().map(into_wasmi_val).collect(); + let args: Vec = args.iter().copied().map(into_wasmi_val).collect(); let func_ty = function.ty(&*store.borrow()); - let mut outputs = vec![ - sandbox_wasmi::Val::ExternRef(sandbox_wasmi::ExternRef::null()); - func_ty.results().len() - ]; + let mut outputs = + vec![wasmi::Val::ExternRef(wasmi::ExternRef::null()); func_ty.results().len()]; // Init func env { diff --git a/sandbox/host/src/store_refcell.rs b/sandbox/host/src/store_refcell.rs index a5f20476205..cf70c8e92f8 100644 --- a/sandbox/host/src/store_refcell.rs +++ b/sandbox/host/src/store_refcell.rs @@ -111,7 +111,7 @@ pub struct StoreRefCell { trait GenericAsStoreMut {} impl<'r, 's> GenericAsStoreMut for &'r mut StoreMut<'s> {} -impl<'s, T> GenericAsStoreMut for sandbox_wasmi::StoreContextMut<'s, T> {} +impl<'s, T> GenericAsStoreMut for wasmi::StoreContextMut<'s, T> {} #[derive(Debug)] pub struct BorrowScopeError; diff --git a/utils/lazy-pages-fuzzer/Cargo.toml b/utils/lazy-pages-fuzzer/Cargo.toml index ced04890460..8a395ad4a1a 100644 --- a/utils/lazy-pages-fuzzer/Cargo.toml +++ b/utils/lazy-pages-fuzzer/Cargo.toml @@ -15,6 +15,6 @@ gear-lazy-pages-common.workspace = true log.workspace = true region.workspace = true wasmer = { workspace = true, features = ["singlepass"] } -sandbox-wasmi.workspace = true +wasmi.workspace = true wasmprinter.workspace = true wat.workspace = true diff --git a/utils/lazy-pages-fuzzer/src/generate/globals.rs b/utils/lazy-pages-fuzzer/src/generate/globals.rs index 7eb338c037d..b2705a4a389 100644 --- a/utils/lazy-pages-fuzzer/src/generate/globals.rs +++ b/utils/lazy-pages-fuzzer/src/generate/globals.rs @@ -182,11 +182,11 @@ mod tests { let module = Module::from_bytes(wasm).unwrap(); let (module, _) = globals.inject(module).unwrap(); - let engine = sandbox_wasmi::Engine::default(); - let mut store = sandbox_wasmi::Store::new(&engine, ()); + let engine = wasmi::Engine::default(); + let mut store = wasmi::Store::new(&engine, ()); - let module = sandbox_wasmi::Module::new(&engine, &module.into_bytes().unwrap()).unwrap(); - let instance = sandbox_wasmi::Instance::new(&mut store, &module, &[]).unwrap(); + let module = wasmi::Module::new(&engine, &module.into_bytes().unwrap()).unwrap(); + let instance = wasmi::Instance::new(&mut store, &module, &[]).unwrap(); let gear_fuzz_a = instance .get_global(&store, "gear_fuzz_a") @@ -197,7 +197,7 @@ mod tests { assert_eq!(gear_fuzz_a, INITIAL_GLOBAL_VALUE); let func = instance.get_func(&store, "main").unwrap(); - func.call(&mut store, &[], &mut [sandbox_wasmi::Val::I64(0)]) + func.call(&mut store, &[], &mut [wasmi::Val::I64(0)]) .unwrap(); // Assert that global was modified (initially 0) diff --git a/utils/lazy-pages-fuzzer/src/generate/mem_accesses.rs b/utils/lazy-pages-fuzzer/src/generate/mem_accesses.rs index 078138b4417..7d70b66d8c8 100644 --- a/utils/lazy-pages-fuzzer/src/generate/mem_accesses.rs +++ b/utils/lazy-pages-fuzzer/src/generate/mem_accesses.rs @@ -186,19 +186,19 @@ mod tests { .inject(module) .unwrap(); - let engine = sandbox_wasmi::Engine::default(); - let mut store = sandbox_wasmi::Store::new(&engine, ()); - let module = sandbox_wasmi::Module::new(&engine, &module.into_bytes().unwrap()).unwrap(); + let engine = wasmi::Engine::default(); + let mut store = wasmi::Store::new(&engine, ()); + let module = wasmi::Module::new(&engine, &module.into_bytes().unwrap()).unwrap(); - let ty = sandbox_wasmi::MemoryType::new(1, None).unwrap(); - let memory = sandbox_wasmi::Memory::new(&mut store, ty).unwrap(); + let ty = wasmi::MemoryType::new(1, None).unwrap(); + let memory = wasmi::Memory::new(&mut store, ty).unwrap(); let original_mem_hash = { let mem_slice = memory.data(&store); calculate_slice_hash(mem_slice) }; - let mut linker = >::new(&engine); + let mut linker = >::new(&engine); linker.define(MODULE_ENV, "memory", memory).unwrap(); let instance = linker @@ -208,7 +208,7 @@ mod tests { .unwrap(); let func = instance.get_func(&store, "main").unwrap(); - func.call(&mut store, &[], &mut [sandbox_wasmi::Val::I32(0)]) + func.call(&mut store, &[], &mut [wasmi::Val::I32(0)]) .unwrap(); let mem_hash = { diff --git a/utils/lazy-pages-fuzzer/src/wasmi_backend.rs b/utils/lazy-pages-fuzzer/src/wasmi_backend.rs index 6d4069f78ea..61bddfa0532 100644 --- a/utils/lazy-pages-fuzzer/src/wasmi_backend.rs +++ b/utils/lazy-pages-fuzzer/src/wasmi_backend.rs @@ -23,7 +23,7 @@ use anyhow::{anyhow, bail, Context}; use gear_wasm_gen::SyscallName; use gear_wasm_instrument::{parity_wasm::elements::Module, GLOBAL_NAME_GAS}; use region::{Allocation, Protection}; -use sandbox_wasmi::{ +use wasmi::{ core::UntypedVal, Caller, Config, Engine, Error, Instance, Linker, Memory, MemoryType, Module as WasmiModule, StackLimits, Store, Val, }; diff --git a/utils/lazy-pages-fuzzer/src/wasmi_backend/error.rs b/utils/lazy-pages-fuzzer/src/wasmi_backend/error.rs index 9724dc9aab1..6d58488f287 100644 --- a/utils/lazy-pages-fuzzer/src/wasmi_backend/error.rs +++ b/utils/lazy-pages-fuzzer/src/wasmi_backend/error.rs @@ -17,7 +17,7 @@ // along with this program. If not, see . use derive_more::Display; -use sandbox_wasmi::core::HostError; +use wasmi::core::HostError; #[derive(Debug, Display)] #[display(fmt = "{message}")] From 6361454a519c91b68d81d9a3188fa16c8d808fef Mon Sep 17 00:00:00 2001 From: Roman Maslennikov Date: Tue, 15 Oct 2024 12:34:24 +0400 Subject: [PATCH 19/25] Fixup --- sandbox/host/src/store_refcell.rs | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/sandbox/host/src/store_refcell.rs b/sandbox/host/src/store_refcell.rs index cf70c8e92f8..23cd88ffc91 100644 --- a/sandbox/host/src/store_refcell.rs +++ b/sandbox/host/src/store_refcell.rs @@ -174,12 +174,6 @@ impl StoreRefCell { store: impl GenericAsStoreMut, f: F, ) -> Result { - // We expect the same store - //debug_assert!( - // self.compare_stores(store.as_store_ref()), - // "stores are different" - //); - // Caller just returned borrowed mutably reference to the store, now we can safely borrow it mutably again let _store = store; @@ -201,16 +195,6 @@ impl StoreRefCell { Ok(result) } - //#[allow(unused)] - //fn compare_stores(&self, returned_store: StoreRef) -> bool { - // // SAFETY: - // // Verified with Miri, it seems safe. - // // Carefully compare the stores while don't using/holding mutable references to them in the same time. - // let orig_store_ref: StoreRef = unsafe { &*self.store.get() }.as_store_ref(); - - // StoreRef::same(&orig_store_ref, &returned_store) - //} - /// Returns store ptr, same semantics as `RefCell::as_ptr` pub unsafe fn as_ptr(&self) -> *mut S { self.store.get() From 59bf430918745454c21cc72456d7e2424fa22097 Mon Sep 17 00:00:00 2001 From: Roman Maslennikov Date: Tue, 15 Oct 2024 12:39:20 +0400 Subject: [PATCH 20/25] Trigger CI From 16a91be9cf54bc9e6619d3b01f10805219a17ad4 Mon Sep 17 00:00:00 2001 From: Roman Maslennikov Date: Tue, 15 Oct 2024 12:59:00 +0400 Subject: [PATCH 21/25] Remove whitespaces --- sandbox/host/src/store_refcell.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sandbox/host/src/store_refcell.rs b/sandbox/host/src/store_refcell.rs index 23cd88ffc91..524fe3afef7 100644 --- a/sandbox/host/src/store_refcell.rs +++ b/sandbox/host/src/store_refcell.rs @@ -35,13 +35,13 @@ //! //! Now we need to borrow store mutably again inside `func`, //! but we can't do it because `mut_borrow` still exists. -//! +//! //! ```ignore //! fn func(ref_cell: &RefCell, mut_borrow: &mut Store) { //! ref_cell.borrow_mut(); // This will panic //! } //! ``` -//! +//! //! With `StoreRefCell` we can do it safely: //! //! ```ignore @@ -52,7 +52,7 @@ //! }); //! } //! ``` -//! +//! //! # Why is this necessary? Can't we do without repeated mutable borrowing? //! //! The issue arises because when handling syscalls within an instance of a program running in the sandbox, From a844d9013971f12f69337dd32734a1231bd87f4f Mon Sep 17 00:00:00 2001 From: Roman Maslennikov Date: Tue, 15 Oct 2024 14:42:19 +0400 Subject: [PATCH 22/25] Add global sandbox backend ty --- Cargo.lock | 12 ++++++++++++ Cargo.toml | 1 + runtime-interface/sandbox/src/detail.rs | 14 +++++++++++--- sandbox/host/Cargo.toml | 1 + sandbox/host/src/sandbox.rs | 1 + 5 files changed, 26 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8a65e281343..7b381c9d2c2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1606,6 +1606,17 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1181e1e0d1fce796a03db1ae795d67167da795f9cf4a39c37589e85ef57f26d3" +[[package]] +name = "atomic_enum" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99e1aca718ea7b89985790c94aad72d77533063fe00bc497bb79a7c2dae6a661" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.71", +] + [[package]] name = "attohttpc" version = "0.24.1" @@ -6882,6 +6893,7 @@ dependencies = [ name = "gear-sandbox-host" version = "1.6.2" dependencies = [ + "atomic_enum", "defer", "environmental", "gear-sandbox-env", diff --git a/Cargo.toml b/Cargo.toml index 9f34f0c3a8b..6d9a7ad2dfc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -495,6 +495,7 @@ demo-wat = { path = "examples/wat" } # # TODO: remove these dependencies (from this file?) or add more docs. +atomic_enum = "0.3.0" cfg-if = "1.0.0" # gear-lazy-pages cargo-http-registry = "0.1.6" # crates-io errno = "0.3" # gear-lazy-pages diff --git a/runtime-interface/sandbox/src/detail.rs b/runtime-interface/sandbox/src/detail.rs index cf530db42bb..54561e280ab 100644 --- a/runtime-interface/sandbox/src/detail.rs +++ b/runtime-interface/sandbox/src/detail.rs @@ -16,7 +16,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use core::cell::RefCell; +use core::{cell::RefCell, sync::atomic::Ordering}; use codec::{Decode, Encode}; use gear_sandbox_host::sandbox::{self as sandbox_env, env::Instantiate}; @@ -61,12 +61,20 @@ impl Sandboxes { } } +// Global sandbox backend type selector +static SANDBOX_BACKEND_TYPE: sandbox_env::AtomicSandboxBackend = + sandbox_env::AtomicSandboxBackend::new(sandbox_env::SandboxBackend::Wasmer); + thread_local! { - static SANDBOXES: RefCell = panic!("Sandbox not initialized"); + static SANDBOXES: RefCell = { + let sandbox_backend = SANDBOX_BACKEND_TYPE.load(Ordering::SeqCst); + RefCell::new(Sandboxes::new(sandbox_backend)) + } } pub fn init(sandbox_backend: sandbox_env::SandboxBackend) { - SANDBOXES.set(Sandboxes::new(sandbox_backend)); + SANDBOX_BACKEND_TYPE.store(sandbox_backend, Ordering::SeqCst); + // At first access sandbox will be initialized with the provided backend SANDBOXES.with_borrow_mut(|sandboxes| { let _store = sandboxes.get(0); }) diff --git a/sandbox/host/Cargo.toml b/sandbox/host/Cargo.toml index 54b0284695d..61fd264d945 100644 --- a/sandbox/host/Cargo.toml +++ b/sandbox/host/Cargo.toml @@ -15,6 +15,7 @@ rust-version.workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] +atomic_enum.workspace = true codec = { workspace = true, features = ["std"] } defer.workspace = true environmental.workspace = true diff --git a/sandbox/host/src/sandbox.rs b/sandbox/host/src/sandbox.rs index 22cde5fa8c9..bbe1cf7ec37 100644 --- a/sandbox/host/src/sandbox.rs +++ b/sandbox/host/src/sandbox.rs @@ -406,6 +406,7 @@ impl UnregisteredInstance { } /// Sandbox backend to use +#[atomic_enum::atomic_enum] pub enum SandboxBackend { /// Wasm interpreter Wasmi, From dc28e128dc2c5d6a06274c7ea410dad3b6b90c8f Mon Sep 17 00:00:00 2001 From: Roman Maslennikov Date: Thu, 17 Oct 2024 18:41:34 +0400 Subject: [PATCH 23/25] Bump wasmi version to 0.38 --- Cargo.lock | 41 +++++++++++++++++++---------------------- Cargo.toml | 2 +- 2 files changed, 20 insertions(+), 23 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7b381c9d2c2..8c19a204241 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6908,7 +6908,7 @@ dependencies = [ "wasmer", "wasmer-cache", "wasmer-types", - "wasmi 0.36.5", + "wasmi 0.38.0", ] [[package]] @@ -9031,7 +9031,7 @@ dependencies = [ "log", "region", "wasmer", - "wasmi 0.36.5", + "wasmi 0.38.0", "wasmprinter", "wat", ] @@ -10954,17 +10954,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" -[[package]] -name = "num-derive" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.71", -] - [[package]] name = "num-format" version = "0.4.4" @@ -19547,18 +19536,17 @@ dependencies = [ [[package]] name = "wasmi" -version = "0.36.5" +version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "446ddc18185880ff32de907a809864283d18f9de568c23d46464b6231973d595" +checksum = "b07e84e3bcdab2f4301827623260ada2557596ca462f7470b60f5182a25270b1" dependencies = [ "arrayvec 0.7.4", "multi-stash", - "num-derive", - "num-traits", "smallvec", "spin 0.9.8", "wasmi_collections", - "wasmi_core 0.36.5", + "wasmi_core 0.38.0", + "wasmi_ir", "wasmparser-nostd", ] @@ -19583,9 +19571,9 @@ checksum = "104a7f73be44570cac297b3035d76b169d6599637631cf37a1703326a0727073" [[package]] name = "wasmi_collections" -version = "0.36.5" +version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eddc10bfb0069e913399ebd66c5a72c7d9aceabddcaa0296f062a55ab61d404" +checksum = "0d0fd5f4f2c4fe0c98554bb7293108ed2b1d0c124dce0974f999de7d517d37bc" dependencies = [ "ahash 0.8.11", "hashbrown 0.14.5", @@ -19631,9 +19619,9 @@ dependencies = [ [[package]] name = "wasmi_core" -version = "0.36.5" +version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f08b12621457c17cfd5349cce25029eeac3769b63b1b02bd850d595a00f375ff" +checksum = "76a5f7bbd933a0fb3bac6c541f8bd90c0c8adcd91bb3ac088a2088995325b3d9" dependencies = [ "downcast-rs", "libm", @@ -19641,6 +19629,15 @@ dependencies = [ "paste", ] +[[package]] +name = "wasmi_ir" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3345445247388df2b5b35250a30c9209c27c8d2c6db1bf4c89b65636264bf9" +dependencies = [ + "wasmi_core 0.38.0", +] + [[package]] name = "wasmparser" version = "0.102.0" diff --git a/Cargo.toml b/Cargo.toml index 6d9a7ad2dfc..34232f93732 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -303,7 +303,7 @@ ethexe-rpc = { path = "ethexe/rpc", default-features = false } ethexe-common = { path = "ethexe/common" } # Common executor between `sandbox-host` and `lazy-pages-fuzzer` -wasmi = { package = "wasmi", version = "0.36"} +wasmi = { package = "wasmi", version = "0.38"} # Substrate deps binary-merkle-tree = { version = "4.0.0-dev", git = "https://github.com/gear-tech/polkadot-sdk.git", branch = "gear-v1.4.0", default-features = false } From d2f11354fbdfdbecbd9f7e84b663f751fcbd51f8 Mon Sep 17 00:00:00 2001 From: Roman Maslennikov Date: Sun, 20 Oct 2024 22:04:21 +0400 Subject: [PATCH 24/25] Review fixes --- sandbox/host/Cargo.toml | 2 + sandbox/host/src/lib.rs | 3 +- sandbox/host/src/sandbox.rs | 17 +--- sandbox/host/src/sandbox/wasmi_backend.rs | 99 ++++++++++--------- sandbox/host/src/store_refcell.rs | 3 +- utils/lazy-pages-fuzzer/src/wasmer_backend.rs | 3 + utils/lazy-pages-fuzzer/src/wasmi_backend.rs | 10 +- 7 files changed, 70 insertions(+), 67 deletions(-) diff --git a/sandbox/host/Cargo.toml b/sandbox/host/Cargo.toml index 61fd264d945..141f06bfb40 100644 --- a/sandbox/host/Cargo.toml +++ b/sandbox/host/Cargo.toml @@ -34,3 +34,5 @@ region.workspace = true [features] default = ["wasmer-cache", "uluru"] +# See wasmi/extra-checks for more information. +wasmi-extra-checks = ["wasmi/extra-checks"] diff --git a/sandbox/host/src/lib.rs b/sandbox/host/src/lib.rs index b0388817754..f144ded1301 100644 --- a/sandbox/host/src/lib.rs +++ b/sandbox/host/src/lib.rs @@ -23,7 +23,8 @@ pub mod error; pub mod sandbox; -pub(crate) mod store_refcell; pub mod util; +pub(crate) mod store_refcell; + use log as _; diff --git a/sandbox/host/src/sandbox.rs b/sandbox/host/src/sandbox.rs index bbe1cf7ec37..b323b31a110 100644 --- a/sandbox/host/src/sandbox.rs +++ b/sandbox/host/src/sandbox.rs @@ -28,7 +28,6 @@ use std::{collections::HashMap, pin::Pin, rc::Rc}; use codec::Decode; use env::Instantiate; use gear_sandbox_env as sandbox_env; -use region::Allocation; use sp_wasm_interface_common::{Pointer, Value, WordSize}; use crate::{ @@ -525,8 +524,6 @@ pub struct SandboxComponents
{ instances: Vec>, DT)>>, /// Memories are `Some` until torn down. memories: Vec>, - /// Allocation used for the Wasmi memory. - allocations: Vec, backend_context: BackendContext, } @@ -536,7 +533,6 @@ impl SandboxComponents
{ SandboxComponents { instances: Vec::new(), memories: Vec::new(), - allocations: Vec::new(), backend_context: BackendContext::new(backend), } } @@ -562,9 +558,6 @@ impl SandboxComponents
{ self.backend_context = BackendContext::Wasmer(WasmerBackend::new()); } } - - // Clear allocations after store is dropped - self.allocations.clear(); } /// Create a new memory instance and return it's index. @@ -575,19 +568,15 @@ impl SandboxComponents
{ /// Typically happens if `initial` is more than `maximum`. pub fn new_memory(&mut self, initial: u32, maximum: u32) -> Result { let memories = &mut self.memories; - let backend_context = &self.backend_context; + let backend_context = &mut self.backend_context; let maximum = match maximum { sandbox_env::MEM_UNLIMITED => None, specified_limit => Some(specified_limit), }; - let memory = match &backend_context { - BackendContext::Wasmi(backend) => { - let (memory, alloc) = wasmi_new_memory(backend.store().clone(), initial, maximum)?; - self.allocations.push(alloc); - memory - } + let memory = match backend_context { + BackendContext::Wasmi(backend) => wasmi_new_memory(backend, initial, maximum)?, BackendContext::Wasmer(backend) => { wasmer_new_memory(backend.store().clone(), initial, maximum)? diff --git a/sandbox/host/src/sandbox/wasmi_backend.rs b/sandbox/host/src/sandbox/wasmi_backend.rs index e56d38ea43a..72e9e30e822 100644 --- a/sandbox/host/src/sandbox/wasmi_backend.rs +++ b/sandbox/host/src/sandbox/wasmi_backend.rs @@ -70,8 +70,8 @@ fn into_wasmi_val(value: Value) -> wasmi::Val { match value { Value::I32(val) => wasmi::Val::I32(val), Value::I64(val) => wasmi::Val::I64(val), - Value::F32(val) => wasmi::Val::F32(val.into()), - Value::F64(val) => wasmi::Val::F64(val.into()), + Value::F32(val) => wasmi::Val::F32(wasmi::core::F32::from_bits(val)), + Value::F64(val) => wasmi::Val::F64(wasmi::core::F64::from_bits(val)), } } @@ -86,8 +86,8 @@ fn into_value(value: &wasmi::Val) -> Option { match value { wasmi::Val::I32(val) => Some(Value::I32(*val)), wasmi::Val::I64(val) => Some(Value::I64(*val)), - wasmi::Val::F32(val) => Some(Value::F32((*val).into())), - wasmi::Val::F64(val) => Some(Value::F64((*val).into())), + wasmi::Val::F32(val) => Some(Value::F32(u32::from(*val))), + wasmi::Val::F64(val) => Some(Value::F64(u64::from(*val))), _ => None, } } @@ -95,6 +95,8 @@ fn into_value(value: &wasmi::Val) -> Option { /// Wasmi specific context pub struct Backend { store: Rc, + // Allocation should be dropped right after the store is dropped + allocations: Vec, } impl Default for Backend { @@ -125,34 +127,47 @@ impl Backend { let store = Store::new(&engine, None); Backend { store: Rc::new(StoreRefCell::new(store)), + allocations: Vec::new(), } } pub fn store(&self) -> &Rc { &self.store } + + pub fn add_allocation(&mut self, alloc: Allocation) { + self.allocations.push(alloc); + } } /// Allocate new memory region pub fn new_memory( - store: Rc, + backend: &mut Backend, initial: u32, maximum: Option, -) -> crate::error::Result<(Memory, Allocation)> { +) -> crate::error::Result { + let store = backend.store().clone(); + let ty = MemoryType::new(initial, maximum).map_err(|error| Error::Sandbox(error.to_string()))?; let mut alloc = region::alloc(u32::MAX as usize, Protection::READ_WRITE) .unwrap_or_else(|err| unreachable!("Failed to allocate memory: {err}")); + // # Safety: // // `wasmi::Memory::new_static()` requires static lifetime so we convert our buffer to it - // but actual lifetime of the buffer is lifetime of `Store` itself, - // so memory will be deallocated when `Store` is dropped. + // but actual lifetime of the buffer is lifetime of `wasmi::Store` itself, + // because the store might hold reference to the memory. + // + // So in accordance with the Rust's drop order rules, the memory will be dropped right after the store is dropped. + // This order ensured by `Backend` structure which contains these fields. let raw = unsafe { slice::from_raw_parts_mut::<'static, u8>(alloc.as_mut_ptr(), alloc.len()) }; let memory = wasmi::Memory::new_static(&mut *store.borrow_mut(), ty, raw) .map_err(|error| Error::Sandbox(error.to_string()))?; - Ok((Memory::Wasmi(MemoryWrapper::new(memory, store)), alloc)) + backend.add_allocation(alloc); + + Ok(Memory::Wasmi(MemoryWrapper::new(memory, store))) } /// Wasmi provides direct access to its memory using slices. @@ -181,12 +196,7 @@ impl MemoryWrapper { impl MemoryTransfer for MemoryWrapper { fn read(&self, source_addr: Pointer, size: usize) -> error::Result> { - let mut buffer = Vec::with_capacity(size); - let spare_cap = buffer.spare_capacity_mut().len(); - // # Safety: - // `Vec::set_len` is safe to call because we have just allocated enough space and never read from it. - unsafe { buffer.set_len(spare_cap) }; - + let mut buffer = vec![0; size]; let ctx = self.store.borrow(); self.memory .read(&*ctx, source_addr.into(), &mut buffer) @@ -242,10 +252,9 @@ pub fn set_global( store: &mut Store, name: &str, value: Value, -) -> std::result::Result, error::Error> { - let global = match instance.get_global(&*store, name) { - Some(e) => e, - None => return Ok(None), +) -> Result, error::Error> { + let Some(global) = instance.get_global(&*store, name) else { + return Ok(None); }; global @@ -260,8 +269,8 @@ pub fn instantiate( context: &Backend, wasm: &[u8], guest_env: GuestEnvironment, - _supervisor_context: &mut dyn SupervisorContext, -) -> std::result::Result { + supervisor_context: &mut dyn SupervisorContext, +) -> Result { let mut store = context.store().borrow_mut(); let module = @@ -269,15 +278,15 @@ pub fn instantiate( let mut linker = Linker::new(store.engine()); for import in module.imports() { - let module = import.module().to_string(); - let name = import.name().to_string(); + let module = import.module(); + let name = import.name(); match import.ty() { ExternType::Global(_) | ExternType::Table(_) => {} ExternType::Memory(_mem_ty) => { let memory = guest_env .imports - .memory_by_name(&module, &name) + .memory_by_name(module, name) .ok_or(InstantiationError::ModuleDecoding)?; let wasmi_memory = memory.as_wasmi().expect( @@ -288,17 +297,13 @@ pub fn instantiate( ); linker - .define(&module, &name, wasmi_memory.memory) + .define(module, name, wasmi_memory.memory) .map_err(|_| InstantiationError::EnvironmentDefinitionCorrupted)?; } ExternType::Func(func_ty) => { - let guest_func_index = guest_env - .imports - .func_by_name(import.module(), import.name()); + let guest_func_index = guest_env.imports.func_by_name(module, name); - let guest_func_index = if let Some(index) = guest_func_index { - index - } else { + let Some(guest_func_index) = guest_func_index else { // Missing import (should we abort here?) continue; }; @@ -318,7 +323,7 @@ pub fn instantiate( }; linker - .define(&module, &name, function) + .define(module, name, function) .map_err(|_| InstantiationError::ModuleDecoding)?; } } @@ -328,9 +333,12 @@ pub fn instantiate( log::trace!("Failed to call wasmi instantiate: {error:?}"); InstantiationError::Instantiation })?; - let instance = instance_pre.start(&mut *store).map_err(|error| { - log::trace!("Failed to call wasmi start: {error:?}"); - InstantiationError::StartTrapped + + let instance = SupervisorContextStore::using(supervisor_context, || { + instance_pre.start(&mut *store).map_err(|error| { + log::trace!("Failed to call wasmi start: {error:?}"); + InstantiationError::StartTrapped + }) })?; Ok(SandboxInstance { @@ -358,17 +366,18 @@ fn dispatch_function( host_trap(format!("Unsupported function argument: {:?}", value)) }) }) - .collect::, _>>()? + .collect::, _>>()? .encode(); let serialized_result_val = dispatch_common(supervisor_func_index, supervisor_context, invoke_args_data)?; - let deserialized_result = std::result::Result::::decode( - &mut serialized_result_val.as_slice(), - ) - .map_err(|_| host_trap("Decoding Result failed!"))? - .map_err(|_| host_trap("Supervisor function returned sandbox::HostError"))?; + let deserialized_result = + Result::::decode(&mut serialized_result_val.as_slice()) + .map_err(|_| host_trap("Decoding Result failed!"))? + .map_err(|_| { + host_trap("Supervisor function returned sandbox::HostError") + })?; for (idx, result_val) in into_wasmi_result(deserialized_result) .into_iter() @@ -411,7 +420,7 @@ fn dispatch_function_v2( host_trap(format!("Unsupported function argument: {:?}", value)) }) }) - .collect::, _>>()? + .collect::, _>>()? .encode(); let serialized_result_val = dispatch_common( @@ -420,7 +429,7 @@ fn dispatch_function_v2( invoke_args_data, )?; - std::result::Result::::decode( + Result::::decode( &mut serialized_result_val.as_slice(), ) .map_err(|_| host_trap("Decoding Result failed!"))? @@ -450,7 +459,7 @@ fn dispatch_common( supervisor_func_index: SupervisorFuncIndex, supervisor_context: &mut dyn SupervisorContext, invoke_args_data: Vec, -) -> std::result::Result, wasmi::Error> { +) -> Result, wasmi::Error> { // Move serialized arguments inside the memory, invoke dispatch thunk and // then free allocated memory. let invoke_args_len = invoke_args_data.len() as WordSize; @@ -519,7 +528,7 @@ pub fn invoke( export_name: &str, args: &[Value], supervisor_context: &mut dyn SupervisorContext, -) -> std::result::Result, Error> { +) -> Result, Error> { let function = instance .get_func(&*store.borrow(), export_name) .ok_or_else(|| Error::Sandbox(format!("function {export_name} export error")))?; diff --git a/sandbox/host/src/store_refcell.rs b/sandbox/host/src/store_refcell.rs index 524fe3afef7..970c9819f8d 100644 --- a/sandbox/host/src/store_refcell.rs +++ b/sandbox/host/src/store_refcell.rs @@ -91,7 +91,6 @@ use std::{ }; use defer::defer; -use wasmer::StoreMut; #[derive(Debug, Clone, Copy)] enum BorrowState { @@ -110,7 +109,7 @@ pub struct StoreRefCell { trait GenericAsStoreMut {} -impl<'r, 's> GenericAsStoreMut for &'r mut StoreMut<'s> {} +impl<'r, 's> GenericAsStoreMut for &'r mut wasmer::StoreMut<'s> {} impl<'s, T> GenericAsStoreMut for wasmi::StoreContextMut<'s, T> {} #[derive(Debug)] diff --git a/utils/lazy-pages-fuzzer/src/wasmer_backend.rs b/utils/lazy-pages-fuzzer/src/wasmer_backend.rs index b12ae31f233..f6a1aed5618 100644 --- a/utils/lazy-pages-fuzzer/src/wasmer_backend.rs +++ b/utils/lazy-pages-fuzzer/src/wasmer_backend.rs @@ -34,6 +34,9 @@ use crate::{ #[derive(Clone)] struct InstanceBundle { instance: Instance, + // NOTE: Due to the implementation of lazy pages, which need to access the Store to retrieve globals, + // we have to use a second mutable reference to the Store in the form of a raw pointer + // to use it within the lazy pages' signal handler context. store: *mut Store, } diff --git a/utils/lazy-pages-fuzzer/src/wasmi_backend.rs b/utils/lazy-pages-fuzzer/src/wasmi_backend.rs index 61bddfa0532..7a56de8344f 100644 --- a/utils/lazy-pages-fuzzer/src/wasmi_backend.rs +++ b/utils/lazy-pages-fuzzer/src/wasmi_backend.rs @@ -40,6 +40,9 @@ mod error; #[derive(Clone)] struct InstanceBundle { instance: Instance, + // NOTE: Due to the implementation of lazy pages, which need to access the Store to retrieve globals, + // we have to use a second mutable reference to the Store in the form of a raw pointer + // to use it within the lazy pages' signal handler context. store: *mut Store<()>, } @@ -92,11 +95,8 @@ fn memory(store: &mut Store<()>) -> anyhow::Result<(Memory, Allocation)> { // # Safety: // // `wasmi::Memory::new_static()` requires static lifetime so we convert our buffer to it - // but actual lifetime of the buffer is lifetime of `Store` itself, - // so memory will be deallocated when `Store` is dropped. - // - // Also, according to Rust drop order semantics, `wasmi::Store` will be dropped first and - // only then our allocated memories will be freed to ensure they are not used anymore. + // but actual lifetime of the buffer is lifetime of `wasmi::Store` itself, + // because the store might hold reference to the memory. let memref = unsafe { slice::from_raw_parts_mut::<'static, u8>(alloc.as_mut_ptr(), alloc.len()) }; let ty = MemoryType::new(INITIAL_PAGES, None).context("failed to create memory type")?; From 101695923653f7cc7918fa48a457100ff19271b9 Mon Sep 17 00:00:00 2001 From: Roman Maslennikov Date: Sun, 20 Oct 2024 22:27:42 +0400 Subject: [PATCH 25/25] Review fixes II --- sandbox/host/src/sandbox/wasmi_backend.rs | 23 ++--------------------- 1 file changed, 2 insertions(+), 21 deletions(-) diff --git a/sandbox/host/src/sandbox/wasmi_backend.rs b/sandbox/host/src/sandbox/wasmi_backend.rs index 72e9e30e822..a52467f5a7e 100644 --- a/sandbox/host/src/sandbox/wasmi_backend.rs +++ b/sandbox/host/src/sandbox/wasmi_backend.rs @@ -26,10 +26,7 @@ use std::{ use codec::{Decode, Encode}; use gear_sandbox_env::{HostError, Instantiate, WasmReturnValue, GLOBAL_NAME_GAS}; use region::{Allocation, Protection}; -use wasmi::{ - core::UntypedVal, AsContext, AsContextMut, Config, Engine, ExternType, Linker, MemoryType, - Module, StackLimits, Val, -}; +use wasmi::{AsContext, AsContextMut, Engine, ExternType, Linker, MemoryType, Module, Val}; use sp_wasm_interface_common::{Pointer, ReturnValue, Value, WordSize}; @@ -107,23 +104,7 @@ impl Default for Backend { impl Backend { pub fn new() -> Self { - let register_len = size_of::(); - - const DEFAULT_MIN_VALUE_STACK_HEIGHT: usize = 1024; - const DEFAULT_MAX_VALUE_STACK_HEIGHT: usize = 1024 * DEFAULT_MIN_VALUE_STACK_HEIGHT; - const DEFAULT_MAX_RECURSION_DEPTH: usize = 16384; - - let mut config = Config::default(); - config.set_stack_limits( - StackLimits::new( - DEFAULT_MIN_VALUE_STACK_HEIGHT / register_len, - DEFAULT_MAX_VALUE_STACK_HEIGHT / register_len, - DEFAULT_MAX_RECURSION_DEPTH, - ) - .expect("infallible"), - ); - - let engine = Engine::new(&config); + let engine = Engine::default(); let store = Store::new(&engine, None); Backend { store: Rc::new(StoreRefCell::new(store)),