Stream: git-wasmtime

Topic: wasmtime / issue #13750 Stack-switching continuation payl...


view this post on Zulip Wasmtime GitHub notifications bot (Jun 29 2026 at 08:26):

sdjasj opened issue #13750:

Test Case

Inline Wasm (save as resume_gc_result_stack.wat). This exercises a continuation that returns a GC struct reference; the result is kept on the operand stack (not in a GC-typed local) across a GC safepoint.

(module
  (type $S (struct (field $x i32)))
  (type $A (array i64))
  (type $ft (func (result (ref $S))))
  (type $ct (cont $ft))

  (func $target (result (ref $S))
    (struct.new $S (i32.const 0x12345678)))
  (elem declare func $target)

  (func (export "run") (result i32)
    (local $k (ref null $ct))
    (local $i i32)
    (local.set $k (cont.new $ct (ref.func $target)))

    ;; Continuation result is a GC reference, kept as an operand-stack SSA
    ;; value rather than stored in a GC-typed local.
    (resume $ct (local.get $k))

    ;; Force frequent collections while the only live copy of the struct ref
    ;; is the unmarked SSA value returned from the continuation payload buffer.
    (local.set $i (i32.const 0))
    (loop $again
      (drop (array.new_default $A (i32.const 256)))
      (local.set $i (i32.add (local.get $i) (i32.const 1)))
      (br_if $again (i32.lt_u (local.get $i) (i32.const 2000))))

    ;; Expected 0x12345678. Vulnerable builds read 256 here: the length field
    ;; of one of the arrays allocated during GC pressure.
    (struct.get $S $x))
)

Steps to Reproduce

  1. Build wasmtime from main (tested on a binary built from commit 897aa00de; affected code is present at main HEAD 08c456e887).
  2. Run the module above with stack switching and GC enabled, forcing a collection on every allocation:
wasmtime run \
    -W stack-switching=y \
    -W exceptions=y \
    -W function-references=y \
    -W gc=y \
    -C collector=copying \
    -O gc-zeal-alloc-counter=1 \
    --invoke run \
    resume_gc_result_stack.wat

Expected Results

305419896 (0x12345678) — the value of the freshly created struct's $x field. The continuation result is still live on the operand stack, so its struct must not be collected.

Actual Results

Incorrect result: the program prints 256, which is the length of one of the array.new_default $A allocations made during the GC pressure loop — i.e. a stale read of a reused object layout. The live GC object was missed by root discovery, reclaimed, and its memory reused while the guest still held a reference (use-after-free-like behavior).

warning: using `--invoke` with a function that returns values is experimental and may break in the future
256

As a control, storing the same continuation result into a GC-typed local before the allocation loop ((local.set $s (resume $ct (local.get $k))) then (struct.get $S $x (local.get $s))) returns 305419896, confirming the failure is specific to the reference living as an unmarked operand-stack SSA value across the safepoint.

Versions and Environment

Wasmtime version or commit: Wasmtime 47.0.0 (binary built from commit 897aa00de, 2026-06-15); affected code present at main HEAD 08c456e8870b0b85566f9e9808b7a2f044eaa265.

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

view this post on Zulip Wasmtime GitHub notifications bot (Jun 29 2026 at 08:26):

sdjasj added the bug label to Issue #13750.

view this post on Zulip Wasmtime GitHub notifications bot (Jun 29 2026 at 12:36):

alexcrichton added the wasm-proposal:stack-switching label to Issue #13750.

view this post on Zulip Wasmtime GitHub notifications bot (Jun 29 2026 at 12:36):

alexcrichton added the wasm-proposal:gc label to Issue #13750.


Last updated: Jul 29 2026 at 05:03 UTC