Skip to content

feat(py): the worker's Linux seccomp filter and network block - #362

Draft
jat255 wants to merge 1 commit into
jat255/tpw5-macos-seatbelt-sandboxfrom
jat255/s87h-seccomp-network-block
Draft

feat(py): the worker's Linux seccomp filter and network block#362
jat255 wants to merge 1 commit into
jat255/tpw5-macos-seatbelt-sandboxfrom
jat255/s87h-seccomp-network-block

Conversation

@jat255

@jat255 jat255 commented Sep 11, 2026

Copy link
Copy Markdown
Collaborator

This PR adds the worker's seccomp filter and its network="none" socket block, built as ctypes BPF programs and installed with prctl. It also fills in the seccomp field of the construction-time gate, so that gate makes a real decision on Linux.

Stacked on #355. Nothing calls engage() yet; the worker that will is task 6.

Agent-written detail

The filter matches raw syscall numbers, so _seccomp.py carries a table for each of the four architectures the R implementation supports. The numbers came from each architecture's kernel headers rather than from memory: they diverge below 425, and pivot_root is 217 on i386 but 218 on arm.

Two entry points the R filter does not screen are screened here, and kata mv69 tracks closing them there. socketcall reaches every socket operation on i386 behind one number. pidfd_getfd hands over a descriptor belonging to another process of the same user, which is a capability no filesystem sandbox granted; it postdates that C and looks like an omission rather than a decision.

x32 is the one ABI reporting a 64-bit machine with 32-bit pointers, and it belongs to neither table, so it gets no entry and such a host is refused rather than handed a filter that would reject its every call. A 32-bit i386 process is not that case: the kernel reports i686 to it.

seccomp_available() finds out whether a filter installs by installing an allow-all one in an isolated child, because a kernel that answers PR_GET_SECCOMP can still refuse PR_SET_SECCOMP, and a gate that missed that would approve a host whose worker then fails to start.

The tests run the filter programs through a small BPF interpreter, which is how one machine checks all four tables. The Linux ones engage for real in a child, since a filter cannot be lifted, and three of them first check the host does not already refuse the call, so they skip rather than passing hollowly inside a container. Verified under three container profiles: default, unconfined, and one that permits PR_GET_SECCOMP while denying PR_SET_SECCOMP, where the suite skips cleanly instead of failing.

CI gains one arm64 leg, so the aarch64 numbers are exercised rather than merely built. kata 9jmj covers the 32-bit tables, which need a container job rather than the current uv matrix. kata hm25 records a separate gap this surfaced: abstract-namespace AF_UNIX sockets are reachable through socketpair and no filesystem sandbox governs them, which belongs with the Landlock and userns tasks.

@jat255 jat255 added py Affects the Python implementation tests Related to testing or the test suite needs-manual-review Agent-created work that needs a human review labels Sep 11, 2026
@jat255 jat255 added this to the py-M6: code execution milestone Sep 11, 2026
The part of the worker's boundary a filesystem sandbox cannot express: the
syscalls that would let it step out of the sandbox rather than read around
it, and, under network="none", the syscalls that open a socket. Both are BPF
programs built as ctypes structures and installed with prctl.

A seccomp filter matches raw syscall numbers, so the module carries a table
per architecture, for the same four the R implementation supports. The
numbers were read from each architecture's kernel headers rather than
written from memory: they diverge below 425, and two of them disagree where
it would be easy to assume otherwise. Two entry points the R filter does not
screen are screened here, and kata mv69 tracks closing them there: socketcall
on i386, which multiplexes the socket calls behind one number, and
pidfd_getfd, which hands over a descriptor belonging to another process of
the same user and so returns a capability the filesystem sandbox never
granted.

Filters carry the calling architecture's tag, so each program confirms that
tag before comparing any number, and refuses the call otherwise. An
architecture with no table is refused outright rather than filtered against
someone else's numbers.

seccomp_available() now answers sandbox_capabilities()'s seccomp field, so
the construction-time gate makes a real decision on Linux. It finds out
whether a filter installs by installing an allow-all one in a child, because
a kernel that answers PR_GET_SECCOMP can still refuse PR_SET_SECCOMP, and a
gate that missed that would approve a host whose worker then fails to start.
The other three mechanisms still report unavailable and belong to their own
tasks. Nothing calls engage() yet; the worker that will is task 6.

The filter programs are tested by running them through a BPF interpreter,
which is how one machine checks all four tables. The Linux tests engage for
real in a child, since a filter cannot be lifted. Three of them guard against
proving nothing: the unshare and pidfd_getfd tests skip where the host
already refuses the call with no filter on, which a container commonly does,
and the install probe is checked under a filter that permits PR_GET_SECCOMP
while denying PR_SET_SECCOMP. CI gains one arm64 leg, so the aarch64 numbers
are exercised and not merely built; kata 9jmj covers the 32-bit tables.
@jat255
jat255 force-pushed the jat255/s87h-seccomp-network-block branch from 7ea79c4 to 83c0775 Compare September 11, 2026 19:48
@jat255
jat255 changed the base branch from jat255/t7d4-sandbox-gate-rlimits to jat255/tpw5-macos-seatbelt-sandbox September 11, 2026 19:48
@jat255
jat255 added this pull request to stack #364 September 11, 2026 19:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-manual-review Agent-created work that needs a human review py Affects the Python implementation tests Related to testing or the test suite

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant