Skip to content

Commit 4610d54

Browse files
committed
Fix a use-after-free of trampoline code
This commit fixes an issue with wasmtime where it was possible for a trampoline from one module to get used for another module after it was freed. This issue arises because we register a module's native trampolines *before* it's fully instantiated, which is a fallible process. Some fallibility is predictable, such as import type mismatches, but other fallibility is less predictable, such as failure to allocate a linear memory. The problem happened when a module was registered with a `Store`, retaining information about its trampolines, but then instantiation failed and the module's code was never persisted within the `Store`. Unlike as documented in #2374 the `Module` inside an `Instance` is not the primary way to hold on to a module's code, but rather the `Arc<ModuleCode>` is persisted within the global frame information off on the side. This persistence only made its way into the store through the `Box<Any>` field of `InstanceHandle`, but that's never made if instantiation fails during import matching. The fix here is to build on the refactoring of #2407 to not store module code in frame information but rather explicitly in the `Store`. Registration is now deferred until just-before an instance handle is created, and during module registration we insert the `Arc<ModuleCode>` into a set stored within the `Store`.
1 parent ae89299 commit 4610d54

3 files changed

Lines changed: 62 additions & 9 deletions

File tree

crates/wasmtime/src/instance.rs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ fn instantiate(
1616
imports: Imports<'_>,
1717
host: Box<dyn Any>,
1818
) -> Result<StoreInstanceHandle, Error> {
19+
// Register the module just before instantiation to ensure we have a
20+
// trampoline registered for every signature and to preserve the module's
21+
// compiled JIT code within the `Store`.
22+
store.register_module(compiled_module);
23+
1924
let config = store.engine().config();
2025
let instance = unsafe {
2126
let instance = compiled_module.instantiate(
@@ -98,11 +103,6 @@ fn instantiate(
98103
#[derive(Clone)]
99104
pub struct Instance {
100105
pub(crate) handle: StoreInstanceHandle,
101-
// Note that this is required to keep the module's code memory alive while
102-
// we have a handle to this `Instance`. We may eventually want to shrink
103-
// this to only hold onto the bare minimum each instance needs to allow
104-
// deallocating some `Module` resources early, but until then we just hold
105-
// on to everything.
106106
module: Module,
107107
}
108108

@@ -165,7 +165,6 @@ impl Instance {
165165
bail!("cross-`Engine` instantiation is not currently supported");
166166
}
167167

168-
store.register_module(module.compiled_module());
169168
let handle = with_imports(store, module.compiled_module(), imports, |imports| {
170169
instantiate(store, module.compiled_module(), imports, Box::new(()))
171170
})?;

crates/wasmtime/src/store.rs

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use crate::Engine;
55
use anyhow::{bail, Result};
66
use std::any::Any;
77
use std::cell::RefCell;
8+
use std::collections::HashSet;
89
use std::fmt;
910
use std::hash::{Hash, Hasher};
1011
use std::rc::{Rc, Weak};
@@ -63,9 +64,9 @@ pub(crate) struct StoreInner {
6364
/// Information about JIT code which allows us to test if a program counter
6465
/// is in JIT code, lookup trap information, etc.
6566
frame_info: RefCell<StoreFrameInfo>,
66-
/// List of all compiled modules that we're holding a strong reference to
67+
/// Set of all compiled modules that we're holding a strong reference to
6768
/// the module's code for. This includes JIT functions, trampolines, etc.
68-
modules: RefCell<Vec<Arc<ModuleCode>>>,
69+
modules: RefCell<HashSet<ArcModuleCode>>,
6970
}
7071

7172
struct HostInfoKey(VMExternRef);
@@ -169,6 +170,15 @@ impl Store {
169170
// a `Func` wrapper for any function in the module, which requires that
170171
// we know about the signature and trampoline for all instances.
171172
self.register_signatures(module);
173+
174+
// And finally with a module being instantiated into this `Store` we
175+
// need to preserve its jit-code. References to this module's code and
176+
// trampolines are not owning-references so it's our responsibility to
177+
// keep it all alive within the `Store`.
178+
self.inner
179+
.modules
180+
.borrow_mut()
181+
.insert(ArcModuleCode(module.code().clone()));
172182
}
173183

174184
fn register_jit_code(&self, module: &CompiledModule) {
@@ -180,7 +190,6 @@ impl Store {
180190
// Only register this module if it hasn't already been registered.
181191
if !self.is_wasm_code(first_pc) {
182192
self.inner.frame_info.borrow_mut().register(module);
183-
self.inner.modules.borrow_mut().push(module.code().clone());
184193
}
185194
}
186195

@@ -435,3 +444,21 @@ impl InterruptHandle {
435444
self.interrupts.interrupt()
436445
}
437446
}
447+
448+
// Wrapper struct to implement hash/equality based on the pointer value of the
449+
// `Arc` in question.
450+
struct ArcModuleCode(Arc<ModuleCode>);
451+
452+
impl PartialEq for ArcModuleCode {
453+
fn eq(&self, other: &ArcModuleCode) -> bool {
454+
Arc::ptr_eq(&self.0, &other.0)
455+
}
456+
}
457+
458+
impl Eq for ArcModuleCode {}
459+
460+
impl Hash for ArcModuleCode {
461+
fn hash<H: Hasher>(&self, hasher: &mut H) {
462+
Arc::as_ptr(&self.0).hash(hasher)
463+
}
464+
}

tests/all/func.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -522,3 +522,30 @@ fn externref_signature_no_reference_types() -> anyhow::Result<()> {
522522
);
523523
Ok(())
524524
}
525+
526+
#[test]
527+
fn trampolines_always_valid() -> anyhow::Result<()> {
528+
let func = {
529+
// Compile two modules up front
530+
let store = Store::default();
531+
let module1 = Module::new(store.engine(), "(module (import \"\" \"\" (func)))")?;
532+
let module2 = Module::new(store.engine(), "(module (func (export \"\")))")?;
533+
// Start instantiating the first module, but this will fail.
534+
// Historically this registered the module's trampolines with `Store`
535+
// before the failure, but then after the failure the `Store` didn't
536+
// hold onto the trampoline.
537+
drop(Instance::new(&store, &module1, &[]));
538+
drop(module1);
539+
540+
// Then instantiate another module which has the same function type (no
541+
// parameters or results) which tries to use the trampoline defined in
542+
// the previous module. Then we extract the function and, in another
543+
// scope where everything is dropped, we call the func.
544+
let i = Instance::new(&store, &module2, &[])?;
545+
i.get_func("").unwrap()
546+
};
547+
548+
// ... and no segfaults! right? right? ...
549+
func.call(&[])?;
550+
Ok(())
551+
}

0 commit comments

Comments
 (0)