Skip to content

eBPF

eBPF lets you load a small sandboxed program into the running Linux kernel and attach it to a hook (a syscall, a kernel function, a network device, a security check). The kernel proves the program is memory-safe and will terminate before it runs it. No kernel module, no reboot.

  • 1992: BPF. Steven McCanne and Van Jacobson wrote “The BSD Packet Filter” (published at USENIX in January 1993). Copying every packet to user space for tcpdump was slow, so they put a tiny register-based virtual machine in the kernel. User space hands it a filter program, and the kernel copies out only the packets that match. This is still what runs when you type tcpdump port 443, and what seccomp filters use.
  • The gap. Anything else that needed logic inside the kernel had two options: get a patch merged upstream and wait years for distributions to ship it, or write a kernel module. A module is unrestricted native code: one bad pointer panics the machine, and it must be rebuilt for every kernel version.
  • 2014: eBPF. Alexei Starovoitov extended the virtual machine: 64-bit registers, more of them, function calls, maps for state, and a verifier. Linux 3.18 (December 2014) added the bpf() system call. Programs could then attach to kprobes, tracepoints and sockets, and to the network driver receive path with XDP (eXpress Data Path, Linux 4.8, 2016).
  • Now. eBPF is a general in-kernel runtime. Networking (Cilium, Katran), observability (bpftrace, Pixie, Parca), security (Falco, Tetragon), and since Linux 6.12 even CPU schedulers (sched_ext) are written as eBPF programs. The original BPF is now called classic BPF (cBPF).
  1. Write. The kernel-side program is restricted C or Rust: no unbounded loops, no arbitrary function calls, a 512-byte stack. A user-space agent (Go, Rust, C, Python) loads it and reads its results.
  2. Compile. clang -target bpf produces an ELF object with BPF bytecode, map definitions and BTF (BPF Type Format, compact type information about the program).
  3. Relocate (CO-RE). Kernel structs change between versions. With CO-RE (Compile Once, Run Everywhere) the object records which struct fields it reads. At load time the loader library (libbpf, cilium/ebpf, Aya) compares that with the running kernel’s BTF in /sys/kernel/btf/vmlinux and patches the field offsets. One binary runs on many kernels, with no compiler or kernel headers on the host. The older approach, used by bcc, compiled C on every host at start-up.
  4. Load. The loader calls bpf(BPF_MAP_CREATE) for each map and bpf(BPF_PROG_LOAD) with the bytecode and a program type. The kernel checks capabilities first (see the control section below).
  5. Verify. The verifier walks every possible execution path and tracks the type and value range of every register and stack slot. It rejects the program unless it can prove that:
    • every memory access is inside a known object (stack, map value, packet bounds, a BTF-typed kernel struct);
    • every pointer that may be NULL is checked before use;
    • the program terminates: loops must have a provable bound (allowed since Linux 5.3), and the whole analysis must finish within 1 million processed instructions;
    • it calls only the helpers and kfuncs allowed for its program type;
    • it leaks no kernel pointers to places an unprivileged user could read.
  6. JIT. The in-kernel JIT compiler translates the bytecode to native machine instructions, so the program runs at close to the speed of compiled kernel code.
  7. Attach. The loader attaches the program to a hook. Modern attachments return a link, a kernel object held by a file descriptor. When the last reference closes, the program detaches. To keep a program after the loader exits, pin the link or program as a file under /sys/fs/bpf (the BPF file system, bpffs).
  8. Run. Each time the hook fires, the program runs to completion in kernel context with a context argument (a packet, the probed function’s arguments, a syscall record). Its return value means something specific to the hook: drop or pass a packet, allow or deny an operation.
  9. Keep state in maps. Maps are kernel key-value stores shared between programs and user space: hash, array, per-CPU variants, LRU hash, LPM trie (longest prefix match, for CIDR lookups), program arrays for tail calls. User space reads and writes them through the bpf() syscall.
  10. Send events to user space. The ring buffer map (BPF_MAP_TYPE_RINGBUF, Linux 5.8) is one buffer shared across CPUs that keeps event order. The older perf buffer is one buffer per CPU. The agent polls with epoll.
  11. Call into the kernel. A program cannot call arbitrary kernel functions. It may call helpers (a fixed, stable list such as bpf_map_lookup_elem, bpf_ktime_get_ns, bpf_probe_read_kernel) and kfuncs (kernel functions explicitly exported to BPF, not a stable interface).
USER SPACE C or Rust source bytecode (ELF) agent: libbpf loader clang -target bpf CO-RE relocation via BTF A: CAP_BPF, sysctl, seccomp, LSM, token KERNEL bpf() verifier JIT to native program at hook B: signature C: verifier, 1M insn budget D: hook type, helper gates maps, ring buffer state, events fd or /sys/fs/bpf pin E: memcg accounting, bpftool, audit
Red labels are the control points. A: who may call bpf() at all. B: optional signature check at load (Linux 6.18). C: the verifier proves safety, not intent. D: capabilities per program type, lockdown and kernel config gate the dangerous helpers. E: what is loaded is visible and its memory is charged to a cgroup.
Hook Where it runs What it can see and do Typical tool
kprobe, kretprobe entry or return of almost any kernel function read arguments and kernel memory; no stable interface, function names change between kernels bpftrace, bcc, Tetragon
fentry, fexit same places, through a BPF trampoline (Linux 5.5), needs BTF typed arguments, lower overhead than kprobes; fmod_ret can replace the return value of functions that allow it newer libbpf tools, Cilium
tracepoint, raw tracepoint fixed points the kernel developers placed (sched:sched_switch, syscalls:sys_enter_openat) stable argument layout; read only Falco, bpftrace
uprobe, uretprobe, USDT a function or marker in a user-space binary or library read arguments and user memory, for example plaintext before SSL_write encrypts it Pixie, bpftrace
perf event a timer or hardware counter, for example 99 times per second per CPU capture kernel and user stack traces Parca, Pyroscope
XDP network driver receive path, before the kernel allocates a socket buffer drop, pass, rewrite, bounce back or redirect a packet (XDP_DROP, XDP_PASS, XDP_TX, XDP_REDIRECT) Katran, Cilium, DDoS filters
tc (traffic control) ingress and egress of a network device, on socket buffers full packet access and rewrite, redirect between devices, both directions Cilium datapath
cgroup socket and network events for every process in a cgroup: connect, bind, sendmsg, socket options, device access allow, deny or rewrite, for example translate a Service IP to a backend at connect() time Cilium kube-proxy replacement, container runtimes for the device allowlist
socket filter, sockmap a socket, or a set of sockets filter packets on a socket; splice data between sockets without leaving the kernel tcpdump (classic BPF), service mesh acceleration
LSM Linux Security Module hooks such as file_open, bprm_check_security, socket_connect (Linux 5.7) return an error to deny the operation Tetragon, systemd filesystem restrictions
struct_ops a kernel operations table implemented in BPF supply TCP congestion control or a CPU scheduler (sched_ext, Linux 6.12) scx schedulers
Tool Uses eBPF for Hooks
Cilium Kubernetes pod networking (CNI), Service load balancing without kube-proxy, network policy, transparent encryption, flow visibility (Hubble) tc, XDP, cgroup socket hooks
Tetragon runtime security: process, file and network events filtered in the kernel, and enforcement by sending a signal or overriding a return value kprobes, tracepoints, LSM
Falco threat detection: streams syscall events to a user-space rule engine. The modern eBPF probe is the default driver; a kernel module is the alternative tracepoints
Pixie Kubernetes observability without code changes: captures HTTP, gRPC and SQL traffic and CPU profiles kprobes, uprobes, perf events
Parca, Pyroscope continuous CPU profiling of the whole node by sampling stack traces perf events
bpftrace one-line and scripted tracing in an awk-like language kprobes, uprobes, tracepoints, USDT, fentry
bcc toolkit and library of ready tracing tools (execsnoop, opensnoop, biolatency); the classic tools compile C on the host, the libbpf-tools ports use CO-RE kprobes, tracepoints, uprobes
Katran layer 4 load balancer from Meta: picks a backend and encapsulates the packet in the driver XDP
Inspektor Gadget runs tracing “gadgets” packaged as OCI images and tags their events with pod and container names kprobes, tracepoints, tc

For Cilium’s use as a service mesh data plane, see mTLS and service mesh.

An eBPF agent with tracing privileges sees every syscall argument, every packet and, through uprobes, plaintext before TLS encrypts it. Treat the right to load eBPF as equal to root on the node.

Capability (all since Linux 5.8, except CAP_SYS_ADMIN) Allows
CAP_BPF create maps, load programs, use most bpf() commands
CAP_BPF + CAP_PERFMON load tracing programs (kprobe, tracepoint, fentry, perf event), read kernel memory
CAP_BPF + CAP_NET_ADMIN load and attach networking programs (XDP, tc, cgroup socket hooks)
CAP_SYS_ADMIN everything above, plus the few operations still reserved for it. Before 5.8 this was the only option
none only if unprivileged_bpf_disabled is 0: socket filter and cgroup skb programs, at most 4096 instructions, extra verifier restrictions

The sysctl kernel.unprivileged_bpf_disabled:

Value Meaning
0 unprivileged bpf() calls are allowed
1 disabled; cannot be changed back until reboot
2 disabled; an administrator can still change it to 0 or 1

The default is 2 when the kernel is built with CONFIG_BPF_UNPRIV_DEFAULT_OFF, which itself defaults to yes. Set it to 1 on production nodes. Capability checks still apply at every value.

These capabilities are checked in the initial user namespace. Root inside a user-namespaced container does not get them.

2. What the verifier guarantees, and what it does not

Section titled “2. What the verifier guarantees, and what it does not”

Guarantees: the program cannot crash the kernel, cannot read or write memory outside objects it was given, cannot loop forever, and calls only what its type permits.

Does not guarantee: that the program is harmless. The verifier checks safety, not intent. A valid program may still:

  • read every syscall argument, file path, command line and environment, and read arbitrary kernel memory with bpf_probe_read_kernel (tracing types);
  • read user memory of any process, including keys and plaintext, with bpf_probe_read_user and uprobes;
  • send all of it anywhere through its user-space agent;
  • write user memory of the current task with bpf_probe_write_user. The kernel logs a warning with the PID and process name whenever a program that uses it is attached;
  • make a kernel function fail with bpf_override_return. This needs CONFIG_BPF_KPROBE_OVERRIDE and works only on functions tagged ALLOW_ERROR_INJECTION, which includes syscall entry points;
  • kill processes with bpf_send_signal;
  • drop, redirect or rewrite packets (XDP, tc), and deny operations (LSM).

The same helpers that make Tetragon able to enforce policy make an eBPF rootkit able to hide files, hide itself from ps, and fake syscall results.

The lockdown LSM (Linux 5.4) limits what even root can do to the running kernel. It has two modes, and each blocks more BPF.

Mode Blocks for BPF and tracing Effect
none nothing default without Secure Boot policy
integrity bpf_probe_write_user; also unsigned modules, /dev/mem, kexec of unsigned images, debugfs observability tools keep working; programs cannot modify user memory
confidentiality everything in integrity, plus BPF reads of kernel memory (bpf_probe_read_kernel), kprobes, perf and tracefs most eBPF tracing and profiling stops working

Check with cat /sys/kernel/security/lockdown; the active mode is in brackets. Several distributions switch to integrity mode when UEFI Secure Boot is on.

Linux 6.18 added signature verification to BPF_PROG_LOAD: the caller passes a signature and a keyring ID, and the kernel refuses the program if the signature does not verify. The design signs a small loader program. The loader carries the hash of the real program and maps and checks it before it loads them, because CO-RE relocation changes the final instructions on each host.

Two limits, as of mid 2026:

  • A signature is optional. An unsigned program loads as before unless a security policy refuses it, and that policy layer (through LSM hooks) was still being developed on the mailing lists in 2026.
  • libbpf’s loader generation becomes part of the trusted base.

Until policy enforcement lands in the kernels you run, enforce provenance one level up: sign the agent’s container image and verify it at admission.

CAP_BPF cannot be granted inside a user namespace, so a rootless or user-namespaced container could not use eBPF at all. BPF tokens (Linux 6.9) fix that by delegation. A privileged process such as the container runtime mounts a bpffs instance for the container with options that list what is allowed: delegate_cmds (which bpf() commands), delegate_maps (map types), delegate_progs (program types), delegate_attachs (attach types). The workload creates a token from that mount with BPF_TOKEN_CREATE and passes it with each bpf() call. The token works only inside the user namespace that owns the mount. LSMs can inspect token creation and use.

This is the way to give one workload “XDP programs and hash maps, nothing else” in place of node-wide CAP_BPF.

  • BPF LSM (Linux 5.7, CONFIG_BPF_LSM, and bpf in the kernel’s active LSM list) attaches eBPF programs to LSM hooks. There are hooks on the bpf() syscall itself, so a small trusted program can decide which processes may load programs or create maps.
  • SELinux has a bpf object class with separate permissions for creating maps and loading programs, so policy can limit bpf() to named domains.

A workload that has no reason to call bpf() should not be able to. Docker’s default seccomp profile denies the bpf syscall unless the container has CAP_SYS_ADMIN. In Kubernetes, seccompProfile.type: RuntimeDefault applies the runtime’s default profile. This matters because a capability check is a single line of defence, and verifier bugs have been exploitable by anyone who could reach the syscall.

  • Pod Security Standards. Enforce restricted (or at least baseline) on every application namespace. Baseline forbids privileged containers, host namespaces (hostPID, hostNetwork, hostIPC), hostPath volumes and capabilities outside a short list that has no BPF, PERFMON, NET_ADMIN or SYS_ADMIN. Restricted also requires dropping ALL capabilities and setting a seccomp profile.
  • Put eBPF agents in their own namespaces with the privileged level, and nothing else there.
  • Allowlist by admission policy. A ValidatingAdmissionPolicy, Kyverno or Gatekeeper rule that rejects any pod with privileged: true, hostPID, or added BPF, PERFMON, SYS_ADMIN or NET_ADMIN, unless namespace, service account and image match a named list (the CNI, the runtime security agent, the profiler).
  • Prefer narrow capabilities over privileged. Many agents run with BPF, PERFMON, NET_ADMIN and a few more, plus specific hostPath mounts (/sys/fs/bpf, /sys/kernel/btf). privileged: true also removes seccomp and device restrictions.
  • Sign and pin the agent images. Verify signatures at admission and reference images by digest. The image is the real trust boundary for what gets loaded.
  • Limit who can create DaemonSets and who can exec into agent pods. Either is node root on every node.
Terminal window
bpftool prog show # every loaded program: id, type, name, load time, uid, map ids
bpftool prog show --json | jq '.[] | {id, type, name, pids}'
bpftool map show # every map: id, type, name, key/value size, max entries, memlock
bpftool link show # attachments that are held by links
bpftool perf show # kprobes, uprobes and tracepoints, with the owning PID
bpftool net show # XDP and tc programs per interface
bpftool cgroup tree # programs attached to cgroups
bpftool prog dump xlated id 42 # the verified instructions of one program
find /sys/fs/bpf -maxdepth 3 # pinned objects that outlive their loader

Output shape:

42: kprobe name trace_execve tag 3b185187f1855c4c gpl
loaded_at 2026-11-12T09:14:03+0000 uid 0
xlated 528B jited 301B memlock 4096B map_ids 7,8
pids agent(1830)
  • Audit log. The kernel writes a record of type BPF (1334) with prog-id=N op=LOAD or op=UNLOAD for every program load and unload when audit is on. Add a syscall rule to see who called it: auditctl -a always,exit -F arch=b64 -S bpf -k bpf.
  • Kernel log. dmesg | grep bpf_probe_write_user shows any program that attached with the user-memory write helper.
  • Runtime statistics. sysctl kernel.bpf_stats_enabled=1 makes the kernel count run time and run count per program (default 0, small overhead). bpftool prog show then prints run_time_ns and run_cnt.
  • Baseline and diff. Record the expected set of program names, types and owning processes per node role. Alert on a program whose loader is not a known agent, on tracing or LSM programs that appear outside a rollout, and on new pins in /sys/fs/bpf.
  • Limit of this. An attacker with the same privileges can hook the syscalls bpftool uses and hide entries. Ship audit records off the node, and rely on prevention (the controls above) more than on detection.
