You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This is not triggered by a .wasm file; it is a host-embedding issue in the C API, so no Wasm module is needed. Below is a self-contained two-file Cargo project that links against the in-tree wasmtime-c-api and calls wasm_memorytype_limits concurrently from 32 threads on 10,000 shared wasm_memorytype_t objects. Copy the two files into a new directory inside the Wasmtime source tree (e.g. <wasmtime-repo>/repro/poc/), then replace the CRATES_CAPI placeholder with the relative path back to crates/c-api (one-liner included below), and cargo run.
use std::sync::{Arc,Barrier};use std::thread;use wasmtime_c_api::{wasm_limits_t, wasm_memorytype_limits, wasm_memorytype_new};#[derive(Copy,Clone)]structSharedPtr(usize);unsafeimplSendforSharedPtr{}unsafeimplSyncforSharedPtr{}fnmain(){let limits = wasm_limits_t{min:1,max: u32::MAX};// 10_000 distinct wasm_memorytype_t objects, all shared across 32 threads.let ptrs:Vec<_> = (0..10_000).map(|_| SharedPtr(Box::into_raw(wasm_memorytype_new(&limits))asusize)).collect();let ptrs = Arc::new(ptrs);let start = Arc::new(Barrier::new(32));letmut threads = Vec::new();for _ in0..32{let start = start.clone();let ptrs = ptrs.clone();
threads.push(thread::spawn(move || {for ptr in ptrs.iter().copied(){// First call on each object races across all 32 threads at once.
start.wait();let mt = unsafe{&*(ptr.0as*const_)};let limits = wasm_memorytype_limits(mt);assert_eq!(limits.min,1);
start.wait();}}));}for t in threads { t.join().unwrap();}}
Steps to Reproduce
From the Wasmtime main branch (repo HEAD 08c456e887; the affected file was last touched by 7948e0ff62, "Expose custom page sizes in the C and C++ APIs (Expose custom page sizes in the C and C++ APIs #11890)"), create the two files above inside the repo (e.g. at <repo>/repro/poc/).
Fix the dependency path, relative to that directory:
sed -i 's#CRATES_CAPI#../../crates/c-api#' Cargo.toml
Build and run it (first run compiles the C API, ~a few minutes):
cargo run
32 threads concurrently make the first call to wasm_memorytype_limits on each shared wasm_memorytype_t.
Expected Results
Per the C API's documented thread-safety contract, the program should run to completion. Every wasm_memorytype_limits call returns the cached wasm_limits_t { min: 1, ... }, all assertions pass, and all threads join.
Actual Results
The process aborts. A std::cell::OnceCell::get_or_init reentrancy check trips during the concurrent first-initialization race, panicking with reentrant init; because the panic occurs inside an extern "C" function it cannot unwind, so the process aborts (exit status 141 / SIGABRT). Aborted output:
thread '<unnamed>' (707599) panicked at /rustc/.../library/core/src/cell/once.rs:300:66:
reentrant init
...
thread '<unnamed>' (707599) panicked at /rustc/.../library/core/src/panicking.rs:225:5:
panic in a function that cannot unwind
stack backtrace:
...
19: wasm_memorytype_limits
20: capi_oncecell_race_poc::main::{{closure}}
thread caused non-unwinding panic. aborting.
Versions and Environment
Wasmtime version or commit: Repo main HEAD 08c456e8870b0b85566f9e9808b7a2f044eaa265 (affected file crates/c-api/src/types/memory.rs, last modified by commit 7948e0ff62). PHPUnit/driver builds of the CLI report Wasmtime 47.0.0.
Operating system: Linux 5.15.0-139-generic (Ubuntu 20.04-based)
Architecture: x86_64
Rust: rustc 1.96.0 (ac68faa20 2026-05-25)
Extra Info
Root cause. crates/c-api/src/types/memory.rs:17 declares limits_cache: std::cell::OnceCell<wasm_limits_t> and wasm_memorytype_limits (:60) initializes it through mt.limits_cache.get_or_init(...) (:62). std::cell::OnceCell is a single-threaded type; this mutates shared state behind an immutable &wasm_memorytype_t argument.
This violates the documented C-API thread-safety contract. crates/c-api/include/wasmtime.h (Thread Safety section, ~lines 129-149) states: "functions which correspond to &T in Rust can be called concurrently with any other methods that take &T" and that "Functions which don't mutate anything, such as learning type information ... can be called concurrently." wasm_memorytype_limits(mt: &wasm_memorytype_t) is precisely such a read-only &T type-information query, so concurrent access is documented as safe; the OnceCell makes it a data race that aborts.
Because the mutation is hidden behind &T, the Rust type system's normal !Sync protection is bypassed — wasm_memorytype_t advertises shared-reference (concurrent) access while internally mutating a single-threaded cell.
Same class of bug exists in other C-API type-query caches that use std::cell::OnceCell behind &T, e.g. wasm_functype_t (params_cache/returns_cache), wasm_importtype_t (module_cache/...), and wasm_frame_t (func_name/module_name). An audit/reuse of the same fix is warranted across those paths.
Impact is denial of service: a multi-threaded host that concurrently queries C-API type metadata on a shared, attacker-influenceable type object can be made to abort. Default single-threaded usage is unaffected.
Suggested fixes: replace these caches with a thread-safe primitive such as std::sync::OnceLock (or once_cell::sync::OnceCell); or remove the small type-query caches and compute the value directly on each call; and apply the same change to the sibling caches noted above.
Test Case
This is not triggered by a
.wasmfile; it is a host-embedding issue in the C API, so no Wasm module is needed. Below is a self-contained two-file Cargo project that links against the in-treewasmtime-c-apiand callswasm_memorytype_limitsconcurrently from 32 threads on 10,000 sharedwasm_memorytype_tobjects. Copy the two files into a new directory inside the Wasmtime source tree (e.g.<wasmtime-repo>/repro/poc/), then replace theCRATES_CAPIplaceholder with the relative path back tocrates/c-api(one-liner included below), andcargo run.Cargo.toml:src/main.rs:Steps to Reproduce
mainbranch (repo HEAD08c456e887; the affected file was last touched by7948e0ff62, "Expose custom page sizes in the C and C++ APIs (Expose custom page sizes in the C and C++ APIs #11890)"), create the two files above inside the repo (e.g. at<repo>/repro/poc/).wasm_memorytype_limitson each sharedwasm_memorytype_t.Expected Results
Per the C API's documented thread-safety contract, the program should run to completion. Every
wasm_memorytype_limitscall returns the cachedwasm_limits_t { min: 1, ... }, all assertions pass, and all threads join.Actual Results
The process aborts. A
std::cell::OnceCell::get_or_initreentrancy check trips during the concurrent first-initialization race, panicking withreentrant init; because the panic occurs inside anextern "C"function it cannot unwind, so the process aborts (exit status 141 /SIGABRT). Aborted output:Versions and Environment
Wasmtime version or commit: Repo
mainHEAD08c456e8870b0b85566f9e9808b7a2f044eaa265(affected filecrates/c-api/src/types/memory.rs, last modified by commit7948e0ff62). PHPUnit/driver builds of the CLI reportWasmtime 47.0.0.Operating system: Linux 5.15.0-139-generic (Ubuntu 20.04-based)
Architecture: x86_64
Rust: rustc 1.96.0 (ac68faa20 2026-05-25)
Extra Info
crates/c-api/src/types/memory.rs:17declareslimits_cache: std::cell::OnceCell<wasm_limits_t>andwasm_memorytype_limits(:60) initializes it throughmt.limits_cache.get_or_init(...)(:62).std::cell::OnceCellis a single-threaded type; this mutates shared state behind an immutable&wasm_memorytype_targument.crates/c-api/include/wasmtime.h(Thread Safety section, ~lines 129-149) states: "functions which correspond to&Tin Rust can be called concurrently with any other methods that take&T" and that "Functions which don't mutate anything, such as learning type information ... can be called concurrently."wasm_memorytype_limits(mt: &wasm_memorytype_t)is precisely such a read-only&Ttype-information query, so concurrent access is documented as safe; theOnceCellmakes it a data race that aborts.&T, the Rust type system's normal!Syncprotection is bypassed —wasm_memorytype_tadvertises shared-reference (concurrent) access while internally mutating a single-threaded cell.std::cell::OnceCellbehind&T, e.g.wasm_functype_t(params_cache/returns_cache),wasm_importtype_t(module_cache/...), andwasm_frame_t(func_name/module_name). An audit/reuse of the same fix is warranted across those paths.std::sync::OnceLock(oronce_cell::sync::OnceCell); or remove the small type-query caches and compute the value directly on each call; and apply the same change to the sibling caches noted above.