BPF (sometimes called extended BPF or eBPF), originally Berkeley Packet Filter (classic BPF or cBPF), is a type of program that can run (as bytecode) in Linux kernelspace without recompiling kernel source or adding modules. Its powers come with significant restrictions, such as a limited stack size (4KB), limited number of instructions for unprivileged programs, etc. Upon loading the program, the BPF verifier checks that the program would not misbehave (e.g., invalid read, kernel pointer leakage) and satisfies all requirements before installing the program. In addition, BPF programs can only invoke specific functions exposed by the kernel, including BPF helper functions and certain kfuncs (i.e., kernel functions).

BPF programs are typically written in C and compiled into a relocatable ELF file using LLVM. You can either use libbpf (BPF CO-RE; start by using libbpf-bootstrap) or BCC (more bulky, as a toolchain is required to compile BPF program to bytecode on the fly) to write and build BPF programs. Under the hood, to actually load and run the kernel, the bpf() syscall is used (with different commands):

  • BPF_PROG_LOAD: loads the program to a fd and verifies it; any JITing to native code (x86, ARM) also happens here
  • BPF_PROG_ATTACH: attach the program to all relevant hooks such as tracepoints
  • BPF_PROG_RUN: well, this runs the program

BPF programs can be installed in various places in the kernel. A common usecase is for performance profiling, diagnostics, and tracing, as BPF programs can attach to various syscalls and kernel functions using tracepoints (statically defined in the kernel), kprobes (dynamically attach to any non-inline functions; less stable), fprobes (dynamic like kprobes; can access function arguments directly without BPF_* functions calls to copy arguments from context), uprobes (can attach to userspace functions), etc. BPF programs can inspect kernel and userspace memory, but can only modify userspace memory (see BPF helper bpf_probe_write_user()), which is still fairly dangerous as it could modify the result of syscalls to fool programs. BPF can also store data in BPF maps (kernel does not impose a limit on map size unlike the stack), which can also be read by the loader program.

In addition to tracing kernel activity, BPF programs are naturally used for network activities as well in the form of XDP (Express Data Path). XDP programs can not only be installed on the system, but also on supported NICs as well (much more stealthy). Such programs can filter, forward, and even modify network traffic, which could be used for good or bad (e.g., disguising malicious C2 traffic).