The eBPF technology implemented in the Linux kernel keeps on gaining traction in various areas: infrastructure, observability, security, debugging, and many more. Its wider adoption reinforces one core concept that drives its development: it must be safe for the kernel to load an eBPF program, and so a program that cannot be proven to be safe must be rejected. This fundamental concept led to the development of some complex mechanisms such as the BPF verifier; but this guarantee is not foolproof: the verifier is written in C and can have the same kinds of bugs as the rest of the kernel. Addressing this kind of risk is a key strategy for Alpha-Omega, which is funding initiatives to improve the security and safety of widely used software projects and technologies. Alpha-Omega allocated a grant to the eBPF Foundation, which teamed up with Bootlin, to improve the security and safety of the eBPF technology by implementing KASAN support in eBPF programs, with the goal of enabling better detection of memory-safety issues for eBPF subsystem developers.
Who verifies the verifier?
The Kernel Address Sanitizer, or KASAN for short, is a development tool aiming to detect invalid memory accesses performed by kernel code. To be able to work, KASAN needs two distinct features:
- a shadow memory region reserved at boot to track the state of the rest of the memory
- some instrumentation inserted by the compiler to prefix each memory access by a call to a small routine that will test the access legitimacy
We don’t need much more knowledge on KASAN internals (but interested readers can go further by reading the Poison, redzones and shadows: inside KASAN blog post on Bootlin’s website) to immediately identify an important consequence for KASAN support: only the code known at compile time can be instrumented. Unfortunately, the kernel is also able to patch itself with some new code at runtime, for a wide variety of features, such as kprobes, ftrace trampolines or eBPF. The last one is particularly interesting (and worrisome, in some ways!) because the runtime-generated code is directly derived from some user input: the goal here is to translate an eBPF object, containing eBPF instructions, to native machine instructions and to have them executed by the kernel. If the verifier misses some incorrect memory accesses in a loaded program, it then becomes a security concern, as such a program could be used to inject malicious payloads into the kernel for the purpose of triggering exploits. Is all hope lost, though? Not really: we can still have KASAN for eBPF programs; the matter is mostly about replicating at runtime the instrumentation step that our kernel compiler is doing!
Updating the eBPF JIT compiler
Just as our kernel compiler (e.g., clang, gcc) does when inserting KASAN instrumentation in our kernel code, we need to find a way to make the kernel insert the needed KASAN instrumentation when it is turning BPF bytecode into native machine code (e.g., x86, ARM64, RISC-V…). We then need to find a relevant place in the whole BPF processing chain in the kernel to do so:

