Skip to content

policy_fn: support event category subscriptions - #306

Merged
congwang-mk merged 4 commits into
mainfrom
issue-290-policy-fn-net-events
Oct 21, 2026
Merged

congwang-mk merged 4 commits into
mainfrom
issue-290-policy-fn-net-events

Conversation

@congwang-mk

@congwang-mk congwang-mk commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

Closes #290.

Any policy_fn used to trap every file syscall and turn on ptrace fork tracking plus the execve freeze, even when the callback only cared about connect. A callback can now subscribe to event categories, and the sandbox installs only the interception those categories need.

API

  • Rust: PolicyFn::new(cb).events(&[SyscallCategory::Network])
  • Python: PolicyFn(cb, events={"network"}); an empty set or unknown name raises ValueError
  • Go: PolicyFn.Events: []sandlock.SyscallCategory{sandlock.CategoryNetwork}
  • C: both sandlock_sandbox_builder_policy_fn* builders take an events mask of SANDLOCK_EVENT_* bits (SANDLOCK_EVENT_ALL keeps every category). This changes the C signature.

The default is still all categories, so existing callers and sandlock learn behave as before.

The categories are now filesystem, network and process. File is renamed to Filesystem everywhere (SyscallCategory::Filesystem, "filesystem", CategoryFilesystem, SANDLOCK_EVENT_FILESYSTEM, event category 0) so it matches the other two names. Memory is removed: policy_fn never trapped memory syscalls, so subscribing to it did nothing unless max_memory was set, and then every mmap went to a callback that had no action to take on it.

What each category costs

Category Subscribing adds
filesystem traps on path syscalls, plus those ctx.deny_path() gates
network traps on connect, sendto, sendmsg, sendmmsg, bind
process execve traps, ptrace fork tracking, and the execve freeze

argv_safety_required, which gates both ptrace mechanisms, is now set only when process is subscribed or an execve handler is registered. Events in unsubscribed categories are dropped even when another feature (procfs, COW, chroot) traps the syscall. Syscalls with no category, such as the memory-limit mmap/brk traps or setpgid, never reach the callback; they used to fall through to a default and get reported as file events.

Behaviour notes

  • Network supervision stays on for every callback, so ctx.restrict_network() keeps working from any event category.
  • ctx.deny_path() needs filesystem events, because only they keep path syscalls trapped. Without them it fails instead of silently not denying: Rust returns PolicyFnError::NotSubscribed, C sandlock_ctx_deny_path returns -1, Python raises RuntimeError, Go returns ErrFilesystemEventsRequired.

Testing

Ran locally only the tests the change touches, leaving the full suite to CI: Rust policy_fn unit (21) and integration (32) tests, FFI policy_fn and C smoke tests, Python test_policy_fn.py (39), and the Go policy tests. New tests check that a network-only callback sees connect but no filesystem or process events while the workload opens a file and runs a subprocess, that deny_path fails without filesystem events, that memory-limit syscalls under max_memory never reach the callback, that the trap plan drops mkdirat, unlinkat and execve, and that the Python events argument is validated.

🤖 Generated with Claude Code

Any policy_fn trapped every file syscall and turned on ptrace fork
tracking plus the execve freeze, even when the callback only cared
about connect. PolicyFn::events() now limits delivery to the chosen
categories and drops the traps and argv safety machinery that only
unsubscribed categories need, so a network-only embedder pays for
network supervision alone.

ctx.deny_path() returns an error without file events: the path
syscalls it relies on are no longer intercepted, and a silent no-op
would leave the path reachable.

Refs: #290
Signed-off-by: Cong Wang <cwang@multikernel.io>
The C builders take a SANDLOCK_EVENT_* mask and Python's PolicyFn takes
events={"network", ...}, so embedders outside Rust can opt out of the
file traps and execve freezing a network-only callback does not need.
Go passes every category until it grows its own option.

sandlock_ctx_deny_path now returns -1 when file events are not
subscribed, and Python raises instead of silently not denying.

Refs: #290
Signed-off-by: Cong Wang <cwang@multikernel.io>
PolicyFn.Events lets a Go embedder take only network events and skip
the file traps and execve freezing the callback would otherwise pay
for. DenyPath reports ErrFileEventsRequired instead of claiming
success when file events are not delivered.

Refs: #290
Signed-off-by: Cong Wang <cwang@multikernel.io>
Explain what a narrower subscription saves, how each binding spells
it, and why deny_path() still needs file events.

Refs: #290
Signed-off-by: Cong Wang <cwang@multikernel.io>
@congwang-mk
congwang-mk force-pushed the issue-290-policy-fn-net-events branch from 1bd3efd to ebb7a44 Compare October 21, 2026 01:20
@congwang-mk
congwang-mk merged commit 93060ed into main Oct 21, 2026
24 checks passed
@congwang-mk
congwang-mk deleted the issue-290-policy-fn-net-events branch October 21, 2026 01:38
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.

policy_fn: support network-only event subscriptions

1 participant