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.
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 fileeBPF Registers
R0: Return value, initialized to NOT_INIT at program start. R1 ‑ R5: Function arguments; R1 is initialized to a pointer to struct bpf_context. R6 ‑ R9: 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 1Read 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_okMissing return value initialization
BPF_MOV64_REG(BPF_REG_2, BPF_REG_1) // R2 = R1
BPF_EXIT_INSN(); // R0 never set
// Verifier output: R0 !read_okStack 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=8Invalid 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_mapMisaligned 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 8Additional 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
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.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
