Disclaimer: This article is provided strictly for educational purposes and authorized security testing. Only run these techniques against systems you own or have explicit written permission to assess. Unauthorized access to computer systems is illegal in virtually every jurisdiction and can carry severe penalties.
Introduction
eBPF (extended Berkeley Packet Filter) lets a verified, sandboxed program run *inside the Linux kernel*, triggered by syscalls, network events, kernel and userspace function entry/exit (kprobes/uprobes), or scheduler events — without writing a kernel module. This is exactly what made it the foundation of modern Linux security tooling: Falco, Cilium’s network policy and Tetragon, Sysdig, and most commercial Linux EDR agents use eBPF to observe process execution, file access, and network activity with near-zero overhead and no kernel patching.
The same properties that make eBPF an excellent observability platform make it a meaningful risk when the ability to load programs is granted too broadly. A process that can load an eBPF program can, depending on the program type and kernel version, read kernel and process memory, intercept and rewrite network traffic before the rest of the network stack sees it, or attach to tracepoints that expose credentials in flight — and because the very telemetry that would catch that abuse can itself run as eBPF, a sufficiently privileged malicious eBPF program can also blind or evade EDR agents built on the same technology.
Attack Prerequisites
Loading and abusing an eBPF program for offense generally requires:
CAP_BPF(kernel 5.8+) orCAP_SYS_ADMINon older kernels, needed for thebpf()syscall to load programs and create maps.kernel.unprivileged_bpf_disabledset to0, the sysctl that, if not set to1or2, allows *any* unprivileged user to load certain BPF program types with no capability check at all.- A kernel new enough to support the desired program/attach type (kprobes, tracepoints, XDP, cgroup-attached programs) but, ideally for the attacker, old enough to lack newer restrictions like BPF LSM signing/verification hardening.
- For persistence/evasion specifically: root, since installing an eBPF program that survives and hooks broadly is itself a privileged action in any reasonably current configuration.
How It Works
An eBPF program is bytecode loaded via the bpf() syscall, checked by the kernel’s verifier — a static analyzer that rejects programs with unbounded loops, unsafe memory access, or paths that could crash the kernel — then JIT-compiled and attached to a hook point. For security monitoring, the interesting hook points are syscall tracepoints (sys_enter_execve, sys_enter_connect), kprobes on internal kernel functions, and LSM hooks (via the newer BPF LSM), all of which let a monitoring program observe — and, for LSM hooks, even allow/deny — activity as it happens, with the results pushed to userspace through a BPF map (a shared, typed key-value structure) or a ring buffer.
The abuse surface mirrors the capability surface exactly. A program attached to sys_enter_execve sees every command executed on the box including arguments; one attached to a TCP-connect tracepoint sees every outbound connection; an XDP program attached at the network driver level can inspect, drop, or rewrite packets before the kernel’s normal network stack or any iptables/nftables rule ever evaluates them, making it a viable position for a stealthy sniffer or covert channel that does not appear in normal socket enumeration. Because eBPF programs execute *in kernel context*, they are also invisible to tools that only inspect userspace processes — there is no PID for an eBPF program the way there is for a rootkit’s userspace helper.
The unprivileged_bpf_disabled sysctl is the crux of the misconfiguration story: some distributions historically shipped with it unset (effectively 0, allowing unprivileged loading of socket-filter programs) for backward compatibility, and even where it is properly set to restrict *unprivileged* loading, any process with CAP_BPF/CAP_SYS_ADMIN — including a compromised monitoring agent that legitimately needs that capability — has essentially the same power a kernel module would have, without the module-signing checks some environments enforce.
Vulnerable Code / Configuration
The enabling misconfiguration is either the sysctl left permissive, or a systemd service granted CAP_BPF/CAP_SYS_ADMIN far beyond what its actual monitoring function requires:
$ sysctl kernel.unprivileged_bpf_disabled
kernel.unprivileged_bpf_disabled = 0 # VULNERABLE: any user can bpf()
Bash# /etc/systemd/system/telemetry-agent.service — overbroad grant
[Service]
User=telemetry
AmbientCapabilities=CAP_BPF CAP_PERFMON CAP_SYS_ADMIN
ExecStart=/opt/telemetry/agent
# CAP_SYS_ADMIN here is legacy over-granting: on 5.8+ kernels the
# narrower CAP_BPF + CAP_PERFMON + CAP_NET_ADMIN combination is
# normally sufficient and leaves far less blast radius if the
# agent itself is compromised.
INIA permissive sysctl combined with any local code-execution bug turns into full traffic interception or credential-tracing capability without the attacker ever needing root.
Walkthrough / Exploitation
Check whether unprivileged BPF loading is possible from a plain user shell first:
sysctl kernel.unprivileged_bpf_disabled
cat /proc/sys/kernel/unprivileged_bpf_disabled
BashWhere the value is 0 (or the attacker already holds CAP_BPF), a tracing tool such as bpftrace can be used directly to observe sensitive activity — for example watching every execve() on the box, including arguments that often contain credentials passed on the command line:
bpftrace -e 'tracepoint:syscalls:sys_enter_execve { printf("%s %s\n", comm, str(args->filename)); }'
BashAn attacker with CAP_BPF/CAP_SYS_ADMIN (e.g. having compromised the over-privileged telemetry agent above) can go further and attach a kprobe or socket filter that captures plaintext data before TLS termination visibility is lost, or enumerate what monitoring is already loaded to plan evasion:
bpftool prog list # enumerate all loaded eBPF programs
bpftool map list # enumerate BPF maps (often reveals config,
# allow/deny lists used by EDR)
BashBecause EDR and observability agents are themselves commonly eBPF-based, an attacker who understands the deployed agent’s hook points can, with sufficient privilege, attach a competing program at the same hook that runs first and short-circuits or filters what the legitimate program observes — a real and actively discussed detection-evasion technique in the Linux security community, not merely theoretical.
Note: BPF LSM (kernel 5.7+) lets an eBPF program participate in access-control decisions, not just observe — this is used defensively (runtime enforcement tools built on it) but the same hook surface means a malicious LSM-attached program, if it can be loaded, could in principle influence access decisions too. This requires
CAP_SYS_ADMIN/CAP_MAC_ADMIN-level privilege and is not reachable by an unprivileged loader.
Opsec: Loading, listing, and unloading eBPF programs is itself observable —
bpftool prog listrun by anything other than the expected monitoring agent, and thebpf()syscall itself, can be audited. An auditd rule on thebpfsyscall (-S bpf -k bpf_syscall) is a direct, high-value detection for this entire technique class.
Detection and Defense
Because eBPF is both the monitoring tool and a potential abuse vector, controlling who can load programs matters as much as what programs are loaded:
- Set
kernel.unprivileged_bpf_disabled=2(hard, no runtime re-enable) so unprivileged users can never load BPF programs. - Grant the narrowest capability set to monitoring agents —
CAP_BPF+CAP_PERFMON+CAP_NET_ADMINon 5.8+ kernels instead of the legacyCAP_SYS_ADMINcatch-all. - Audit the
bpfsyscall directly:auditctl -a always,exit -F arch=b64 -S bpf -k bpf_syscall, and alert on any loader other than the known monitoring agent. - Periodically inventory loaded programs with
bpftool prog list/bpftool map listand diff against an expected baseline for the host’s role. - Enable BPF LSM and module/program signing where supported, and keep kernels current — much of the hardening around restricting unprivileged BPF and adding CAP_BPF granularity is recent and version-dependent.
Real-World Impact
eBPF underpins most modern Linux runtime security products — Falco, Tetragon, and numerous commercial EDR agents — which makes both correct deployment and its abuse potential directly operationally relevant rather than academic. Security researchers have repeatedly demonstrated eBPF-based rootkit and EDR-evasion proof-of-concepts at conferences such as DEF CON and Black Hat, and the trend has driven kernel hardening work (the CAP_BPF split, BPF LSM, tighter unprivileged_bpf_disabled defaults on recent distributions) specifically to narrow this attack surface.
Conclusion
eBPF gives defenders kernel-level visibility without kernel modules, but the same hook points and the same bpf() syscall are available to anyone with the right capability — which means the sysctl and capability grants controlling *who* can load programs are the actual security boundary. Lock unprivileged_bpf_disabled to 2, scope monitoring-agent capabilities narrowly, and audit the bpf syscall directly so that eBPF stays a monitoring asset rather than becoming an attacker’s vantage point.
You Might Also Like
If you found this useful, these related deep-dives cover adjacent techniques and their defenses:



Comments