Skip to content

fix(vm): harden WASM host-ABI boundary and read into guest memory in place - #518

Merged
taubyte0 merged 1 commit into
mainfrom
fix/vm-low-orbit-wasm-boundary
Aug 8, 2026
Merged

fix(vm): harden WASM host-ABI boundary and read into guest memory in place#518
taubyte0 merged 1 commit into
mainfrom
fix/vm-low-orbit-wasm-boundary

Conversation

@samyfodil

Copy link
Copy Markdown
Contributor

What

Hardening pass on the pkg/vm-low-orbit host functions (the ABI a tenant's WASM function calls), plus a read-path change that removes an unbounded allocation while dropping a copy.

Fixes

Storage content files collided across tenants. storageNewContent / storageOpenCid created scratch files at a fixed relative path ("tempFile"+counter) in the shared substrate process CWD, and the counter resets to 0 per instance. Two functions running concurrently — even within one project — opened the same tempFile0, so one guest's staged content could be read or truncated by another; the files were also never removed. Now uses os.CreateTemp (namespaced by project) and removes the file on close and on factory teardown.

memoryViewRead integer overflow. The length clamp if size < offset+count overflows uint32 for a large guest-supplied count, skipping the clamp and indexing data[offset:offset+count] out of range. Clamped against the remaining bytes (count > mv.size-offset) instead.

Unbounded host allocation on guest reads. contentReadFile, storageReadFile, readHttpResponseBody, readHttpEventBody and cryptoRead did make([]byte, guestLen) before any bound — a multi-GiB host allocation per call, outside the wasm page limit (cryptoRead additionally rand.Reads it, forcing real RSS).

The read-path change

Rather than cap the allocation, the read functions now read directly into the guest's linear memory: wazy's Memory().Read returns a slice aliasing guest memory, so the reader (os.File, http.Body, crypto/rand) fills it in place. This range-checks the request — an oversized length is rejected without allocating — and removes both the allocation and the subsequent copy on every read.

Tests

Regression tests for each fix, each verified to fail on the pre-fix code:

  • TestContentTempFilesDoNotCollideAcrossInstances / TestContentCloseRemovesTempFile
  • TestMemoryViewReadCountOverflowIsClamped
  • TestContentReadRejectsOversizedLength / TestCryptoReadRejectsOversizedLength (assert bytes allocated, since the pre-fix code returned the same errno after allocating gigabytes)

The existing end-to-end pkg/vm-low-orbit/tests harness (real wazy runtime) passes, confirming the in-place reads land correct data and the caps reject nothing legitimate.

…place

Three tenant-isolation / DoS fixes on the vm-low-orbit host functions, plus a
read-path change that also removes the unbounded-allocation vector:

- Storage content files were created at a fixed relative path
  ("tempFile"+counter) in the shared process CWD, with the counter reset per
  instance, so concurrent functions (even within one project) aliased the same
  file — a cross-tenant read/corrupt, and the files were never removed. Use
  os.CreateTemp, namespaced by project, and remove on close and factory close.

- memoryViewRead clamped its count with `size < offset+count`, which overflows
  uint32 for a large guest-supplied count and slips into an out-of-range
  data[offset:offset+count] slice. Clamp against the remaining bytes instead.

- The guest-read host functions (contentReadFile, storageReadFile,
  readHttpResponseBody, readHttpEventBody, cryptoRead) did make([]byte,
  guestLen) before any bound — a multi-GiB host allocation per call, outside
  the wasm page limit. Read directly into the guest's linear memory instead:
  Memory().Read returns an aliasing slice, so the reader fills guest memory in
  place. That range-checks the request (rejecting an oversized length without
  allocating) and drops both the allocation and the extra copy on every read.

Regression tests cover each fix: the cross-instance temp-file collision, the
count-overflow clamp, and the oversized-read rejection (asserting bytes
allocated, since the pre-fix code returned the same errno after allocating).
@taubyte0
taubyte0 merged commit a2d903e into main Aug 8, 2026
23 checks passed
@taubyte0
taubyte0 deleted the fix/vm-low-orbit-wasm-boundary branch August 8, 2026 16:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants