On this page

eBPF Basics

eBPF allows user-space programs to inject sandboxed code into the kernel for execution—the verifier statically proves safety, JIT compiles to native instructions, maps share data with user-space, and CO-RE enables "compile once, run anywhere." It is to the kernel what JavaScript is to the browser.

Overview

Traditionally, there were only two ways to add custom logic to the kernel: modify the kernel source and recompile (a process taking months), or write a kernel module (where a single null pointer dereference can panic the entire machine). eBPF (extended Berkeley Packet Filter) offers a third path: submit the program as data to the kernel; the kernel first uses a static verifier to prove it is harmless, then JIT-compiles it to native code and attaches it to a hook for execution. Just as browsers trust JavaScript from arbitrary web pages, the kernel now trusts code submitted by users—this is why it evolved from the original packet filter (cBPF) into a general-purpose kernel programmability platform.

Version coverage: 3.18 ~ 6.x. Today, it supports four major domains:

  • Observability: bpftrace, bcc, Cilium Hubble
  • Networking: XDP, TC BPF, Cilium container networking
  • Security: BPF LSM, seccomp
  • Performance: Custom logic on hot paths without recompiling the kernel

Lifecycle

flowchart LR
    subgraph user["👩‍💻 User Space"]
        C["C Code"] -->|"clang -target bpf -O2"| O["eBPF ELF (.o)"]
        O -->|"libbpf + CO-RE relocation"| L["bpf(BPF_PROG_LOAD)"]
        R["Read maps / ringbuf"]
    end
    subgraph kernel["🛡️ Kernel Space"]
        L --> V{"<b>Verifier</b><br><small>Static proof of safety for all paths</small>"}
        V ==>|"Pass"| J["<b>JIT Compilation</b><br><small>bytecode → native instructions</small>"}
        V -.->|"Reject -EACCES<br>+ verifier log"| C
        J --> A["Attach to hook<br><small>kprobe / tracepoint / XDP…</small>"]
        A -->|"Event triggers execution"| M[("BPF maps")]
    end
    M --> R
    classDef gate fill:#d2992226,stroke:#d29922,stroke-width:2.5px
    classDef fast fill:#4493f81f,stroke:#4493f8,stroke-width:2px
    classDef store fill:#3fb9501f,stroke:#3fb950,stroke-width:2px
    class V gate
    class J,A fast
    class M store

The key lies in the order: verify first, then compile, then attach. A program cannot reach a hook unless the verifier has already proven it is safe on all execution paths.

Verifier: The Foundation of Safety

kernel/bpf/verifier.c, approximately 16,000 lines, is the most complex verifier in the kernel. It performs symbolic execution on the program, exhaustively checking all possible paths for three categories of properties:

CategorySpecific Constraints
Control FlowNo unreachable instructions; no infinite loops (since 5.3, provably terminating bounded loops are allowed); no out-of-bounds jumps; call depth ≤ 32
Data FlowTracks the type of each register (PTR_TO_CTX / PTR_TO_MAP_VALUE / SCALAR_VALUE…); tracks the value range of each scalar; enforces null pointer checks; ensures map access key/value sizes match
ResourcesInstruction count limit of 1 million (since 5.1); stack depth of 512 bytes

When verification fails, BPF_PROG_LOAD returns -EACCES, accompanied by an instruction-by-instruction verifier log—understanding this log is a daily part of eBPF development.

Bound Tracking

The verifier's most ingenious mechanism: maintaining four bounds [smin, smax, umin, umax] for each register, learning conditions along branches:

if (x < 10) {
    // The verifier deduces x ∈ [0, 9] within this branch
    bpf_map_lookup_elem(&map, &x);   // OK: index proven to be within bounds
}
// On the else path, it deduces x ∈ [10, MAX]

Array index bounds, valid map keys, and pointer arithmetic not going out of bounds—all statically proven at load time via this interval deduction, with zero runtime overhead.

JIT Compilation

After passing verification, BPF bytecode is translated instruction-by-instruction into native machine code (on x86, in arch/x86/net/bpf_jit_comp.c), with each BPF instruction corresponding to 1~3 x86 instructions:

BPF_ADD  reg, imm  →  add $imm, %reg
BPF_LDX  reg, src  →  mov (%src), %reg
BPF_CALL fn        →  call fn        (tail call optimized to jmp)
BPF_EXIT           →  ret

Thus, eBPF is not an "interpreter inside the kernel"—execution performance after JIT is close to hand-written kernel code, which is why it can be placed on hot paths like XDP that process every packet.

