From 4803a1f14b057f9bbbc9263a5ba16c1c659c3c34 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Mon, 27 Jun 2022 06:55:50 -0700 Subject: [PATCH] Add a method to Linker and flag to wasmtime-cli to trap unknown import funcs (#4312) * Add a method to Linker and flag to wasmtime-cli to trap unknown import funcs Sometimes users have a Command module which imports functions unknown to the wasmtime-cli, but does not call them at runtime. This PR provides a convenience method on Linker to define all unknown import functions in a given Module as a trivial implementation which traps, and hooks this up to a new cli flag --trap-unknown-imports. * add cfg guards - func_new requires compiler (naturally) --- crates/wasmtime/src/linker.rs | 46 +++++++++++++++++++++++++++++++++-- src/commands/run.rs | 12 ++++++++- tests/all/linker.rs | 37 ++++++++++++++++++++++++++++ 3 files changed, 92 insertions(+), 3 deletions(-) diff --git a/crates/wasmtime/src/linker.rs b/crates/wasmtime/src/linker.rs index 2a45b1ca0e0d..fd5230d1f7dc 100644 --- a/crates/wasmtime/src/linker.rs +++ b/crates/wasmtime/src/linker.rs @@ -2,8 +2,8 @@ use crate::func::HostFunc; use crate::instance::InstancePre; use crate::store::StoreOpaque; use crate::{ - AsContextMut, Caller, Engine, Extern, Func, FuncType, ImportType, Instance, IntoFunc, Module, - StoreContextMut, Trap, Val, ValRaw, + AsContextMut, Caller, Engine, Extern, ExternType, Func, FuncType, ImportType, Instance, + IntoFunc, Module, StoreContextMut, Trap, Val, ValRaw, }; use anyhow::{anyhow, bail, Context, Result}; use log::warn; @@ -237,6 +237,48 @@ impl Linker { self } + /// Implement any imports of the given [`Module`] with a function which traps. + /// + /// By default a [`Linker`] will error when unknown imports are encountered + /// in a command module while using [`Linker::module`]. Use this function + /// when + /// + /// This method can be used to allow unknown imports from command modules. + /// + /// # Examples + /// + /// ``` + /// # use wasmtime::*; + /// # fn main() -> anyhow::Result<()> { + /// # let engine = Engine::default(); + /// # let module = Module::new(&engine, "(module (import \"unknown\" \"import\" (func)))")?; + /// # let mut store = Store::new(&engine, ()); + /// let mut linker = Linker::new(&engine); + /// linker.define_unknown_imports_as_traps(&module)?; + /// linker.instantiate(&mut store, &module)?; + /// # Ok(()) + /// # } + /// ``` + #[cfg(compiler)] + #[cfg_attr(nightlydoc, doc(cfg(feature = "cranelift")))] // see build.rs + pub fn define_unknown_imports_as_traps(&mut self, module: &Module) -> anyhow::Result<()> { + for import in module.imports() { + if self._get_by_import(&import).is_err() { + if let ExternType::Func(func_ty) = import.ty() { + let err_msg = format!( + "unknown import: `{}::{}` has not been defined", + import.module(), + import.name(), + ); + self.func_new(import.module(), import.name(), func_ty, move |_, _, _| { + Err(Trap::new(err_msg.clone())) + })?; + } + } + } + Ok(()) + } + /// Defines a new item in this [`Linker`]. /// /// This method will add a new definition, by name, to this instance of diff --git a/src/commands/run.rs b/src/commands/run.rs index 05f466d9b4ec..7dc398bb8c44 100644 --- a/src/commands/run.rs +++ b/src/commands/run.rs @@ -82,6 +82,11 @@ pub struct RunCommand { #[clap(long = "allow-unknown-exports")] allow_unknown_exports: bool, + /// Allow the main module to import unknown functions, using an + /// implementation that immediately traps, when running commands. + #[clap(long = "trap-unknown-imports")] + trap_unknown_imports: bool, + /// Allow executing precompiled WebAssembly modules as `*.cwasm` files. /// /// Note that this option is not safe to pass if the module being passed in @@ -313,8 +318,13 @@ impl RunCommand { } // Read the wasm module binary either as `*.wat` or a raw binary. - // Use "" as a default module name. let module = self.load_module(linker.engine(), &self.module)?; + // The main module might be allowed to have unknown imports, which + // should be defined as traps: + if self.trap_unknown_imports { + linker.define_unknown_imports_as_traps(&module)?; + } + // Use "" as a default module name. linker .module(&mut *store, "", &module) .context(format!("failed to instantiate {:?}", self.module))?; diff --git a/tests/all/linker.rs b/tests/all/linker.rs index c356fedbc990..cc8e060afd05 100644 --- a/tests/all/linker.rs +++ b/tests/all/linker.rs @@ -340,3 +340,40 @@ fn instance_pre() -> Result<()> { instance_pre.instantiate(&mut store)?; Ok(()) } + +#[test] +fn test_trapping_unknown_import() -> Result<()> { + const WAT: &str = r#" + (module + (type $t0 (func)) + (import "" "imp" (func $.imp (type $t0))) + (func $run call $.imp) + (func $other) + (export "run" (func $run)) + (export "other" (func $other)) + ) + "#; + + let mut store = Store::<()>::default(); + let module = Module::new(store.engine(), WAT).expect("failed to create module"); + let mut linker = Linker::new(store.engine()); + + linker.define_unknown_imports_as_traps(&module)?; + let instance = linker.instantiate(&mut store, &module)?; + + // "run" calls an import function which will not be defined, so it should trap + let run_func = instance + .get_func(&mut store, "run") + .expect("expected a run func in the module"); + + assert!(run_func.call(&mut store, &[], &mut []).is_err()); + + // "other" does not call the import function, so it should not trap + let other_func = instance + .get_func(&mut store, "other") + .expect("expected an other func in the module"); + + other_func.call(&mut store, &[], &mut [])?; + + Ok(()) +}