auditd captures kernel syscalls. But modern cloud workloads — containers, microservices, serverless functions — run in environments where auditd’s static rule model struggles. eBPF-based tools like Falco and Tetragon extend security visibility into these environments with behavioral assertions rather than static syscall rules.
Why eBPF Changes Security Monitoring
Traditional kernel tracing (auditd, strace) works by intercepting specific syscalls. eBPF goes further:
- In-kernel filtering: An eBPF program runs inside the kernel and decides whether to emit an event before it reaches userspace — reducing overhead from logging millions of syscalls to logging thousands of relevant events
- Kernel function tracing: Attach to arbitrary kernel functions (kprobes), not just syscalls — captures internal kernel operations that don’t surface as syscalls
- Syscall argument inspection: Read function arguments at the kernel level, before any userspace decoding
- Network stack integration: Hook into the TCP/IP stack at multiple layers — socket creation, connection establishment, packet transmission
Syscall (execve, connect, open)
│
▼
Kernel eBPF program ─── filters in-kernel ──→ drops if not interesting
│
│ (only interesting events)
▼
Ring buffer → userspace consumer (Falco, Tetragon, custom)
│
▼
Security event with full context
Falco: Behavioral Runtime Detection
Falco is CNCF (Cloud Native Computing Foundation) project that uses eBPF (or a kernel module) to detect suspicious runtime behavior. Its rules are written in a YAML DSL:
# Falco rule: shell spawned inside a container
- rule: Terminal shell in container
desc: A shell was used as the entrypoint/exec point into a container with an attached terminal.
condition: >
spawned_process
and container
and shell_procs
and proc.tty != 0
output: >
A shell was spawned in a container with an attached terminal
(user=%user.name %container.info shell=%proc.name parent=%proc.pname
cmdline=%proc.cmdline terminal=%proc.tty container_id=%container.id
image=%container.image.repository)
priority: NOTICE
tags: [container, shell, mitre_execution]
Key Falco rule macros
# Built-in macros you'll use in custom rules
# Is this a container?
container: container.id != host
# Spawned a new process?
spawned_process: evt.type = execve and evt.dir = <
# Is this a shell?
shell_procs: proc.name in (ash, bash, csh, ksh, sh, tcsh, zsh, dash)
# Is this a known sensitive file?
sensitive_files: (
fd.name startswith /etc/shadow or
fd.name startswith /etc/sudoers or
fd.name startswith /root/.ssh
)
Falco event output example
{
"time": "2024-03-15T09:23:14.882Z",
"rule": "Terminal shell in container",
"priority": "NOTICE",
"output_fields": {
"container.id": "abc123def456",
"container.image.repository": "company/webapi",
"container.image.tag": "v2.1.3",
"k8s.pod.name": "webapi-7d9b8c-xk2np",
"k8s.ns.name": "production",
"proc.name": "bash",
"proc.cmdline": "bash",
"proc.pname": "python3",
"user.name": "www-data",
"proc.tty": 34816
}
}
What this tells you: bash was spawned inside the production webapi container, parent process was python3, user is www-data. The Kubernetes context (pod name, namespace) is attached automatically. This is a web shell execution in a container.
Tetragon: eBPF with Enforcement
Tetragon extends eBPF security from detection to enforcement. Where Falco sends an alert and waits for a human response, Tetragon can terminate the offending process in-kernel before the malicious action completes.
# Tetragon TracingPolicy: kill any process that reads /etc/shadow outside /usr/sbin/
apiVersion: cilium.io/v1alpha1
kind: TracingPolicy
metadata:
name: protect-shadow
spec:
kprobes:
- call: "security_file_open"
syscall: false
args:
- index: 0
type: "file"
selectors:
- matchArgs:
- index: 0
operator: "Postfix"
values:
- "/etc/shadow"
matchBinaries:
- operator: "NotIn"
values:
- "/usr/sbin/unix_chkpwd"
- "/usr/bin/passwd"
matchActions:
- action: Sigkill # ← terminate the offending process
This policy kills any process that tries to open /etc/shadow unless it’s unix_chkpwd or passwd — both legitimate.