eBPF: The Linux Kernel Feature That's Changing Security (and Attack Surfaces)
Running sandboxed code in the kernel, for defenders and attackers alike

Contents
For most of Linux’s life there was a hard wall between code you wrote and the kernel that ran underneath it. If you wanted the kernel to do something new, you wrote a kernel module — which is to say, you wrote code with the power to crash, corrupt, or backdoor the entire machine, loaded it with your fingers crossed, and hoped. eBPF tore a window in that wall. And like any window, it lets the light in and gives someone a way to look out.
eBPF is one of those technologies that quietly became load-bearing for half the modern infrastructure stack while nobody was watching. Understanding what it is has stopped being optional, because it’s reshaping both how we defend Linux boxes and, less comfortably, how they get attacked. The name is a historical accident — “extended Berkeley Packet Filter” — and tells you almost nothing about what it does today. The filter that classified packets in 1992 has grown into a general-purpose, in-kernel virtual machine. The “packet filter” part is now the least interesting thing about it.
What eBPF actually is
eBPF lets you load small programs into the running kernel and attach them to events — a system call firing, a network packet arriving, a function being entered — without writing a kernel module or rebooting. The trick that makes this safe-ish is the verifier: before your program runs, the kernel statically analyses it to prove it terminates, doesn’t read uninitialised memory, and can’t wander off into arbitrary kernel addresses. Programs that don’t pass simply don’t load.
So you get something genuinely new: sandboxed, verified, near-native-speed code running inside the kernel, with visibility into everything the kernel sees. The reason it runs fast rather than crawling like an interpreted hook is the JIT — eBPF bytecode is compiled to native machine code the first time it loads, so a tracepoint that fires a million times a second adds nanoseconds, not microseconds, to each event. That combination — verified-safe, JIT-fast, kernel-resident — is what makes it an extraordinary capability, and the security world has fallen on it from two directions at once.
A few terms you’ll keep meeting: probes (kprobes and uprobes attach to kernel or userspace function entry/exit), tracepoints (stable, named hook points the kernel maintainers promise not to break), maps (the shared key/value memory an eBPF program uses to talk to userspace), and XDP (eXpress Data Path, the hook that runs before the kernel networking stack even allocates a packet structure, which is how eBPF firewalls drop millions of packets per second). You don’t need to memorise these to follow the security argument, but you’ll see them in every tool’s documentation.
The defender’s dream: total visibility
For defence, eBPF is close to a superpower. Because it can hook the syscall layer, you can observe every process that spawns, every file opened, every outbound connection — system-wide, with negligible overhead, and without modifying the applications you’re watching. This is the foundation under tools like Cilium (kernel-level network policy and observability) and Falco and Tetragon (runtime threat detection).
The classic demonstration is watching every new process on the box, live. The bpftrace tool makes this a one-liner:
| |
Run that and you’ll see cron jobs, your shell, and — if something nasty has got in — the cryptominer it’s trying to launch, the moment it execs. For runtime security this is gold: you can write policy that says “no process in this container should ever exec a shell” and enforce it in the kernel, where malware can’t easily lie to you about what it’s doing.
A more security-flavoured example: alerting on any connection to a port outside an expected set, or counting which binaries open /etc/shadow. The data was always there in principle — strace, auditd, and packet captures could all see pieces of it. eBPF makes collecting it cheap enough to do continuously, system-wide, in production, without the overhead that made the older tools something you reached for only when investigating a specific problem.
This is why it pairs so well with the kernel’s older accounting machinery rather than replacing it. The Linux audit framework that tracks who did what on your servers gives you a durable, policy-driven log of security-relevant events; eBPF-based tools give you high-frequency, low-overhead behavioural telemetry. Run both and you get the compliance-grade record and the real-time detection, with the eBPF layer catching the fast, transient things auditd would either miss or drown you in. The two are complementary, not competing.
Here’s a slightly more pointed bpftrace one-liner — count, per process, how many times anything opens the password shadow file, which on a quiet box should be approximately never:
| |
Leave that running and the moment something unexpected touches /etc/shadow, you have a process name and a count. That’s a detection you can build in thirty seconds and would have taken a real instrumentation effort a decade ago.
The attacker’s new toy: the same superpower, inverted
Here’s the uncomfortable half. Everything that makes eBPF a brilliant observability tool makes it a brilliant tool for the wrong people. A program that can see every packet can also hide packets. A program that can hook getdents (the syscall behind directory listings) can filter its own files out of ls. eBPF has become a fashionable substrate for rootkits precisely because it sits below the userspace tools an investigator would normally trust.
A defensive eBPF agent watches syscalls; a malicious eBPF program can intercept those same syscalls to feed the agent — and the human at the keyboard — a sanitised version of reality. This is not theoretical. Synacktiv’s late-2025 analysis of the LinkPro rootkit documents exactly this pattern: it installs a “Hide” module using a tracepoint and a kretprobe to intercept getdents (so its files vanish from directory listings) and sys_bpf (so the rootkit hides itself from bpftool prog list), plus a “Knock” module built on XDP and TC programs that waits for a magic TCP packet before opening its command-and-control channel. Earlier families chained eBPF with io_uring to dodge syscall-based monitoring entirely. The very layer you deployed for visibility can be turned into a layer of concealment — and the more sophisticated specimens hide from the very tool you’d use to enumerate them.
The honest comparison is to the long lineage of kernel-level exploits and tricks that keep teaching the same lesson: a primitive built for legitimate power is a primitive available to whoever reaches the privilege level it requires. The pattern that runs through the Dirty Pipe / Dirty Frag family of kernel exploits — that the kernel’s own mechanisms become weapons once an attacker is inside — applies cleanly to eBPF. The difference is that eBPF’s abuse doesn’t even need a bug; it just needs the capability.
The mitigation is partly governance: loading eBPF programs requires privilege. Historically that meant CAP_SYS_ADMIN, the catch-all “basically root” capability; since Linux 5.8 the kernel split out a dedicated CAP_BPF, usually paired with CAP_NET_ADMIN for network programs or CAP_PERFMON for tracing ones, so you can grant exactly the BPF power a workload needs and nothing more. An attacker therefore needs to already be root-ish to abuse it. But “they were already root” describes most of the post-exploitation phase, which is exactly when hiding tools earn their keep — and a rootkit that escalated five minutes ago has the capability it needs to load its own programs regardless of how tidily you’ve partitioned CAP_BPF for your legitimate services.
What this means for hardening
The practical takeaways for anyone running Linux boxes:
- Treat eBPF capability as the sensitive thing it is. Don’t hand
CAP_BPForCAP_SYS_ADMINto containers that have no business loading kernel programs. - Inventory what’s loaded.
bpftool prog listshows every eBPF program currently attached. An unexpected entry on a box that shouldn’t be running any is a finding.
| |
- Lock the kernel down. Set
kernel.unprivileged_bpf_disabled=1so unprivileged users can’t load BPF at all, and enable kernel lockdown mode (available since Linux 5.4), which restricts sensitive kernel operations even for root. Neither slams the door entirely — they don’t help against an attacker who already holdsCAP_BPFthrough a prior escalation — but they raise the bar and shrink the unprivileged attack surface.
| |
What goes wrong, and how you’d actually catch it
The uncomfortable corollary of all this is that bpftool prog list is not trustworthy on a box you think might be compromised, because a competent eBPF rootkit hides its own entries from exactly that enumeration path. So the practical detection workflow is a layered one, and it’s worth rehearsing before you need it:
- Baseline when the box is known-clean. Record
bpftool prog showandbpftool map showoutput on a freshly built host and diff against it later. A program that appears between baselines is a finding; a program that should be there and has vanished from the list (while the behaviour it implements persists) is a worse one. - Cross-check from outside the suspect kernel’s view. Because hiding works by lying to the running userspace, the most reliable checks come from somewhere the rootkit doesn’t control: inspect raw kernel memory, or — cleanest of all — capture a memory image and analyse it offline. Forensics tooling that walks kernel structures directly will see programs that
bpftoolhas been told to hide. - Watch for the tell-tale hooks. A rootkit hiding files almost always hooks
getdents/getdents64; one hiding itself hookssys_bpf. Abpftracescript that simply enumerates which tracepoints and kprobes are attached to those syscalls, run early in boot before a userspace rootkit loads, is a cheap tripwire. - Don’t run your detection as a userspace agent the kernel can blind. This is the architectural catch-22 of eBPF security: an eBPF-based detection agent can itself be fed sanitised data by an eBPF rootkit sitting below it. Pair behavioural detection with out-of-band integrity checks, and don’t treat any single layer as ground truth.
If that sounds like the same defence-in-depth mindset that runs through hardening anything internet-facing, it is — the principles in hardening a Linux server from zero to SSH hero (least privilege, assume-breach, verify don’t trust) are exactly what keeps the eBPF attack surface manageable.
The verdict
eBPF is not optional knowledge any more. If you run containers, modern networking, or any serious observability stack, it’s already underneath you, whether you invited it or not. For defenders it’s the best visibility into Linux behaviour we’ve ever had cheaply. For attackers who reach root, it’s a tidy way to become invisible.
Who is this for? Anyone responsible for a Linux host that matters. Learn bpftrace for the genuine debugging and detection wins — they’re real and they’re addictive. Then go and check what bpftool prog list shows on your boxes, because the answer should be “exactly the programs I expect,” and the day it isn’t, you’ll want to have looked before.




