Restricted Linux sandbox breaks connected Unix socket sends and asyncio wakeups
Summary
The restricted Linux network seccomp filter unconditionally denies sendto(2). On Linux, send(2) is implemented as sendto(..., dest_addr = NULL), so this also breaks writes to already-connected Unix-domain sockets.
One visible consequence is that Python asyncio cross-thread wakeups stop working inside the default Codex workspace sandbox. loop.call_soon_threadsafe() writes to asyncio's AF_UNIX wakeup socket; the write receives EPERM, asyncio suppresses that OSError, and the event loop remains asleep.
Reproduction
On current main (315195492c), build Codex and run:
./target/debug/codex sandbox -P :workspace -C /tmp /usr/bin/python3 -c 'import asyncio, threading; loop=asyncio.new_event_loop(); threading.Thread(target=loop.run_forever, daemon=True).start(); print(asyncio.run_coroutine_threadsafe(asyncio.sleep(0, result=42), loop).result(timeout=2))'
Expected:
42
Actual:
TimeoutError
This also causes Daft's native runner to hang on a simple collect() operation.
Isolation
- The same program succeeds outside the Codex sandbox.
- It succeeds with the same bubblewrap filesystem isolation and a full-network Codex profile.
- It succeeds with plain bubblewrap plus
--unshare-net. - It fails only when Codex's restricted network seccomp filter is installed.
Root cause and possible fix
In codex-rs/linux-sandbox/src/landlock.rs, restricted mode unconditionally denies SYS_sendto.
A narrowly scoped fix is to deny sendto only when its fifth argument (dest_addr) is non-null. This permits send() on an already-connected socket, including AF_UNIX socketpairs used for local IPC, while continuing to deny sends to explicit destinations such as unconnected Unix datagram socket paths.
I have a tested patch with regression coverage for both sides of this boundary:
- connected AF_UNIX socket
send()succeeds; sendto()with an explicit destination still returnsEPERM.
The codex-linux-sandbox test suite passes (120/120), and both the minimal asyncio reproducer and the original Daft collect() scenario pass with the patched binary.
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