Skip to content

embedder: transferring a busy readable handle causes read-against-read rendezvous #324

Description

@lannbot

Summary

A host can transfer a Stream/Future readable handle while a host read is outstanding. The receiver gets a fresh readable endpoint while the old read buffer remains installed. A subsequent guest read attempts to rendezvous with another read.

Priority: high. Reproduced with real receiving Wasm on fd4266b516632e80943456a45aa64b95ea42f36f, Deno 2.9.5 / V8 15.0, Linux aarch64.

Reproduction and observed result

Create a stream, round-trip it through a guest pass-through export, then:

const pending = liftedStream.read(1);
await exports.readStream(liftedStream); // takes stream<u8>, invokes canon stream.read

Do not supply any writer data. The transfer succeeds, but the receiving canonical read raises internal AssertionError: int store. The instance is poisoned and a later independent ping export is refused. The original host stream read resolves with [0] even though no writer supplied data.

The future variant also reaches AssertionError: int store and poisons the instance; its parked host await rejects with PeerTrappedError.

Real-Wasm fixtures and type-checked probes are preserved locally in /tmp/opencode/stream-review/busy-read.wat and repro_test.ts. Lower-level host-buffer probes additionally demonstrate read-against-read rendezvous; the reported guest behavior above comes from actual compiled Wasm, not those substitutes.

Cause and authority

  • streams.ts:227–236: Stream transfer checks prior consumption, not an outstanding read.
  • streams.ts:594–623: Future has the same issue; awaiting host materialization introduces an additional initiation window.
  • contracts/embedder-api.md:474–489,513–516 defines readable ownership transfer and one in-flight operation per end/direction.
  • The reference lift_async_value checks IDLE on guest-to-host transfer. Its lower_stream / lower_future helpers create fresh readable endpoints without that check; this finding does not claim otherwise. The facade must maintain its host-end exclusion invariant.

Acceptance criteria

  • Refuse transfer while a read is outstanding, before destructive transfer or guest entry can corrupt shared state.
  • Cover Stream read, direct read, and Future reads, including operation initiation before host-materialization continuations run.
  • Preserve the pending operation after refusal and keep the instance usable.
  • Review already-consumed Future payloads so a fresh endpoint cannot read a single-use payload again.
  • Retain real-Wasm regression coverage; run just gates for the repair.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingp0Known crash or major correctness bug

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions