hmmnm.com — eBPF explained: bytecode squares passing a teal verifier gate into the kernel box, one amber program rejected

eBPF Explained: Verified Programs Inside the Kernel

  • Post author:
  • Post category:Technology
📋 Key Takeaways
  • From Packet Filter to Virtual Machine
  • The Verifier: Proving Safety Before Execution
  • Where Programs Attach
  • What eBPF Changed for Security Engineering
  • The Trade-Offs, Honestly
8 min read · 1,525 words
hmmnm.com — eBPF explained: bytecode squares passing a teal verifier gate into the kernel box, one amber program rejected and deflected out

TL;DR — eBPF lets untrusted code run inside the Linux kernel safely — not with sandbox escapes or luck, but because a static verifier proves each program terminates, touches only memory it owns, and calls only permitted helpers before a line executes. Since landing in Linux 3.18 (2014), that verified-program model has become the substrate for high-speed packet processing (XDP), container networking (Cilium), and runtime security enforcement (Tetragon). Understanding the verifier is understanding why eBPF became infrastructure.

There is a rule as old as kernels: code that runs in kernel space can do anything, so only vetted code may enter it. Loadable kernel modules follow that rule and occasionally break systems anyway. eBPF — extended Berkeley Packet Filter — inverted the bargain: it lets anyone submit code to the kernel, because the kernel mathematically checks the code first. The result is a general-purpose, safely programmable kernel, and a decade of infrastructure built on top of it. If you run containers behind Cilium, or a CDN that drops attack traffic at the NIC, or a runtime security agent, eBPF is already in your stack — which is why it belongs on a systems-engineering blog next to topics like how time synchronization quietly underpins correctness.

From Packet Filter to Virtual Machine

The original BPF came from McCanne and Jacobson’s 1992 packet-filter design: a tiny register machine that ran in the kernel to decide which packets a sniffing application should see, replacing per-packet trips to userspace. Classic BPF did that job for twenty years. In 2014, Alexei Starovoitov and Daniel Borkmann rewrote the BPF engine for Linux 3.18: 64-bit registers, a richer instruction set, maps (kernel-managed key/value stores shared with userspace), and hooks beyond packet filtering. The kernel internally translates classic BPF into the new instruction set, so the old world kept working — but the “e” for extended was earned: this was now a general in-kernel virtual machine with a JIT compiler translating its fixed-width instructions one-to-one into native machine code.

What makes it usable isn’t speed alone — it’s that programs are provably contained. That property comes from one component: the verifier.

The Verifier: Proving Safety Before Execution

When a program is loaded, the verifier simulates every possible path through it, tracking the type and bounds of every register and every pointer. A program is accepted only if the verifier can prove, statically, that:

  • It terminates. Early eBPF banned loops entirely; since kernel 5.3, bounded loops are allowed where the verifier can prove an upper iteration count.
  • It touches only permitted memory. No arbitrary pointer dereferences — pointers must derive from validated contexts (a packet buffer, a map value), and accesses are bounds-checked against what the verifier has tracked.
  • It calls only approved helpers. Side effects go through a fixed helper-function API (maps, packet push/pull, redirect, tracing reads), not raw kernel symbols.

Programs that fail verification are rejected at load time with a reason — which makes eBPF failures development-time annoyances rather than production kernel panics. This is the design decision everything else hangs on: it’s why a cloud provider can safely let customers attach eBPF programs to their own workloads, and why Starovoitov has argued kernel extensions should generally be written as BPF programs for exactly these safety properties.

Two more mechanisms complete the execution model. Maps are the communication layer: kernel-managed key/value stores (hashes, arrays, ring buffers, per-CPU variants) that programs read and write atomically and userspace tools share — a tracing program streams events into a ring buffer, a firewall program looks up its policy in a hash, a load balancer keeps session state in an LRU. The program logic and its state stay cleanly separated, and state survives program replacement. Tail calls let one verified program hand execution to another in constant time — the eBPF answer to function dispatch — which is how large programs are composed from small, independently verifiable pieces without the verifier’s path explosion becoming a wall.

Where Programs Attach

Hook Fires when Typical use
XDP Packet arrives at the NIC driver — earliest possible point Line-rate packet filtering, DDoS drops, load-balancer forwarding
TC (traffic control) Packets entering/leaving the kernel’s traffic-control layer Container networking, per-pod bandwidth, NAT
kprobes / tracepoints Kernel functions or predefined trace sites execute Observability, syscall-level security telemetry
LSM (BPF LSM) Linux Security Module hooks evaluate an action Runtime policy enforcement with real credentials context

XDP deserves special mention: because it runs before the kernel allocates an skb (socket buffer) — at the driver, in many setups — it can drop or redirect malicious traffic at a cost the normal network stack can’t match. That property is why Meta’s open-source layer-4 load balancer, Katran, uses XDP as its forwarding plane for facebook.com traffic, and why DDoS scrubbing at CDN scale leans on the same hook. The lesson generalizes: attach where the work is cheapest, before the expensive machinery spins up.

What eBPF Changed for Security Engineering

