Skip to content

Latest commit

Β 

History

35 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

SYSCAGE

Behavioral syscall profiler and seccomp policy generator for Linux process confinement.

Platform-Linux Language-C11 License-MIT Status CI CodeQL Docker Tested-on Domain


Etymology & Origin

The name SYSCAGE is a portmanteau of Syscall and Cage β€” a cage for system calls. It encapsulates the tool's purpose: confining a process to only the system calls it needs, blocking everything else.

Where LinSpec checks if protections are active and K-Scanner detects violations, SYSCAGE shifts from detection to active restriction. It learns what a process needs at runtime, then builds a seccomp-bpf filter that enforces that behavior.


Overview

SYSCAGE is a three-phase tool that observes, generates, and enforces syscall-level policies on Linux processes.

Core Pipeline:

  1. Learn β€” syscage learn β€” Running PID or command β†’ .trace file
  2. Generate β€” syscage gen β€” .trace file β†’ .syscage profile
  3. Enforce β€” syscage enforce / syscage watch β€” .syscage profile β†’ seccomp-bpf filter via prctl(2)

Features

  • Behavioral syscall profiling β€” traces a process and records every syscall it makes
  • Automatic seccomp generation β€” converts traced behavior into a ready-to-use seccomp-bpf filter
  • Ptrace-based tracing (zero deps) β€” works on any Linux system without external libraries
  • eBPF tracing (optional) β€” lower overhead when libbpf is available (--ebpf)
  • Critical syscall injection β€” automatically includes essential syscalls (read, write, exit, etc.)
  • Multiple enforcement modes β€” attach to running process (ptrace injection), spawn new process, or watch with violation monitoring
  • JSON export β€” machine-readable profile output for toolchain integration (--json)
  • Trace merging β€” combine multiple trace files before profile generation
  • C header export β€” generate embeddable seccomp filter code (--header)
  • Subcommand CLI β€” follows the git/docker pattern (learn, gen, enforce, watch)
  • Text-based profiles β€” human-readable, diffable, and version-controllable
  • Pure C11 β€” minimal dependencies (system headers + libc)

Example Output

β”Œβ”€ SYSCAGE ──────────────────┐
β”‚ Kernel Policy Fence        β”‚
β”‚ Syscall Profiling/Seccomp  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

[INF] 14:30:01  Tracing PID 1234 for 30 seconds...
[INF] 14:30:31  Traced 14203 syscalls in 30.0s
[INF] 14:30:31  Trace complete: 14203 syscalls observed, 21 unique.
[INF] 14:30:31  Trace saved to nginx.trace

[INF] 14:30:32  Profile saved to nginx.trace.syscage

  Profile Summary
  ──────────────────────────────
  Rules:        37
  Observations: 14203
  Source PID:   1234
  Duration:     30.0s
  Default:      KILL

  NR     SYSCALL
  ─────────────────
  0      read
  1      write
  2      open
  3      close
  4      stat
  5      fstat
  9      mmap
  10     mprotect
  11     munmap
  12     brk
  ... and 27 more

[INF] 14:30:33  Spawned PID 1298 under seccomp profile.
[INF] 14:30:33  Watching PID 1298 under seccomp profile.

How It Works

SYSCAGE reads syscall data from the kernel through two backends:

  • Ptrace (default): Uses PTRACE_SYSCALL to intercept every syscall entry, reads orig_rax from struct user_regs_struct, and records the syscall number.
  • eBPF (optional): Uses raw_tracepoint/sys_enter for lower-overhead syscall capture.

The pipeline flow:

 PID or command
      β”‚
      β–Ό
 β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
 β”‚  TRACER  │──>β”‚ PROFILER   │──>β”‚  ENFORCER    β”‚
 β”‚ ptrace/  β”‚   β”‚ generates  β”‚   β”‚ applies via  β”‚
 β”‚ eBPF     β”‚   β”‚ .syscage   β”‚   β”‚ prctl +      β”‚
 β”‚          β”‚   β”‚ profile    β”‚   β”‚ seccomp(2)   β”‚
 β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
      β”‚               β”‚                β”‚
      β–Ό               β–Ό                β–Ό
   .trace        .syscage         process with
  (raw data)    (text rules)     seccomp filter

Profile Generation

  1. All syscalls observed above the frequency threshold are added to the allowlist
  2. Critical syscalls (read, write, exit, getpid, etc.) are automatically injected
  3. The profile is sorted by syscall number for readability
  4. The default action for unknown syscalls is KILL

Enforcement

The enforcer builds a seccomp-bpf filter (the same BPF used by tcpdump) and applies it via:

prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)   // prevent filter bypass
prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog)  // apply filter

Quick Start

git clone https://github.com/jeffersoncesarantunes/SYSCAGE.git
cd SYSCAGE && make
sudo ./bin/syscage learn -d 5 -o ls.trace -- /bin/ls -la /tmp
sudo ./bin/syscage gen ls.trace
sudo ./bin/syscage watch -p ls.trace.syscage -- /bin/ls /tmp

Three commands and you've confined ls to only the syscalls it used during profiling. If it runs the same as without SYSCAGE, the profile is complete.

Build

make                # Compile
make clean && make  # Clean rebuild from scratch

Usage

Learn β€” Profile a process

Trace every syscall a process makes. The recommended way (works on any Linux) is to launch a command under trace. You can also attach to a running process if your kernel allows it.

# Recommended: launch and trace a command (works everywhere)
sudo ./bin/syscage learn -d 5 -o ls.trace -- /bin/ls -la /tmp

# Optional: trace an already running process (by PID or name)
sudo ./bin/syscage learn -d 15 -o nginx.trace 1234
sudo ./bin/syscage learn -d 15 -o nginx.trace $(pidof nginx)

What each part means:

  • -d 5 β€” trace for 5 seconds
  • -o ls.trace β€” save results to file ls.trace
  • -- /bin/ls -la /tmp β€” the -- separates SYSCAGE options from the command to launch
  • 1234 β€” PID of a running process to trace
  • $(pidof nginx) β€” resolves "nginx" to its PID automatically

Note: Attaching to a running process requires ptrace_scope = 0. Most Linux distros (including Arch) default to 1, which only allows tracing child processes. This is why attaching by PID may fail with "Operation not permitted" β€” launch the command under trace instead, which always works.

Gen β€” Generate a seccomp profile

Convert the raw trace into a human-readable policy file:

sudo ./bin/syscage gen ls.trace

This creates ls.trace.syscage β€” a text file listing every allowed syscall.

You can also generate a C header for embedding into other programs:

sudo ./bin/syscage gen --header ls.trace

Enforce / Watch β€” Apply the profile

Apply the policy to a process. Use enforce to spawn a command silently, or watch to see its output and exit status:

# watch shows the command's output and waits for it to finish (recommended)
sudo ./bin/syscage watch -p ls.trace.syscage -- /bin/ls /tmp

# enforce spawns and returns immediately (for scripts/production)
sudo ./bin/syscage enforce -p ls.trace.syscage -e -- /bin/ls /tmp

What each part means:

  • -p ls.trace.syscage β€” which profile to apply
  • -e β€” spawn a new process (instead of attaching to a running one)
  • -- /bin/ls /tmp β€” command to confine under the profile
  • watch β€” same as enforce, but shows output and logs if a blocked syscall is attempted

Operational Integrity

SYSCAGE is designed for safe profiling and enforcement:

  • Read-only during learning: The ptracer never modifies the target's memory or execution
  • No kernel modification: All profiling uses standard ptrace/seccomp APIs
  • Self-contained profiles: Profiles are plain text β€” no binary state
  • Graceful fallback: If ptrace fails, no changes are made to the system
  • Seccomp is process-scoped: A restricted process cannot affect other processes
  • NO_NEW_PRIVS enforced: Prevents filter bypass via setuid binaries

Repository Structure

β”œβ”€β”€ bin/
β”‚   └── syscage
β”œβ”€β”€ build/
β”œβ”€β”€ docs/
β”‚   β”œβ”€β”€ ARCHITECTURE.md
β”‚   β”œβ”€β”€ BPF_REFERENCE.md
β”‚   β”œβ”€β”€ OPERATION_MODEL.md
β”‚   └── THREAT_MODEL.md
β”œβ”€β”€ examples/
β”‚   └── profiles/
β”œβ”€β”€ include/
β”‚   β”œβ”€β”€ enforcer.h
β”‚   β”œβ”€β”€ profiler.h
β”‚   β”œβ”€β”€ syscage.h
β”‚   └── tracer.h
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ common.c
β”‚   β”œβ”€β”€ enforcer.c
β”‚   β”œβ”€β”€ main.c
β”‚   β”œβ”€β”€ profiler.c
β”‚   └── tracer.c
β”œβ”€β”€ tests/
β”‚   └── test_profiler.c
β”œβ”€β”€ .clang-format
β”œβ”€β”€ .gitignore
β”œβ”€β”€ LICENSE
β”œβ”€β”€ Makefile
└── README.md

Tech Stack

  • Language: C (C11)
  • Kernel Interface: ptrace(2), prctl(2), seccomp(2)
  • Optional Backend: eBPF (raw_tracepoint/sys_enter)
  • Filter Format: seccomp-bpf (struct sock_fprog / struct sock_filter)
  • Build Tool: GNU Make
  • Target Platforms: Linux Kernel 5.x, 6.x (x86_64)

Roadmap

  • Ptrace-based syscall tracer (no deps)
  • Trace file I/O (save/load .trace)
  • Profile generation from trace data
  • Text profile format (.syscage)
  • C header export for embedding
  • Seccomp filter building
  • Process spawn under filter (enforce -e)
  • Watch mode with violation monitoring
  • Profile merging (combine multiple traces)
  • JSON profile export
  • Running process attach (ptrace seccomp injection)
  • Systemd integration (generator mode)
  • Container-aware profiling (Docker/k8s)
  • eBPF backend (libbpf)
  • Remote profiling over SSH

Documentation

Docs-Architecture Docs-Operation Docs-ThreatModel Docs-BPF

About

🐧 Behavioral syscall profiler and seccomp policy generator for Linux process confinement

Topics

Resources

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages