BPF Kernel Functions (kfuncs)
A BPF kernel function, or kfunc, is a kernel function deliberately exposed for BPF programs to call by name, resolved through the kernel’s type metadata (BTF, the BPF Type Format) rather than through a frozen integer table. kfuncs are the modern, intentionally-unstable answer to “what may a BPF program call.” Unlike a helper — whose identifier and signature are user-facing API (UAPI) and therefore frozen forever — a kfunc has no stability guarantee: it can change or disappear from one kernel release to the next, so a BPF program using it may need to be updated for a new kernel (kfuncs.rst, v6.12). A kernel function becomes a kfunc by being tagged with the
__bpf_kfuncmacro and listed in aBTF_KFUNCS_START(...) / BTF_KFUNCS_END(...)set that is registered per program type viaregister_btf_kfunc_id_set(). Each entry may carry flags (KF_ACQUIRE,KF_RELEASE,KF_RET_NULL,KF_TRUSTED_ARGS,KF_SLEEPABLE,KF_RCU, …) that tell the verifier how to reason about reference counting, NULL-ability, pointer trust, and sleeping. The kfunc mechanism exists precisely so the kernel can extend what BPF can call without the UAPI handcuffs that make adding a helper a permanent commitment — which is why new BPF-callable functionality now lands almost exclusively as kfuncs.
This note is the deliberately-unstable half of a contrasting pair. Read it alongside BPF Helper Functions, which covers the stable, UAPI-frozen mechanism. The spine running through both notes is the stability trade-off: a frozen ABI you can rely on across kernels forever, versus a flexible kernel↔kernel interface that the kernel can evolve freely but that couples your program to a specific kernel version.
All version-specific claims below are pinned to Linux 6.12 LTS (released 2024-11-17), verified against the raw source tree at tag
v6.12, with a 6.18 LTS cross-check on theKF_*flag set. Treat statements as “as of 6.12 LTS” unless a delta is called out.
Mental Model
Think of a kfunc as the BPF analogue of EXPORT_SYMBOL_GPL. When a kernel subsystem wants its functions callable by modules, it exports the symbols; modules are kernel↔kernel code, so the interface carries no UAPI promise and the maintainer is free to change it (in-tree callers get fixed up in the same patch). A kfunc is the same idea pointed at BPF: a subsystem maintainer marks a function callable by BPF programs, and because that is also a kernel↔kernel interface, it is bound by none of the strict stability restrictions of a kernel↔user UAPI (kfuncs.rst §3, v6.12). The kernel docs make this analogy explicitly: kfuncs “can be thought of as similar to EXPORT_SYMBOL_GPL, and can therefore be modified or removed by a maintainer of the subsystem they’re defined in when it’s deemed necessary.”
The crucial difference from that analogy is who the caller is. An in-tree module that uses an exported symbol gets updated in the same commit that changes the symbol. A BPF program that calls a kfunc is almost always out-of-tree — it lives in someone’s observability tool or CNI, not in the kernel source — so a kfunc change cannot fix up its callers. That is the whole reason kfuncs are “unstable”: the kernel reserves the right to change them, and out-of-tree BPF programs must keep up.
flowchart LR SRC["kernel function<br/>tagged __bpf_kfunc"] --> SET["BTF_KFUNCS_START(set)<br/>BTF_ID_FLAGS(func, name, KF_*)<br/>BTF_KFUNCS_END(set)"] SET --> REG["register_btf_kfunc_id_set<br/>(BPF_PROG_TYPE_X, &set)"] PROG["BPF program<br/>BPF_CALL src_reg=KFUNC_CALL<br/>imm = btf_id of function"] -->|"resolve by BTF id"| REG REG --> VERIF["Verifier<br/>match arg BTF types,<br/>enforce KF_* semantics"] VERIF --> JIT["JIT emits a direct call<br/>to the kernel function"]
The kfunc call pipeline. What it shows: a kernel function is tagged __bpf_kfunc, grouped into a BTF id set with per-function KF_* flags, and registered for a specific program type. A BPF program calls it with a BPF_CALL whose src_reg marks it a kfunc call and whose immediate holds the BTF id of the function in the running kernel. The verifier resolves the id, matches the program’s argument BTF types against the kfunc’s declared types, and enforces the flag semantics (reference acquire/release, NULL-checks, pointer trust). The insight to take: this is the mirror image of the helper pipeline — where a helper is gated by a get_func_proto() switch and checked via bpf_func_proto arg-type enums, a kfunc is gated by register_btf_kfunc_id_set() and checked via BTF type matching plus KF_* flags. Same two gates (“may you call it?” and “are your arguments shaped right?”), built on BTF instead of a frozen table.
Mechanical Walk-through
Defining a kfunc
There are two ways to expose a function to BPF: make an existing kernel function visible, or write a thin wrapper. Either way the function must be tagged __bpf_kfunc. In 6.12 that macro is (btf.h, v6.12):
#define __bpf_kfunc __used __retain noinlineEach attribute matters: __used and __retain stop the compiler and the linker from discarding the function as dead code (a kfunc is never called from inside the kernel, so without these it could be elided, especially in a link-time-optimised build); noinline stops it being inlined away. The kernel docs are emphatic that developers must not hand-roll these attributes — if a new attribute is needed it goes into the macro so every kfunc is protected uniformly (kfuncs.rst §2.4, v6.12).
A wrapper kfunc is written between __bpf_kfunc_start_defs() and __bpf_kfunc_end_defs(), which suppress the missing-prototype warnings that a function with no header declaration would otherwise raise (its real “declaration” is its BTF):
__bpf_kfunc_start_defs();
__bpf_kfunc struct task_struct *bpf_find_get_task_by_vpid(pid_t nr)
{
return find_get_task_by_vpid(nr);
}
__bpf_kfunc_end_defs();Parameter annotations — teaching the verifier about arguments
kfuncs convey extra per-argument meaning to the verifier by suffixing the parameter name with a __tag. These are not C types; they are markers the verifier reads from BTF (kfuncs.rst §2.2, v6.12):
__sz— the previous pointer argument is a memory region and this integer is its size, so the verifier treats the pointer asPTR_TO_MEMand bounds-checks the length:void bpf_memzero(void *mem, int mem__sz). Without__sz, a kfunc cannot accept avoid *at all.__k— a scalar that must be a known compile-time constant whose value matters for safety (e.g. a type id used to size an allocation); the verifier treats each distinct constant value as a distinct call during state pruning.__uninit— the argument is an uninitialised output (e.g. a dynptr to be filled in), so the verifier does not require it to be initialised first:bpf_dynptr_from_skb(..., struct bpf_dynptr_kern *ptr__uninit).__opt— the buffer for an__sz/__szkpair may be NULL; the kfunc itself must check.__str— the argument is a constant string, so you may pass a string literal directly:bpf_get_file_xattr(..., "xattr_name", ...).
This naming-convention approach is itself a sign of the design philosophy: rather than extending a fixed bpf_func_proto struct (the helper approach), kfuncs encode verifier hints in the source-level argument names, which the compiler bakes into BTF. The contract is flexible because BTF is flexible.
The KF_* flags — verifier semantics
A kfunc set lists each function with BTF_ID_FLAGS(func, name, flags). The flags are bit masks defined in include/linux/btf.h; in 6.12 the complete set is (btf.h, v6.12):
#define KF_ACQUIRE (1 << 0) /* kfunc is an acquire function */
#define KF_RELEASE (1 << 1) /* kfunc is a release function */
#define KF_RET_NULL (1 << 2) /* kfunc returns a pointer that may be NULL */
#define KF_TRUSTED_ARGS (1 << 4) /* kfunc only takes trusted pointer arguments */
#define KF_SLEEPABLE (1 << 5) /* kfunc may sleep */
#define KF_DESTRUCTIVE (1 << 6) /* kfunc performs destructive actions */
#define KF_RCU (1 << 7) /* kfunc takes either rcu or trusted pointer arguments */
#define KF_ITER_NEW (1 << 8) /* BPF iterator constructor */
#define KF_ITER_NEXT (1 << 9) /* BPF iterator next method */
#define KF_ITER_DESTROY (1 << 10) /* BPF iterator destructor */
#define KF_RCU_PROTECTED (1 << 11) /* must be invoked inside an RCU read section */Walking the load-bearing ones:
KF_ACQUIRE marks a kfunc that returns a pointer to a reference-counted object. The verifier then insists the program eventually releases that reference — by calling a KF_RELEASE kfunc, or by transferring it into a map with bpf_kptr_xchg. If any reachable path through the program could leave the reference un-released, the program is rejected. This is how BPF gets safe reference counting without a garbage collector: the verifier proves, statically, that every acquired reference is balanced.
KF_RELEASE marks the matching release kfunc; it consumes exactly one referenced pointer and invalidates all copies of it. A KF_RELEASE kfunc automatically gets KF_TRUSTED_ARGS treatment.
KF_RET_NULL says the returned pointer may be NULL, forcing a NULL-check before any use — the direct analogue of a helper’s RET_PTR_TO_..._OR_NULL return type. It is commonly paired with KF_ACQUIRE (an acquire that might fail returns NULL), though the two are orthogonal.
KF_TRUSTED_ARGS is the strict pointer-trust flag: every pointer argument must be a trusted pointer — one that came either from a tracepoint/struct_ops callback argument (the kernel handed it to you directly) or from a KF_ACQUIRE kfunc, passed in unmodified (zero offset, not obtained by walking another pointer). The docs’ worked example: bpf_task_acquire(task) is allowed when task is a tracepoint arg, but bpf_task_acquire(task->last_wakee) is rejected because the pointer was walked and is therefore no longer trusted; and a task pulled from an arbitrary kretprobe is rejected because BPF cannot guarantee it is still valid (btf.h comment, v6.12). The explicit warning in the docs — “the definition of ‘valid’ pointers is subject to change at any time, and has absolutely no ABI stability guarantees” — is the unstable nature stated outright.
KF_RCU is a weaker form of KF_TRUSTED_ARGS: the kfunc accepts either trusted or RCU-protected pointers. The verifier guarantees no use-after-free (the object will not be freed while you hold it under RCU), but the object’s refcount may already be zero, so an KF_ACQUIRE | KF_RCU kfunc should usually also be KF_RET_NULL.
KF_SLEEPABLE marks a kfunc that may block; it may only be called from a sleepable BPF program (one loaded with BPF_F_SLEEPABLE). KF_DESTRUCTIVE marks a kfunc whose call can crash or reboot the system (e.g. crash_kexec); calling it requires CAP_SYS_BOOT and is otherwise heavily restricted. The KF_ITER_* trio implements the open-coded BPF iterator pattern (constructor / next / destructor), and KF_RCU_PROTECTED requires the call to happen inside an RCU read-side critical section.
Registering a kfunc set — the per-program-type gate
Visibility is granted per BPF program type. A set is declared and registered like this (kfuncs.rst §2.5, v6.12):
BTF_KFUNCS_START(bpf_task_set)
BTF_ID_FLAGS(func, bpf_get_task_pid, KF_ACQUIRE | KF_RET_NULL)
BTF_ID_FLAGS(func, bpf_put_pid, KF_RELEASE)
BTF_KFUNCS_END(bpf_task_set)
static const struct btf_kfunc_id_set bpf_task_kfunc_set = {
.owner = THIS_MODULE,
.set = &bpf_task_set,
};
static int init_subsystem(void)
{
return register_btf_kfunc_id_set(BPF_PROG_TYPE_TRACING, &bpf_task_kfunc_set);
}
late_initcall(init_subsystem);register_btf_kfunc_id_set(prog_type, set) is the kfunc analogue of a helper’s get_func_proto switch: it answers “may this program type call the kfuncs in this set?” A real example from the core BPF code shows one set registered for several program types (helpers.c, v6.12):
BTF_KFUNCS_START(generic_btf_ids)
BTF_ID_FLAGS(func, bpf_obj_new_impl, KF_ACQUIRE | KF_RET_NULL)
BTF_ID_FLAGS(func, bpf_obj_drop_impl, KF_RELEASE)
BTF_ID_FLAGS(func, bpf_task_acquire, KF_ACQUIRE | KF_RCU | KF_RET_NULL)
BTF_ID_FLAGS(func, bpf_task_release, KF_RELEASE)
BTF_ID_FLAGS(func, bpf_task_from_pid, KF_ACQUIRE | KF_RET_NULL)
/* ... rbtree, list, cgroup, throw ... */
BTF_KFUNCS_END(generic_btf_ids)
/* in kfunc_init(): */
register_btf_kfunc_id_set(BPF_PROG_TYPE_TRACING, &generic_kfunc_set);
register_btf_kfunc_id_set(BPF_PROG_TYPE_SCHED_CLS, &generic_kfunc_set);
register_btf_kfunc_id_set(BPF_PROG_TYPE_XDP, &generic_kfunc_set);
register_btf_kfunc_id_set(BPF_PROG_TYPE_STRUCT_OPS,&generic_kfunc_set);
/* ... */The flag combinations here read like documentation: bpf_task_acquire is KF_ACQUIRE | KF_RCU | KF_RET_NULL — it returns a refcounted, maybe-NULL task pointer and accepts RCU-or-trusted args; bpf_task_release is KF_RELEASE. The verifier reads exactly these flags to enforce the acquire/release balance and the NULL-check.
How a kfunc is called — the BTF-id mechanism
The mechanical heart of the helper-vs-kfunc distinction is the call encoding. A kfunc call is a BPF_CALL instruction whose src_reg == BPF_PSEUDO_KFUNC_CALL (value 2), and whose immediate is “btf_id of a BTF_KIND_FUNC in the running kernel” (uapi/linux/bpf.h, v6.12):
/* when bpf_call->src_reg == BPF_PSEUDO_KFUNC_CALL,
* bpf_call->imm == btf_id of a BTF_KIND_FUNC in the running kernel
*/
#define BPF_PSEUDO_KFUNC_CALL 2Contrast a helper call, where src_reg == 0 and the immediate is a frozen integer id from the UAPI enum bpf_func_id. So a helper is named by a number the BPF maintainers assigned and promised never to change; a kfunc is named by a BTF id that only means something relative to the kernel you are running on. If the kernel’s BTF changes — the function is renamed, its signature changes, it is removed — the BPF program’s kfunc reference must be re-resolved (libbpf does this at load by matching kfunc names against the target kernel’s BTF and patching in the live id). That per-kernel resolution is the literal mechanism behind “version-coupled.”
Lifecycle and Deprecation — Why “Unstable” Is the Point
The kernel documents a deliberate stance: “A kfunc will never have any hard stability guarantees. BPF APIs cannot and will not ever hard-block a change in the kernel purely for stability reasons” (kfuncs.rst §3, v6.12). The reasoning is consistent with the EXPORT_SYMBOL_GPL analogy: whether a kfunc gets changed or removed is a case-by-case technical decision weighing how widely it is used, how long it has existed, whether an alternative exists, and the cost of keeping it. Widely-used, long-lived kfuncs are harder to justify removing — but nothing guarantees their survival. The docs explicitly ask BPF developers who depend on a kfunc to make that dependence known upstream, because out-of-tree programs are otherwise invisible to the maintainer deciding the kfunc’s fate.
The documented deprecation flow uses a KF_DEPRECATED flag: a kfunc scheduled for change/removal is flagged, its kernel-doc records remaining lifespan and a recommended replacement, it is kept for a best-effort grace period, and after that the verifier rejects programs that call it (kfuncs.rst §3.1, v6.12).
Uncertain
Verify: the
KF_DEPRECATEDflag. The kfuncs documentation (Documentation/bpf/kfuncs.rst) describesKF_DEPRECATEDand a deprecation procedure, but the#define KF_DEPRECATEDis not present ininclude/linux/btf.hat either tagv6.12orv6.18— at both tags theKF_*bit flags stop atKF_RCU_PROTECTED(bit 11). Reason: doc-and-implementation mismatch in the LTS trees I fetched; the flag was likely added on mainline after these LTS branch points. To resolve: grepgit.kernel.orgforKF_DEPRECATEDininclude/linux/btf.hat the exact target tag before relying on the flag existing. The deprecation policy (flag, grace period, then verifier rejection) is documented; the flag’s presence in 6.12/6.18 LTS source is not confirmed. uncertain
Failure Modes and Common Misunderstandings
“My program loaded on kernel A but the verifier rejects the kfunc on kernel B.” This is kfuncs working as designed. The kfunc was renamed, re-flagged, removed, or never registered for your program type on kernel B. Helpers do not have this failure mode (their ABI is frozen); kfuncs trade exactly that guarantee for flexibility. The fix is to update the program, gate the kfunc behind a feature check, or fall back to a helper.
“Reference X acquired at … is not released” / leaked reference. You called a KF_ACQUIRE kfunc and the verifier found a path where the reference is neither passed to a KF_RELEASE kfunc nor stored via bpf_kptr_xchg. Every reachable path must balance the acquire. This is the verifier’s static refcount proof; the fix is to release on all paths (including the early-return-on-NULL path after a KF_RET_NULL acquire).
“R1 must be referenced or trusted” passing a walked pointer to a KF_TRUSTED_ARGS kfunc. You dereferenced a struct field to get a nested pointer and passed that. Walking breaks trust. Either pass the original trusted pointer, or — if the nested field is genuinely safe — the kernel must declare it so with a BTF_TYPE_SAFE_TRUSTED / BTF_TYPE_SAFE_RCU annotation; you cannot force it from the program side.
“Calling a sleepable kfunc from a non-sleepable program.” A KF_SLEEPABLE kfunc requires the program be loaded BPF_F_SLEEPABLE and attached to a sleepable-capable hook. The fix is to use a sleepable program type or a non-sleeping kfunc.
Assuming a kfunc that looks like a helper is as portable as one. Many kfuncs have bpf_-prefixed names just like helpers (bpf_task_acquire, bpf_cgroup_from_id). The name does not tell you which mechanism it is; the flags and registration do. Treat any bpf_-named call you cannot find in enum bpf_func_id as a kfunc — and therefore version-coupled.
kfuncs vs Helpers — the Trade-off That Defines Both
This is the spine connecting this note to BPF Helper Functions. The two are mirror images for the same task — letting a BPF program call into the kernel — with opposite stability postures:
| Dimension | kfuncs (this note) | Helpers (sibling) |
|---|---|---|
| Selected by | BTF id of a BTF_KIND_FUNC, via BPF_PSEUDO_KFUNC_CALL (src_reg 2) | integer id in imm of BPF_CALL (enum bpf_func_id) |
| Stability | explicitly unstable — may change/disappear per release | UAPI-frozen — id never recycled, signature never broken |
| Type-checked via | BTF type matching + KF_* flags + __tag annotations | struct bpf_func_proto (arg*_type / ret_type enums) |
| Per-prog-type gate | register_btf_kfunc_id_set(prog_type, &set) | get_func_proto() switch returns the proto or NULL |
| Cost to add | low — tag __bpf_kfunc, list it, register the set | high — append to the uapi enum, a permanent commitment |
| Coupled to | the running kernel’s BTF — version-coupled | nothing — portable across kernel versions |
Why the kernel community steers new functionality here. Every helper is a forever-promise: once enum bpf_func_id gains a number, the BPF maintainers must support that exact signature for every future kernel and every program type that ever gained access. That bar is deliberately high, which made the helper set effectively closed (the last 6.12 helper is id 211). kfuncs remove the handcuffs: a subsystem maintainer exposes a function as a kfunc the way they would EXPORT_SYMBOL_GPL, with full freedom to evolve it, and the cost of that freedom — version coupling — is pushed onto the (out-of-tree) BPF program rather than onto the kernel (kfuncs.rst §3, v6.12). For the kernel, that is a strictly better deal: it can grow BPF’s reach as fast as subsystems evolve, without accumulating permanent UAPI debt. The headline modern surfaces — struct_ops/sched_ext, the dynptr and iterator families, the rbtree/list data structures, the networking conntrack helpers — are all kfunc-based for exactly this reason.
The flip side, which BPF Helper Functions covers from its own side, is that helpers buy you something kfuncs cannot: a tool built only on helpers (plus CO-RE for data layout) is portable across a wide span of kernels with no per-kernel adjustment. A tool that calls kfuncs must track the kernels it runs on. The practical guidance follows directly: prefer a helper when one exists and portability matters; reach for a kfunc when you need capability the helper set does not offer and you accept version coupling.
Production Notes
In practice, libbpf hides much of the kfunc machinery: a BPF program declares a kfunc with an extern prototype (often via a generated vmlinux.h from the target kernel’s BTF), and at load time libbpf resolves each kfunc by name against /sys/kernel/btf/vmlinux, patching the live BTF id into the instruction. This is why a kfunc-using program can still load across kernels where the kfunc exists with a compatible signature — and why it fails cleanly where it does not, rather than silently mis-calling. The dependency on the running kernel’s BTF is the same dependency that powers CO-RE data relocation, which is why BTF is the common substrate beneath both the portability story and the kfunc story.
The most consequential real-world kfunc surface is sched_ext (merged in 6.12): an entire pluggable-scheduler framework built on struct_ops whose callbacks call a large, frankly unstable set of scheduling kfuncs (scx_bpf_*). Scheduler authors accept that their BPF schedulers are coupled to kernel versions — a price they pay willingly because helpers could never have delivered that surface area on a frozen-ABI timeline. This is the helper→kfunc trend at its most visible (see struct_ops and sched_ext and BPF Program Types).
Uncertain
Verify: the claim that libbpf resolves kfuncs by name against the running kernel’s BTF at load time and patches in the live BTF id. Reason: this is the well-understood libbpf behaviour but I did not fetch the specific libbpf relocation source (
libbpf.ckfunc-relocation path) at a pinned tag during this task; it is stated from the documented call encoding (BPF_PSEUDO_KFUNC_CALLwithimm == btf_id) plus general knowledge, not from reading libbpf’s relocation code directly. To resolve: read the kfunc relocation handling intools/lib/bpf/libbpf.cat the target tag. uncertain
See Also
- BPF Helper Functions — the contrasting, stable/UAPI-frozen mechanism; read together with this note for the full stability trade-off
- BTF (BPF Type Format) — the type metadata that resolves kfunc ids and matches argument types; the mechanical root of kfuncs’ version coupling
- eBPF Verifier — the engine that enforces
KF_*flag semantics (acquire/release balance, trusted args, NULL-checks) - BPF Program Types — each type’s registered kfunc sets define its kfunc allow-list (the mirror of the helper allow-list)
- struct_ops and sched_ext — the largest modern kfunc-driven surface; the helper→kfunc trend made visible
- CO-RE (Compile Once Run Everywhere) — the BTF-based portability mechanism kfuncs share a substrate with
- CAP_BPF and BPF Privilege Model —
KF_DESTRUCTIVEkfuncs and the capability checks that gate them - Linux eBPF MOC — parent map of content