Three shifts made eBPF a security-industry event rather than a networking curiosity:

  • Kernel-level visibility without kernel modules. Agents like Tetragon attach to tracepoints and LSM hooks, seeing every exec, file operation, and network connection with the full credentials context (namespace, pod, container) — no custom module to build per kernel version, no agent blind spots at the syscall boundary. It composes naturally with the detection-as-code workflow from the detection engineering with Sigma post: eBPF supplies high-fidelity events; your rules supply the logic.
  • Enforcement, not just observation. BPF LSM programs can deny an action in the moment — a different proposition than generating an alert after the fact, and one that container platforms increasingly wire into policy (Cilium’s model of identity-aware network policy on Kubernetes, covered as part of the Kubernetes security fundamentals, rides on the same machinery).
  • Portability at scale. Compile Once – Run Everywhere (CO-RE) uses BPF Type Format (BTF) debug data so one program binary runs across kernel versions and distributions — the operational objection (“a build per kernel”) that killed kernel modules as a product strategy, solved.

The Trade-Offs, Honestly

eBPF is not a free lunch. The verifier’s constraints are real: programs have instruction limits, restricted loops (bounded since 5.3, but still bounded), no floating point in many contexts, and a programming model that can feel adversarial — verification failures on pointer arithmetic are a rite of passage. Kernel version skew still bites at the edges (a hook or helper your fleet’s oldest node doesn’t have). Debugging is different in kind: when something misbehaves, you are reasoning about verifier state, helper semantics, and JIT’d code rather than an ordinary process, and the tooling reflects that (bpftool, bpftrace, and the BCC toolkit exist because none of this is intuitive). And privileged access is required to load programs today, so eBPF itself becomes attack surface worth monitoring: an eBPF-based agent with a signing or loading flaw is a kernel-adjacent problem. The mature posture is to treat eBPF programs like the privileged software they are — versioned, reviewed, and attested.

Key Takeaways

  • eBPF (Linux 3.18, 2014, Starovoitov & Borkmann) rebuilt classic BPF into a general in-kernel VM: 64-bit instructions, maps, helpers, and JIT compilation to native code.
  • The verifier is the whole trick — load-time proof of termination, memory safety, and helper-only side effects; bounded loops arrived with kernel 5.3.
  • Attachment points determine cost: XDP runs at the driver (Katran, DDoS mitigation), TC handles container networking, tracepoints/kprobes and BPF LSM power observability and enforcement.
  • Security tooling (Tetragon and friends) gets kernel-complete telemetry plus inline enforcement, without per-kernel modules; CO-RE/BTF makes one binary portable across kernels.
  • Constraints are the price of safety — and eBPF programs themselves are privileged software that should be versioned and attested like any other.

FAQ

What does eBPF stand for?
Historically “extended Berkeley Packet Filter.” Today the kernel community treats eBPF as a name in its own right — the technology long outgrew packet filtering.

How can untrusted code run in the kernel safely?
It doesn’t run until the verifier has simulated all paths and proven termination, memory safety, and helper-only effects; the JIT then emits native code for the verified program only.

What is XDP?
eXpress Data Path: an eBPF hook at the NIC driver that sees packets before the kernel’s network stack allocates buffers for them — cheap enough for line-rate drops, redirects, and load balancing.

Why do security tools like Tetragon use eBPF?
It gives them every syscall and LSM event with full container/credential context, inline enforcement capability, and no kernel-module build matrix — high-fidelity events without agent blind spots.

Can eBPF programs loop?
Since kernel 5.3, yes — if the verifier can prove a fixed upper bound on iterations. Unbounded loops are still rejected by design.

Is eBPF a security risk?
Loading programs requires privileges, so the eBPF subsystem itself is sensitive. Treat loaded programs as privileged artifacts: reviewed, version-pinned, and monitored like other critical software.

References

  1. Linux kernel documentation — Classic vs extended BPF
  2. Linux kernel documentation — BPF subsystem index
  3. ebpf.io — What is eBPF?
  4. Wikipedia — eBPF (history and architecture)
  5. Meta Engineering — Open-sourcing Katran, a scalable network load balancer (XDP)
  6. iovisor — BCC tools for BPF-based tracing
  7. Meta — Katran repository (GitHub)
  8. Tetragon — eBPF-based security observability and runtime enforcement
  9. Cilium — Networking and security for containers with BPF and XDP
  10. LWN.net — BPF and security
  11. Kernel Internals — BPF architecture and program types

Current as of September 2026. Educational engineering reference — kernel capabilities vary by version; verify hook availability against your fleet before deployment.

Hmmnm
Published by Hmmnm

Hands-on cybersecurity tutorials, CVE breakdowns, and guided learning paths — written and lab-tested by the Hmmnm team.

🛡️ Hmmnm also delivers this expertise as a service — security testing, assessment & training.

Hmmnm

Application Security Consultant at TCS. Certifications: CompTIA SecurityX, Burp Suite Certified Practitioner, Azure Security Engineer, Azure AI Engineer, Certified Red Team Operator, eWPTX v3, LPT, CompTIA PenTest+, Professional Cloud Security Engineer, SC-900, SC-200, PSPO I, CEH, Oracle Java SE 8, ISP, Six Sigma Green Belt, DELF, AutoCAD. Writing about ethical hacking, security tutorials, and tech education at Hmmnm.