eBPF (Extended Berkeley Packet Filter)
At its core, eBPF is a restricted, register-based virtual machine embedded in the Linux kernel. Programs are written in a subset of C, compiled to eBPF bytecode via LLVM/Clang, and loaded via the bpf() syscall. Before execution, the bytecode is passed through the in-kernel verifier, a static analysis engine that walks every possible execution path to guarantee the program terminates, never accesses out-of-bounds memory, and holds no unbounded loops (bounded loops are permitted since kernel 5.3 via the verifier’s loop-detection). Programs that pass verification are then translated by a JIT compiler into native machine code, so execution cost approaches that of a compiled kernel module rather than an interpreted script.
Programs attach to specific hook points, and the hook type determines both the program type (BPF_PROG_TYPE_*) and the set of kernel helper functions it may call. Common attach points include kprobes/uprobes (dynamic tracing of kernel/user functions), tracepoints, XDP (eXpress Data Path, operating at the NIC driver level before sk_buff allocation), TC (traffic control ingress/egress), cgroup hooks, and LSM hooks for security enforcement. Data is exchanged between kernel-space programs and userspace consumers through eBPF maps — key/value structures such as HASH, ARRAY, LRU_HASH, PERCPU variants, and the modern BPF_MAP_TYPE_RINGBUF, which superseded the older perf buffer for lower-overhead, lock-free event streaming to userspace.
- CO-RE (Compile Once – Run Everywhere): Uses BTF (BPF Type Format) metadata to resolve struct offset relocations at load time, decoupling compiled programs from a specific kernel’s memory layout — critical for portability across heterogeneous fleet kernel versions.
- Tail calls: Allow one eBPF program to jump into another (via
bpf_tail_call) without growing the call stack, used to work around the historically limited instruction-count and complexity budget enforced by the verifier. - Verifier complexity limits: Each program is capped by a processed-instruction ceiling (historically 1M, tunable in newer kernels via
bpf_jit_limitand complexity heuristics), forcing careful program decomposition for anything beyond trivial logic. - Safe memory access: Reads from arbitrary kernel/user memory require
bpf_probe_read_kernel/bpf_probe_read_userrather than raw pointer dereference, since the verifier cannot statically prove the validity of arbitrary addresses.
Architecturally, eBPF underpins sidecar-less service mesh data planes (Cilium, Isovalent), kernel-level runtime security enforcement (Falco, Tetragon), and low-overhead continuous profiling (Parca, Pixie) by replacing packet-copying userspace agents with in-kernel filtering and aggregation, cutting per-packet or per-syscall overhead dramatically. The trade-off is operational: debugging verifier rejections requires reading raw bytecode dumps, kernel version skew still breaks non-CO-RE programs in mixed-fleet environments, and privileged CAP_BPF/CAP_SYS_ADMIN requirements mean eBPF-based tooling itself becomes part of the security attack surface that must be audited alongside the workloads it observes.