Fundamentals 16 min read

Deep Dive into the eBPF Verifier: Avoid Common Security Pitfalls and Programming Errors

This article explains how the eBPF verifier checks user‑supplied BPF programs, details register types, instruction encoding, and the verification workflow, and illustrates typical verifier errors with concrete code examples and the exact kernel checks that trigger them.

Linux Code Review Hub
Linux Code Review Hub
Linux Code Review Hub
Deep Dive into the eBPF Verifier: Avoid Common Security Pitfalls and Programming Errors

eBPF Verifier Overview

eBPF programs run in the kernel, so any bug can crash the whole system. The kernel provides a verifier (implemented in kernel/bpf/verifier.c) that performs a series of static checks to ensure the program is safe before it is loaded.

Common eBPF Errors

The verifier rejects programs for illegal memory access, address leaks, missing termination, and other safety violations. Example load failure:

l0calh0st@lima-ubuntu22:~/workspace$ sudo bpftool prog load example.bpf.o /sys/fs/bpf/example
libbpf: prog 'kprobe__kfree_skb_reason': BPF program load failed: -EACCES
libbpf: prog 'kprobe__kfree_skb_reason': -- BEGIN PROG LOAD LOG --
0: (95) exit
R0 !read_ok
processed 1 insns (limit 1000000) max_states_per_insn 0 total_states 0 peak_states 0 mark_read 0
-- END PROG LOAD LOG --
libbpf: prog 'kprobe__kfree_skb_reason': failed to load: -EACCES
libbpf: failed to load object 'example.bpf.o'
Error: failed to load object file

eBPF Registers

R0

: Return value, initialized to NOT_INIT at program start. R1R5: Function arguments; R1 is initialized to a pointer to struct bpf_context. R6R9: Caller‑saved registers, initialized to NOT_INIT. R10: Stack frame pointer, points to a 512‑byte stack and is initialized to PTR_TO_STACK.

Register Types

NOT_INIT

: Uninitialized; cannot be read or returned. SCALAR_VALUE: Non‑pointer scalar data. POINTER: Valid pointer; only certain pointer types may be used in load/store operations.

eBPF Instruction Format

struct bpf_insn {
  __u8 code;   /* opcode */
  __u8 dst_reg:4; /* dest register */
  __u8 src_reg:4; /* source register */
  __s16 off;  /* signed offset */
  __s32 imm;  /* signed immediate */
};

The opcode encodes the class (e.g., BPF_ALU, BPF_LDX), source flag ( BPF_K for immediate, BPF_X for register), and the specific operation (e.g., BPF_ADD, BPF_CALL). Example: b7 00 00 00 01 00 00 00 translates to r0 = 1 (class BPF_ALU64, opcode BPF_MOV).

Verification Process

static int do_check(struct bpf_verifier_env *env) {
    for (;;) {
        if (class == BPF_ALU || class == BPF_ALU64) {
            // ALU checks
        } else if (class == BPF_LDX) {
            err = check_alu_op(env, insn);
        } else if (class == BPF_STX) {
            // store‑register checks
        } else if (class == BPF_ST) {
            // store‑immediate checks
        } else if (class == BPF_JMP || class == BPF_JMP32) {
            u8 opcode = BPF_OP(insn->code);
            if (opcode == BPF_CALL) {
                // call handling
            } else if (opcode == BPF_EXIT) {
                err = check_reference_leak(env);
                err = check_return_code(env);
                if (err)
                    return err;
            }
        } else if (class == BPF_LD) {
            // load checks
        } else {
            verbose(env, "unknown insn class %d
", class);
            return -EINVAL;
        }
        env->insn_idx++;
    }
    return 0;
}

The verifier walks each instruction, selects the appropriate check function based on the instruction class, and validates register states, pointer safety, and stack bounds. For a BPF_EXIT instruction, check_return_code ensures that R0 is initialized; otherwise it emits R0 !read_ok.

Typical Constraints

Pointer arithmetic is only allowed on registers of type PTR_TO_CTX, PTR_TO_MAP, or PTR_TO_STACK. Adding two pointers (e.g., R2 = R1 + R1) converts the result to SCALAR_VALUE and is rejected.

Uninitialized registers ( NOT_INIT) cannot be read or returned.

Load/store operations may only target pointer registers; the verifier inserts bounds checks for stack accesses.

Common Verification Errors (Examples)

Unreachable instruction

static struct bpf_insn prog[] = {
    BPF_EXIT_INSN(),
    BPF_EXIT_INSN(),
};
// Verifier output: unreachable insn 1

Read uninitialized register

BPF_MOV64_REG(BPF_REG_0, BPF_REG_2)   // R2 is NOT_INIT
BPF_EXIT_INSN();
// Verifier output:
// 0: (bf) r0 = r2
// R2 !read_ok

Missing return value initialization

BPF_MOV64_REG(BPF_REG_2, BPF_REG_1)   // R2 = R1
BPF_EXIT_INSN();                     // R0 never set
// Verifier output: R0 !read_ok

Stack out‑of‑bounds access

BPF_ST_MEM(BPF_DW, BPF_REG_10, 8, 0)   // write at R10+8 (outside 0‑512 stack)
BPF_EXIT_INSN();
// Verifier output: invalid stack off=8 size=8

Invalid map descriptor

BPF_MOV64_REG(BPF_REG_2, BPF_REG_10)
BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, -8)
BPF_LD_MAP_FD(BPF_REG_1, 0)          // fd 0 is not a valid map
BPF_RAW_INSN(BPF_JMP|BPF_CALL, 0,0,0,BPF_FUNC_map_lookup_elem)
// Verifier output: fd 0 is not pointing to valid bpf_map

Misaligned memory access

BPF_ST_MEM(BPF_DW, BPF_REG_0, 4, 0)   // offset 4 is not 8‑byte aligned
// Verifier output: misaligned access off 4 size 8

Additional Constraints

Pointer addition that yields a non‑pointer (e.g., R2 = R1 + R1) converts R2 to SCALAR_VALUE and is rejected to prevent kernel address leaks.

Stack memory must be initialized before being read; reading *(u32*)(R10‑4) without a prior store results in “invalid mem access”.

Map lookup helpers require a valid map file descriptor; using fd 0 triggers “fd 0 is not pointing to valid bpf_map”.

Returning a pointer from an eBPF program is prohibited; the verifier reports “R0 leaks addr as return value”.

References

《深入理解eBpf与可观测性》

https://docs.kernel.org/bpf/verifier.html#eBpf-verifier

https://getanteon.com/blog/unveiling-eBpf-verifier-errors/#prerequisites-19c11593-f1b7-46a3-a07e-6960356915f3

debuggingKernelsecurityeBPFBPF instructionsverifier
Linux Code Review Hub
Written by

Linux Code Review Hub

A professional Linux technology community and learning platform covering the kernel, memory management, process management, file system and I/O, performance tuning, device drivers, virtualization, and cloud computing.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.