Blog
Thoughts on engineering, design, and building great products.
LSM BPF: Enforcing Security Right Inside the Kernel
So far our eBPF has only observed. LSM BPF enforces: it attaches to the kernel's security hooks (Linux Security Modules) that SELinux and AppArmor use, and a program returns 0 to allow or -EPERM to block. This article writes an LSM program that blocks opening a file, and hits a lesson: it loaded and attached but blocked nothing — because bpf wasn't an active LSM. After enabling bpf via a boot parameter and rebooting, it blocks for real — both cat and python get Operation not permitted.
The Verifier: Why eBPF Doesn't Crash the Kernel
Article 1 said the eBPF virtual machine design lets the verifier prove safety. This article watches it for real: we compile an XDP program that reads the first byte of a packet but forgets the bounds check, load it — the verifier rejects it with a log naming the exact register and reason. Add one data_end check and it goes through. The verifier is a safety prover at load time, tracking each register's state across every branch — letting eBPF load foreign code into the kernel safely.
eBPF From Scratch: Running Programs Inside the Linux Kernel
Right now, on a worker of the Kubernetes cluster we built in the previous series, 140 eBPF programs are running inside the Linux kernel — routing every packet, controlling device access, collecting metrics. eBPF lets you load code into the kernel and run it safely at hooks, without changing kernel source and without loading a module. This opening article explains what eBPF is, why it changes how the kernel is extended, and how a program goes from code to native machine code.
BTF and CO-RE: Compile Once, Run on Every Kernel
Kernel data structures like task_struct have different layouts across kernel versions — a field sits at a different offset per build. So how does a pre-compiled eBPF program read the right field on every kernel? BTF and CO-RE. This final Part I post generates vmlinux.h from the kernel's BTF, writes a program that reads ppid by walking task->real_parent->tgid, compiles once and runs — libbpf finds the right offset from the running kernel's BTF. The foundation for Part III.
Program Types and Hooks: Where You Attach, What You See
An eBPF program doesn't run in a vacuum — it attaches to a hook in the kernel, and the kind of hook decides three things: when the program runs, what context it receives, and which helpers it may call. This post lists the program types the kernel supports, then attaches a tracepoint to the openat syscall to watch it run for real on every file open — contrasted with the XDP packet receiver from the previous post, to show why two programs that are both eBPF each see a completely different world.
Maps: Memory and the Bridge to Userspace
An eBPF program runs per event then shuts off, keeping no variables between runs. Maps are how it remembers state and talks to userspace. This article writes a program that counts every process exec into a map, loads it into the kernel, runs a few commands, then reads the map from userspace with bpftool — watching the number actually go up. Plus inspecting a real Cilium map holding per-CPU metrics, and distinguishing a plain array from a per-CPU one.
The eBPF Virtual Machine: Registers, Instruction Set, and Bytecode
Last article we saw an eBPF program with 'xlated 512B' (verified bytecode) and 'jited 333B' (machine code). This article goes inside that bytecode: eBPF is a RISC-style virtual machine with 11 64-bit registers and a small instruction set, designed to translate quickly to native code and be verifiable for safety. We read a running Cilium program's bytecode directly, see how each instruction maps to registers and its class, then why this design lets the verifier prove safety.