Limit Value Notes
Verifier budget 1 million processed instructions per program counts instructions the verifier analyses across all paths, not program size; large programs are split with tail calls
Program size without CAP_BPF 4096 instructions
Stack 512 bytes per program larger data goes in maps
Tail calls 33 in a chain
Map memory charged to the memory cgroup of the creating process since Linux 5.11 before 5.11 it was charged to RLIMIT_MEMLOCK. A map sized for millions of entries can push an agent pod into its memory limit

Overhead comes from how often the hook fires, not from the program alone. A kprobe on a function called millions of times per second (scheduler, memory allocation, per-packet paths) costs real CPU. Prefer tracepoints and fentry over kprobes, filter in the kernel before sending events to user space, and prefer per-CPU maps on hot paths. Uprobes are the most expensive, because each hit traps from user space into the kernel.

  • Verifier bugs. The verifier is large and has been wrong. Each of these let a program that passed verification read and write kernel memory:

    • CVE-2020-8835: wrong 32-bit register bounds, Linux 5.5 and stable 5.4, fixed in 5.6.1.
    • CVE-2021-3490: wrong 32-bit bounds for bitwise operations, fixed in 5.13-rc4.
    • CVE-2023-2163: incorrect path pruning, kernels from 5.4, leading to container escape.

    The first two were reachable by unprivileged users where unprivileged BPF was enabled. This is the main reason it is now off by default.

  • Speculative execution. Unprivileged BPF let an attacker place chosen code in the kernel, which made Spectre-class attacks practical. The verifier adds mitigations for unprivileged programs, and the kernel configuration help text gives this as the reason for the default-off setting.

  • eBPF rootkits. With load privileges an attacker needs no kernel module: programs can hide processes and files by rewriting syscall results in user memory, intercept credentials, open a network backdoor at XDP level before any firewall sees the packet, and survive through pinned objects. Lockdown integrity mode removes the user-memory write. The rest depends on not handing out the capabilities.

  • Supply chain. The agent you installed on purpose has all of this power. Its image, its Helm chart and its update channel deserve the same review as a kernel module.

Tools that touch the kernel or hardware directly.

Mechanism What can go wrong What limits it How to audit it
eBPF programs data exposure across all tenants on the node; tampering through write, override and signal helpers; verifier bugs leading to kernel code execution; CPU overhead on hot hooks capabilities, unprivileged_bpf_disabled, verifier, lockdown, seccomp, LSM, BPF tokens, signatures (6.18) bpftool prog/map/link/perf show, audit BPF records, /sys/fs/bpf
Kernel modules unrestricted native kernel code: any bug is a panic or memory corruption, any malice is a full rootkit; breaks on kernel upgrades CAP_SYS_MODULE, module signature enforcement (module.sig_enforce, lockdown), kernel.modules_disabled=1 after boot lsmod, /proc/modules, modinfo, taint flags in /proc/sys/kernel/tainted, audit on init_module and finit_module
Privileged containers, hostPath and host device access privileged: true grants all capabilities, all devices and no seccomp: mount the host disk, load modules, enter host namespaces. A writable hostPath to /, the runtime socket or /dev is the same thing Pod Security Standards, admission policy, user namespaces (hostUsers: false), read-only and narrow hostPath, device cgroup allowlist kubectl get pods -A -o json filtered on privileged, hostPID, hostPath; crictl inspect; capsh --decode on CapEff from /proc/<pid>/status
Kernel bypass and async I/O (DPDK, AF_XDP, SPDK, io_uring) DPDK and SPDK take the device away from the kernel: no netfilter, no tc, no kernel counters, and the process needs hugepages and direct device access. io_uring stays in the kernel but exposes a large, fast-moving interface that has had many privilege escalation bugs dedicated nodes and NICs, SR-IOV virtual functions with the IOMMU on, vfio in place of raw memory access, seccomp to block io_uring_setup where not needed dpdk-devbind.py --status, lspci -k (which driver owns the device), /proc/<pid>/maps for hugepage mappings, seccomp profile review
Device plugins (GPU, FPGA, SR-IOV) a vendor kernel module plus a privileged DaemonSet on every node; device memory may keep data between tenants; a large driver ioctl surface is exposed to every pod that gets the device signed vendor drivers, device cgroup limited to the allocated device, no sharing of one device across trust boundaries, dedicated node pools, sandboxed runtimes for untrusted tenants kubectl describe node (allocatable extended resources), plugin pod spec, lsmod, nvidia-smi or the vendor tool, /var/lib/kubelet/device-plugins/

