Loader
Relevant source files
- kernel/crates/kernel_bpf/src/loader/mod.rs
- kernel/crates/kernel_bpf/src/loader/elf.rs
- kernel/crates/kernel_bpf/src/loader/reloc.rs
- kernel/crates/kernel_bpf/src/loader/normalize.rs
- kernel/crates/kernel_bpf/src/loader/error.rs
- kernel/src/syscall/bpf.rs
- kernel/crates/kernel_abi/src/bpf.rs
- userspace/tools/bpf_loader/src/main.rs
- userspace/tools/signed_bpf_loader/src/main.rs
The loader is a libbpf-free ELF64 reader. It turns an ELF object into one or more LoadedProgram values (flat instruction vectors with a program type) plus map definitions. It does not verify anything; verification is the next stage (Verifier).
Stages
ELF parsing
ElfParser::new rejects data shorter than 64 bytes, a bad magic, a non-64-bit class, an unknown endianness byte, and any e_machine other than EM_BPF (247). Both little- and big-endian ELF files are parsed. Errors are typed in LoadError (for example ElfTooSmall, InvalidMagic, UnsupportedClass, UnsupportedMachine, SectionOutOfBounds).
The loader enforces per-object counts: the defaults are 8 programs and 16 maps on the embedded profile, and 64 programs and 128 maps on the cloud profile (TooManyPrograms, TooManyMaps).
Map definitions
Map definitions are read from a .maps section as fixed 20-byte records (type, key_size, value_size, max_entries, flags, each a native-endian u32). The loader recognises these raw type numbers: 0 unspec, 1 hash, 2 array, 3 prog array, 4 perf event array, 5 per-CPU hash, 6 per-CPU array, 7 stack trace, 8 cgroup array, and 27 ring buffer; types 9, 10 and 11 (LRU hash, LRU per-CPU hash, LPM trie) exist only in cloud builds. Anything else is UnsupportedMapType.
Recognising a map type in an ELF definition is separate from being creatable at runtime. The kernel manager only creates hash, array, ring buffer and time-series maps (see Maps and Helpers). BTF-defined maps are not parsed: the source carries a TODO for BTF parsing on the cloud profile.
Program sections and types
Executable sections (SHF_EXECINSTR) are treated as programs. If at least one executable section is not named .text or text, those two names are considered subprogram sections and are skipped as entry points. The program type is derived from the section name prefix:
| Section prefix | BpfProgType |
|---|---|
socket, sk_ | SocketFilter |
kprobe, kretprobe, fentry, fexit | Kprobe |
tracepoint, tp/, raw_tracepoint, raw_tp/, iter/ | Tracepoint |
xdp | Xdp |
perf_event | PerfEvent |
cgroup | CgroupSkb |
sched_cls, tc | SchedCls |
lwt_ | LwtIn |
| anything else | SocketFilter (default) |
The program type does not select the hook: the attach type passed to BPF_PROG_ATTACH does (see Hooks). Note that BpfManager::load_program_authorized takes only the first program found in an object (obj.programs().first()).
Relocations
Relocator handles ELF REL entries against the symbol table:
| Relocation | Handling |
|---|---|
R_BPF_64_64 (1) | Map reference: the ld_imm64 is rewritten with src_reg = 1 (pseudo map) and imm set to the index of the named map in the object’s map list; an unknown map name is UndefinedSymbol |
R_BPF_64_32 (10) | Call: a known helper name is rewritten to the runtime helper ID; a function symbol defined in an executable section becomes a BPF-to-BPF pseudo call |
R_BPF_64_ABS64, R_BPF_64_ABS32 | Accepted and left unchanged |
| other types | Ignored |
Helper names resolve through the runtime HelperId enum, not the Linux uapi numbering. Seventeen names are recognised: bpf_map_lookup_elem, bpf_map_update_elem, bpf_map_delete_elem, bpf_probe_read, bpf_ktime_get_ns, bpf_trace_printk, bpf_get_prandom_u32, bpf_get_smp_processor_id, bpf_get_current_pid_tgid, bpf_get_current_uid_gid, bpf_get_current_comm, bpf_ringbuf_output, bpf_ringbuf_reserve, bpf_ringbuf_submit, bpf_ringbuf_discard, bpf_timeseries_push, and bpf_sensor_last_timestamp. A helper the loader does not know is left unrelocated, so the verifier rejects it rather than risking dispatch to a different helper. Names such as bpf_gpio_set or bpf_pwm_write are not in the relocation table; programs that need them must encode the helper ID directly (the bundled demo does this with kernel_abi constants).
Subprograms and call normalization
When a program section calls functions in other executable sections, those sections are appended after the root section (linked_call_sections) and calls are resolved to relative pseudo-call offsets. normalize then removes pseudo calls entirely by inline expansion, so the verifier only ever sees helper calls. If a program has no subprogram calls it is returned unchanged.
| Constant | Value |
|---|---|
BPF_PSEUDO_CALL (src_reg marker) | 1 |
FRAME_SIZE per inlined frame | 512 bytes |
MAX_CALL_DEPTH | 8 |
Normalization rejects: recursion (RecursiveCall), depth over 8 (CallDepthExceeded), malformed or non-entry call targets (MalformedPseudoCall), expanded size over the profile instruction limit (ExpansionTooLarge), jump offsets that overflow i16 or leave their subprogram (JumpOffsetOverflow, JumpTargetOutOfRange), and direct r10-relative accesses outside the callee’s own frame (StackOffsetOutOfFrame). Each inlined callee’s stack accesses are displaced by its depth times the frame size. A source_map from new to original instruction indices is produced but is not yet threaded to LoadedProgram.
sys_bpf commands
sys_bpf(cmd, attr_ptr, size) requires size of at least size_of::<BpfAttr>(). Before dispatch it checks the caller’s BpfCapabilities. Commands defined in kernel_abi are numerous (mirroring Linux numbering), but the ABI catalog lists these as supported:
| Command | Value | Capability required | Notes |
|---|---|---|---|
BPF_MAP_CREATE | 0 | MAP_CREATE | prog_type carries the map type, insn_cnt the key size, insns low 32 bits the value size and high 32 bits max_entries |
BPF_MAP_LOOKUP_ELEM | 1 | MAP_READ | Returns -2 when the key is absent |
BPF_MAP_UPDATE_ELEM | 2 | MAP_WRITE | Write access to the map is checked |
BPF_MAP_DELETE_ELEM | 3 | MAP_WRITE | |
BPF_PROG_LOAD | 5 | PROGRAM_LOAD | Raw instructions; insn_cnt must be 1 to 4096; rejected when signature enforcement is on |
BPF_OBJ_PIN | 6 | OBJECT_PIN | Path up to 256 bytes; offered access in file_flags |
BPF_OBJ_GET | 7 | OBJECT_PIN | Requested access must be within the pin’s offered rights |
BPF_PROG_ATTACH | 8 | any attach capability, then the one for the attach type | |
BPF_PROG_DETACH | 9 | as attach | |
BPF_OBJ_GET_INFO_BY_FD | 15 | MAP_READ | Returns a BpfObjectInfo (maps only) |
BPF_PROG_LOAD_ELF | 36 | PROGRAM_LOAD | Custom command; insn_cnt is the file size (1 byte to 1 MiB) and insns the pointer |
BPF_RINGBUF_POLL | 37 | MAP_READ | Custom, nonblocking; returns bytes copied, 0 if empty, -28 if the buffer is too small |
BPF_PROG_UNLOAD | 101 | PROGRAM_LOAD | Fails with busy while attached or referenced |
BPF_MAP_DESTROY | 102 | MAP_WRITE | |
BPF_OBJ_UNPIN | 103 | OBJECT_PIN | OBJECT_ADMIN allows unpinning other owners’ pins |
BPF_BENCH_EXEC | 100 | PRIVILEGED_VERIFY | Only compiled with the verifier-cost feature |
Other command numbers hit the unknown-command path and return -1.
The load path in kernel/src/syscall/bpf.rs derives a BpfLoadAuthorization from the process’s capabilities: PRIVILEGED_VERIFY selects the Privileged verifier tier (otherwise Unprivileged), ACTUATE enables actuation helpers, and MAP_READ / MAP_WRITE decide which maps the program may reference. See Trust Model.
Error mapping (bpf_error_errno): out-of-memory and resource limits map to ENOMEM, busy objects to EBUSY, not-loaded to ENOENT, read-only map, signature rejected and permission denied to EPERM, and everything else to EINVAL. Several paths return a bare -1.
How userspace invokes loading
userspace/tools/signed_bpf_loaderreads a signed container from/var/lib/rkbpf/programs/startup.rbpf(up to 256 KiB), fills aBpfAttrwithinsn_cntas the length andinsnsas the pointer, and callsBPF_PROG_LOAD_ELF. It printsSIGNED_BPF_LOAD_OKorSIGNED_BPF_LOAD_REJECTED.userspace/tools/bpf_loaderis an end-to-end demo that creates an array map and a ring buffer map, loads a hand-assembled raw program withBPF_PROG_LOAD, attaches it to the timer hook, and polls the ring buffer. It also contains negative checks (unprivileged map create, load and attach denials).examples/bpf/README.mdis stale in places: it lists attach type 2 as syscall, whereaskernel_abidefines type 2 as GPIO and syscall entry as 5.