Introduction

Intro to eBPF

eBPF is a Linux mechanism that allows you to hook custom code directly into kernel events.

When an event triggers, the kernel pauses its normal execution, runs your eBPF program, and then continues. This happens at the kernel level, giving you visibility into system behavior that would be impossible from user space alone.

Examples of events you can hook:

  • A process opens a file
  • A program is executed
  • A network packet arrives

This makes eBPF powerful for observability, security monitoring, and network filtering without modifying the kernel itself or loading kernel modules.

Program types

eBPF programs are categorized by where they attach in the kernel. These are the most common:

Tracepoints

Tracepoints are hook points defined throughout the kernel code. They expose almost all kernel events, such as process scheduling, file operations, network operations and system calls.

Kprobes

Kprobes attach to any kernel function by name. Unlike tracepoints, they’re not part of the kernel’s stable API - function names and signatures can change between versions.

Kprobes provide access to kernel internals that aren’t exposed through tracepoints, allowing deeper inspection of kernel behavior.

XDP

XDP programs attach at the network driver level, processing packets before they reach the kernel’s network stack.

XDP programs run so early that they can drop or redirect packets with minimal overhead.

The verifier

If you could run arbitrary code in the kernel, you could crash the system, leak sensitive data, or create infinite loops that hang the machine.

The eBPF verifier prevents this by analyzing your program before it runs and enforcing strict safety rules.

As you work through the exercises, you’ll encounter verifier errors. While these errors can be cryptic, they always point to something the verifier couldn’t prove was safe. Learning to read these errors is part of learning eBPF.

Guarantee of termination

Programs must be guaranteed to terminate. To enforce this, the verifier imposes limits:

  • Programs have a maximum complexity (counted as “instructions processed” by the verifier)
  • Loops must have a fixed upper bound that the verifier can verify at load time
  • To prevent infinite recursion, helper functions can’t call back into your program

Memory safety

All memory access must be safe:

  • You can’t do pointer arithmetic or dereference arbitrary addresses
  • Reading kernel or user memory requires special helpers (bpf_probe_read_kernel, bpf_probe_read_user)
  • Array access must be bounds-checked before use
  • Pointers from map lookups must be checked for NULL

The verifier analyzes all branches in your code and will reject programs where any branch path can’t be proven safe.