Ways to get custom logic into the kernel path.

Classic BPF eBPF Kernel module Upstream kernel patch User-space agent (ptrace, audit, /proc)
Safety check before running simple validity check verifier none code review n/a, runs outside the kernel
Can crash the kernel no not by design yes yes no
Portability across kernel versions high high with CO-RE for stable hooks rebuild per kernel n/a high
Time to deploy seconds seconds minutes, plus build pipeline months to years seconds
Overhead per event very low low lowest lowest high: context switches per event
Still used for tcpdump filters, seccomp most new work hardware drivers, file systems core features debuggers, compliance logging
Symptom Cause Check
bpf: Operation not permitted (EPERM) on load missing CAP_BPF, CAP_PERFMON or CAP_NET_ADMIN; seccomp denies bpf; lockdown; running in a user namespace without a token capsh --print, grep Seccomp /proc/self/status, cat /sys/kernel/security/lockdown, sysctl kernel.unprivileged_bpf_disabled
Load fails with a long verifier log ending in invalid mem access, R1 type=... expected=... or unbounded memory access the program did not prove a bounds or NULL check read the log from the last instruction upward; add the explicit check the verifier asks for
BPF program is too large. Processed 1000001 insn verifier budget exhausted split with tail calls or subprograms, reduce loop bounds
Loads on one kernel, fails on another with failed to resolve CO-RE relocation or unknown func the field, helper or kfunc does not exist on that kernel, or the kernel has no BTF ls /sys/kernel/btf/vmlinux, bpftool feature probe, uname -r
kprobe attach fails: no such file or symbol not found the function was renamed or inlined in this kernel grep -w FUNC /proc/kallsyms, bpftrace -l 'kprobe:FUNC*'
Tracing tools show nothing or fail under Secure Boot lockdown confidentiality mode cat /sys/kernel/security/lockdown, dmesg | grep -i lockdown
Agent pod OOMKilled soon after start maps are charged to its memory cgroup bpftool map show (memlock column), raise the limit or shrink max_entries
Node CPU rises after installing an agent a probe on a hot path, or too many events sent to user space sysctl kernel.bpf_stats_enabled=1, then bpftool prog show and sort by run_time_ns
Programs still loaded after the agent was removed pinned in bpffs or held by a link find /sys/fs/bpf, bpftool link show, remove the pin
Lost events ring buffer full because user space reads too slowly the agent’s own drop counter; enlarge the buffer or filter in the kernel

eBPF, BPF, classic BPF (cBPF), bpf() syscall, BPF_PROG_LOAD, bytecode, clang, LLVM, libbpf, cilium/ebpf, Aya, verifier, bounded loops, BPF_COMPLEXITY_LIMIT_INSNS, JIT, hook, kprobe, kretprobe, uprobe, USDT, tracepoint, raw tracepoint, fentry, fexit, BPF trampoline, perf event, XDP, XDP_DROP, XDP_REDIRECT, tc, cgroup BPF, sockmap, BPF LSM, struct_ops, sched_ext, BPF maps, ring buffer, perf buffer, LPM trie, tail call, helper, kfunc, BTF, CO-RE, vmlinux.h, bpffs, pinning, BPF link, CAP_BPF, CAP_PERFMON, CAP_NET_ADMIN, CAP_SYS_ADMIN, unprivileged_bpf_disabled, BPF_UNPRIV_DEFAULT_OFF, bpf_probe_read_kernel, bpf_probe_write_user, bpf_override_return, bpf_send_signal, kernel lockdown, integrity, confidentiality, signed BPF programs, BPF token, delegate_cmds, seccomp, Pod Security Standards, admission policy, bpftool, bpf_stats_enabled, AUDIT_BPF, memcg accounting, CVE-2020-8835, CVE-2021-3490, CVE-2023-2163, eBPF rootkit, Spectre, Cilium, Hubble, Tetragon, Falco, Pixie, Parca, Pyroscope, bpftrace, bcc, Katran, Inspektor Gadget, kernel module, DPDK, AF_XDP, io_uring, device plugin.