Skip to content

seccomp: enforce the TCP bind allowlist on the on-behalf bind path - #227

Open
congwang-mk wants to merge 2 commits into
mainfrom
bind-allowlist-on-behalf
Open

seccomp: enforce the TCP bind allowlist on the on-behalf bind path#227
congwang-mk wants to merge 2 commits into
mainfrom
bind-allowlist-on-behalf

Conversation

@congwang-mk

Copy link
Copy Markdown
Contributor

Problem

--net-allow-bind was enforced only by Landlock BIND_TCP rules in the child. Any network supervision (--net-allow, --net-deny, the HTTP ACL, a policy function, --port-remap) moves bind() onto the on-behalf path, where the supervisor binds a dup of the child's socket outside the child's Landlock domain. The kernel rules never saw the bind and the handler checked only the denylist, so the allowlist silently stopped applying as soon as the sandbox also needed egress.

The default deny-all bind was bypassed the same way: --net-allow alone or --port-remap alone let the sandbox bind any TCP port.

Reproduction with --net-allow-bind 18080:

Policy bind 18080 bind 18099
allow-bind alone allowed refused
allow-bind + net-allow allowed allowed (bug)
allow-bind + net-deny allowed allowed (bug)
net-allow alone, no allow-bind allowed (bug) allowed (bug)

Fix

The supervisor's network state now carries the allowlist next to the existing denylist. The on-behalf bind handler refuses TCP binds outside it with EACCES, including bind(0), matching Landlock on the direct path. UDP and non-IP binds are untouched. The allowlist is unset for the '*' wildcard, when a deny-bind policy is active, or when the NetTcp protection is not active, so those cases behave as before.

Tests

  • Two new integration tests: the allowlist under net_allow, net_deny, and port_remap; the default deny-all under port_remap. Both failed before the fix.
  • Four Python port-remap tests bound ports with no allowlist and passed only through the bypass; they now declare one.
  • Full Rust integration suite, Python bind and port-remap tests, and the Go network tests pass locally.

🤖 Generated with Claude Code

The bind allowlist was enforced only by Landlock BIND_TCP rules in the
child. Any network supervision (net_allow, net_deny, the HTTP ACL, a
policy function, port_remap) moves bind() onto the on-behalf path, where
the supervisor binds a dup of the child's socket outside the child's
Landlock domain. The kernel rules never saw the bind and the handler
checked only the denylist, so the allowlist and the default deny-all
silently stopped applying as soon as the sandbox also needed egress.

The supervisor now carries the allowlist and refuses TCP binds outside
it with EACCES, including bind(0), matching Landlock's behaviour on the
direct path. Four Python port-remap tests bound ports without any
allowlist and passed only because of the bypass; they now declare one.

Signed-off-by: Cong Wang <cwang@multikernel.io>
Learn mode grants wildcard egress but never declared a bind allowlist,
so its binds only succeeded through the on-behalf bypass that the
previous commit closed. Observation has to see every bind the program
attempts, so the allowlist is now the wildcard.

Signed-off-by: Cong Wang <cwang@multikernel.io>
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.

1 participant