Skip to content

linux-sandbox: allow sendto(NULL, 0) for asyncio self-pipe wakeups#10109

Open
etraut-openai wants to merge 7 commits intomainfrom
etraut/linux_sandbox
Open

linux-sandbox: allow sendto(NULL, 0) for asyncio self-pipe wakeups#10109
etraut-openai wants to merge 7 commits intomainfrom
etraut/linux_sandbox

Conversation

@etraut-openai
Copy link
Copy Markdown
Collaborator

Fixes a hang in async SQLite / asyncio under no-network sandboxing by allowing sendto only when dest_addr == NULL and addrlen == 0. This permits send()/self-pipe wakeups (e.g., call_soon_threadsafe) while keeping connect/bind/listen/etc. blocked.

Risk assessment:

  • Main risk: a sandboxed process could write to a pre-connected network socket inherited via FD leakage (since send() maps to sendto(NULL, 0)).
  • Mitigation: the sandbox still blocks creating/connecting sockets; risk depends on connected socket FD inheritance.

Addresses #9906

@etraut-openai
Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector
Copy link
Copy Markdown
Contributor

Codex Review: Didn't find any major issues. Another round soon, please!

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@etraut-openai
Copy link
Copy Markdown
Collaborator Author

Code review feedback from Codex (thanks to @viyatb-oai):

Inherited connected sockets could exfiltrate via sendto(NULL,0)
This rule allows sendto on any connected socket when dest_addr==NULL and addrlen==0. The sandbox helper does not close non-stdio file descriptors before applying seccomp, so any non-CLOEXEC network socket inherited from the parent would become a viable egress channel. This was previously blocked by denying sendto outright. Consider explicitly closing all non-stdio FDs (with a whitelist for known-needed sockets like escalation) or add a regression test that clears CLOEXEC on a pre-connected TCP socket and verifies sendto still fails under the sandbox.

Other review notes
The seccomp logic itself looks correct: it denies sendto unless dest_addr == NULL && addrlen == 0.The new regression test is directionally good, but it doesn’t prove the network boundary. I’d add a negative/inherited-FD test if we treat this as a security boundary.

@sudonym1
Copy link
Copy Markdown

sudonym1 commented Feb 5, 2026

FWIW, this also breaks python async code. The black code formatter hangs when processing more than one file at a time.

@github-actions
Copy link
Copy Markdown
Contributor

Closing this pull request because it has had no updates for more than 14 days. If you plan to continue working on it, feel free to reopen or open a new PR.

@github-actions github-actions Bot closed this Feb 19, 2026
@Xdynix
Copy link
Copy Markdown

Xdynix commented Mar 18, 2026

Requesting reconsideration on this.

I can still reproduce the same problem in the Codex sandbox, and it blocks valid standard Python asyncio usage.

Minimal repro:

import asyncio

async def main():
    result = await asyncio.to_thread(lambda: 1)
    print(result, flush=True)

asyncio.run(main())
print("done", flush=True)

Expected:

  • prints 1
  • prints done
  • exits normally

Observed in the sandbox:

  • prints 1
  • then hangs before printing done

Observed outside the sandbox:

  • exits normally

The lower-level wakeup repro is:

import socket

r, w = socket.socketpair()
w.send(b"x")
print("ok")

Observed in the sandbox:

  • PermissionError: [Errno 1] Operation not permitted

Observed outside the sandbox:

  • works normally

Important detail: os.write(w.fileno(), b"x") works on the same socketpair inside the sandbox. So this does not look like “sockets are broadly unsupported”, it looks more like socket.send(...) on the loop wakeup socket is being blocked.

That matches how CPython selector event loops wake themselves:

  • call_soon_threadsafe() uses _write_to_self()
  • _write_to_self() uses csock.send(b"\0")

So this appears to break a normal Python runtime mechanism, not just a specific framework.

Given that asyncio.to_thread() is standard library functionality, this still seems worth reopening or otherwise tracking as a sandbox compatibility bug.

Environment:

  • codex-cli 0.115.0
  • uv 0.10.11
  • Python 3.14.3
  • Linux 6.6.87.2-microsoft-standard-WSL2 x86_64 GNU/Linux

@viyatb-oai viyatb-oai reopened this Apr 8, 2026
Co-authored-by: Codex noreply@openai.com
@viyatb-oai viyatb-oai force-pushed the etraut/linux_sandbox branch from 544af30 to d6696b1 Compare April 8, 2026 16:40
viyatb-oai and others added 6 commits April 8, 2026 15:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants