seccomp-ruby
Native CRuby bindings for libseccomp. The gem exposes
the C API through Seccomp::LowLevel and a Ruby-friendly filter, argument DSL, transaction, and
user-notification API.
[!WARNING] A loaded seccomp filter cannot be removed or relaxed before the process exits. Ruby itself uses many system calls; an incomplete allowlist can crash the VM immediately. Always validate a filter in a forked child process before using it in production.
Requirements
- Linux and CRuby 3.1 or newer
- libseccomp 2.5 or newer and its development headers
- A C compiler
On Debian or Ubuntu:
sudo apt-get install libseccomp-dev build-essential
bundle install
bundle exec rake compile
The gem name is currently seccomp-ruby:
gem "seccomp-ruby"
Quick start
Construct and inspect filters in the parent process, but load them only after fork:
require "libseccomp"
filter = Seccomp.filter(default: :kill_process) do
no_new_privs true
deny :ptrace, errno: Errno::EPERM
allow :read, :write, :exit, :exit_group, :rt_sigreturn
end
puts filter.to_pfc
pid = fork do
filter.load!
# Run sandboxed work here.
Process.exit!(0)
end
Process.waitpid(pid)
filter.close
Argument comparisons use explicit keywords for masked equality:
filter.allow(:openat, filter.arg(2).masked_eq(mask: 0o3, value: 0))
filter.deny(:close, filter.arg(0).eq(2), errno: Errno::EBADF)
Use Seccomp.supports? before calling optional APIs. It checks both compile-time availability and
the runtime API level where applicable:
filter.transaction { filter.allow(:read) } if Seccomp.supports?(:transaction)
filter.precompute if Seccomp.supports?(:precompute)
notification.add_fd(file) if Seccomp.supports?(:notify_addfd)
User notifications
Seccomp::Notifier#receive integrates with Ruby IO waiting and accepts a timeout. Notification
errors use normal positive errno values at the Ruby boundary:
notification.deny(Errno::EPERM) # converted to the kernel's negative errno convention
notification.respond(error: Errno::EPERM) # direct responses use the same conversion
notification.continue!
[!CAUTION] Notification arguments may contain pointers into another process. If you read through
/proc/<pid>/mem, callnotification.valid?again after reading and before responding. This reduces, but cannot eliminate, time-of-check/time-of-use races.continue!must only be used when the target syscall is safe to execute with its current arguments.
libseccomp keeps one notification listener in process-global state. Closing its Notifier or
owning Filter closes and resets that listener. A second notification filter is rejected while
the listener is active; use one notification supervisor per process.
[!WARNING] A loaded notification filter must allow
close(2)while its listener is being released. A rule that denies or killsclose(2)can leave the listener open, and a notification rule forclose(2)requires a supervisor to answer during cleanup. This cannot be repaired after the irreversible filter is loaded; keep the supervisor running or end the sandbox withProcess.exit!.
If file descriptor 0 is closed when a notification filter is loaded, the gem reserves it with
/dev/null and keeps it open after a successful load; closing it afterward could itself be blocked
by the irreversible filter. Filters without notification actions leave file descriptor 0 unchanged.
If a notification load fails after an earlier filter was loaded, the reservation is kept rather than
risk an internal close(2) being intercepted. Rolling back a transaction that added a notification
rule requires Filter#reset or a new notification rule before loading that filter, due to native
libseccomp state retained by the rejected transaction.
Thread and architecture notes
tsyncapplies a filter to every thread. Ruby VM threads require syscalls such asfutex,rt_sigprocmask, andmembarrier; blocking them may terminate or deadlock the VM.width: 32masks comparison values to 32 bits. On 32-bit ABIs, upper halves of 64-bit syscall arguments may not be meaningful.- x32 and x86_64 are distinct architecture tokens.
Seccomp.api_level=changes process-global libseccomp state and should be used once at startup.
Development
bundle exec rake compile
bundle exec rake spec:unit
bundle exec rake spec:live # all load! calls are fork-isolated
bundle exec rubocop
bundle exec rbs validate
bundle exec yard stats --list-undoc
The live suite must run on Linux. In a container, use --security-opt seccomp=unconfined.
License
This project retains its existing MIT license. It dynamically links to libseccomp, which is licensed under LGPL-2.1.