Skip to content

fix(wasi): component file read returns bytes in a guest-owned buffer (SR-38, #405) - #407

Merged
avrabe merged 1 commit into
mainfrom
fix/sr-38-component-file-read
Jul 10, 2026
Merged

fix(wasi): component file read returns bytes in a guest-owned buffer (SR-38, #405)#407
avrabe merged 1 commit into
mainfrom
fix/sr-38-component-file-read

Conversation

@avrabe

@avrabe avrabe commented Jul 10, 2026

Copy link
Copy Markdown
Collaborator

What

Second layer of #405. After SR-37 (#406) a preview2 component could open+stat a file under --wasi-fs but reading it crashed with [Runtime][E07DA] Function not found in exports.

Root cause (measured, trace-driven)

wasi:io/streams input-stream.blocking-read wrote the returned list<u8> to a fixed raw address (0x100000 + handle*0x10000) the guest never allocated. wasi-libc copies then frees the returned list — freeing an unallocated pointer corrupted the guest allocator → bad indirect call → E07DA. It also re-read the whole file from offset 0 every call with no EOF, so the read loop never terminated.

Fix

The component WASI path is dispatch_canon_lowered, which auto-lowers a handler's returned core values to the retptr via lower_results_to_retptr using the Result(ListU8, stream-error) signature. So:

  • blocking-read/read now return core values [I32(0), buf_ptr, n] (ok) / [I32(1), I32(1)] (stream-error::closed) — no manual retptr write.
  • The bytes live in a fresh cabi_realloc'd buffer per read (allocate_wasi_read_buffer, sized to the requested len, capped 1 MiB), set via new HostImportHandler::set_read_buffer_allocation. On-demand allocation added in dispatch_canon_lowered (component path) and call_wasi_function (core-module path).
  • A per-stream read offset advances by bytes returned and yields closed at EOF.

Verification

  • Read-only preview2 component now prints READ:CANARY and completes cleanly, matching wasmtime run --dir .::..
  • 2 unit oracles (// rivet: verifies SR-38): read returns bytes in the owned buffer + EOFs; fails loud without a buffer.
  • kiln-runtime lib suite: 95/95 pass (engine changes clean).

Scope

#405 stays open — directory enumeration (read-directory / read-directory-entry) is still unimplemented, filed as SR-39. v0.4.1 cuts when that lands and closes #405 end-to-end.

Trace: SR-38

🤖 Generated with Claude Code

…(SR-38, #405)

After SR-37 (get-directories) a preview2 component could open+stat a file under
--wasi-fs but not READ it: wasi:io/streams input-stream.blocking-read wrote the
returned list<u8> to a FIXED raw address (0x100000 + handle*0x10000) the guest
never allocated. The guest copies then FREES that list (wasi-libc), so freeing an
unallocated pointer corrupted the guest allocator, causing a bad indirect call
and [Runtime][E07DA] Function not found in exports. It also re-read the whole
file from offset 0 each call with no EOF, so the read loop never terminated.

The fix (the component WASI path is dispatch_canon_lowered, which auto-lowers a
handler's returned core values to the retptr via lower_results_to_retptr):
- blocking-read/read now return core values [I32(0), buf_ptr, n] for ok and
  [I32(1), I32(1)] for stream-error::closed, matching the Result(ListU8,
  stream-error) signature, with no manual retptr write.
- The list<u8> bytes live in a fresh cabi_realloc'd buffer allocated per read
  (allocate_wasi_read_buffer, sized to the requested len, capped 1 MiB), set on
  the dispatcher via new HostImportHandler::set_read_buffer_allocation. On-demand
  allocation is added in dispatch_canon_lowered (component path) and
  call_wasi_function (core-module path), before delegating to the handler.
- A per-stream read offset (HashMap) advances by bytes returned and yields
  closed once the file is exhausted, which is the guest's EOF signal.

Confirmed by a read-only preview2 component that now prints READ:CANARY and
completes cleanly, matching `wasmtime run --dir .::.`. Two unit oracles assert
the read returns bytes in the owned buffer and EOFs, and fails loud without one.

#405 stays open: directory enumeration (read-directory / read-directory-entry)
is still unimplemented, filed as SR-39. v0.4.1 cuts when that lands and closes
#405 end-to-end.

Trace: SR-38
@github-actions

Copy link
Copy Markdown

🔍 Build Diagnostics Report

Summary

Metric Base Branch This PR Change
Errors 0 0 0
Warnings 5 5 0

🎯 Impact Analysis

Issues in Files You Modified

  • 0 new errors introduced by your changes
  • 0 new warnings introduced by your changes
  • 0 total errors in modified files
  • 0 total warnings in modified files
  • 0 files you modified

Cascading Issues (Your Changes Breaking Other Files)

  • 0 new errors in unchanged files
  • 0 new warnings in unchanged files
  • 0 unchanged files now affected

Note: "Cascading issues" are errors in files you didn't modify, caused by your changes (e.g., breaking API changes, dependency issues).

✅ No Issues Detected

Perfect! Your changes don't introduce any new errors or warnings, and don't break any existing code.


📊 Full diagnostic data available in workflow artifacts

🔧 To reproduce locally:

# Install cargo-kiln
cargo install --path cargo-kiln

# Analyze your changes
cargo-kiln build --output json --filter-severity error
cargo-kiln check --output json --filter-severity warning

@codecov

codecov Bot commented Jul 10, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@avrabe
avrabe merged commit 8cd2f30 into main Jul 10, 2026
20 checks passed
@avrabe
avrabe deleted the fix/sr-38-component-file-read branch July 10, 2026 10:20
avrabe added a commit that referenced this pull request Jul 10, 2026
…) (#410)

Version bump 0.4.0 → 0.4.1. Ships the complete component filesystem read/list
fix for WASI Preview2 components under kilnd --wasi --component --wasi-fs:
- SR-37 (#406): get-directories returns a guest-usable preopen list.
- SR-38 (#407): file read (blocking-read) returns bytes in a guest-owned buffer
  with offset/EOF tracking.
- SR-39 (#409): directory enumeration (read-directory / read-directory-entry).

Together these make a preview2 component read and list files under a preopen,
matching `wasmtime run --dir`. Closes #405.

Trace: SR-39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant