Repository: bytecodealliance/wasmtime
Pull request #2593 (2021-01-21 15:36:36)

Add an instance limit to `Config`
This commit adds a new parameter to `Config` which limits the number of
instances that can be created within a store connected to that `Config`.
The intention here is to provide a default safeguard against
module-linking modules that recursively create too many instances.



Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 4a3f6b091..612d9d3c2 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2959,18 +2959,18 @@ dependencies = [
 
 [[package]]
 name = "zstd"
-version = "0.5.4+zstd.1.4.7"
+version = "0.6.0+zstd.1.4.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "69996ebdb1ba8b1517f61387a883857818a66c8a295f487b1ffd8fd9d2c82910"
+checksum = "d4e44664feba7f2f1a9f300c1f6157f2d1bfc3c15c6f3cf4beabf3f5abe9c237"
 dependencies = [
  "zstd-safe",
 ]
 
 [[package]]
 name = "zstd-safe"
-version = "2.0.6+zstd.1.4.7"
+version = "3.0.0+zstd.1.4.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "98aa931fb69ecee256d44589d19754e61851ae4769bf963b385119b1cc37a49e"
+checksum = "d9447afcd795693ad59918c7bbffe42fdd6e467d708f3537e3dc14dc598c573f"
 dependencies = [
  "libc",
  "zstd-sys",
@@ -2978,9 +2978,9 @@ dependencies = [
 
 [[package]]
 name = "zstd-sys"
-version = "1.4.18+zstd.1.4.7"
+version = "1.4.19+zstd.1.4.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a1e6e8778706838f43f771d80d37787cb2fe06dafe89dd3aebaf6721b9eaec81"
+checksum = "ec24a9273d24437afb8e71b16f3d9a5d569193cccdb7896213b59f552f387674"
 dependencies = [
  "cc",
  "glob",
diff --git a/crates/c-api/include/wasmtime.h b/crates/c-api/include/wasmtime.h
index 6752459a4..948552b63 100644
--- a/crates/c-api/include/wasmtime.h
+++ b/crates/c-api/include/wasmtime.h
@@ -274,6 +274,14 @@ WASMTIME_CONFIG_PROP(void, static_memory_guard_size, uint64_t)
  */
 WASMTIME_CONFIG_PROP(void, dynamic_memory_guard_size, uint64_t)
 
+/**
+ * \brief Configures the maximum number of instances that can be created.
+ *
+ * For more information see the Rust documentation at
+ * https://bytecodealliance.github.io/wasmtime/api/wasmtime/struct.Config.html#method.max_instances.
+ */
+WASMTIME_CONFIG_PROP(void, max_instances, size_t)
+
 /**
  * \brief Enables Wasmtime's cache and loads configuration from the specified
  * path.
diff --git a/crates/c-api/src/config.rs b/crates/c-api/src/config.rs
index 08da598ec..ae0cb6abd 100644
--- a/crates/c-api/src/config.rs
+++ b/crates/c-api/src/config.rs
@@ -171,3 +171,8 @@ pub extern "C" fn wasmtime_config_static_memory_guard_size_set(c: &mut wasm_conf
 pub extern "C" fn wasmtime_config_dynamic_memory_guard_size_set(c: &mut wasm_config_t, size: u64) {
     c.config.dynamic_memory_guard_size(size);
 }
+
+#[no_mangle]
+pub extern "C" fn wasmtime_config_max_instances(c: &mut wasm_config_t, limit: usize) {
+    c.config.max_instances(limit);
+}
diff --git a/crates/cache/Cargo.toml b/crates/cache/Cargo.toml
index 970ea91b9..9995dbfb6 100644
--- a/crates/cache/Cargo.toml
+++ b/crates/cache/Cargo.toml
@@ -18,7 +18,7 @@ log = { version = "0.4.8", default-features = false }
 serde = { version = "1.0.94", features = ["derive"] }
 sha2 = "0.9.0"
 toml = "0.5.5"
-zstd = "0.5"
+zstd = { version = "0.6", default-features = false }
 
 [target.'cfg(target_os = "windows")'.dependencies]
 winapi = "0.3.7"
diff --git a/crates/wasmtime/src/config.rs b/crates/wasmtime/src/config.rs
index 5a0686de9..15b5ad58c 100644
--- a/crates/wasmtime/src/config.rs
+++ b/crates/wasmtime/src/config.rs
@@ -33,6 +33,7 @@ pub struct Config {
     pub(crate) max_wasm_stack: usize,
     pub(crate) features: WasmFeatures,
     pub(crate) wasm_backtrace_details_env_used: bool,
+    pub(crate) max_instances: usize,
 }
 
 impl Config {
@@ -79,6 +80,7 @@ impl Config {
                 multi_value: true,
                 ..WasmFeatures::default()
             },
+            max_instances: 10_000,
         };
         ret.wasm_backtrace_details(WasmBacktraceDetails::Environment);
         return ret;
@@ -635,6 +637,15 @@ impl Config {
         self
     }
 
+    /// Configures the maximum number of instances which can be created within
+    /// this `Store`.
+    ///
+    /// Instantiation will fail with an error if this limit is exceeded.
+    pub fn max_instances(&mut self, instances: usize) -> &mut Self {
+        self.max_instances = instances;
+        self
+    }
+
     pub(crate) fn target_isa(&self) -> Box<dyn TargetIsa> {
         self.isa_flags
             .clone()
diff --git a/crates/wasmtime/src/instance.rs b/crates/wasmtime/src/instance.rs
index 60c52d627..fed2bb29c 100644
--- a/crates/wasmtime/src/instance.rs
+++ b/crates/wasmtime/src/instance.rs
@@ -46,6 +46,8 @@ fn instantiate(
         &mut ImportsBuilder<'_>,
     ) -> Result<()>,
 ) -> Result<RuntimeInstance, Error> {
+    store.bump_instance_count()?;
+
     let compiled_module = module.compiled_module();
     let env_module = compiled_module.module();
 
diff --git a/crates/wasmtime/src/store.rs b/crates/wasmtime/src/store.rs
index 9c622faad..be4aaf156 100644
--- a/crates/wasmtime/src/store.rs
+++ b/crates/wasmtime/src/store.rs
@@ -4,7 +4,7 @@ use crate::trampoline::StoreInstanceHandle;
 use crate::{Engine, Module};
 use anyhow::{bail, Result};
 use std::any::Any;
-use std::cell::RefCell;
+use std::cell::{Cell, RefCell};
 use std::collections::HashSet;
 use std::fmt;
 use std::hash::{Hash, Hasher};
@@ -67,6 +67,8 @@ pub(crate) struct StoreInner {
     /// Set of all compiled modules that we're holding a strong reference to
     /// the module's code for. This includes JIT functions, trampolines, etc.
     modules: RefCell<HashSet<ArcModuleCode>>,
+    /// The number of instantiated instances in this store.
+    instance_count: Cell<usize>,
 }
 
 struct HostInfoKey(VMExternRef);
@@ -109,6 +111,7 @@ impl Store {
                 stack_map_registry: StackMapRegistry::default(),
                 frame_info: Default::default(),
                 modules: Default::default(),
+                instance_count: Default::default(),
             }),
         }
     }
@@ -213,6 +216,15 @@ impl Store {
         }
     }
 
+    pub(crate) fn bump_instance_count(&self) -> Result<()> {
+        let n = self.inner.instance_count.get();
+        self.inner.instance_count.set(n + 1);
+        if n >= self.engine().config().max_instances {
+            bail!("instance limit of {} exceeded", n);
+        }
+        Ok(())
+    }
+
     pub(crate) unsafe fn add_instance(&self, handle: InstanceHandle) -> StoreInstanceHandle {
         self.inner.instances.borrow_mut().push(handle.clone());
         StoreInstanceHandle {


Test patch:
diff --git a/tests/all/module_linking.rs b/tests/all/module_linking.rs
index 10ccfc098..b81a9deb3 100644
--- a/tests/all/module_linking.rs
+++ b/tests/all/module_linking.rs
@@ -185,3 +185,39 @@ fn imports_exports() -> Result<()> {
     }
     Ok(())
 }
+
+#[test]
+fn limit_instances() -> Result<()> {
+    let mut config = Config::new();
+    config.wasm_module_linking(true);
+    config.max_instances(10);
+    let engine = Engine::new(&config);
+    let module = Module::new(
+        &engine,
+        r#"
+            (module $PARENT
+              (module $m0)
+              (module $m1
+                (instance (instantiate (module outer $PARENT $m0)))
+                (instance (instantiate (module outer $PARENT $m0))))
+              (module $m2
+                (instance (instantiate (module outer $PARENT $m1)))
+                (instance (instantiate (module outer $PARENT $m1))))
+              (module $m3
+                (instance (instantiate (module outer $PARENT $m2)))
+                (instance (instantiate (module outer $PARENT $m2))))
+              (module $m4
+                (instance (instantiate (module outer $PARENT $m3)))
+                (instance (instantiate (module outer $PARENT $m3))))
+              (module $m5
+                (instance (instantiate (module outer $PARENT $m4)))
+                (instance (instantiate (module outer $PARENT $m4))))
+              (instance (instantiate $m5))
+            )
+        "#,
+    )?;
+    let store = Store::new(&engine);
+    let err = Instance::new(&store, &module, &[]).err().unwrap();
+    assert!(err.to_string().contains("instance limit of 10 exceeded"));
+    Ok(())
+}