seccomp-notify

Pure Ruby bindings for Linux seccomp user notifications

Gem Version Downloads Ruby Version License

Features · Installation · Quick Start · API Docs · Responses · Security · Development


seccomp-notify provides the plumbing for Linux SECCOMP_RET_USER_NOTIF. It builds cBPF filters, transfers listener file descriptors, supervises syscall notifications, reads target memory, and injects file descriptors without a native extension.

[!WARNING] This gem is a mechanism, not a security policy or a complete sandbox. Never inspect a pointer argument and then use continue! as a security decision: another target thread can change that memory between inspection and syscall execution (TOCTOU). Reject or emulate the syscall instead.

Features

  • Pure Ruby cBPF filter generation and seccomp installation
  • Parent supervisor with a filtered child through Seccomp::Notify.spawn
  • Existing-process supervision through Seccomp::Notify.supervise_self
  • Syscall handlers with errno, emulation, continuation, and process termination responses
  • Target memory helpers for strings, bytes, and socket addresses
  • File descriptor injection with atomic and fallback ADDFD paths
  • Runtime detection for optional kernel features
  • Handler timeouts, concurrent workers, and supervisor health checks
  • Linux x86_64 and aarch64 support

Installation

Add the gem to your Gemfile:

gem "seccomp-notify"

Then install:

bundle install

Requirements

  • Linux on x86_64 or aarch64
  • Ruby 3.1 or newer
  • Linux 5.0 or newer; ADDFD requires Linux 5.9 or newer
  • CONFIG_SECCOMP_FILTER=y

Container runtimes may block seccomp(2) with their own profile. For Docker development, use an isolated test container with seccomp disabled:

docker run --rm --security-opt seccomp=unconfined -v "$PWD:/app" -w /app ruby:3.4 bundle exec rake

Quick Start

Keep the unfiltered supervisor in the parent and install the filter in a child:

require "seccomp/notify"

policy = Seccomp::Notify::Policy.new do
  notify :connect, :sendto
end

supervisor = Seccomp::Notify.spawn(policy) do
  exec("curl", "https://example.com")
end

supervisor.on(:connect) { |request| request.error!(Errno::ENETUNREACH) }
supervisor.on(:sendto) { |request| request.error!(Errno::ENETUNREACH) }

status = supervisor.run

request.pid is an alias for request.tid: the kernel reports the thread ID that issued the syscall, not the process ID.

Supervise the current process

supervise_self(policy, supervisor: :fork) accepts a block that configures the child supervisor. The safer :spawn form starts a clean Ruby VM and supports the default pass-through handler only. Both forms detach the supervisor child, so broad Process.wait calls in the target can still observe related lifecycle effects.

Responses

Method Behavior Requirement
allow!(value = 0) Emulate a successful return value without running the syscall Linux 5.0+
error!(Errno::EPERM) Return an errno Linux 5.0+
continue! Run the original syscall Linux 5.5+
add_fd!(io) Inject a file descriptor and return its number Linux 5.9+
kill! Terminate the issuing process

Use Seccomp::Notify.features to inspect runtime support. Set SECCOMP_NOTIFY_DISABLE_FEATURES=addfd,continue to force feature fallbacks in tests.

Targets started by this gem can call Seccomp::Notify.supervisor_alive? to poll the health pipe. If the target calls exec, the pipe descriptor is inherited and published as SECCOMP_NOTIFY_HEALTH_FD.

How It Works

  1. The parent creates a Unix socket pair and forks the target.
  2. The target installs a cBPF filter and sends its listener descriptor to the parent with SCM_RIGHTS.
  3. The parent receives syscall notifications and dispatches them to registered handlers.
  4. Each handler returns an errno, emulated value, continuation, or injected descriptor.

Security

  • Pointer data may change after inspection. Never use it to justify continue!.
  • vDSO calls, existing file descriptors, and indirect shared-memory effects are outside seccomp's control.
  • I/O submitted through an existing io_uring does not pass through the filter; io_uring_setup is denied by default.
  • If the supervisor dies, blocked notification syscalls return ENOSYS.
  • fcntl and sendmsg must remain allowed because Ruby uses them to wrap and transfer the listener descriptor. Policies that notify or deny them are rejected.

Examples

Development

bundle install
bundle exec rake

Linux runs include real seccomp integration tests; other systems run portable filter and layout tests only. spike/notify_min.c is the reference C round trip, and tools/gen_syscall_table.rb regenerates architecture tables from Linux kernel syscall tables.

Contributing

Bug reports and pull requests are welcome at https://github.com/ydah/seccomp-notify.

Releases before 1.0 may change the API.

License

Released under the MIT License.