Stream: git-wasmtime

Topic: wasmtime / PR #13662 Add bounds check in VMContinuationSt...


view this post on Zulip Wasmtime GitHub notifications bot (Jun 16 2026 at 00:43):

SebTardif opened PR #13662 from SebTardif:fix-stack-switching-initialize-bounds-check to bytecodealliance:main:

\`VMContinuationStack::initialize\` writes control data and an args buffer at offsets below the top of the continuation stack, but does not verify that the total size fits within the allocated stack region.

The problem

A Wasm module can define a continuation function type with an extremely large number of parameters or return values. When \`cont.new\` is executed, the call chain reaches \`initialize()\` (\`stack/unix.rs:226\`), which computes:

\`\`\`
args_data_size = max(param_count, return_count) * size_of::<ValRaw>()
\`\`\`

With the default 2 MiB continuation stack (\`async_stack_size\`), the fixed header occupies 0x40 (64) bytes. A function type with 131,069 or more parameters produces an \`args_data_size\` of 2,097,104 bytes, exceeding the stack allocation. The write loop at line 298 then stores values at offsets like \`0x28 + args_data_size\`, placing data past the guard page into adjacent memory mappings.

Additionally, the original \`args_data_size\` computation uses unchecked multiplication (\`capacity * 16\`), which, while safe on 64-bit (the inputs are \`u32\`), is not self-documenting about overflow safety.

The fix

  1. Replace the unchecked multiplication with \`checked_mul\` + \`checked_add\`, returning an error on arithmetic overflow.
  2. Add a bounds check (\`ensure!(total_control_size <= self.len)\`) before any writes, so a high-arity function type produces a clean trap instead of memory corruption.
  3. Change the return type of \`initialize\` from \`()\` to \`Result<()>\` and propagate the error through the \`stack.rs\` wrapper and the \`cont_new\` caller (which already returns \`Result\`).

Bug origin

Introduced in #10388 ("Stack switching: Infrastructure and runtime support", Frank Emrich, 2025-06-04).

view this post on Zulip Wasmtime GitHub notifications bot (Jun 16 2026 at 00:43):

SebTardif requested pchickey for a review on PR #13662.

view this post on Zulip Wasmtime GitHub notifications bot (Jun 16 2026 at 00:43):

SebTardif requested wasmtime-core-reviewers for a review on PR #13662.

view this post on Zulip Wasmtime GitHub notifications bot (Jun 16 2026 at 00:53):

SebTardif updated PR #13662.

view this post on Zulip Wasmtime GitHub notifications bot (Jun 16 2026 at 01:00):

SebTardif updated PR #13662.

view this post on Zulip Wasmtime GitHub notifications bot (Jun 16 2026 at 01:34):

SebTardif updated PR #13662.

view this post on Zulip Wasmtime GitHub notifications bot (Jun 16 2026 at 02:22):

SebTardif edited PR #13662:

VMContinuationStack::initialize writes control data and an args buffer at offsets below the top of the continuation stack, but does not verify that the total size fits within the allocated stack region.

The problem

A Wasm module can define a continuation function type with a large number of parameters or return values. When cont.new is executed, the call chain reaches initialize() (stack/unix.rs:226), which computes:

args_data_size = max(param_count, return_count) * size_of::<ValRaw>
()

The fixed header occupies 0x40 (64) bytes. If the total control data size (header + args buffer) exceeds the stack allocation, the write loop stores values past the stack region into adjacent memory. With the default 2 MiB stack, this requires 131,069+ params; with a smaller async_stack_size, fewer params suffice (e.g., 800 params on an 8 KiB stack).

Without this fix, the out-of-bounds write hits the guard page and crashes with SIGSEGV. With the fix, a clean error is returned instead.

The fix

  1. Replace the unchecked multiplication with checked_mul + checked_add, returning an error on arithmetic overflow.
  2. Add a bounds check (ensure!(total_control_size <= self.len)) before any writes, so a high-arity function type produces a clean trap instead of a crash.
  3. Change the return type of initialize from () to Result<()> and propagate the error through the stack.rs wrapper and the cont_new caller (which already returns Result).

Test

A regression test (stack_switching_cont_new_high_arity_rejected) creates a function type with 800 i32 params on an 8 KiB continuation stack, verifying that cont.new returns an error instead of crashing. The test skips gracefully on platforms where stack switching is not supported.

Bug origin

Introduced in #10388 ("Stack switching: Infrastructure and runtime support", Frank Emrich, 2025-06-04).

view this post on Zulip Wasmtime GitHub notifications bot (Jun 16 2026 at 04:05):

github-actions[bot] added the label wasmtime:api on PR #13662.

view this post on Zulip Wasmtime GitHub notifications bot (Jun 16 2026 at 17:41):

alexcrichton edited PR #13662:

VMContinuationStack::initialize writes control data and an args buffer at offsets below the top of the continuation stack, but does not verify that the total size fits within the allocated stack region.

The problem

A Wasm module can define a continuation function type with a large number of parameters or return values. When cont.new is executed, the call chain reaches initialize() (stack/unix.rs:226), which computes:

args_data_size = max(param_count, return_count) * size_of::<ValRaw>()

The fixed header occupies 0x40 (64) bytes. If the total control data size (header + args buffer) exceeds the stack allocation, the write loop stores values past the stack region into adjacent memory. With the default 2 MiB stack, this requires 131,069+ params; with a smaller async_stack_size, fewer params suffice (e.g., 800 params on an 8 KiB stack).

Without this fix, the out-of-bounds write hits the guard page and crashes with SIGSEGV. With the fix, a clean error is returned instead.

The fix

  1. Replace the unchecked multiplication with checked_mul + checked_add, returning an error on arithmetic overflow.
  2. Add a bounds check (ensure!(total_control_size <= self.len)) before any writes, so a high-arity function type produces a clean trap instead of a crash.
  3. Change the return type of initialize from () to Result<()> and propagate the error through the stack.rs wrapper and the cont_new caller (which already returns Result).

Test

A regression test (stack_switching_cont_new_high_arity_rejected) creates a function type with 800 i32 params on an 8 KiB continuation stack, verifying that cont.new returns an error instead of crashing. The test skips gracefully on platforms where stack switching is not supported.

Bug origin

Introduced in #10388 ("Stack switching: Infrastructure and runtime support", Frank Emrich, 2025-06-04).

view this post on Zulip Wasmtime GitHub notifications bot (Jun 16 2026 at 17:50):

:memo: alexcrichton submitted PR review.

view this post on Zulip Wasmtime GitHub notifications bot (Jun 16 2026 at 17:50):

:speech_balloon: alexcrichton created PR review comment:

Since this function now returns Result could this use ? instead of .unwrap()?

view this post on Zulip Wasmtime GitHub notifications bot (Jun 16 2026 at 17:50):

:speech_balloon: alexcrichton created PR review comment:

Could this avoid the dance here of adding/subtracting/adding 0x40 by setting the original computation to total_control_size, then calculating args_data_size by subtracing 0x40 from that?

view this post on Zulip Wasmtime GitHub notifications bot (Jun 16 2026 at 17:50):

alexcrichton requested alexcrichton for a review on PR #13662.

view this post on Zulip Wasmtime GitHub notifications bot (Jun 16 2026 at 17:50):

alexcrichton unassigned pchickey from PR #13662 Add bounds check in VMContinuationStack::initialize for args buffer size.

view this post on Zulip Wasmtime GitHub notifications bot (Jun 16 2026 at 17:50):

:speech_balloon: alexcrichton created PR review comment:

I realize that this is preexisting, but could you add an assert!(!cfg(target_arch = "x86_64")) here? That way we can at least verify this test passes on one architecture instead of accidentally always falling through to here silently

view this post on Zulip Wasmtime GitHub notifications bot (Jun 16 2026 at 18:34):

SebTardif updated PR #13662.

view this post on Zulip Wasmtime GitHub notifications bot (Jun 16 2026 at 18:34):

:memo: SebTardif submitted PR review.

view this post on Zulip Wasmtime GitHub notifications bot (Jun 16 2026 at 18:34):

:memo: SebTardif submitted PR review.

view this post on Zulip Wasmtime GitHub notifications bot (Jun 16 2026 at 18:34):

:memo: SebTardif submitted PR review.

view this post on Zulip Wasmtime GitHub notifications bot (Jun 16 2026 at 18:34):

:speech_balloon: SebTardif created PR review comment:

Added.

view this post on Zulip Wasmtime GitHub notifications bot (Jun 16 2026 at 18:34):

:speech_balloon: SebTardif created PR review comment:

Done, replaced all three .unwrap() calls in initialize with ?.

view this post on Zulip Wasmtime GitHub notifications bot (Jun 16 2026 at 18:34):

:speech_balloon: SebTardif created PR review comment:

Good call. Now computes total_control_size directly from the checked arithmetic, then derives args_data_size = total_control_size - 0x40. Also replaced tos.sub(0x40 + args_data_size) with tos.sub(total_control_size) in the to_store array for consistency.

view this post on Zulip Wasmtime GitHub notifications bot (Jun 16 2026 at 18:54):

:thumbs_up: alexcrichton submitted PR review:

Thanks! Happy to merge once CI is green

view this post on Zulip Wasmtime GitHub notifications bot (Jun 16 2026 at 20:07):

SebTardif updated PR #13662.

view this post on Zulip Wasmtime GitHub notifications bot (Jun 16 2026 at 20:11):

alexcrichton has enabled auto merge for PR #13662.

view this post on Zulip Wasmtime GitHub notifications bot (Jun 16 2026 at 20:18):

alexcrichton added PR #13662 Add bounds check in VMContinuationStack::initialize for args buffer size to the merge queue.

view this post on Zulip Wasmtime GitHub notifications bot (Jun 16 2026 at 20:43):

github-merge-queue[bot] removed PR #13662 Add bounds check in VMContinuationStack::initialize for args buffer size from the merge queue.

view this post on Zulip Wasmtime GitHub notifications bot (Jun 16 2026 at 21:14):

:memo: alexcrichton submitted PR review.

view this post on Zulip Wasmtime GitHub notifications bot (Jun 16 2026 at 21:14):

:speech_balloon: alexcrichton created PR review comment:

Ah, sorry, this'll need adjusting to:

assert!(!(cfg!(target_arch = "x86_64") && cfg!(unix)));

view this post on Zulip Wasmtime GitHub notifications bot (Jun 16 2026 at 21:30):

SebTardif updated PR #13662.

view this post on Zulip Wasmtime GitHub notifications bot (Jun 16 2026 at 21:30):

:memo: SebTardif submitted PR review.

view this post on Zulip Wasmtime GitHub notifications bot (Jun 16 2026 at 21:30):

:speech_balloon: SebTardif created PR review comment:

Done, updated to check both conditions.

view this post on Zulip Wasmtime GitHub notifications bot (Jun 16 2026 at 22:22):

alexcrichton added PR #13662 Add bounds check in VMContinuationStack::initialize for args buffer size to the merge queue.

view this post on Zulip Wasmtime GitHub notifications bot (Jun 16 2026 at 22:47):

:check: alexcrichton merged PR #13662.

view this post on Zulip Wasmtime GitHub notifications bot (Jun 16 2026 at 22:47):

alexcrichton removed PR #13662 Add bounds check in VMContinuationStack::initialize for args buffer size from the merge queue.


Last updated: Jul 29 2026 at 05:03 UTC