From a1bb681b5ba6c4fbccfe45df8d3f65eb24838e14 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Sat, 13 Jun 2026 12:24:15 -0700 Subject: [PATCH] Fix leaking fiber stacks on OOM This commit fixes an issue where a fiber stack could be allocated, but then allocating a fiber itself could fail, which would leak the fiber stack within the pooling allocator. --- crates/fiber/src/lib.rs | 16 +++++++++-- crates/fiber/src/windows.rs | 7 ++--- crates/fuzzing/tests/oom/fiber_stack.rs | 35 +++++++++++++++++++++++++ crates/fuzzing/tests/oom/main.rs | 1 + crates/wasmtime/src/runtime/fiber.rs | 11 +++++++- 5 files changed, 64 insertions(+), 6 deletions(-) create mode 100644 crates/fuzzing/tests/oom/fiber_stack.rs diff --git a/crates/fiber/src/lib.rs b/crates/fiber/src/lib.rs index 5f6d630840d2..287ce6eaa9db 100644 --- a/crates/fiber/src/lib.rs +++ b/crates/fiber/src/lib.rs @@ -41,6 +41,12 @@ cfg_if::cfg_if! { /// Represents an execution stack to use for a fiber. pub struct FiberStack(imp::FiberStack); +impl core::fmt::Debug for FiberStack { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("FiberStack").finish_non_exhaustive() + } +} + fn _assert_send_sync() { fn _assert_send() {} fn _assert_sync() {} @@ -171,11 +177,17 @@ impl<'a, Resume, Yield, Return> Fiber<'a, Resume, Yield, Return> { /// This function returns a `Fiber` which, when resumed, will execute `func` /// to completion. When desired the `func` can suspend itself via /// `Fiber::suspend`. + /// On error the provided `stack` is handed back to the caller (paired with + /// the error) so that it can be deallocated rather than leaked; the stack is + /// only consumed by this `Fiber` on success. pub fn new( stack: FiberStack, func: impl FnOnce(Resume, &mut Suspend) -> Return + 'a, - ) -> Result { - let inner = imp::Fiber::new(&stack.0, func)?; + ) -> Result { + let inner = match imp::Fiber::new(&stack.0, func) { + Ok(inner) => inner, + Err(e) => return Err((e, stack)), + }; Ok(Self { stack: Some(stack), diff --git a/crates/fiber/src/windows.rs b/crates/fiber/src/windows.rs index 2a85d1ca8952..d1a0d565400b 100644 --- a/crates/fiber/src/windows.rs +++ b/crates/fiber/src/windows.rs @@ -6,6 +6,7 @@ use std::io; use std::mem::needs_drop; use std::ops::Range; use std::ptr; +use wasmtime_environ::prelude::*; use windows_sys::Win32::Foundation::*; use windows_sys::Win32::System::Threading::*; @@ -102,13 +103,13 @@ where } impl Fiber { - pub fn new(stack: &FiberStack, func: F) -> io::Result + pub fn new(stack: &FiberStack, func: F) -> Result where F: FnOnce(A, &mut super::Suspend) -> C, { unsafe { let state = Box::new(StartState { - initial_closure: Cell::new(Box::into_raw(Box::new(func)).cast()), + initial_closure: Cell::new(Box::into_raw(try_new(func)?).cast()), parent: Cell::new(ptr::null_mut()), result_location: Cell::new(ptr::null()), }); @@ -123,7 +124,7 @@ impl Fiber { if fiber.is_null() { drop(Box::from_raw(state.initial_closure.get().cast::())); - return Err(io::Error::last_os_error()); + return Err(io::Error::last_os_error().into()); } Ok(Self { fiber, state }) diff --git a/crates/fuzzing/tests/oom/fiber_stack.rs b/crates/fuzzing/tests/oom/fiber_stack.rs new file mode 100644 index 000000000000..546202fe04ec --- /dev/null +++ b/crates/fuzzing/tests/oom/fiber_stack.rs @@ -0,0 +1,35 @@ +#![cfg(arc_try_new)] + +use wasmtime::{ + Config, Engine, InstanceAllocationStrategy, Linker, Module, PoolingAllocationConfig, Result, + Store, +}; +use wasmtime_fuzzing::oom::OomTest; + +#[tokio::test] +async fn pooling_allocator_fiber_stack_slot_leak_on_oom() -> Result<()> { + let mut pool = PoolingAllocationConfig::default(); + pool.total_stacks(1); + pool.total_memories(10); + pool.total_tables(10); + pool.total_core_instances(10); + + let mut config = Config::new(); + config.concurrency_support(false); + config.allocation_strategy(InstanceAllocationStrategy::Pooling(pool)); + let engine = Engine::new(&config)?; + let module = Module::new(&engine, r#"(module (func (export "f")))"#)?; + let linker = Linker::<()>::new(&engine); + let instance_pre = linker.instantiate_pre(&module)?; + + OomTest::new() + .allow_alloc_after_oom(true) + .test_async(|| async { + let mut store = Store::try_new(&engine, ())?; + let instance = instance_pre.instantiate_async(&mut store).await?; + let f = instance.get_typed_func::<(), ()>(&mut store, "f")?; + f.call_async(&mut store, ()).await?; + Ok(()) + }) + .await +} diff --git a/crates/fuzzing/tests/oom/main.rs b/crates/fuzzing/tests/oom/main.rs index 66b546d6c449..f917c76caed9 100644 --- a/crates/fuzzing/tests/oom/main.rs +++ b/crates/fuzzing/tests/oom/main.rs @@ -14,6 +14,7 @@ mod config; mod engine; mod entity_set; mod error; +mod fiber_stack; mod func; mod func_type; mod fuzz; diff --git a/crates/wasmtime/src/runtime/fiber.rs b/crates/wasmtime/src/runtime/fiber.rs index bf9282fcfb77..779763b26a2c 100644 --- a/crates/wasmtime/src/runtime/fiber.rs +++ b/crates/wasmtime/src/runtime/fiber.rs @@ -836,7 +836,16 @@ where let reset = ResetCurrentPointersToNull(store_ref); fun(reset.0) - })?; + }); + let fiber = match fiber { + Ok(fiber) => fiber, + Err((e, stack)) => { + unsafe { + engine.allocator().deallocate_fiber_stack(stack); + } + return Err(e); + } + }; Ok(StoreFiber { state: Some( FiberResumeState {