BPF Maps: Data Sharing Between Programs

BPF programs themselves are stateless and event-driven; state is stored in maps—kernel-space key-value stores that allow data sharing between BPF programs and between BPF and user-space.

// include/uapi/linux/bpf.h — excerpt of common types
enum bpf_map_type {
    BPF_MAP_TYPE_HASH,          // General hash table (most common)
    BPF_MAP_TYPE_ARRAY,         // Fixed-size array (O(1) lookup, no deletion)
    BPF_MAP_TYPE_PERCPU_HASH,   // Per-CPU copy, lock-free
    BPF_MAP_TYPE_LRU_HASH,      // Evicts using LRU when full
    BPF_MAP_TYPE_RINGBUF,       // Ring buffer, streams events to user-space
    BPF_MAP_TYPE_STACK_TRACE,   // Kernel stack trace storage
    BPF_MAP_TYPE_BLOOM_FILTER,  // Fast "possibly in set" check
};

BPF side operations via helpers: bpf_map_lookup_elem / bpf_map_update_elem / bpf_map_delete_elem; user-space reads/writes the same map via the bpf() system call or libbpf wrappers.

Ring Buffer vs Perf Buffer

Streaming kernel events continuously to user-space is the main highway for observability scenarios. Before 5.8, perf buffers were used; since then, ringbuf should be used exclusively:

Perf Buffer (Legacy)Ring Buffer (5.8+)
Buffer StructureOne per CPUShared across all CPUs
OrderingOut-of-order across CPUsGlobally ordered
Memory EfficiencyReserved based on busiest CPUShared, saves memory
CopyingTwo copiesReserve-commit, mmap zero-copy
Record LengthFixedVariable

CO-RE: Compile Once, Run Everywhere

Problem: BPF programs reference kernel structures at compile time, with field offsets hardcoded into the bytecode; if a field moves in the next kernel version, the program reads incorrect memory. Early bcc therefore had to compile on the target machine (dragging in a full LLVM + kernel headers).

Solution is split into two parts:

  1. BTF (BPF Type Format)—type descriptions built into the kernel, embedded in /sys/kernel/btf/vmlinux, essentially a "kernel debug info" compressed to a few MB.
  2. libbpf's CO-RE relocation—when loading .o, it reads the current kernel's BTF and dynamically corrects all field offsets in the bytecode to the real offsets of the local machine.

Thus, the same .o file can run unmodified on 5.10 and 6.5:

#include <bpf/bpf_core_read.h>
x = BPF_CORE_READ(task, pid);   // Offset resolved at load time according to local kernel

bpftool: Managing BPF Objects

bpftool prog list                          # List loaded programs
bpftool map dump id <map_id>               # Export map contents
bpftool prog dump xlated id <prog_id>      # View BPF bytecode
bpftool prog dump jited  id <prog_id>      # View JIT-compiled x86 assembly
bpftool prog load myprog.o /sys/fs/bpf/myprog   # Load and pin to bpffs

Security Model

eBPF programs are constrained to a subset that "cannot harm the kernel." They cannot:

  • Run infinitely (instruction count limit + termination verification)
  • Perform pointer access without bounds checking
  • Read arbitrary kernel memory directly—must go through helpers (bpf_probe_read_kernel)
  • Write arbitrary kernel memory directly—can only write to their own maps
  • Sleep or call schedule (sleepable BPF is a restricted exception)
  • Call kernel functions outside the whitelist (helpers/kfuncs)

Privilege levels:

CapabilityWhat it can do
CAP_BPFLoad most BPF program types
CAP_SYS_ADMINAll types, including tracing
No privilegesOnly if kernel.unprivileged_bpf_disabled=0, and only for limited types like socket filters

Which hooks a program can attach to, and what context each type receives, can be found in eBPF Program Types; for practical tracing toolchains, see Tracing and Debugging and ftrace and bpftrace User Space.

References

  • Source Code: kernel/bpf/ (verifier, syscall, helpers), arch/x86/net/bpf_jit_comp.c (JIT), tools/lib/bpf/ (libbpf), tools/bpf/bpftool/
  • Kernel Documentation: Documentation/bpf/ (excellent documentation)
  • Book: "BPF Performance Tools" (Brendan Gregg)
  • LWN: "A thorough introduction to eBPF" (lwn.net/Articles/740157/)

Keywords: eBPF, verifier, JIT, BPF maps, BTF, CO-RE, libbpf, bpftool, ring buffer, CAP_BPF