Skip to content

seccomp install fails on kernels < 5.19 — Rust port lost Python fallback for SECCOMP_FILTER_FLAG_WAIT_KILLABLE_RECV #62

@dzerik

Description

@dzerik

While running VM validation for the Protection opt-out work in #17, I hit a second kernel-version gate that prevents sandlock from running on RHEL 9 / Ubuntu 22.04 even with the v6 Landlock scopes opted out.

Symptom

On stock Rocky Linux 9.6 (kernel 5.14.0-570.17.1.el9_6, Landlock ABI v5):

$ sandlock run --fs-read /usr --fs-read /lib64 --fs-read /etc \
    --disable signal-scope --disable abstract-unix-scope-socket \
    -- /usr/bin/true
sandlock child: seccomp install: Invalid argument (os error 22)
Error: process error: child process error: read notif fd from child: pipe closed
exit=1

Reproduces identically on Rocky 9.7 (5.14.0-611.5.1.el9_7) and Ubuntu 22.04 (5.15.0-179-generic).

Root cause

crates/sandlock-core/src/seccomp/bpf.rs:154:

let flags = SECCOMP_FILTER_FLAG_NEW_LISTENER | SECCOMP_FILTER_FLAG_WAIT_KILLABLE_RECV;
let fd = seccomp(SECCOMP_SET_MODE_FILTER, flags, &fprog as *const _ as *const _)?;

SECCOMP_FILTER_FLAG_WAIT_KILLABLE_RECV was added in Linux 5.19. Passing it on an older kernel returns EINVAL, and the Rust path has no fallback.

This is a Rust-port regression

The pre-Rust Python implementation handled this correctly — commit 50d5eb9 ("Enable SECCOMP_FILTER_FLAG_WAIT_KILLABLE_RECV for reliable notifications"):

# src/sandlock/_notif.py (pre-rewrite)
flags = SECCOMP_FILTER_FLAG_NEW_LISTENER | SECCOMP_FILTER_FLAG_WAIT_KILLABLE_RECV
fd = _libc.syscall(..., ctypes.c_uint(flags), ...)
if fd < 0:
    # Fall back without WAIT_KILLABLE_RECV on older kernels
    fd = _libc.syscall(..., ctypes.c_uint(SECCOMP_FILTER_FLAG_NEW_LISTENER), ...)

The Rust rewrite (ae8fbd1 — "rewrite sandlock in Rust with full feature parity") dropped the fallback. WAIT_KILLABLE_RECV is a robustness flag (it prevents signals from aborting in-flight notifications) — losing it on older kernels degrades only reliability of supervised syscalls, not the security boundary, which matches the original Python intent.

Verified fix

Removing the WAIT_KILLABLE_RECV bit from the flags on Rocky 9.6 makes the full pipeline succeed (exit=0 after --disable signal-scope --disable abstract-unix-scope-socket). Porting the Python try-with-flag → fallback-without-flag pattern into install_filter should be sufficient.

Why this matters for #17

This gate compounds with #17: even after the Protection opt-out lets users select v5 Landlock semantics on RHEL 9 / Ubuntu 22.04, the seccomp install fails earlier in the same Sandbox::run() pipeline. sandlock check doesn't surface this — users only discover it at runtime. The two need to be fixed together for the LTS use case to actually work end-to-end.

Happy to take the PR if the fallback approach is acceptable in principle.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions