Loading learning content...
What if you could detect a container escape the moment it happens? What if you could see every file a compromised process touches, every network connection it makes, every privilege escalation it attempts—all in real-time with minimal overhead?
This is eBPF security.
eBPF has revolutionized runtime security by enabling deep visibility into system behavior without the overhead of traditional security tools. Tools like Falco, Tetragon, and Tracee leverage eBPF to detect threats, enforce policies, and provide forensic data in ways that were previously impossible.
By the end of this page, you will understand eBPF's security capabilities including LSM programs, runtime threat detection, seccomp integration, and how production security tools leverage eBPF.
eBPF provides unprecedented visibility into system security events:
What eBPF Can Observe
| Category | Events | Security Relevance |
|---|---|---|
| Process | exec, fork, exit, setuid | Privilege escalation, malware execution |
| File | open, read, write, unlink | Data exfiltration, tampering |
| Network | connect, accept, sendmsg | C2 communication, lateral movement |
| System | mount, ptrace, module_load | Container escape, rootkit installation |
| Credentials | setuid, capset, keyring | Privilege manipulation |
123456789101112131415161718192021222324252627282930313233
// Process execution monitoringSEC("tracepoint/syscalls/sys_enter_execve")int trace_execve(struct trace_event_raw_sys_enter *ctx) { struct event *e; e = bpf_ringbuf_reserve(&events, sizeof(*e), 0); if (!e) return 0; e->pid = bpf_get_current_pid_tgid() >> 32; e->uid = bpf_get_current_uid_gid() & 0xffffffff; bpf_get_current_comm(&e->comm, sizeof(e->comm)); // Read executable path const char *pathname = (const char *)ctx->args[0]; bpf_probe_read_user_str(&e->filename, sizeof(e->filename), pathname); bpf_ringbuf_submit(e, 0); return 0;} // Privilege escalation detectionSEC("tracepoint/syscalls/sys_enter_setuid")int trace_setuid(struct trace_event_raw_sys_enter *ctx) { uid_t target_uid = ctx->args[0]; uid_t current_uid = bpf_get_current_uid_gid() & 0xffffffff; // Detect setuid(0) - potential privilege escalation if (target_uid == 0 && current_uid != 0) { bpf_printk("ALERT: setuid(0) by non-root uid=%d", current_uid); } return 0;}Unlike traditional audit systems (auditd), eBPF filters in-kernel, sending only relevant events to user space. This reduces overhead from 5-10% to <1% for equivalent visibility.
LSM (Linux Security Modules) BPF programs can enforce security policies by denying operations. Unlike observability programs that only watch, LSM programs can block malicious activity.
LSM Hook Categories
| Category | Hooks | Example Use |
|---|---|---|
| File | file_open, file_permission | Block access to sensitive files |
| Process | bprm_check_security, task_fix_setuid | Prevent execution, privilege changes |
| Network | socket_connect, socket_sendmsg | Block C2 connections |
| IPC | msg_queue_, shm_ | Control inter-process communication |
1234567891011121314151617181920212223242526272829303132333435
// Block execution of specific binariesSEC("lsm/bprm_check_security")int BPF_PROG(block_exec, struct linux_binprm *bprm) { char filename[256]; bpf_probe_read_kernel_str(&filename, sizeof(filename), bprm->filename); // Block execution of /bin/sh in containers (example) if (__builtin_memcmp(filename, "/bin/sh", 7) == 0) { u64 cgid = bpf_get_current_cgroup_id(); // Check if in container cgroup if (is_container_cgroup(cgid)) { return -EPERM; // Deny execution } } return 0; // Allow} // Block network connections to specific IPsSEC("lsm/socket_connect")int BPF_PROG(block_connect, struct socket *sock, struct sockaddr *addr, int addrlen) { if (addr->sa_family != AF_INET) return 0; struct sockaddr_in *sin = (struct sockaddr_in *)addr; __be32 dst_ip = sin->sin_addr.s_addr; // Check blocklist if (bpf_map_lookup_elem(&blocked_ips, &dst_ip)) return -EPERM; return 0;}LSM BPF requires kernel 5.7+ with CONFIG_BPF_LSM=y. The kernel must boot with 'lsm=...,bpf' to enable the BPF LSM. Not all distributions enable this by default.
eBPF enables detecting threats as they happen, not after the fact.
Common Detection Patterns
| Threat | Detection Method | eBPF Hook |
|---|---|---|
| Container escape | Mount namespace changes, ptrace to host | tracepoint/syscalls/* |
| Privilege escalation | setuid(0), capability changes | lsm/task_fix_setuid |
| Reverse shell | Socket + dup2 + exec pattern | kprobe/connect + tracepoint/execve |
| Cryptominer | CPU-heavy process, known binary hashes | perf_event + tracepoint |
| Rootkit | hidden files, module loading | lsm/kernel_module_load |
| Data exfiltration | Large outbound transfers | kprobe/tcp_sendmsg |
12345678910111213141516171819202122232425262728293031323334353637383940414243
// Detect reverse shell pattern:// 1. Socket created// 2. Socket connected to remote// 3. dup2(socket_fd, 0/1/2) - redirect stdin/stdout/stderr// 4. execve("/bin/sh") struct { __uint(type, BPF_MAP_TYPE_HASH); __uint(max_entries, 10240); __type(key, u32); // PID __type(value, struct shell_state);} shell_tracker SEC(".maps"); struct shell_state { bool socket_created; bool socket_connected; bool fd_redirected; __be32 remote_ip;}; SEC("tracepoint/syscalls/sys_exit_socket")int track_socket(struct trace_event_raw_sys_exit *ctx) { if (ctx->ret < 0) return 0; u32 pid = bpf_get_current_pid_tgid() >> 32; struct shell_state state = { .socket_created = true }; bpf_map_update_elem(&shell_tracker, &pid, &state, BPF_ANY); return 0;} SEC("tracepoint/syscalls/sys_enter_execve")int detect_shell_exec(void *ctx) { u32 pid = bpf_get_current_pid_tgid() >> 32; struct shell_state *state = bpf_map_lookup_elem(&shell_tracker, &pid); if (state && state->socket_connected && state->fd_redirected) { // ALERT: Reverse shell pattern detected! bpf_printk("REVERSE SHELL: pid=%d ip=%x", pid, state->remote_ip); } return 0;}Several production-grade security tools leverage eBPF:
| Tool | Organization | Capabilities |
|---|---|---|
| Falco | Sysdig/CNCF | Runtime threat detection, rule engine, Kubernetes integration |
| Tetragon | Cilium/Isovalent | Security observability, enforcement, process tracking |
| Tracee | Aqua Security | Runtime security, forensics, signature detection |
| KubeArmor | AccuKnox | Kubernetes-native security policies |
| Pixie | New Relic/CNCF | Security observability as part of full-stack visibility |
Falco Example Rule
- rule: Container Shell Spawn
desc: Detect shell spawned in container
condition: >
spawned_process and container and
proc.name in (bash, sh, zsh)
output: >
Shell spawned in container
(user=%user.name container=%container.name
parent=%proc.pname cmdline=%proc.cmdline)
priority: WARNING
Falco translates rules into eBPF programs that monitor syscalls and generate alerts when conditions match.
Combine eBPF security with traditional controls: network policies, RBAC, seccomp, and AppArmor/SELinux. eBPF provides visibility and detection; layered security provides prevention.
Seccomp-BPF uses classic BPF (not eBPF) to filter system calls. While limited compared to eBPF, it provides a hardened syscall filter:
| Feature | Seccomp-BPF | eBPF LSM |
|---|---|---|
| Syscall filtering | ✅ Primary use | ✅ Via LSM hooks |
| Argument inspection | Limited (6 args, no pointers) | Full (can read memory) |
| Performance | Minimal overhead | Low overhead |
| Kill/Block actions | ✅ | ✅ |
| Container integration | Docker, K8s built-in | Requires additional tooling |
123456789101112131415
{ "defaultAction": "SCMP_ACT_ERRNO", "architectures": ["SCMP_ARCH_X86_64"], "syscalls": [ { "names": ["read", "write", "exit", "exit_group"], "action": "SCMP_ACT_ALLOW" }, { "names": ["execve"], "action": "SCMP_ACT_ERRNO", "comment": "Block new process execution" } ]}Use both: seccomp-BPF for hard syscall blocking (minimal overhead), eBPF for rich detection and observability. Kubernetes supports seccomp natively; add eBPF tools like Falco for visibility.
eBPF is particularly valuable for container security because it operates at the kernel level, below container boundaries.
Container-Aware Monitoring
12345678910111213141516171819202122
// Filter security events by containerSEC("tracepoint/syscalls/sys_enter_execve")int trace_container_exec(void *ctx) { u64 cgid = bpf_get_current_cgroup_id(); // Check if container cgroup if (cgid == host_cgid) return 0; // Skip host processes // Get container ID from cgroup path // In production, use cgroup ID mapping struct event *e = bpf_ringbuf_reserve(&events, sizeof(*e), 0); if (!e) return 0; e->cgroup_id = cgid; e->pid = bpf_get_current_pid_tgid() >> 32; bpf_get_current_comm(&e->comm, sizeof(e->comm)); bpf_ringbuf_submit(e, 0); return 0;}You've completed the eBPF module! You now understand eBPF's architecture, program development, tracing, networking, and security applications. eBPF represents the future of Linux kernel programmability—use this knowledge to build observable, performant, and secure systems.