#792·Bend

IO receive effects eagerly allocate the caller-supplied max and abort the whole process (_exit) on allocation failure

Author: docxologyCreated Sep 18, 2026Updated Sep 18, 2026

Summary

The receive effects allocate the full caller-supplied max up front and treat allocation failure as fatal _exit(1), not as a catchable Fail:

  • bend2/effs/tcp_recv.c:22-23, file_read.c, file_read_bytes.c, udp_recv_from.c, udp_poll.c (same pattern): w->made = f[1] < INT32_MAX ? (intptr_t)f[1] : INT32_MAX; w->data = io_mem(malloc((size_t)w->made + 1));f[1] is the raw U32 max, so the buffer is sized entirely from caller input before a single byte is read.
  • io_memerr_fail_exit(1): allocation failure kills every spawned computation and is never observable as Result.Fail.
  • comp.ts:5744 (chan_open) mallocs room*8 on the same fatal path.
  • JS lane: tcp_recv.js / udp_recv_from.js / udp_poll.js do new Uint8Array(Math.max(Number(max), 1)) with no clamp (a clamp PR is separately proposed); file_read.js clamps.

Impact

File.read(f, 4294967295n) or TCP.recv(sock, 4294967295n) — plain bend, no @unsafe — asks the OS for a 2-4 GiB buffer. On this audit's dev machine (macOS, overcommit) the allocation lazily succeeded and the program survived, so no end-to-end crash was reproduced here; but on hosts with strict overcommit / memory limits (typical Linux containers) the process aborts uncatchably, and when max derives from network input (Content-Length pattern) this is a remote one-request kill switch per connection. Note the JS FFI passes the length as u32 (recv:ipUi>I), so there is no sign bug — the issue is purely eager allocation of untrusted sizes.

Suggested fix

Grow the receive buffer on demand (recv loop), or return io_fail(ENOMEM) instead of err_fail so allocation failure surfaces as Fail{...}.

Version / system

  • bend 2.0.5 (main @ e6676b0)
  • Darwin arm64; C analysis by reading the kit spliced into emitted programs