eBPF Tools and Concepts
Verifier and BTF/CO-RE
BPF Verifier security mechanism and program portability with BTF/CO-RE.
Verifier
Lab: eBPF Verifier
What you will learn in this lab:
- License checks (GPL requirement)
- Mandatory NULL pointer checks
- Bounds checking
- Complexity limit
Verifier Rules
You will encounter these rules in the eBPFHub exercises:
| Rule | Error Message |
|---|---|
| NULL check after map lookup | R0 invalid mem access 'map_value_or_null' |
| Stack buffer fixed size | variable stack access var_off |
| Loop bounds compile-time | back-edge from insn X to Y |
| Pointer arithmetic restricted | R1 pointer arithmetic prohibited |
Verifier Security Guarantees
- Termination: Fixed loop bounds, max complexity limit
- Memory Safety: No arbitrary pointers, helper functions required
- Bounds Check: Boundary check on every array access
- NULL Check: Map lookup result must always be checked
Common Errors and Fixes
1. Missing NULL check after map lookup:
// BROKEN — verifier rejects
char *val = bpf_map_lookup_elem(&my_map, &key);
bpf_printk("value: %s", val); // R0 could be NULL!
// FIXED
char *val = bpf_map_lookup_elem(&my_map, &key);
if (!val) return 0; // NULL check required
bpf_printk("value: %s", val);
2. Unbounded loop:
// BROKEN — verifier rejects: "back-edge from insn X to Y"
for (int i = 0; i < len; i++) { // 'len' is runtime value
buf[i] = 0;
}
// FIXED — compile-time bound
#pragma unroll
for (int i = 0; i < 64; i++) { // constant bound
if (i >= len) break;
buf[i] = 0;
}
3. Out-of-bounds packet access (XDP):
// BROKEN — verifier rejects: "invalid access to packet"
struct iphdr *ip = data + sizeof(struct ethhdr);
__u32 src = ip->saddr; // no bounds check!
// FIXED — always check before dereferencing
struct iphdr *ip = data + sizeof(struct ethhdr);
if ((void *)(ip + 1) > data_end)
return XDP_PASS;
__u32 src = ip->saddr; // safe after bounds check
Practice: Intentionally trigger a verifier error, understand the message, and fix it.
Tool: ebpf-cover - VS Code extension, code coverage visualization from verifier logs.
BTF and CO-RE (Portability)
Lab 1: Portable eBPF Programs
What you will learn in this lab:
- Why programs may not work across different kernels
- What
vmlinux.his - The
BPF_CORE_READmacro - BTF (BPF Type Format)
Generating vmlinux.h
bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h
This single header gives your BPF program access to all kernel types without installing kernel headers.
BPF_CORE_READ
Reading nested struct fields across different kernel versions:
#include "vmlinux.h"
#include <bpf/bpf_core_read.h>
SEC("kprobe/tcp_connect")
int trace(struct pt_regs *ctx) {
struct sock *sk = (void *)PT_REGS_PARM1(ctx);
// WITHOUT CO-RE — breaks if struct layout changes between kernels
// __u16 port = sk->__sk_common.skc_dport;
// WITH CO-RE — works across kernel versions
__u16 port = BPF_CORE_READ(sk, __sk_common.skc_dport);
return 0;
}
BPF_CORE_READ generates relocatable reads — the loader adjusts field offsets at load time based on the running kernel’s BTF.
Lab 2: Truly Portable eBPF
What you will learn in this lab:
- Using BTFHub
- Generating minimal BTF
- Running on kernels without BTF
Note: When using Go + bpf2go, most things are automatic. But understand why things work the way they do.