- First, userspace requests to load a program through the dedicated bpf() system call (emitting the BPF_PROG_LOAD command). This can be done through a custom application, or from standard tools such as bpftool.
- The kernel first processes the program through the verifier: it will validate a number of rules against the program to make sure that it is safe to load, for example:
- the program does not run indefinitely,
- the program does not read memory outside of the memory regions passed by the kernel,
- the program does not try to overwrite kernel data,
- etc.
Validating a program is not, in fact, the only mission of the verifier: it will also perform rewrites on some parts of the program, for both security and performance optimization.
- In most cases, the system next runs a JIT compilation step: each architecture implements a JIT compiler able to translate BPF instructions into native machine instructions. Rather than doing it instruction by instruction when the program is executing, the JIT compiler performs the whole program translation right after the verification step to avoid the translation cost when the program execution is triggered.
- The program can then be attached to the corresponding hook point: a kprobe, a tracepoint, a network interface, etc…
- Finally, any time the corresponding attach place is triggered, the kernel automatically executes the BPF program(s) attached to this event.
If we want to insert KASAN memory checks in BPF programs, the perfect place, then, is the JIT compiler: it is running a loop over all instructions in the verified program, and for each instruction, it evaluates the instruction type and properties to generate the corresponding native instruction(s). We can then use this opportunity to identify all the instructions accessing memory, and prefix those by the relevant instrumentation. Before jumping into the implementation, let’s set a few requirements and assumptions:
- KASAN is a development feature, not a production feature; the actual users will be kernel developers, not BPF end users. Moreover, the end goal is to detect and fix verifier issues; hence it may not make sense to implement those KASAN checks for all architectures supporting BPF. This work is then focused on x86 (the most popular architecture running BPF in the industry), and so it will mostly affect the x86_64 JIT compiler.
- While most of the memory is covered by some shadow memory, there’s still one type of memory that needs more complex instrumentation: stack memory. Indeed, variables sitting on the stack need to be carefully laid out to benefit from KASAN (for example, we need a systematic “gap” between stack variables to get proper KASAN redzones). The kernel cannot assume that BPF programs have been built with such concern, hence making it pretty difficult to instrument the corresponding stack accesses. Moreover, stack memory can already be protected thanks to some guard pages (see CONFIG_VMAP_STACK), so lacking KASAN instrumentation here does not leave too big a gap. We will then deliberately discard KASAN instrumentation for stack accesses in BPF programs.
- There are different methods available to implement KASAN: generic (inline or outline), software tag based, or hardware tag based. As our main target is the x86_64 architecture, only generic KASAN can be used. For the sake of simplicity, we will implement a generic outline KASAN instrumentation, meaning that each instrumented access will be prefixed by an explicit call to a KASAN check function.
With our specification now complete, we can take a look at the implementation details!
Emitting KASAN calls in x86_64
The JIT compilation loop going over all BPF instructions in a program can be found in arch/x86/net/bpf_jit_comp.c, in the do_jit() function. This function is mostly a loop containing a large switch/case testing each possible BPF instruction type:
switch (insn->code) {
/* ALU */
case BPF_ALU | BPF_ADD | BPF_X:
case BPF_ALU | BPF_SUB | BPF_X:
case BPF_ALU | BPF_AND | BPF_X:
case BPF_ALU | BPF_OR | BPF_X:
case BPF_ALU | BPF_XOR | BPF_X:
case BPF_ALU64 | BPF_ADD | BPF_X:
case BPF_ALU64 | BPF_SUB | BPF_X:
case BPF_ALU64 | BPF_AND | BPF_X:
case BPF_ALU64 | BPF_OR | BPF_X:
case BPF_ALU64 | BPF_XOR | BPF_X:
maybe_emit_mod(&prog, dst_reg, src_reg,
BPF_CLASS(insn->code) == BPF_ALU64);
b2 = simple_alu_opcodes[BPF_OP(insn->code)];
EMIT2(b2, add_2reg(0xC0, dst_reg, src_reg));
break;
[...]
To identify the instructions actually accessing memory, we can take a look at the BPF ISA documentation, which is fortunately pretty straightforward to interpret: from it we can understand that the instructions accessing memory are BPF_ST, BPF_STX or BPF_LDX, with the BPF_MEM or BPF_MEMSX bits set in the mode field. The “case” arms matching those conditions span in fact over multiple places, because of other attributes that can be set on the target instructions (for example, BPF_ATOMIC). Once we have identified those places, we can augment the JIT compiler by writing a helper that will emit at runtime the necessary instructions to call KASAN checks. Those functions are already implemented in the kernel, and are the ones called by the kernel code when generic outline KASAN is enabled: those are the __asan_load_X/__asan_store_X functions in mm/kasan/generic.c. We can’t just write a bare call to those functions as we would in C though; we have to emit the relevant function calls (meaning: with bare x86_64 machine-level instructions), and so to respect the requirements enforced by our platform ABI. As we are focusing on x86_64, the relevant ABI is AMD64 System V: this specification first defines how arguments are passed to a function. In our case, the list of arguments for KASAN functions is pretty straightforward: they take a single argument containing the address to check (there’s no parameter for the tested access size, there is rather one KASAN helper per access size). The ABI states that the first argument of a function must be stored in the %rdi register, so we just have to store the value of the tested address in %rdi. Where exactly is this address sitting when we want to emit our check? Looking back at the BPF ISA, we know that it is either in the destination register or the source register of our instruction, but those are BPF registers! Fortunately, there is a direct mapping between BPF registers and x86 registers. We would have to deduce, from the source/destination operand in the BPF instruction, which x86_64 register is actually containing the address value, but this lookup is quite a common operation in the JIT compiler, so we don’t need to do it manually, and we can directly emit our address move with a single EMIT_mov macro: EMIT_mov(BPF_REG_1, addr_reg) (BPF_REG_1 is pointing to %rdi in our case). But this instruction alone is not enough: taking a look back at our bpf instructions specification, we see that they can also provide an offset from the value in the source/destination register (stored in offset field of the instruction), so we also have to emit an ADD instruction to sum the offset to the value we have set in %rdi.
Aside from the target address, we also need to deduce from the raw BPF instruction the correct KASAN helper to call in front of our memory access:
- is the instruction a read (BPF_LDX) or a write (BPF_ST, BPF_STX)?
- What is the size of the access?
- A single byte (BPF_B)?
- A half-word (BPF_H)?
- A full word (BPF_W)?
- A double-word (BPF_DW)?
From this matrix, we can derive the exact KASAN helper to call:
| BPF_B | BPF_H | BPF_W | BPF_DW | |
|---|---|---|---|---|
| BPF_LD | __asan_load1 | __asan_load2 | __asan_load4 | __asan_load8 |
| BPF_ST/BPF_STX | __asan_store1 | __asan_store2 | __asan_store4 | __asan_store8 |
The System V specification also splits the available registers list into two categories, when considering a function call:
- The caller-saved registers: the calling function is in charge of saving/restoring registers on/from the stack if it wants to preserve values and be able to use those after the called function returns
- The callee-saved registers: the called function must preserve the registers content so that the caller can keep on using them once the call has returned. This does not mean that those registers can’t be used at all by the called function of course, but it must take care of saving/restoring those registers on/from the stack, if it uses them.
The direct consequence of this split is that we must be careful about the list of registers to save before calling the KASAN helper, as the called helper has the permission to completely overwrite the caller-saved registers! Not preserving those registers in the caller would likely break any code running after the call to the KASAN check. This means that, on x86_64, before calling a function, we have to save at least:
- %rax
- %rcx
- %rdx
- %rsi
- %rdi
- %r8
- %r9
- %r10
- %r11
That’s quite a long list of registers to save, and so quite some additional overhead in front of each memory access. Classic compilers are smarter than that when dealing with function calls: they are able to identify what registers are actually used in the current function, so they can save only those that are relevant. In our case, we have limited info about used registers; the JIT compiler could in theory get the information from the verifier, but for the sake of simplicity, and because overhead is not a primary concern for this feature (this is a development/debug feature, which by default already brings quite a large overhead), we can afford for now to keep the algorithm simple and save/restore all those registers. We can then just emit a list of PUSH instructions with the relevant register identifiers.
Once we have saved our registers, we can finally emit a call to the KASAN helper that will check the memory access. Once again, this is a pretty common operation in the JIT compiler, so we can directly use the corresponding JIT compiler helper: emit_call(&prog, kasan_func, ip)
- The prog parameter is the buffer receiving the instructions being emitted
- The kasan_func parameter is the address of the KASAN helper we want to call (and so, the one that will read the “address+offset” value we have put in the %rdi register)
- In our case, the x86_64 call instruction we are using works with an offset relative to the offset of the instruction following the call instruction; we must then provide the current offset in the JITed program to the helper so that it can compute the final offset to put in the generated instruction.
Once this step is reached, we are pretty much done! We do not even need to take a look at any return value, as those KASAN helpers do not return values; they just call the needed functions to emit a KASAN report (or “splat”) if an access is deemed invalid. The final step just involves restoring all the registers we have saved on the stack, in reverse order, with the relevant POP instruction. We can wrap this whole sequence into a dedicated JIT helper, and call this helper in front of each memory-accessing instruction being JITed. Of course, there are some small details here that are deliberately skipped to keep the overall picture simpler: for example, as we are emitting a function call, we have to make sure not to break call depth tracking (used for retbleed mitigation) when the corresponding mitigation is enabled.
Skipping unneeded KASAN checks
While the overall algorithm looks pretty straightforward to implement, the devil, as usual, hides in the details. Aside from the basic requirement of not breaking any other JIT-related feature (which would be embarrassingly easy to do without noticing, if not for the extensive BPF selftests suite), one major concern for this work is to avoid over-instrumenting the programs. As mentioned above, BPF programs are frequently manipulating stack memory for various operations. If we add instrumentation to stack accesses, it can at best make the kernel pay the full KASAN price for an access that will always be valid, or at worst trigger a crash (because of the way the verifier and JIT compilers are dealing with stack accesses, for example through intermediate “virtual” BPF registers). We then want to avoid adding instrumentation to such instructions, but how to identify those correctly? Just looking at the source/target operands in the instructions when the JIT compiler is running, hoping to catch some BPF_REG_FP (the BPF pointer on the stack, a.k.a R10) is not enough: maybe the BPF compiler has generated a stack accessing instruction using an intermediate register; in this case the JIT is clueless about the fact that this intermediate register actually points to the stack. The knowledge is still available somewhere though: the verifier knows exactly what kind of memory each instruction is accessing! It tracks each register state in fine detail, for all the paths the program may take. This whole state is saved in a bpf_verifier_env structure that we can then pass to the JIT compiler to get more info about our program. We can use the opportunity to add a new array field in this structure to mark all the instructions that access stack memory. Is it enough? Not really, because as stated earlier, the verifier performs plenty of modifications on the program after the verification; for example if it rewrites a stack accessing instruction into a new set of instructions, we lose the marking. We do not have many options to deal with those small corner cases, which are in fact quite common: it is then better to be safe than sorry; if we cannot reliably detect whether an instruction is accessing the stack or not, we have to emit some KASAN instrumentation for it, even at the risk of emitting an ultimately unnecessary check.
This specific issue is a good example of the challenges that can appear during the implementation, and how working with the community (through the discussions on the mailing lists, or by presenting those topics during conferences) helps identify and solve them.
Integrating the feature in the kernel
As with any new feature being added to the Linux kernel source code (and in fact, in pretty much every software project!), adding KASAN to eBPF programs has not been a one-night project: this work has started many months ago, and while the feature is now merged in the BPF tree, no kernel release includes the new feature yet. Ensuring that this work went in the right direction involved multiple steps:
- After a research/documentation step, a Request For Comments (RFC) has been posted on the BPF mailing list to discuss the implementation plan with the community and to refine the scope of the feature. This first RFC did not come with any code yet; it was really about validating a plan before starting to develop anything.
- A few weeks later, a new RFC has been sent, this time with some code showcasing how the feature would work. This code was of course not ready for integration, but proved the feasibility of the discussed plan.
- To ease discussions and work, the project was also presented and discussed at the Linux Storage, Filesystem, Memory Management and BPF conference (LSFMMBPF) in Zagreb, Croatia, in May 2026. It was an opportunity to discuss the deep technical details in person, and to validate that the implementation was moving in the right direction.
- During the months that followed the conference, multiple iterations of this work have been sent to the BPF mailing list, aiming this time to get the feature integrated into the kernel. This step followed the traditional kernel integration process, with each new revision being sent to fix issues and concerns raised either by the BPF testing infrastructure or by the reviewers, or to adjust the implementation.
- The feature has eventually been merged into the kernel as revision 9 in September. The feature is not available to users yet: this is only the first step to make it publicly available; we still need to have a new release cycle started with a BPF pull request sent to Linus for inclusion in the next kernel version. The estimated first version that will thus receive this work is 7.4, which should be released in December 2026.
This series has been merged at a pretty good time, as the Linux Plumbers conference took place in Prague from October 5 to 7. The conference has a dedicated BPF track, so this was the perfect opportunity to propose another talk to clarify the feature implementation and its current status with the community (interested readers can find the presentation slides here).

As the feature has been merged pretty recently, there were no statistics yet to present about the emitted instrumentation: no bug has been caught so far by KASAN in BPF programs. The talk still brought some valuable outcomes though: people in charge of developing and maintaining the syzkaller kernel fuzzer were present in the audience, so the follow-up discussions led to the conclusion that it would be greatly beneficial to update syzkaller to make it actually run eBPF programs, hoping that those programs will eventually trigger KASAN splats if the verifier lets some invalid accesses slip through (syzkaller already knows how to fuzz eBPF programs, but it generally does not go beyond the verification step).
How to test it
In the meantime, interested readers can already start experimenting with it:
- clone the kernel sources from the bpf-next tree:
git clone https://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf-next.git
- configure the kernel: make sure to have both CONFIG_KASAN_GENERIC and CONFIG_BPF_JIT (or even better, CONFIG_BPF_JIT_ALWAYS_ON, otherwise you will also have to make sure that JIT is enabled at runtime too)
- build and boot your custom kernel
- that’s it, you can start loading and executing BPF programs. If any of them performs an invalid access, you should see a KASAN splat such as this one, generated from the BPF selftests during the development of the feature (notice the culprit function name at the beginning of the report):
[ 2217.398190] ================================================================== [ 2217.398606] BUG: KASAN: slab-use-after-free in bpf_prog_dac77fbc04532208_bpf_kasan_uaf+0x2e/0x4d [ 2217.398622] Read of size 1 at addr ffff888111b8f500 by task test_progs/199 [ 2217.398622] [ 2217.398622] CPU: 3 UID: 0 PID: 199 Comm: test_progs Tainted: G B OE 7.0.0-kasan+ #202 PREEMPT(full) [ 2217.398622] Tainted: [B]=BAD_PAGE, [O]=OOT_MODULE, [E]=UNSIGNED_MODULE [ 2217.398622] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS Arch Linux 1.17.0-2-2 04/01/2014 [ 2217.398622] Call Trace: [ 2217.398622] <TASK> [ 2217.398622] dump_stack_lvl+0x5d/0x80 [ 2217.398622] print_report+0x153/0x4c6 [ 2217.398622] ? bpf_prog_dac77fbc04532208_bpf_kasan_uaf+0x2e/0x4d [ 2217.398622] ? __virt_addr_valid+0x225/0x4c0 [ 2217.398622] ? bpf_prog_dac77fbc04532208_bpf_kasan_uaf+0x2e/0x4d [ 2217.398622] kasan_report+0xe4/0x1b0 [ 2217.398622] ? bpf_prog_dac77fbc04532208_bpf_kasan_uaf+0x2e/0x4d [ 2217.398622] bpf_prog_dac77fbc04532208_bpf_kasan_uaf+0x2e/0x4d [ 2217.398622] ? trace_hardirqs_on+0x53/0x1a0 [ 2217.398622] ? kasan_quarantine_put+0xdd/0x220 [ 2217.398622] bpf_test_run+0x376/0x910 [...] [ 2217.398622] Allocated by task 199: [ 2217.398622] kasan_save_stack+0x30/0x50 [ 2217.398622] kasan_save_track+0x14/0x30 [ 2217.398622] __kasan_kmalloc+0x7f/0x90 [ 2217.398622] bpf_kfunc_kasan_uaf_1+0x3b/0x80 [bpf_testmod] [ 2217.398622] bpf_prog_dac77fbc04532208_bpf_kasan_uaf+0x19/0x4d [ 2217.398622] bpf_test_run+0x376/0x910 [ 2217.398622] bpf_prog_test_run_skb+0x1247/0x3060 [ 2217.398622] __sys_bpf+0xf3e/0x5100 [...]
The BPF bug hunting season is now open
Despite not being available in a public Linux kernel release yet, the feature is in fact already actively being exercised: it is present in the bpf-next branch in the BPF maintainers repository, and so it is already frequently running in the testing infrastructure for the BPF subsystem on GitHub (each series sent on the mailing list is rebased on top of this integration branch and tested with the wide variety of BPF programs from the BPF selftests suite). We can in fact see some splats already, but those are expected: the recently merged KASAN feature has also received its own set of tests that validate that some splats are indeed properly emitted when an invalid access is performed; the only peculiarity of those splats is that the testing infrastructure properly ignores them (and only them). So far, the automation has not detected any spurious KASAN splat, but it is ready to raise alerts if any ever occur. Also, as stated above, teaching syzkaller/syzbot to exercise the BPF subsystem further will possibly reveal more memory issues in BPF programs in the future.
Thanks to this cooperation between Alpha-Omega, the eBPF Foundation and Bootlin, the eBPF community now has a new development tool available that will hopefully help catch bugs in the eBPF subsystem earlier: if a change in the verifier ever gives BPF programs the opportunity to perform an invalid memory access, this should be caught right at patch submission time, without it having a chance to reach a released kernel.
