Stream: git-wasmtime

Topic: wasmtime / issue #14065 wasmtime's stdin worker thread ov...


view this post on Zulip Wasmtime GitHub notifications bot (Aug 02 2026 at 20:52):

eduardomourar opened issue #14065:

Test Case

use std::io::Read;
fn main() {
    let mut buf = [0u8; 1];
    let n = std::io::stdin().read(&mut buf).unwrap();
    println!("read {n} bytes: {:?}", &buf[..n]);
}

Steps to Reproduce

Expected Results

The host fd's position should advance by exactly 1 (the number of bytes the guest actually requested and read), matching what happens when a native (non-wasm) program does the equivalent read() on the same fd. This matters for any caller that shares one host file descriptor across multiple separate WASI guest invocations, expecting each one to pick up where the last left off — the same pattern POSIX dd/cat/etc. rely on when chained via shell pipelines against a seekable input.

Actual Results

The host fd's position ends up at 5 (end of file) — the entire file was consumed, not just the 1 byte the guest asked for and printed. Confirmed on wasmtime 47.0.3 (latest release as of 2026-08-02), on both wasm32-wasip1 and wasm32-wasip2.

Root cause (traced through wasmtime source): crates/wasi/src/cli/worker_thread_stdin.rs's dedicated stdin-reading thread calls std::io::stdin().read(&mut bytes) (see the create() function's read loop) — but std::io::stdin() returns a handle to a process-global BufReader::with_capacity(STDIN_BUF_SIZE, ...) (8KB by default) that lives inside the wasmtime host process. A single read() call against that BufReader can pull far more than size_hint bytes from the underlying OS file descriptor into the BufReader's internal buffer whenever the fd is backed by a regular (seekable) file — unlike a pipe or terminal, where a read() typically returns only what's immediately available. The excess bytes sitting in that internal buffer are never handed back to the guest and are simply lost when the wasmtime process exits, having already been consumed from the underlying fd.

This is user-visible for any real program: in uutils/coreutils, dd's Source::stdin_as_file() already takes the raw fd directly instead of going through std::io::Stdin specifically to avoid coreutils' own internal buffering — but that has no effect here, since the over-read happens one layer down, inside wasmtime's own host-side stdin handling, before the guest's fd_read call is even serviced. No guest-side code change can work around this.

Versions and Environment

Wasmtime version or commit: 47.0.3 (latest release as of 2026-08-02)

Operating system: macOS (Darwin); the bug is in generic std::io::Stdin buffering behavior, not an OS-specific code path, so likely reproduces on Linux too

Architecture: aarch64 host, wasm32-wasip1 and wasm32-wasip2 guests (both affected identically, since both route through the same worker_thread_stdin.rs module)

Extra Info

Suggested fix: read from the raw file descriptor directly (e.g. via std::os::fd::AsRawFd + a raw read(2)/rustix::io::read) instead of going through std::io::stdin()'s buffered handle, so the amount actually read from the OS never exceeds size_hint. This is exactly the same fix shape as coreutils' own uucore::io::RawReader (a zero-buffering wrapper around rustix::io::read), which exists for the identical reason.

view this post on Zulip Wasmtime GitHub notifications bot (Aug 02 2026 at 20:52):

eduardomourar added the bug label to Issue #14065.

view this post on Zulip Wasmtime GitHub notifications bot (Aug 03 2026 at 17:37):

alexcrichton added the wasi:impl label to Issue #14065.

view this post on Zulip Wasmtime GitHub notifications bot (Aug 05 2026 at 20:49):

pchickey closed issue #14065:

Test Case

use std::io::Read;
fn main() {
    let mut buf = [0u8; 1];
    let n = std::io::stdin().read(&mut buf).unwrap();
    println!("read {n} bytes: {:?}", &buf[..n]);
}

Steps to Reproduce

Expected Results

The host fd's position should advance by exactly 1 (the number of bytes the guest actually requested and read), matching what happens when a native (non-wasm) program does the equivalent read() on the same fd. This matters for any caller that shares one host file descriptor across multiple separate WASI guest invocations, expecting each one to pick up where the last left off — the same pattern POSIX dd/cat/etc. rely on when chained via shell pipelines against a seekable input.

Actual Results

The host fd's position ends up at 5 (end of file) — the entire file was consumed, not just the 1 byte the guest asked for and printed. Confirmed on wasmtime 47.0.3 (latest release as of 2026-08-02), on both wasm32-wasip1 and wasm32-wasip2.

Root cause (traced through wasmtime source): crates/wasi/src/cli/worker_thread_stdin.rs's dedicated stdin-reading thread calls std::io::stdin().read(&mut bytes) (see the create() function's read loop) — but std::io::stdin() returns a handle to a process-global BufReader::with_capacity(STDIN_BUF_SIZE, ...) (8KB by default) that lives inside the wasmtime host process. A single read() call against that BufReader can pull far more than size_hint bytes from the underlying OS file descriptor into the BufReader's internal buffer whenever the fd is backed by a regular (seekable) file — unlike a pipe or terminal, where a read() typically returns only what's immediately available. The excess bytes sitting in that internal buffer are never handed back to the guest and are simply lost when the wasmtime process exits, having already been consumed from the underlying fd.

This is user-visible for any real program: in uutils/coreutils, dd's Source::stdin_as_file() already takes the raw fd directly instead of going through std::io::Stdin specifically to avoid coreutils' own internal buffering — but that has no effect here, since the over-read happens one layer down, inside wasmtime's own host-side stdin handling, before the guest's fd_read call is even serviced. No guest-side code change can work around this.

Versions and Environment

Wasmtime version or commit: 47.0.3 (latest release as of 2026-08-02)

Operating system: macOS (Darwin); the bug is in generic std::io::Stdin buffering behavior, not an OS-specific code path, so likely reproduces on Linux too

Architecture: aarch64 host, wasm32-wasip1 and wasm32-wasip2 guests (both affected identically, since both route through the same worker_thread_stdin.rs module)

Extra Info

Suggested fix: read from the raw file descriptor directly (e.g. via std::os::fd::AsRawFd + a raw read(2)/rustix::io::read) instead of going through std::io::stdin()'s buffered handle, so the amount actually read from the OS never exceeds size_hint. This is exactly the same fix shape as coreutils' own uucore::io::RawReader (a zero-buffering wrapper around rustix::io::read), which exists for the identical reason.

view this post on Zulip Wasmtime GitHub notifications bot (Aug 06 2026 at 11:48):

eduardomourar commented on issue #14065:

I really appreciate the fix done so quickly. Thanks!


Last updated: Aug 30 2026 at 09:07 UTC