eBPF Tools and Concepts

Fix the Verifier Errors

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:

RuleError Message
NULL check after map lookupR0 invalid mem access 'map_value_or_null'
Stack buffer fixed sizevariable stack access var_off
Loop bounds compile-timeback-edge from insn X to Y
Pointer arithmetic restrictedR1 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.h is
  • The BPF_CORE_READ macro
  • 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.

Run your code to see execution events here