From 82581af77d1c465ce49fb8e6ca042bc274bf3ca2 Mon Sep 17 00:00:00 2001 From: Flavio Castelli Date: Tue, 31 Mar 2026 14:23:14 +0200 Subject: [PATCH] fix(environ): repair unsound StringPool::try_clone() The 43.0 release introduced a soundness bug in StringPool::try_clone(): the cloned map retains &'static str keys pointing into the original pool's strings storage. Once the original Linker is dropped those keys dangle. Cloning a Linker, then dropping the original one, leaves a linker whose registered imports could no longer be found, causing instantiation to fail with "unknown import". Signed-off-by: Flavio Castelli --- crates/environ/src/string_pool.rs | 18 +++++++++++++---- tests/all/linker.rs | 33 +++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 4 deletions(-) diff --git a/crates/environ/src/string_pool.rs b/crates/environ/src/string_pool.rs index f29a90edfd5e..f72a1060bdf7 100644 --- a/crates/environ/src/string_pool.rs +++ b/crates/environ/src/string_pool.rs @@ -73,10 +73,20 @@ impl fmt::Debug for StringPool { impl TryClone for StringPool { fn try_clone(&self) -> Result { - Ok(StringPool { - map: self.map.try_clone()?, - strings: self.strings.try_clone()?, - }) + let mut new_pool = StringPool::new(); + // Re-intern strings in index order so that each Atom value is + // identical in the clone — callers that hold Atoms from the original + // can use them interchangeably with the clone. + // + // Directly cloning `self.map` would copy &'static str keys that point + // into the *original* pool's `strings` allocation. Those pointers + // become dangling once the original is dropped, leading to UB on any + // subsequent lookup. Re-interning ensures the cloned map's keys point + // into the clone's own `strings`. + for s in self.strings.iter() { + new_pool.insert(s)?; + } + Ok(new_pool) } } diff --git a/tests/all/linker.rs b/tests/all/linker.rs index 0cb65f167cfb..39fb3773ebf2 100644 --- a/tests/all/linker.rs +++ b/tests/all/linker.rs @@ -774,3 +774,36 @@ fn linker_defines_table_subtype_err() -> Result<()> { Ok(()) } + +// Regression test for a soundness bug in StringPool::try_clone() introduced +// in wasmtime 43.0.0: cloning a StringPool (and thus a Linker) copied the +// map's &'static str keys that pointed into the *original* pool's string +// storage. After the original was dropped those keys became dangling, causing +// get_atom() on the clone to silently fail to find registered imports. +#[test] +fn linker_clone_drop_original_then_instantiate() -> Result<()> { + let engine = Engine::default(); + let wat = r#"(module + (import "env" "answer" (func (result i32))) + (func (export "run") (result i32) + call 0 + ) + )"#; + let module = Module::new(&engine, wat)?; + + let original = { + let mut l: Linker<()> = Linker::new(&engine); + l.func_wrap("env", "answer", || -> i32 { 42 })?; + l + }; + + let clone = original.clone(); + // Dropping the original must not invalidate the clone's string pool. + drop(original); + + let mut store = Store::new(&engine, ()); + let instance = clone.instantiate(&mut store, &module)?; + let f = instance.get_typed_func::<(), i32>(&mut store, "run")?; + assert_eq!(f.call(&mut store, ())?, 42); + Ok(()) +}