Project

vivarium

0.0
The project is in a healthy, maintained state
Vivarium visualizes low-level events such as file open paths and relates them to Ruby method boundaries by combining RbBCC (eBPF LSM) and TracePoint.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies

Development

Runtime

~> 0.11.8
 Project Readme

Vivarium

Gem Version

Vivarium is an observation and sandbox helper for Ruby.

Logo - generated by Nano Banana 2

It combines:

  • eBPF LSM monitoring via RbBCC (vivariumd)
  • Ruby-side method boundary observation via TracePoint (Vivarium.observe)

The goal is to visualize which Ruby method context triggered low-level events.

Current Scope

Implemented in this repository:

BPF LSM hooks on inode_symlink, inode_link, inode_rename, inode_unlink (filename and parent directory name are captured as reference information only), path_chmod

  • BPF LSM hooks for suspicious behavior checks:
    • ptrace_access_check (emits ptrace_check)
    • sb_mount (emits sb_mount)
    • kernel_read_file (emits kernel_read_file)
    • task_kill (emits task_kill)
    • task_fix_setuid (emits setid_change)
    • capable for high-risk capabilities only (emits capable_check)
    • bprm_creds_from_file (emits bprm_creds)
  • BPF LSM hook on socket_create (flags unusual socket creation as odd_socket)
  • BPF LSM hook on socket_connect (captures destination family/address/port as sock_connect)
  • BPF tracepoints on sys_enter_sendmsg, sys_enter_sendto, sys_enter_sendmmsg (capture UDP/53 DNS QNAME raw bytes as dns_req)
  • USDT probes for method span boundaries and exceptions (span_start, span_stop, span_raise)
  • OpenSSL SSL_write uprobe event (ssl_write)
  • Shared pinned maps on bpffs
    • config_root_targets (root PID -> 0/1)
    • config_spawned_targets (spawned TID -> 0/1)
    • events (BPF_RINGBUF_OUTPUT, shared by system events and span events)
  • Ruby API Vivarium.observe do ... end
    • Registers current PID to config_root_targets
    • eBPF tracks spawned descendants into config_spawned_targets via sched_process_fork
    • TracePoint emits span probes on allowlisted call/return and emits span_raise on Ruby :raise
    • Correlator thread consumes ringbuf events and joins them to spans by tid/time window
    • Renders a process tree once at session end
    • Unregisters PID on block exit

event_t currently:

struct event_t {
	u64 ktime_ns;
	u32 pid;
	u32 tid;
	char event_name[16];
	char payload[256];
};

Requirements

  • Linux kernel/environment supporting BPF LSM
  • libbcc installed
  • bpftool installed (used to resolve struct file::f_path and struct dentry::d_name offsets from BTF)
  • root privileges for vivariumd
  • bpffs mounted (typically /sys/fs/bpf)

Installation

Add to Gemfile:

gem "vivarium"

Then:

bundle install

Usage

  1. Start daemon (root):
sudo bundle exec vivariumd
  1. Observe in Ruby process:
require "vivarium"

Vivarium.observe do
  File.read("/etc/passwd")
end
  1. Network monitoring demo client:
bundle exec ruby examples/network_client_demo.rb

This demo intentionally triggers sock_connect, dns_req, and odd_socket events.

  1. File operation demo client (only touches /tmp):
bundle exec ruby examples/file_operation_demo.rb

This demo intentionally triggers path_open, file_symlink, file_hardlink, file_rename, file_chmod, file_unlink, and file_getdents events under /tmp.

  1. Execve demo client:
bundle exec ruby examples/execve_demo.rb

This demo intentionally triggers proc_exec with several argument patterns using direct execve-style process launches.

  1. Signal demo client:
bundle exec ruby examples/signal_kill_demo.rb

This demo forks a child process and sends TERM with Process.kill, which is useful for triggering task_kill.

  1. Privilege-related event demo client:
bundle exec ruby examples/privilege_event_demo.rb

This demo attempts setuid/setgid changes, sensitive file access, and sudo exec to trigger privilege-related events such as setid_change, capable_check, and bprm_creds.

  1. Ruby internal ENV access demo client:
bundle exec ruby examples/env_access_ruby_demo.rb

This demo triggers Ruby-side ENV methods ([], fetch, key?, []=, store, delete, clear, replace) and is intended to produce SPAN events.

  1. External command ENV libc access demo client:
bundle exec ruby examples/env_access_external_demo.rb

This demo spawns an external process that directly calls libc getenv, setenv, unsetenv, putenv, and clearenv, intended to trigger env_caccess eBPF events.

  1. Box context demo:
bundle exec ruby examples/box_demo.rb

This demo shows how to use Vivarium::Box to isolate Ruby code evaluation and trace method calls within that isolated context.

You can also start top-level observation without a block (it keeps observing until process exit):

require "vivarium"

observer = Vivarium.top_observe
# or: Vivarium.observe
# do anything ...
observer.stop

Vivarium.observe / Vivarium.top_observe produce one process-tree report at session end (block exit, observer.stop, or process exit).

You can override pin directory via VIVARIUM_BPF_PIN_DIR on both sides:

VIVARIUM_BPF_PIN_DIR=/sys/fs/bpf/vivarium bundle exec vivariumd

Use Vivarium.bpf_pin_dir = "/sys/fs/bpf/..." in Ruby code to set it programmatically.

require "vivarium"
Vivarium.bpf_pin_dir = "/sys/fs/bpf/vivarium"

Development

Run tests:

bundle exec rake test

Daemon entrypoint:

bundle exec vivariumd --pin-dir /sys/fs/bpf/vivarium

Notes

  • Thread/Ractor-awareness is not yet implemented.
  • Current transport is ring buffer (events) pinned under bpffs.
  • Ring buffer is single-consumer by nature; v1 supports a single observer per host.
  • payload is 256 bytes in event_t; some event types intentionally use smaller structured slices inside that buffer.
  • proc_exec currently stores the executable path plus up to 3 argv entries in 4 fixed 64-byte slots to keep the BPF verifier happy.
  • span_raise is emitted on Ruby :raise and rendered as EXCP lines within the enclosing span.
  • Events that do not match any real span are grouped into synthetic <no-span> spans.
  • Each event is tagged with severity metadata: high for setid_change, capable_check, bprm_creds, task_kill, ptrace_check, sb_mount, and kernel_read_file; others are medium by default.
  • capable_check is intentionally filtered to high-risk capabilities to reduce noise from extremely frequent capable hook calls.
  • Output format is textual process tree with a session header and per-span relative timing.
  • vivariumd resolves struct file::f_path offset from /sys/kernel/btf/vmlinux at startup.
  • vivariumd also resolves struct dentry::d_name offset from /sys/kernel/btf/vmlinux at startup.
  • You can override offsets manually with VIVARIUM_FILE_F_PATH_OFFSET and VIVARIUM_DENTRY_D_NAME_OFFSET if auto-detection fails.

Contributing

Issues and pull requests are welcome.