The M0 Spike E recorded that Cap'n Proto two-party RPC over an AF_UNIX
socketpair stalled (server processed requests, responses never reached the
client). Root cause, confirmed with a standalone reproduction: EzRpcServer(int
fd, ...) expects a LISTENING socket and calls accept() on it; a socketpair end
is already CONNECTED, so accept() fails with EINVAL and the bootstrap never
completes. Fix: server side uses the low-level path —
LowLevelAsyncIoProvider::wrapSocketFd(fd) + TwoPartyVatNetwork(SIDE_SERVER) +
makeRpcServer(network, bootstrap). The client uses EzRpcClient(fd), which is
designed for an already-connected socket. This is the production M2 transport
shape.
- spike/E_sandbox/DocumentProcess.cpp: low-level TwoPartyVatNetwork server
over wrapSocketFd; serves DocumentProcess::Server capability with 16 MiB
payload cap (ADR-0004 bidirectional trust boundary)
- spike/E_sandbox/UIProcess.cpp: EzRpcClient(fd) + parse() RPC; validates
every response field. ASan caught a use-after-free (ParseResult::Reader
outliving its Response) — fixed by validating inside the Response scope
(validates §7.2 sanitizers-from-day-one)
- spike/E_sandbox/Sandbox.cpp: allow ioctl (FIONBIO) — KJ's wrapSocketFd sets
non-blocking mode via ioctl; strace identified it as the denied syscall
- spike/CMakeLists.txt: Cap'n Proto via pkg-config (the Debian CMake config
hard-requires libatomic via a check that fails on x86-64 where it isn't
needed); schema compiled with the capnp tool + --src-prefix
- docs/spike-results/0004: record the resolution; gate still MET (1000/1000
under seccomp, ~58us avg; ASan-clean with sandbox disabled — ASan's pipe2
conflicts with the seccomp allow-list, an ASan-only artefact)