With eBPF, programmers can execute custom bytecodes in the kernel without modifying the kernel or loading kernel modules. eBPF is closely related to the kernel. Let’s first introduce some related basic concepts.
Linux system is divided into kernel space and user space.
The kernel space is the core of the operating system and has unrestricted and complete access to all hardware, such as memory, storage, and CPU. Since the kernel has such super authority, it is bound to be strictly protected, allowing only the most reliable code to run.
The user space runs non-kernel processes - such as I/O, file systems, etc. These processes have limited access to the hardware through system calls exposed by the kernel. In other words, user space programs must be filtered in kernel space.
The system call interface can meet most needs, and developers still need more flexibility when facing new hardware, file systems, network protocols and even custom system calls.
What if the user code wants to directly access the hardware without modifying the kernel source code? A Linux Kernel Module (LKM) can be used. User space generally needs to access kernel space through system calls, while LKM is directly loaded into the kernel and is a part of the kernel. One of the most valuable features of LKM is that it can be loaded at runtime without compiling the kernel or rebooting the machine.
LKM is very useful, but also introduces a lot of risk. The kernel and user space are different and have different security considerations. Kernel space is reserved for privileged code like the operating system kernel. System calls connect the kernel and user space, allowing user space to perform appropriate operations on the hardware. In other words, LKM can crash the kernel. The close relationship between the module and the kernel makes the security and upgrade costs skyrocket.
eBPF is a new method for accessing Linux kernel services and hardware. This new technology has been used for networking, errors, tracking and firewalls.
This technology can run sandboxed programs in a privileged context such as the operating system kernel. It is used to safely and efficiently extend the capabilities of the kernel without requiring to change kernel source code or load kernel modules.
eBPF programs are event-driven and attached to a code path. The code path contains specific triggers called hooks which execute any attached eBPF programs when they’re passed. Some examples of hooks include network events, system calls, function entries, and kernel tracepoints.
When triggered, the code is compiled first to the BPF bytecode. In turn, the bytecode is verified before it runs, to ensure it doesn’t create a loop. This step prevents the program from compromising the Linux kernel either accidentally or on purpose.
After a program is triggered at a hook, it then makes helper calls. These helper calls are functions that equip eBPF with many features for accessing memory. Helper calls need to be pre-defined by the kernel, but the list of what functions exist continues to grow.
eBPF was initially used as a way to increase observability and security when filtering network packets. However, over time, it became a way to make the implementation of user-supplied code safer, more convenient, and better-performing.
eBPF is typically used to trace user-space processes, and its advantages shine here. It’s a safe and useful method to ensure:
Speed and performance. eBPF can move packet processing from the kernel-space and into the user-space. Likewise, eBPF is a just-in-time (JIT) compiler. After the bytecode is compiled, eBPF is invoked rather than a new interpretation of the bytecode for every method.
Low intrusiveness. When leveraged as a debugger, eBPF doesn’t need to stop a program to observe its state.
Security. Programs are effectively sandboxed, meaning kernel source code remains protected and unchanged. The verification step ensures that resources don’t get choked up with programs that run infinite loops.
Convenience. It’s less work to create code that hooks kernel functions than it is to build and maintain kernel modules.
Unified tracing. eBPF gives you a single, powerful, and accessible framework for tracing processes. This increases visibility and security.
Programmability. Using eBPF helps increase the feature-richness of an environment without adding additional layers. Likewise, since code is run directly in the kernel, it’s possible to store data between eBPF events instead of dumping it like other tracers do.
Expressiveness. eBPF is expressive, capable of performing functions usually only found in high-level languages.
Since eBPF is such a new technology, many things remain unexplored. Best practices around eBPF are still evolving as the technology gains prominence. While no defined set of best practices exist, there are a few things that you can do to ensure effective, efficient programs.
If you’re using eBPF for your ecosystem, I recommend that you:
Use LLVM Clang to compile C into bytecode.
When eBPF first hit the scene, it was necessary to code and assemble the program by hand. Then, developers used the kernel’s assembler to generate bytecode. Fortunately, it’s no longer necessary to do this. Clang provides infrastructure for frontend and tooling in C languages.
Use the BCC toolkit when writing BPF programs.
The BPF Compiler Collection (BCC) is a toolkit that can help you create efficient kernel tracing and manipulation programs. It’s especially suited for tasks related to performance analysis and controlling network traffic.
© 2022 - Sofiane Hamlaooui - Making the world a better place 🌎