Oct 3, 2026·7 min read·14 visits
A state desynchronization flaw in Wasmtime's bulk operations allowed host reentrancy during loop preemption checks. Under specific configurations, a callback could modify the linear memory layout or garbage-collection heap, causing the resumed loop to perform use-after-free operations or out-of-bounds writes, potentially leading to WebAssembly sandbox escape.
CVE-2026-104855 is a critical vulnerability involving a race condition and reentrant state desynchronization within Wasmtime, a standalone WebAssembly runtime. Due to incremental mid-operation preemption points in compiler-generated loops for bulk memory and table operations, a host-defined epoch or fuel deadline callback could mutate the WebAssembly Store. Upon resuming, the virtual machine utilized stale cached pointers, resulting in use-after-free, out-of-bounds writes, and sandbox escape.
The affected component is the Cranelift compiler within Wasmtime, specifically the logic responsible for translating bulk operations such as memory.copy, table.grow, and array.copy into target-specific machine instructions. WebAssembly bulk operations are inherently expensive because they process large contiguous buffers or table structures in a single logical instruction. In multi-tenant environments, executing these instructions monolithically could block the execution thread, circumventing scheduling constraints and deadline mechanisms. To prevent thread monopolization, Wasmtime's compiler partitioned these bulk operations into chunked loops interspersed with preemption points.
These preemption points were designed to check current fuel levels and epoch-based deadlines. When an epoch expired or fuel was exhausted, control returned to the host embedder via a user-defined callback. This architecture created an unexpected reentrancy vector where the host application could execute arbitrary code, modify VM state, or trigger garbage collection before yielding execution back to the guest. Consequently, the assumption of atomic execution during bulk operations was violated.
The security boundary breached is the WebAssembly sandbox itself. WebAssembly relies on strict memory isolation and type safety to guarantee that guest code cannot access host memory or other guest instances. By allowing the host to mutate internal store structures mid-operation, the compiler's assumptions about pointer validity were invalidated, converting safe WebAssembly operations into native memory corruption vulnerabilities.
The underlying bug is classified as a concurrent execution synchronization issue, mapping specifically to CWE-362 (Race Condition) and CWE-825 (Expired Pointer Dereference). The root cause lies in how the Cranelift compiler optimized bulk memory operations by caching native pointers. Before entering a chunked loop (such as one executing a memory.copy instruction), the compiled machine code resolved the base address of the WebAssembly linear memory and stored it in a CPU register or a stack slot to avoid the overhead of resolving it on every iteration.
If the cooperative preemption check triggered during an iteration, the WebAssembly virtual machine suspended execution and invoked the host-defined Store::epoch_deadline_callback. Because the callback is fully custom, it could perform actions that modified the layout of the WebAssembly store. For instance, the host could call memory.grow, which often forces Wasmtime to reallocate the linear memory buffer to a new virtual memory address, freeing the previous memory space.
Once the host callback completed and returned execution to the guest, the compiled loop resumed at the next chunk boundary. However, the machine code did not re-evaluate or re-resolve the base pointer of the linear memory. Instead, it continued writing data using the stale, cached base pointer that now pointed to deallocated native memory. This state desynchronization bypassed the bounds checks enforced at the start of the instruction, yielding a classic use-after-free or out-of-bounds write primitive.
Within crates/cranelift/src/func_environ.rs, the Cranelift compiler managed the compilation of bulk memory operations through helper functions like raw_bulk_memory_operation and manual fuel check injection functions. The compiler would insert a manual_fuel_check or an epoch_check inside loop headers generated for bulk instructions. These checks called back into the runtime when thresholds were crossed, leaving the execution state partially suspended while waiting for the host to handle the yield.
// Vulnerable compiler logic that inserted mid-operation checks:
fn manual_fuel_check(&mut self, builder: &mut FunctionBuilder<'_>, fuel_to_consume: ir::Value) {
self.fuel_increment_var(builder);
let fuel = builder.use_var(self.fuel_var);
let fuel = builder.ins().iadd(fuel, fuel_to_consume);
builder.def_var(self.fuel_var, fuel);
self.fuel_check(builder);
}The compiled loop structure can be conceptualized using the following diagram, showing how the preemption check breaks the assumption of atomicity.
Additionally, in table.grow operations, Wasmtime temporarily populated newly allocated slots with null references before completing the fill process with target initialization references. If preemption occurred mid-loop and the host decided to abort execution or trap, the table remained in an intermediate, partially initialized state. Because non-nullable tables enforce the invariant that references must never be null, subsequent operations on these tables assumed type safety, leading to type confusion and native segmentation faults when dereferencing the unexpected null values.
To trigger this vulnerability, an attacker must first be able to execute arbitrary WebAssembly payloads on a host that implements cooperative preemption via epoch-based deadlines or fuel metering. The host application must also configure a reentrant or state-modifying callback. In a typical exploitation scenario, the attacker crafts a WebAssembly module containing a large memory.copy or array.copy operation designed to cross multiple chunk boundaries and force a preemption event.
The timing of the preemption is critical. If the host uses epoch-based deadlines, the background epoch thread must increment the epoch precisely while the bulk operation loop is executing. Once the loop triggers the preemption, control flows to the host callback. Inside this callback, the attacker-controlled host environment or a misconfigured handler triggers a memory reallocation event (e.g., calling memory.grow from a concurrent thread or within the reentrant context) or initiates a garbage collection cycle.
During garbage collection, reference-typed pointers on the GC heap are compacted and moved to optimize memory layout. When the callback finishes and the VM resumes the chunked loop, the stored references inside the CPU registers are now pointing to invalid memory. The resumed copy operation then writes guest-controlled data directly to these obsolete addresses, giving the attacker a powerful read-and-write primitive over the host process's virtual address space.
The security impact of CVE-2026-104855 is severe, as it directly undermines the core safety guarantees of the WebAssembly execution model. Although the CVSS score is computed as 2.0 (Low) due to the high complexity and the requirement for specific host configurations, the real-world impact in vulnerable configurations is a complete sandbox escape. An attacker with the ability to write to arbitrary native memory locations can overwrite host execution pointers, such as function pointers or return addresses on the stack.
By corrupting the host process's memory space, the guest code can transition from restricted WebAssembly instructions to executing arbitrary native code on the host process. This allows the attacker to inherit the full permissions of the host process, potentially leading to unauthorized data access, local privilege escalation, or lateral movement within the hosting network.
Furthermore, because this flaw corrupts the underlying memory allocator and GC structures, it can cause unstable VM states that manifest as intermittent native crashes. In multi-tenant environments, such as cloud-native edge computing platforms or serverless database extensions, this vulnerability could allow one tenant to read or modify the state of other tenants sharing the same host process.
The vulnerability has been addressed by removing the incremental preemption checks inside bulk operation loops. In commits 3ebfbe5af4927c157d6fcaca42b8dbb6d17b73fb (v47.0.3) and 99b0bc39d447317a4102c056081c83a9a84a46e0 (v46.0.2), the Cranelift compiler was refactored to perform a single, atomic preemption check before commencing the bulk memory operation. This upfront validation calculates the total fuel cost of the operation and verifies the epoch deadline before any data transfer begins.
// Patched compiler code implementing upfront checks:
fn pre_translate_bulk_op(
&mut self,
builder: &mut FunctionBuilder,
cost: ir::Value,
) -> WasmResult<()> {
let const_cost =
Self::value_as_const_int(builder, cost).map(|c| i64::try_from(c).unwrap_or(i64::MAX));
if self.tunables.consume_fuel {
// Fold constant costs or dynamically increment fuel up front
// ...
}
const SMALL_BULK_OP_COST: i64 = 128;
if let Some(cost) = const_cost && cost <= SMALL_BULK_OP_COST {
return Ok(());
}
self.translate_loop_header(builder)
}By transitioning to atomic checks, the compiled code guarantees that once a bulk memory operation starts, it executes to completion without yielding back to the host. This eliminates the reentrancy vector entirely. To verify the completeness of the fix, security teams should ensure that all instances of Wasmtime are upgraded to version 46.0.2, 47.0.3, or higher, and verify that any custom resource limiters are configured to prevent thread-blocking abuse from large, non-preemptible bulk operations.
CVSS:4.0/AV:N/AC:H/AT:P/PR:H/UI:P/VC:N/VI:L/VA:L/SC:N/SI:N/SA:N| Product | Affected Versions | Fixed Version |
|---|---|---|
Wasmtime Bytecode Alliance | >= 46.0.0, < 46.0.2 | 46.0.2 |
Wasmtime Bytecode Alliance | >= 47.0.0, < 47.0.3 | 47.0.3 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-362 (Concurrent Execution using Shared Resource with Improper Synchronization) |
| Attack Vector | Network / Local |
| CVSS Score | 2.0 (Low Base / Critical Downstream Impact) |
| EPSS Score | Not Listed |
| Impact | Sandbox Escape, Memory Corruption (Use-After-Free, Out-of-Bounds Write) |
| Exploit Status | Proof-of-Concept |
| KEV Status | Not Listed |
The software performs multiple concurrent operations on a shared resource, but does not properly synchronize access, leading to unexpected behaviors or race conditions.
A path traversal vulnerability (CWE-22) in Contao CMS allows unauthenticated remote attackers to bypass directory boundary restrictions in ImagesController and access files within the project directory.
Contao Open Source CMS versions 4.0.0 through 5.3.49 and 5.4.0-RC1 through 5.7.11 contain a Cross-Site Request Forgery (CSRF) vulnerability in backend parameter handling. The `RequestTokenListener` component validates anti-CSRF tokens solely for HTTP POST requests, while GET-based declarative guards run only when an `act` parameter is present in the query string. Consequently, custom backend actions dispatched via alternative parameters such as `key=` can execute without CSRF token verification when triggered by an authenticated user.
Contao CMS versions 4.1.0 through 5.3.49 and 5.4.0-RC1 through 5.7.11 fail to validate form submission tokens and enforce rate limiting when processing activation email resend requests via HTTP POST, enabling resource exhaustion and account state enumeration.
An information disclosure vulnerability in Contao CMS allows unauthenticated site visitors to view protected page titles, URLs, and text excerpts through search queries when protected page indexing is disabled after previously being enabled.
In Vikunja prior to version 2.6.0, relation creation via the CalDAV endpoint fails to invoke the TaskRelation.CanCreate authorization check. This missing access control allows an authenticated user to establish unauthorized relationships and perform write operations against any task, provided its unique identifier (UID) is known.
A cross-project information disclosure vulnerability in Vikunja allows authenticated users with read access to one project to view private task details from unauthorized projects via subtask expansion parameters.