BPF Subsystem Details
Verifier Invariants
- All memory accesses must be bounds-checked by verifier
- Register types tracked through program flow
- Stack slots must be initialized before use
- Helper functions have specific argument requirements
BPF Map Operations
- Map lookups can return NULL
- Map updates need to check max_entries
- Spin locks in maps require bpf_spin_lock/unlock
- Per-CPU maps need bpf_get_cpu_ptr/put_cpu_ptr
Reference Tracking
- Some helpers return "acquired" references
- Must release with corresponding release helper
- Verifier tracks reference state per register
Context Access
- Context pointer is read-only
- Field access must be within ctx structure size
- Some fields require specific program types
BPF Kernel Functions (kfuncs)
- _bpf_kfunc, BTF_KFUNCS*, KF* flags, etc
- read
Documentation/bpf/kfuncs.rstto better understand these
Kfunc Argument Validation by the Verifier
The BPF verifier performs validation of kfunc arguments at program load time. Understanding what the verifier actually guarantees is critical to correctly identify real bugs.
Scalar and Enum Arguments
When a kfunc takes a scalar argument (int, u32, enum, etc.), the verifier:
- Checks register type: Ensures the register is
SCALAR_VALUE(not a pointer) - Tracks value ranges: Maintains min/max bounds for scalar values internally
- For
__constannotated args: Requires value to be known at verification time
Critical: The verifier does NOT enforce that enum values are valid members of the enum. Enum types are treated identically to plain integers - the verifier only checks it's a scalar, not that it falls within valid enum range.
From check_kfunc_args() in kernel/bpf/verifier.c:
if (btf_type_is_scalar(t)) {
if (reg->type != SCALAR_VALUE) {
verbose(env, "R%d is not a scalar\n", regno);
return -EINVAL;
}
// ... handles __const and special cases ...
continue; // No enum range validation!
}
Kfuncs MUST Include Bounds Checks for Array Indices
Because the verifier does not validate enum ranges, kfuncs that use enum or integer parameters as array indices MUST include runtime bounds checks.
Example - correct bounds checking:
__bpf_kfunc unsigned long bpf_mem_cgroup_page_state(struct mem_cgroup *memcg, int idx)
{
if (idx < 0 || idx >= MEMCG_NR_STAT) // Required!
return (unsigned long)-1;
return memcg_page_state_output(memcg, idx);
}
For enum parameters, the check must handle negative values because enums are signed int in C:
__bpf_kfunc unsigned long bpf_mem_cgroup_memory_events(struct mem_cgroup *memcg,
enum memcg_memory_event event)
{
// Must check BOTH bounds - enum is signed int, negative values bypass >= check
if ((unsigned int)event >= MEMCG_NR_MEMORY_EVENTS)
return (unsigned long)-1;
// OR: if (event < 0 || event >= MEMCG_NR_MEMORY_EVENTS)
return atomic_long_read(&memcg->memory_events[event]);
}
Why negative check matters: If event = -1:
- Signed comparison:
-1 >= 10is FALSE (check passes incorrectly) - Function accesses
memory_events[-1]- out-of-bounds read
When Values Are Guaranteed Safe
The only case where bounds checks are truly unnecessary:
__constannotated parameters: The verifier requirestnum_is_const(), meaning only compile-time constants from vmlinux.h can be passed.
Without __const, BPF programs can pass values from:
- Map lookups (user-controlled)
- Runtime computations
- Any scalar register
Summary: Kfunc Scalar/Enum Argument Safety
| Scenario | Bounds Check Needed? | Reason |
|---|---|---|
__const annotated enum/int |
No | Verifier enforces constant value |
Enum parameter (no __const) |
Yes | Verifier doesn't check enum ranges |
| Plain int/u32 parameter | Yes | No constraint on values |
| Any index used for array access | Yes | Must prevent OOB access |
REPORT as bugs: Kfuncs that use enum or integer parameters as array indices without proper bounds checking (including negative value check for signed types).
Quick Checks
- Helpers marked with BPF_RET_PTR_TO_MAP_VALUE_OR_NULL need NULL checks
- ARG_PTR_TO_MEM arguments need size validation
- Tail calls limited to 33 levels
- Stack usage limited to 512 bytes
BPF Skeleton API (Selftests)
Generated Skeleton Functions
BPF skeletons are generated by bpftool gen skeleton and provide type-safe wrappers.
Each skeleton includes these functions (where example is the object name):
example__open()- Opens BPF object (does not load programs)example__load()- Creates maps, loads and verifies all BPF programsexample__open_and_load()- Combines open + load in one operationexample__destroy()- Detaches, unloads programs, frees resources
Skeleton Guarantees After Successful __open_and_load()
IMPORTANT: After successful skel = example__open_and_load():
- The skeleton pointer is valid (not NULL/ERR_PTR)
- ALL programs are loaded with valid FDs (>= 0)
- ALL maps are created with valid FDs (>= 0)
- Skeleton fields like
skel->progs.prog_nameandskel->maps.map_nameare guaranteed valid
This means:
bpf_program__fd(skel->progs.prog_name)CANNOT return negative after successful loadbpf_map__fd(skel->maps.map_name)CANNOT return negative after successful load- NO additional FD validation needed when using skeleton-generated fields
When FD Checks ARE Required
FD checks with CHECK_FAIL(fd < 0) or similar are needed when using:
- Manual lookup APIs:
bpf_object__find_program_by_name()- can return NULL if name not found - Manual lookup APIs:
bpf_object__find_map_by_name()- can return NULL if name not found - Old-style loading:
bpf_prog_test_load()- different API contract
Skeleton vs Manual Lookup Pattern
// Skeleton pattern - NO FD checks needed after successful __open_and_load()
skel = example__open_and_load();
if (!ASSERT_OK_PTR(skel, "open_and_load"))
return;
prog_fd = bpf_program__fd(skel->progs.my_prog); // Cannot fail here
map_fd = bpf_map__fd(skel->maps.my_map); // Cannot fail here
// Manual lookup pattern - FD checks REQUIRED (using modern ASSERT_* macros)
obj = bpf_object__open_file("example.o", NULL);
prog = bpf_object__find_program_by_name(obj, "my_prog"); // Can return NULL
if (!ASSERT_OK_PTR(prog, "find_program"))
goto cleanup;
prog_fd = bpf_program__fd(prog); // Can return negative if prog is invalid
if (!ASSERT_GE(prog_fd, 0, "bpf_program__fd"))
goto cleanup;
BPF Selftest Assertion Macros
Modern ASSERT_*() Macros (Preferred)
Use ASSERT_*() macros for all new tests and when updating existing tests.
The modern ASSERT family includes type-specific macros like ASSERT_OK(), ASSERT_ERR(),
ASSERT_EQ(), ASSERT_OK_PTR(), ASSERT_OK_FD(), and many others. See
tools/testing/selftests/bpf/test_progs.h for the complete list.
Deprecated CHECK() Macros (Avoid in New Code)
DO NOT use in new tests or patches:
CHECK(condition, tag, format...)- DEPRECATED - UseASSERT_*()insteadCHECK_FAIL(condition)- DEPRECATED - UseASSERT_*()insteadCHECK_ATTR(condition, tag, format...)- DEPRECATED - UseASSERT_*()instead
Why ASSERT_*() is preferred:
- Uses static duration variable instead of requiring global
durationvariable - More specific and type-safe macros for different check types
- Better error messages with actual vs expected values
- Modern BPF selftest standard since 2020
Migration example:
// OLD (deprecated):
static int duration = 0; // Global/static variable required
if (CHECK(fd < 0, "open_fd", "failed to open: %d\n", errno))
return;
// NEW (preferred):
if (!ASSERT_OK_FD(fd, "open_fd")) // No duration variable needed
return;
BPF Patterns
Conditionally load these additional patterns
- BPF-001 (map operations with copy_map_value*) →
btf.md - LIBBPF-001 (tools/lib/bpf*) →
libbpf.md