ahirner opened issue #14354:
Linked records, tuples, variants, enums, and fixed lists exceeding 1,000 items trap with
call stack exhausted.
Wasmtime charges each type layer against a new 1,000-unit adapter budget recursively.Test Case
see generated WAT below.
Steps to Reproduce
Standalone reproducer. Requires Node and Wasmtime on PATH.
Run: node wasmtime-adapter-recursion.mjs [output-directory]Both should return
a0. The 1,000-case variant succeeds and 1,001 cases trap.import { mkdirSync, writeFileSync } from 'node:fs'; import { resolve } from 'node:path'; import { spawnSync } from 'node:child_process'; const output = resolve(process.argv[2] ?? 'out/wasmtime-adapter-repro'); mkdirSync(output, { recursive: true }); const version = spawnSync('wasmtime', ['--version'], { encoding: 'utf8', timeout: 30_000 }); if (version.error || version.status !== 0) throw version.error ?? new Error(version.stderr); const report = { engine: version.stdout.trim(), platform: process.platform, arch: process.arch, cases: [] }; for (const count of [1000, 1001]) { const variant = `(variant ${Array.from({ length: count }, (_, i) => `(case "a${i}")`).join(' ')})`; const wat = `(component (type $original ${variant}) (export $v "value" (type $original)) (component $provider (type $original ${variant}) (import "value" (type $v (eq $original))) (core module $m (func (export "id") (param i32) (result i32) local.get 0)) (core instance $m (instantiate $m)) (func $id (param "x" $v) (result $v) (canon lift (core func $m "id"))) (export "id" (func $id))) (instance $provider (instantiate $provider (with "value" (type $v)))) (alias export $provider "id" (func $id)) (component $consumer (type $original ${variant}) (import "value" (type $v (eq $original))) (import "id" (func $id (param "x" $v) (result $v))) (core func $id (canon lower (func $id))) (core module $m (import "host" "id" (func $id (param i32) (result i32))) (func (export "run") (param i32) (result i32) local.get 0 call $id)) (core instance $host (export "id" (func $id))) (core instance $m (instantiate $m (with "host" (instance $host)))) (func $run (param "x" $v) (result $v) (canon lift (core func $m "run"))) (export "run" (func $run))) (instance $consumer (instantiate $consumer (with "value" (type $v)) (with "id" (func $id)))) (alias export $consumer "run" (func $run)) (export "run" (func $run))) `; const path = resolve(output, `adapter-${count}.wat`); writeFileSync(path, wat); const args = ['run', '-W', 'gc=n', '--invoke', 'run(a0)', path]; const result = spawnSync('wasmtime', args, { encoding: 'utf8', timeout: 30_000, maxBuffer: 4 * 1024 * 1024 }); writeFileSync(path + '.stderr', result.stderr ?? ''); report.cases.push({ count, args, status: result.status, signal: result.signal, stdout: result.stdout?.trim(), error: result.error?.message, stderrFile: path + '.stderr' }); if (result.error || result.status !== 0 || result.stdout.trim() !== 'a0') process.exitCode = 1; } writeFileSync(resolve(output, 'results.json'), JSON.stringify(report, null, 2) + '\n'); console.log(JSON.stringify(report, null, 2));Expected Results
TODO: What do you expect to happen?
Actual Results
TODO: What actually happens? Panic? Segfault? Incorrect result?
Versions and Environment
Wasmtime version or commit: 48.0.2 probably, main and earlier also
Operating system: Mac OS
Architecture: aarch64
Extra Info
Since that traps wasm execution, it should not be a DoS vector in most cases.
ahirner added the bug label to Issue #14354.
alexcrichton closed issue #14354:
Linked records, tuples, variants, enums, and fixed lists exceeding 1,000 items trap with
call stack exhausted.
Wasmtime charges each type layer against a new 1,000-unit adapter budget recursively.Test Case
see generated WAT below.
Steps to Reproduce
Standalone reproducer. Requires Node and Wasmtime on PATH.
Run: node wasmtime-adapter-recursion.mjs [output-directory]Both should return
a0. The 1,000-case variant succeeds and 1,001 cases trap.import { mkdirSync, writeFileSync } from 'node:fs'; import { resolve } from 'node:path'; import { spawnSync } from 'node:child_process'; const output = resolve(process.argv[2] ?? 'out/wasmtime-adapter-repro'); mkdirSync(output, { recursive: true }); const version = spawnSync('wasmtime', ['--version'], { encoding: 'utf8', timeout: 30_000 }); if (version.error || version.status !== 0) throw version.error ?? new Error(version.stderr); const report = { engine: version.stdout.trim(), platform: process.platform, arch: process.arch, cases: [] }; for (const count of [1000, 1001]) { const variant = `(variant ${Array.from({ length: count }, (_, i) => `(case "a${i}")`).join(' ')})`; const wat = `(component (type $original ${variant}) (export $v "value" (type $original)) (component $provider (type $original ${variant}) (import "value" (type $v (eq $original))) (core module $m (func (export "id") (param i32) (result i32) local.get 0)) (core instance $m (instantiate $m)) (func $id (param "x" $v) (result $v) (canon lift (core func $m "id"))) (export "id" (func $id))) (instance $provider (instantiate $provider (with "value" (type $v)))) (alias export $provider "id" (func $id)) (component $consumer (type $original ${variant}) (import "value" (type $v (eq $original))) (import "id" (func $id (param "x" $v) (result $v))) (core func $id (canon lower (func $id))) (core module $m (import "host" "id" (func $id (param i32) (result i32))) (func (export "run") (param i32) (result i32) local.get 0 call $id)) (core instance $host (export "id" (func $id))) (core instance $m (instantiate $m (with "host" (instance $host)))) (func $run (param "x" $v) (result $v) (canon lift (core func $m "run"))) (export "run" (func $run))) (instance $consumer (instantiate $consumer (with "value" (type $v)) (with "id" (func $id)))) (alias export $consumer "run" (func $run)) (export "run" (func $run))) `; const path = resolve(output, `adapter-${count}.wat`); writeFileSync(path, wat); const args = ['run', '-W', 'gc=n', '--invoke', 'run(a0)', path]; const result = spawnSync('wasmtime', args, { encoding: 'utf8', timeout: 30_000, maxBuffer: 4 * 1024 * 1024 }); writeFileSync(path + '.stderr', result.stderr ?? ''); report.cases.push({ count, args, status: result.status, signal: result.signal, stdout: result.stdout?.trim(), error: result.error?.message, stderrFile: path + '.stderr' }); if (result.error || result.status !== 0 || result.stdout.trim() !== 'a0') process.exitCode = 1; } writeFileSync(resolve(output, 'results.json'), JSON.stringify(report, null, 2) + '\n'); console.log(JSON.stringify(report, null, 2));Expected Results
TODO: What do you expect to happen?
Actual Results
TODO: What actually happens? Panic? Segfault? Incorrect result?
Versions and Environment
Wasmtime version or commit: 48.0.2 probably, main and earlier also
Operating system: Mac OS
Architecture: aarch64
Extra Info
Since that traps wasm execution, it should not be a DoS vector in most cases.
Last updated: Sep 20 2026 at 18:08 UTC