diff --git a/Cargo.lock b/Cargo.lock index 94275f1ff5f..2ec6039cf82 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1610,6 +1610,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" @@ -5955,6 +5966,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", @@ -6377,19 +6389,21 @@ dependencies = [ name = "gear-sandbox-host" version = "1.6.2" dependencies = [ + "atomic_enum", "defer", "environmental", "gear-sandbox-env", "gear-wasmer-cache", "log", "parity-scale-codec", + "region", "sp-allocator", "sp-wasm-interface-common", "tempfile", "thiserror", "wasmer", "wasmer-types", - "wasmi 0.13.2", + "wasmi 0.38.0", ] [[package]] @@ -8450,7 +8464,7 @@ dependencies = [ "log", "region", "wasmer", - "wasmi 0.13.2", + "wasmi 0.38.0", "wasmprinter", "wat", ] @@ -9920,6 +9934,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" @@ -16539,6 +16559,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 = "strsim" version = "0.8.0" @@ -18605,6 +18636,22 @@ dependencies = [ "wasmparser-nostd", ] +[[package]] +name = "wasmi" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b07e84e3bcdab2f4301827623260ada2557596ca462f7470b60f5182a25270b1" +dependencies = [ + "arrayvec 0.7.4", + "multi-stash", + "smallvec", + "spin 0.9.8", + "wasmi_collections", + "wasmi_core 0.38.0", + "wasmi_ir", + "wasmparser-nostd", +] + [[package]] name = "wasmi-validation" version = "0.5.0" @@ -18619,6 +18666,17 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "104a7f73be44570cac297b3035d76b169d6599637631cf37a1703326a0727073" +[[package]] +name = "wasmi_collections" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d0fd5f4f2c4fe0c98554bb7293108ed2b1d0c124dce0974f999de7d517d37bc" +dependencies = [ + "ahash 0.8.11", + "hashbrown 0.14.5", + "string-interner", +] + [[package]] name = "wasmi_core" version = "0.2.1" @@ -18644,6 +18702,27 @@ dependencies = [ "paste", ] +[[package]] +name = "wasmi_core" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a5f7bbd933a0fb3bac6c541f8bd90c0c8adcd91bb3ac088a2088995325b3d9" +dependencies = [ + "downcast-rs", + "libm", + "num-traits", + "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 94d6641b6e1..564b668f00b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -308,9 +308,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", -] } +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 } @@ -503,6 +501,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/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/runtime-interface/sandbox/src/detail.rs b/runtime-interface/sandbox/src/detail.rs index 647b0b76d1e..28ac3f1127a 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}; @@ -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::Wasmer), + store: sandbox_env::SandboxComponents::new(sandbox_backend), } } @@ -61,8 +61,21 @@ 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 = RefCell::new(Sandboxes::new()); + static SANDBOXES: RefCell = { + let sandbox_backend = SANDBOX_BACKEND_TYPE.load(Ordering::SeqCst); + RefCell::new(Sandboxes::new(sandbox_backend)) + } +} + +/// Sets the global sandbox backend type. +/// Buy default, it's set to `Wasmer`, so in case of `Wasmer` it's not necessary to call this function. +pub fn init(sandbox_backend: sandbox_env::SandboxBackend) { + SANDBOX_BACKEND_TYPE.store(sandbox_backend, Ordering::SeqCst); } struct SupervisorContext<'a, 'b> { diff --git a/runtime-interface/sandbox/src/lib.rs b/runtime-interface/sandbox/src/lib.rs index cac05370ae2..4830f369266 100644 --- a/runtime-interface/sandbox/src/lib.rs +++ b/runtime-interface/sandbox/src/lib.rs @@ -21,13 +21,16 @@ #![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; #[cfg(feature = "std")] pub mod detail; +#[cfg(feature = "std")] +pub use detail::init; + /// Wasm-only interface that provides functions for interacting with the sandbox. #[runtime_interface(wasm_only)] pub trait Sandbox { diff --git a/runtime-interface/src/lib.rs b/runtime-interface/src/lib.rs index 5ce2809ca84..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, Instantiate}; +pub use gear_sandbox_interface::{ + detail as sandbox_detail, init as sandbox_init, Instantiate, SandboxBackend, +}; const _: () = assert!(size_of::() >= size_of::()); diff --git a/sandbox/host/Cargo.toml b/sandbox/host/Cargo.toml index df634883545..ac7a4949df5 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 @@ -22,13 +23,16 @@ thiserror.workspace = true log = { workspace = true, features = ["std"] } wasmer.workspace = true 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"] } gear-wasmer-cache = { workspace = true, optional = true } tempfile = { workspace = true, optional = true } +region.workspace = true [features] default = ["wasmer-cache"] wasmer-cache = ["gear-wasmer-cache", "tempfile"] +# See wasmi/extra-checks for more information. +wasmi-extra-checks = ["wasmi/extra-checks"] diff --git a/sandbox/host/src/error.rs b/sandbox/host/src/error.rs index 28d1945e88e..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,10 +107,10 @@ pub enum Error { AbortedDueToTrap(MessageWithBacktrace), } -impl sandbox_wasmi::HostError for Error {} +impl 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/lib.rs b/sandbox/host/src/lib.rs index 24997b5e6f2..f144ded1301 100644 --- a/sandbox/host/src/lib.rs +++ b/sandbox/host/src/lib.rs @@ -25,4 +25,6 @@ pub mod error; pub mod sandbox; 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 2efdc0093de..b323b31a110 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, }, }; @@ -165,19 +165,15 @@ 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 - Wasmi(sandbox_wasmi::ModuleRef), + Wasmi { + /// Wasmer module instance + instance: wasmi::Instance, + /// Wasmer store + store: Rc, + }, /// Wasmer module instance and store Wasmer { @@ -204,7 +200,6 @@ enum BackendInstanceBundle { /// [`invoke`]: #method.invoke pub struct SandboxInstance { backend_instance: BackendInstanceBundle, - guest_to_supervisor_mapping: GuestToSupervisorFunctionMapping, } impl SandboxInstance { @@ -219,8 +214,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 +229,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 +248,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 +267,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 +291,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. @@ -405,6 +405,7 @@ impl UnregisteredInstance { } /// Sandbox backend to use +#[atomic_enum::atomic_enum] pub enum SandboxBackend { /// Wasm interpreter Wasmi, @@ -497,7 +498,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 +507,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 +551,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()); } @@ -565,15 +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 => wasmi_new_memory(initial, maximum)?, + 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)? @@ -684,7 +687,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 d94a0c6f0ed..41300013a71 100644 --- a/sandbox/host/src/sandbox/wasmer_backend.rs +++ b/sandbox/host/src/sandbox/wasmer_backend.rs @@ -31,11 +31,11 @@ use crate::{ BackendInstanceBundle, GuestEnvironment, InstantiationError, Memory, SandboxInstance, SupervisorContext, SupervisorFuncIndex, }, + store_refcell, util::MemoryTransfer, }; -pub use store_refcell::StoreRefCell; -mod store_refcell; +pub type StoreRefCell = store_refcell::StoreRefCell; #[cfg(feature = "gear-wasmer-cache")] use gear_wasmer_cache::*; @@ -296,7 +296,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.rs b/sandbox/host/src/sandbox/wasmi_backend.rs index eccab3efef2..b0c1d142756 100644 --- a/sandbox/host/src/sandbox/wasmi_backend.rs +++ b/sandbox/host/src/sandbox/wasmi_backend.rs @@ -18,393 +18,561 @@ //! Wasmi specific impls for sandbox -use std::fmt; +use std::{ + rc::{Rc, Weak}, + slice, +}; use codec::{Decode, Encode}; -use gear_sandbox_env::HostError; -use sandbox_wasmi::{ - memory_units::Pages, ImportResolver, MemoryInstance, Module, ModuleInstance, RuntimeArgs, - RuntimeValue, Trap, TrapCode, +use gear_sandbox_env::{HostError, Instantiate, WasmReturnValue, GLOBAL_NAME_GAS}; +use region::{Allocation, Protection}; +use wasmi::{ + AsContext, AsContextMut, 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, }; +use super::SupervisorFuncIndex; + +type Store = wasmi::Store>; +pub type StoreRefCell = store_refcell::StoreRefCell; + environmental::environmental!(SupervisorContextStore: trait SupervisorContext); -#[derive(Debug)] -struct CustomHostError(String); +pub struct FuncEnv { + store: Weak, + gas_global: wasmi::Global, +} -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: Weak, gas_global: wasmi::Global) -> Self { + Self { store, gas_global } } } -impl sandbox_wasmi::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) -> wasmi::Error { + wasmi::Error::host(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 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(wasmi::core::F32::from_bits(val)), + Value::F64(val) => wasmi::Val::F64(wasmi::core::F64::from_bits(val)), } +} - 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_wasmi_result(value: ReturnValue) -> Vec { + match value { + ReturnValue::Value(v) => vec![into_wasmi_val(v)], + ReturnValue::Unit => vec![], } +} - 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 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.to_bits())), + wasmi::Val::F64(val) => Some(Value::F64(val.to_bits())), + _ => None, } +} + +/// Wasmi specific context +pub struct Backend { + store: Rc, + // Allocation should be dropped right after the store is dropped + allocations: Vec, +} - 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 - ))) +impl Default for Backend { + fn default() -> Self { + Self::new() } } -/// 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()))?, - )); +impl Drop for Backend { + fn drop(&mut self) { + // Ensure what we actually dropping the store and not just the RC reference to it. + // This is important because it enforces the drop order of the store and its allocations. + assert_eq!( + Rc::strong_count(&self.store), + 1, + "Attempt to drop Backend while references to Store still exist" + ); + } +} + +impl Backend { + pub fn new() -> Self { + const DEFAULT_MAX_RECURSION_DEPTH: usize = 10 * 1024; + + // Increase recursion limit because it was not enough for some programs on testnet to run + let mut config = wasmi::Config::default(); + config.set_stack_limits(StackLimits { + maximum_recursion_depth: DEFAULT_MAX_RECURSION_DEPTH, + ..Default::default() + }); + + let engine = Engine::new(&config); + let store = Store::new(&engine, None); + + Backend { + store: Rc::new(StoreRefCell::new(store)), + allocations: Vec::new(), + } + } + + pub fn store(&self) -> &Rc { + &self.store + } - Ok(memory) + pub fn add_allocation(&mut self, alloc: Allocation) { + self.allocations.push(alloc); + } +} + +/// Allocate new memory region +pub fn new_memory( + backend: &mut Backend, + initial: u32, + maximum: Option, +) -> 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 `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()))?; + + backend.add_allocation(alloc); + + Ok(Memory::Wasmi(MemoryWrapper::new(memory, store))) } /// 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); +#[derive(Clone)] +pub struct MemoryWrapper { + memory: wasmi::Memory, + store: Rc, +} + +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::MemoryRef) -> Self { - Self(memory) + fn new(memory: wasmi::Memory, store: Rc) -> Self { + Self { memory, store } } } 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![0; 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: &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: &wasmi::Instance, + store: &mut Store, + name: &str, + value: Value, +) -> Result, error::Error> { + let Some(global) = instance.get_global(&*store, name) else { + 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, - }; +) -> Result { + 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(); + 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) + .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 guest_func_index = guest_env.imports.func_by_name(module, name); + + let Some(guest_func_index) = guest_func_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 store, func_ty) + } + Instantiate::Version2 => { + dispatch_function_v2(supervisor_func_index, &mut store, func_ty) + } + }; + + // Filter out duplicate imports + if linker.get(&*store, module, name).is_none() { + linker + .define(module, name, function) + .map_err(|_| InstantiationError::ModuleDecoding)?; + } + } + } + } - with_guest_externals(&sandbox_instance, |guest_externals| { - SupervisorContextStore::using(supervisor_context, || { - wasmi_instance - .run_start(guest_externals) - .map_err(|_| 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 = SupervisorContextStore::using(supervisor_context, || { + instance_pre.start(&mut *store).map_err(|error| { + log::trace!("Failed to call wasmi start: {error:?}"); + InstantiationError::StartTrapped }) })?; - Ok(sandbox_instance) + Ok(SandboxInstance { + backend_instance: BackendInstanceBundle::Wasmi { + instance, + store: context.store().clone(), + }, + }) } -/// 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()) - }) - }) - }) +fn dispatch_function( + supervisor_func_index: SupervisorFuncIndex, + store: &mut Store, + func_ty: &wasmi::FuncType, +) -> wasmi::Func { + wasmi::Func::new( + store, + func_ty.clone(), + move |_caller, params, results| -> Result<(), wasmi::Error> { + SupervisorContextStore::with(|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 = + 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") + }, + ) } -/// 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(), - )) +fn dispatch_function_v2( + supervisor_func_index: SupervisorFuncIndex, + store: &mut Store, + func_ty: &wasmi::FuncType, +) -> wasmi::Func { + wasmi::Func::new( + store, + func_ty.clone(), + 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"); + 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, + )?; + + Result::::decode( + &mut serialized_result_val.as_slice(), + ) + .map_err(|_| host_trap("Decoding Result failed!"))? + .map_err(|_| host_trap("Supervisor function returned sandbox::HostError")) + }) + .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") + }, + ) } -/// 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), +fn dispatch_common( + supervisor_func_index: SupervisorFuncIndex, + supervisor_context: &mut dyn SupervisorContext, + invoke_args_data: Vec, +) -> 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; + 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 dyn SupervisorContext, ptr, fail_msg| { + fe.deallocate_memory(ptr).map_err(|_| host_trap(fail_msg)) }; - let global = match export.as_global() { - Some(g) => g, - None => return Ok(None), + 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) }; - global - .set(From::from(value)) - .map(|_| Some(())) - .map_err(error::Error::Wasmi) + 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 +pub fn invoke( + instance: &wasmi::Instance, + store: &Rc, + export_name: &str, + args: &[Value], + supervisor_context: &mut dyn SupervisorContext, +) -> Result, Error> { + let function = instance + .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 func_ty = function.ty(&*store.borrow()); + + let mut outputs = + vec![wasmi::Val::ExternRef(wasmi::ExternRef::null()); func_ty.results().len()]; + + // 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(Rc::downgrade(store), 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), + [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(), + )), + } } diff --git a/sandbox/host/src/sandbox/wasmer_backend/store_refcell.rs b/sandbox/host/src/store_refcell.rs similarity index 80% rename from sandbox/host/src/sandbox/wasmer_backend/store_refcell.rs rename to sandbox/host/src/store_refcell.rs index 548ec2de725..970c9819f8d 100644 --- a/sandbox/host/src/sandbox/wasmer_backend/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,20 +83,18 @@ //! 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, + num::NonZeroUsize, ops::{Deref, DerefMut}, ptr::NonNull, }; use defer::defer; -use wasmer::{AsStoreMut, AsStoreRef, Store, StoreRef}; #[derive(Debug, Clone, Copy)] enum BorrowState { - Shared(NonZero), + Shared(NonZeroUsize), Mutable, NonShared, } @@ -104,17 +102,22 @@ 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 wasmer::StoreMut<'s> {} +impl<'s, T> GenericAsStoreMut for wasmi::StoreContextMut<'s, T> {} + #[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,17 +126,16 @@ 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( - NonZero::::new(n.get() + 1).expect("non zero"), + NonZeroUsize::new(n.get() + 1).expect("non zero"), )); } BorrowState::NonShared => { - self.state.set(BorrowState::Shared( - NonZero::::new(1).expect("non zero"), - )); + self.state + .set(BorrowState::Shared(NonZeroUsize::new(1).expect("non zero"))); } BorrowState::Mutable => { panic!("store already borrowed mutably"); @@ -148,7 +150,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,17 +167,12 @@ 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" - ); - // Caller just returned borrowed mutably reference to the store, now we can safely borrow it mutably again let _store = store; @@ -197,29 +194,19 @@ 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 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 +215,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 => { @@ -236,7 +223,7 @@ impl Drop for Ref<'_> { } BorrowState::Shared(n) => { self.state.set(BorrowState::Shared( - NonZero::::new(n.get() - 1).expect("non zero"), + NonZeroUsize::new(n.get() - 1).expect("non zero"), )); } _ => unreachable!(), @@ -244,13 +231,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 +246,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 +254,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 +267,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 +294,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 +326,6 @@ mod tests { }; let mut borrow = rc.borrow_mut(); - callback(env, borrow.as_store_mut()) + callback(env, &mut borrow) } } diff --git a/sandbox/sandbox/src/embedded_executor.rs b/sandbox/sandbox/src/embedded_executor.rs index 6a3b08f387a..e319cf38bcd 100644 --- a/sandbox/sandbox/src/embedded_executor.rs +++ b/sandbox/sandbox/src/embedded_executor.rs @@ -26,7 +26,10 @@ use alloc::string::String; use gear_sandbox_env::GLOBAL_NAME_GAS; use gear_wasmer_cache::get_or_compile_with_cache; use sp_wasm_interface_common::HostPointer; -use std::{collections::btree_map::BTreeMap, fs, marker::PhantomData, path::PathBuf, ptr::NonNull}; +use std::{ + collections::btree_map::BTreeMap, fs, marker::PhantomData, path::PathBuf, ptr::NonNull, + sync::OnceLock, +}; use wasmer::{ sys::{BaseTunables, VMConfig}, vm::{ @@ -39,12 +42,17 @@ use wasmer::{ use wasmer_types::{ExternType, Target}; fn fs_cache() -> PathBuf { - let out_dir = PathBuf::from(env!("OUT_DIR")); - let cache = out_dir.join("wasmer-cache"); - if !cache.exists() { - fs::create_dir(&cache).unwrap(); - } - cache + static CACHE_DIR: OnceLock = OnceLock::new(); + CACHE_DIR + .get_or_init(|| { + let out_dir = PathBuf::from(env!("OUT_DIR")); + let cache = out_dir.join("wasmer-cache"); + if !cache.exists() { + fs::create_dir(&cache).unwrap(); + } + cache + }) + .into() } struct CustomTunables { diff --git a/utils/gear-replay-cli/src/cmd/mod.rs b/utils/gear-replay-cli/src/cmd/mod.rs index 0557362be4c..89c5c6396b0 100644 --- a/utils/gear-replay-cli/src/cmd/mod.rs +++ b/utils/gear-replay-cli/src/cmd/mod.rs @@ -63,6 +63,8 @@ pub enum Command { impl Command { pub async fn run(&self, shared: &SharedParams) -> sc_cli::Result<()> { + gear_runtime_interface::sandbox_init(gear_runtime_interface::SandboxBackend::Wasmer); + match &self { Command::ReplayBlock(cmd) => { replay_block::run::(shared.clone(), cmd.clone()).await diff --git a/utils/lazy-pages-fuzzer/Cargo.toml b/utils/lazy-pages-fuzzer/Cargo.toml index ae84c472580..4405740707e 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 -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 bd71156ca9e..b2705a4a389 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 = wasmi::Engine::default(); + let mut store = wasmi::Store::new(&engine, ()); - let gear_fuzz_a: i64 = instance - .export_by_name("gear_fuzz_a") - .unwrap() - .as_global() + 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") .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 [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..7d70b66d8c8 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 = wasmi::Engine::default(); + let mut store = wasmi::Store::new(&engine, ()); + let module = wasmi::Module::new(&engine, &module.into_bytes().unwrap()).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.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 [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); diff --git a/utils/lazy-pages-fuzzer/src/wasmer_backend.rs b/utils/lazy-pages-fuzzer/src/wasmer_backend.rs index b12ae31f233..b234659e896 100644 --- a/utils/lazy-pages-fuzzer/src/wasmer_backend.rs +++ b/utils/lazy-pages-fuzzer/src/wasmer_backend.rs @@ -34,6 +34,12 @@ 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. + // + // We consider it relatively safe because we rely on the fact that during an external function call, + // Wasmer does not access globals mutably, allowing us to access them mutably from the lazy pages' signal handler. store: *mut Store, } diff --git a/utils/lazy-pages-fuzzer/src/wasmi_backend.rs b/utils/lazy-pages-fuzzer/src/wasmi_backend.rs index c03403fa270..ea2d1c6e694 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 sandbox_wasmi::{ - memory_units::Pages, ExternVal, FuncInstance, FuncRef, ImportsBuilder, MemoryInstance, - MemoryRef, Module as WasmiModule, ModuleImportResolver, ModuleInstance, ModuleRef, - RuntimeValue, Trap, TrapCode, ValueType, +use region::{Allocation, Protection}; +use wasmi::{ + core::UntypedVal, Caller, Config, Engine, Error, Instance, Linker, Memory, MemoryType, + Module as WasmiModule, StackLimits, Store, Val, }; use crate::{ @@ -35,127 +37,151 @@ use crate::{ use error::CustomHostError; mod error; -struct Resolver { - memory: MemoryRef, +#[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. + // + // We consider it relatively safe because we rely on the fact that during an external function call, + // Wasmi does not access globals mutably, allowing us to access them mutably from the lazy pages' signal handler. + 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 `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")?; + 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..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::HostError; +use wasmi::core::HostError; #[derive(Debug, Display)] #[display(fmt = "{message}")]