* [PATCH] bpf: fix btf_types_are_same for cross-BTF type comparison
@ 2026-04-07 8:09 chenyuan_fl
2026-04-07 8:58 ` Leon Hwang
` (4 more replies)
0 siblings, 5 replies; 35+ messages in thread
From: chenyuan_fl @ 2026-04-07 8:09 UTC (permalink / raw)
To: martin.lau, ast, daniel, andrii, eddyz87, memxor, song,
yonghong.song, jolsa
Cc: bpf, linux-kernel, Yuan Chen
From: Yuan Chen <chenyuan@kylinos.cn>
When comparing types from different BTF objects (e.g., module BTF vs
vmlinux BTF), the original btf_types_are_same() returns false because:
- Type IDs are local to each BTF
- Pointer comparison of btf_type_by_id results always fails
This prevents kfuncs with KF_IMPLICIT_ARGS flag from modules (like
bpf_kfunc_multi_st_ops_test_1_assoc) from properly recognizing implicit
arguments such as 'struct bpf_prog_aux *', causing the verifier to not
inject the aux pointer value during fixup.
Fix by comparing actual type content (kind, size, name) when BTFs are
different instead of comparing pointers.
Signed-off-by: Yuan Chen <chenyuan@kylinos.cn>
---
kernel/bpf/btf.c | 32 ++++++++++++++++++++++++++++----
1 file changed, 28 insertions(+), 4 deletions(-)
diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c
index a62d78581207..daad28ae32e5 100644
--- a/kernel/bpf/btf.c
+++ b/kernel/bpf/btf.c
@@ -7432,15 +7432,39 @@ int btf_struct_access(struct bpf_verifier_log *log,
* the same. Trivial ID check is not enough due to module BTFs, because we can
* end up with two different module BTFs, but IDs point to the common type in
* vmlinux BTF.
+ *
+ * When comparing types across different BTF objects (e.g., module BTF vs
+ * vmlinux BTF), we need to compare the actual type content (name, kind, size)
+ * since type IDs may differ between BTF objects even for the same type.
*/
bool btf_types_are_same(const struct btf *btf1, u32 id1,
const struct btf *btf2, u32 id2)
{
- if (id1 != id2)
- return false;
+ const struct btf_type *t1, *t2;
+
+ /* If same BTF object, ID comparison is sufficient */
if (btf1 == btf2)
- return true;
- return btf_type_by_id(btf1, id1) == btf_type_by_id(btf2, id2);
+ return id1 == id2;
+
+ /* Different BTF objects - compare actual type content.
+ * Type IDs may differ between module BTF and vmlinux BTF,
+ * so we need to check if the types are semantically identical.
+ */
+ t1 = btf_type_by_id(btf1, id1);
+ t2 = btf_type_by_id(btf2, id2);
+ if (!t1 || !t2)
+ return false;
+
+ /* Must be same kind and have same name */
+ if (BTF_INFO_KIND(t1->info) != BTF_INFO_KIND(t2->info))
+ return false;
+ if (t1->size != t2->size)
+ return false;
+ if (strcmp(__btf_name_by_offset(btf1, t1->name_off),
+ __btf_name_by_offset(btf2, t2->name_off)) != 0)
+ return false;
+
+ return true;
}
bool btf_struct_ids_match(struct bpf_verifier_log *log,
--
2.53.0
^ permalink raw reply related [flat|nested] 35+ messages in thread* Re: [PATCH] bpf: fix btf_types_are_same for cross-BTF type comparison 2026-04-07 8:09 [PATCH] bpf: fix btf_types_are_same for cross-BTF type comparison chenyuan_fl @ 2026-04-07 8:58 ` Leon Hwang 2026-04-07 9:01 ` bot+bpf-ci ` (3 subsequent siblings) 4 siblings, 0 replies; 35+ messages in thread From: Leon Hwang @ 2026-04-07 8:58 UTC (permalink / raw) To: chenyuan_fl, martin.lau, ast, daniel, andrii, eddyz87, memxor, song, yonghong.song, jolsa Cc: bpf, linux-kernel, Yuan Chen pls also check sashiko's review: https://sashiko.dev/#/patchset/20260407080900.551797-1-chenyuan_fl%40163.com. Target tree should be specified: [PATCH bpf] bpf: ... On 7/4/26 16:09, chenyuan_fl@163.com wrote: > From: Yuan Chen <chenyuan@kylinos.cn> > > When comparing types from different BTF objects (e.g., module BTF vs > vmlinux BTF), the original btf_types_are_same() returns false because: > - Type IDs are local to each BTF > - Pointer comparison of btf_type_by_id results always fails > > This prevents kfuncs with KF_IMPLICIT_ARGS flag from modules (like > bpf_kfunc_multi_st_ops_test_1_assoc) from properly recognizing implicit > arguments such as 'struct bpf_prog_aux *', causing the verifier to not > inject the aux pointer value during fixup. Should add a selftest to verify the BUG and this fix. Thanks, Leon > > Fix by comparing actual type content (kind, size, name) when BTFs are > different instead of comparing pointers. > > Signed-off-by: Yuan Chen <chenyuan@kylinos.cn> > --- [...] ^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH] bpf: fix btf_types_are_same for cross-BTF type comparison 2026-04-07 8:09 [PATCH] bpf: fix btf_types_are_same for cross-BTF type comparison chenyuan_fl 2026-04-07 8:58 ` Leon Hwang @ 2026-04-07 9:01 ` bot+bpf-ci 2026-04-07 11:19 ` Alan Maguire ` (2 subsequent siblings) 4 siblings, 0 replies; 35+ messages in thread From: bot+bpf-ci @ 2026-04-07 9:01 UTC (permalink / raw) To: chenyuan_fl, martin.lau, ast, daniel, andrii, eddyz87, memxor, song, yonghong.song, jolsa Cc: bpf, linux-kernel, chenyuan, ast, andrii, daniel, martin.lau, eddyz87, yonghong.song, clm, ihor.solodrai [-- Attachment #1: Type: text/plain, Size: 788 bytes --] > diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c > index a62d78581207..daad28ae32e5 100644 > --- a/kernel/bpf/btf.c > +++ b/kernel/bpf/btf.c > @@ -7432,15 +7432,39 @@ int btf_struct_access(struct bpf_verifier_log *log, [ ... ] > + return true; > } This looks like a bug fix for btf_types_are_same() which was introduced by commit 2ab3b3808eb1 ("bpf: Make BTF type match stricter for release arguments"). Should this patch include a Fixes: tag? Suggested tag: Fixes: 2ab3b3808eb1 ("bpf: Make BTF type match stricter for release arguments") --- AI reviewed your patch. Please fix the bug or email reply why it's not a bug. See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md CI run summary: https://github.com/kernel-patches/bpf/actions/runs/24072076699 ^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH] bpf: fix btf_types_are_same for cross-BTF type comparison 2026-04-07 8:09 [PATCH] bpf: fix btf_types_are_same for cross-BTF type comparison chenyuan_fl 2026-04-07 8:58 ` Leon Hwang 2026-04-07 9:01 ` bot+bpf-ci @ 2026-04-07 11:19 ` Alan Maguire 2026-05-15 18:27 ` Ihor Solodrai 2026-06-01 6:46 ` [PATCH bpf v2 0/2] bpf: Fix kfunc implicit arg injection and add selftest chenyuan_fl 4 siblings, 0 replies; 35+ messages in thread From: Alan Maguire @ 2026-04-07 11:19 UTC (permalink / raw) To: chenyuan_fl, martin.lau, ast, daniel, andrii, eddyz87, memxor, song, yonghong.song, jolsa Cc: bpf, linux-kernel, Yuan Chen On 07/04/2026 09:09, chenyuan_fl@163.com wrote: > From: Yuan Chen <chenyuan@kylinos.cn> > > When comparing types from different BTF objects (e.g., module BTF vs > vmlinux BTF), the original btf_types_are_same() returns false because: > - Type IDs are local to each BTF > - Pointer comparison of btf_type_by_id results always fails > > This prevents kfuncs with KF_IMPLICIT_ARGS flag from modules (like > bpf_kfunc_multi_st_ops_test_1_assoc) from properly recognizing implicit > arguments such as 'struct bpf_prog_aux *', causing the verifier to not > inject the aux pointer value during fixup. > > Fix by comparing actual type content (kind, size, name) when BTFs are > different instead of comparing pointers. > > Signed-off-by: Yuan Chen <chenyuan@kylinos.cn> > --- > kernel/bpf/btf.c | 32 ++++++++++++++++++++++++++++---- > 1 file changed, 28 insertions(+), 4 deletions(-) > > diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c > index a62d78581207..daad28ae32e5 100644 > --- a/kernel/bpf/btf.c > +++ b/kernel/bpf/btf.c > @@ -7432,15 +7432,39 @@ int btf_struct_access(struct bpf_verifier_log *log, > * the same. Trivial ID check is not enough due to module BTFs, because we can > * end up with two different module BTFs, but IDs point to the common type in > * vmlinux BTF. > + * > + * When comparing types across different BTF objects (e.g., module BTF vs > + * vmlinux BTF), we need to compare the actual type content (name, kind, size) > + * since type IDs may differ between BTF objects even for the same type. > */ > bool btf_types_are_same(const struct btf *btf1, u32 id1, > const struct btf *btf2, u32 id2) > { > - if (id1 != id2) > - return false; > + const struct btf_type *t1, *t2;it wo > + > + /* If same BTF object, ID comparison is sufficient */ > if (btf1 == btf2) > - return true; > - return btf_type_by_id(btf1, id1) == btf_type_by_id(btf2, id2); > + return id1 == id2; > + > + /* Different BTF objects - compare actual type content. > + * Type IDs may differ between module BTF and vmlinux BTF, > + * so we need to check if the types are semantically identical. > + */ > + t1 = btf_type_by_id(btf1, id1); > + t2 = btf_type_by_id(btf2, id2); > + if (!t1 || !t2) > + return false; > + > + /* Must be same kind and have same name */ > + if (BTF_INFO_KIND(t1->info) != BTF_INFO_KIND(t2->info)) > + return false; > + if (t1->size != t2->size) > + return false; > + if (strcmp(__btf_name_by_offset(btf1, t1->name_off), > + __btf_name_by_offset(btf2, t2->name_off)) != 0) > + return false; > + > + return true; > } > > bool btf_struct_ids_match(struct bpf_verifier_log *log, This feels insufficient as a type equality check; for pointer types as cited in the commit message, it will only really work if the pointed-to type ids (t1->size, t2->size above) are identical ids. That may work narrowly in the specific case you're dealing with but I'd suggest looking at how libbpf dedup handles this and figure out a depth-limited recursive equivalence check that winds up comparing structs/base types rather than reference types. ^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH] bpf: fix btf_types_are_same for cross-BTF type comparison 2026-04-07 8:09 [PATCH] bpf: fix btf_types_are_same for cross-BTF type comparison chenyuan_fl ` (2 preceding siblings ...) 2026-04-07 11:19 ` Alan Maguire @ 2026-05-15 18:27 ` Ihor Solodrai 2026-06-01 6:46 ` [PATCH bpf v2 0/2] bpf: Fix kfunc implicit arg injection and add selftest chenyuan_fl 4 siblings, 0 replies; 35+ messages in thread From: Ihor Solodrai @ 2026-05-15 18:27 UTC (permalink / raw) To: chenyuan_fl, martin.lau, ast, daniel, andrii, eddyz87, memxor, song, yonghong.song, jolsa Cc: bpf, linux-kernel, Yuan Chen On 4/7/26 1:09 AM, chenyuan_fl@163.com wrote: > From: Yuan Chen <chenyuan@kylinos.cn> > > When comparing types from different BTF objects (e.g., module BTF vs > vmlinux BTF), the original btf_types_are_same() returns false because: > - Type IDs are local to each BTF > - Pointer comparison of btf_type_by_id results always fails > > This prevents kfuncs with KF_IMPLICIT_ARGS flag from modules (like > bpf_kfunc_multi_st_ops_test_1_assoc) from properly recognizing implicit > arguments such as 'struct bpf_prog_aux *', causing the verifier to not > inject the aux pointer value during fixup. Hi Yuan, Could you please provide an example of what is failing? For example, a selftest, verifier log, or at least the (btf, type_id) pair? I am guessing the root cause might be that the distill_base deletes the types used by implicit args (such as bpf_prog_aux), causing .BTF.base to be incomplete, and module BTF duplicating kernel types. But I'm speculating here due to lack of an example. Module BTF is supposed to reference base BTF types through the distilled base. If struct bpf_prog_aux ends up as a separate module-local type instead of resolving to the vmlinux BTF type, we should fix the distill_base/relocation handling so the module BTF points at the canonical vmlinux type ID. Then the existing strict comparison will work unchanged. To Alan's point, changing btf_types_are_same() to compare only kind, size and name makes it too permissive. Two different structs can have the same name and size, but different members or semantics. I think we should identify the root cause and fix it. This patch is a no go IMO. Thanks. > > Fix by comparing actual type content (kind, size, name) when BTFs are > different instead of comparing pointers. > > Signed-off-by: Yuan Chen <chenyuan@kylinos.cn> > --- > kernel/bpf/btf.c | 32 ++++++++++++++++++++++++++++---- > 1 file changed, 28 insertions(+), 4 deletions(-) > > diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c > index a62d78581207..daad28ae32e5 100644 > --- a/kernel/bpf/btf.c > +++ b/kernel/bpf/btf.c > @@ -7432,15 +7432,39 @@ int btf_struct_access(struct bpf_verifier_log *log, > * the same. Trivial ID check is not enough due to module BTFs, because we can > * end up with two different module BTFs, but IDs point to the common type in > * vmlinux BTF. > + * > + * When comparing types across different BTF objects (e.g., module BTF vs > + * vmlinux BTF), we need to compare the actual type content (name, kind, size) > + * since type IDs may differ between BTF objects even for the same type. > */ > bool btf_types_are_same(const struct btf *btf1, u32 id1, > const struct btf *btf2, u32 id2) > { > - if (id1 != id2) > - return false; > + const struct btf_type *t1, *t2; > + > + /* If same BTF object, ID comparison is sufficient */ > if (btf1 == btf2) > - return true; > - return btf_type_by_id(btf1, id1) == btf_type_by_id(btf2, id2); > + return id1 == id2; > + > + /* Different BTF objects - compare actual type content. > + * Type IDs may differ between module BTF and vmlinux BTF, > + * so we need to check if the types are semantically identical. > + */ > + t1 = btf_type_by_id(btf1, id1); > + t2 = btf_type_by_id(btf2, id2); > + if (!t1 || !t2) > + return false; > + > + /* Must be same kind and have same name */ > + if (BTF_INFO_KIND(t1->info) != BTF_INFO_KIND(t2->info)) > + return false; > + if (t1->size != t2->size) > + return false; > + if (strcmp(__btf_name_by_offset(btf1, t1->name_off), > + __btf_name_by_offset(btf2, t2->name_off)) != 0) > + return false; > + > + return true; > } > > bool btf_struct_ids_match(struct bpf_verifier_log *log, ^ permalink raw reply [flat|nested] 35+ messages in thread
* [PATCH bpf v2 0/2] bpf: Fix kfunc implicit arg injection and add selftest 2026-04-07 8:09 [PATCH] bpf: fix btf_types_are_same for cross-BTF type comparison chenyuan_fl ` (3 preceding siblings ...) 2026-05-15 18:27 ` Ihor Solodrai @ 2026-06-01 6:46 ` chenyuan_fl 2026-06-01 6:46 ` [PATCH bpf v2 1/2] bpf: Fix kfunc implicit arg inject type detection to prevent invalid pointer deref chenyuan_fl 2026-06-01 6:46 ` [PATCH bpf v2 2/2] selftests/bpf: Add regression test for kfunc implicit arg injection with stale register chenyuan_fl 4 siblings, 2 replies; 35+ messages in thread From: chenyuan_fl @ 2026-06-01 6:46 UTC (permalink / raw) To: chenyuan_fl Cc: andrii, ast, bpf, chenyuan, daniel, eddyz87, jolsa, linux-kernel, martin.lau, memxor, song, yonghong.song From: Yuan Chen <chenyuan@kylinos.cn> v1: https://lore.kernel.org/bpf/20260407080900.551797-1-chenyuan_fl@163.com/ v1 took the approach of making btf_types_are_same() cross-BTF aware by comparing kind, size and name. This was correctly rejected by reviewers (Alan Maguire, sashiko) because: - Two different structs can have the same name and size but different members, making this too permissive - The real root cause is that pahole 1.30 generated BTF that broke the distilled base deduplication for modules, causing the module to retain its own copy of struct bpf_prog_aux instead of resolving to the vmlinux definition v2 takes a different approach: instead of weakening btf_types_are_same(), it introduces a two-layer detection in the verifier's check_kfunc_args() that cleanly handles the mismatch case. A selftest is added (patch 2) as requested by Leon and Alan. Yuan Chen (2): bpf: Fix kfunc implicit arg inject type detection to prevent invalid pointer deref selftests/bpf: Add regression test for kfunc implicit arg injection with stale register kernel/bpf/verifier.c | 48 ++++++++++++++++++- .../bpf/prog_tests/test_struct_ops_assoc.c | 5 ++ .../selftests/bpf/progs/struct_ops_assoc.c | 40 ++++++++++++++++ .../selftests/bpf/test_kmods/bpf_testmod.c | 9 ++++ .../bpf/test_kmods/bpf_testmod_kfunc.h | 1 + 5 files changed, 101 insertions(+), 2 deletions(-) -- 2.54.0 ^ permalink raw reply [flat|nested] 35+ messages in thread
* [PATCH bpf v2 1/2] bpf: Fix kfunc implicit arg inject type detection to prevent invalid pointer deref 2026-06-01 6:46 ` [PATCH bpf v2 0/2] bpf: Fix kfunc implicit arg injection and add selftest chenyuan_fl @ 2026-06-01 6:46 ` chenyuan_fl 2026-06-01 7:42 ` bot+bpf-ci ` (2 more replies) 2026-06-01 6:46 ` [PATCH bpf v2 2/2] selftests/bpf: Add regression test for kfunc implicit arg injection with stale register chenyuan_fl 1 sibling, 3 replies; 35+ messages in thread From: chenyuan_fl @ 2026-06-01 6:46 UTC (permalink / raw) To: chenyuan_fl Cc: andrii, ast, bpf, chenyuan, daniel, eddyz87, jolsa, linux-kernel, martin.lau, memxor, song, yonghong.song From: Yuan Chen <chenyuan@kylinos.cn> When a module kfunc declares an implicit struct bpf_prog_aux * argument, the verifier must identify it so the kernel injects env->prog->aux into the correct register at runtime. The original check used is_kfunc_arg_prog_aux() which calls btf_types_are_same() to compare the module BTF type against vmlinux. Root Cause ---------- This issue was triggered by pahole 1.30 generating module BTF with incorrect type information, which caused the kernel's distilled base BTF deduplication for modules to fail. As a result, the module retained its own copy of struct bpf_prog_aux with a different BTF ID than vmlinux's definition. While pahole 1.31 fixed the BTF generation issue, the kernel must be robust against such inconsistencies: a BTF mismatch should result in a clean rejection, not a kernel crash or information disclosure. When the distilled base dedup fails and btf_types_are_same() cannot match the module's bpf_prog_aux type against vmlinux's, is_kfunc_arg_prog_aux() returned false and the code fell through silently without setting arg_prog. The kfunc then received whatever value was in the argument register and dereferenced it as a bpf_prog_aux pointer, leading to: BUG: kernel invalid pointer dereference, address: 00000000000009e2 RIP: bpf_prog_get_assoc_struct_ops+0xa/0xc0 RDI: 0x000000000000046d (stale register value) In the observed crash the stale value was the process PID, causing a dereference within the unmapped NULL page. However, an attacker able to control the register value -- for example by writing a BPF program that explicitly sets R2 before calling a KF_IMPLICIT_ARGS kfunc -- could redirect the dereference to arbitrary kernel memory, turning this into an information disclosure. The fix ensures the verifier either validates and injects the correct bpf_prog_aux pointer, or rejects the program outright -- no silent fallthrough that could be exploited. Crash Stack Trace ----------------- PID: 1133 TASK: ffff8881057d3900 CPU: 3 COMMAND: "test_progs" #0 machine_kexec at ffffffff812f6e26 #1 __crash_kexec at ffffffff8145a788 #2 crash_kexec at ffffffff8145ac24 #3 oops_end at ffffffff812bb67c #4 page_fault_oops at ffffffff813053a1 #5 exc_page_fault at ffffffff828e60a1 #6 asm_exc_page_fault at ffffffff810012a6 [exception RIP: bpf_prog_get_assoc_struct_ops+10] RIP: ffffffff815c024a RSP: ffffc90001b57e48 RFLAGS: 00010283 RAX: ffff8881057d3900 RBX: ffffc90001b57e68 RCX: ffff8881057d3900 RDX: 0000607d4d1768b8 RSI: 000000000000046d RDI: 000000000000046d #7 bpf_kfunc_multi_st_ops_test_1_assoc at ffffffffc0013a85 [bpf_testmod] #8 bpf_trace_run2 at ffffffff814f8332 #9 __traceiter_sys_enter at ffffffff81415f45 #10 trace_syscall_enter at ffffffff81416735 #11 do_syscall_64 at ffffffff828e06a1 Fix --- Introduce a two-layer argument-injection detection: 1. get_kfunc_arg_inject_type() -- lightweight name-based classification of injectable types (currently only KF_INJECT_ARG_PROG_AUX). This ensures we recognize injection candidates regardless of BTF type IDs. 2. is_kfunc_arg_prog_aux() -- strict type validation within the inject case; if validation fails the program is rejected with -EINVAL instead of silently bypassing injection setup. This design ensures that BTF inconsistencies result in a clean verification failure instead of a crash or a potential information disclosure, and the approach is extensible for future injection types. Fixes: 64e1360524b9 ("bpf: Verifier support for KF_IMPLICIT_ARGS") Signed-off-by: Yuan Chen <chenyuan@kylinos.cn> --- kernel/bpf/verifier.c | 48 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 8dd79b735a69..928b6c42a4bf 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -10857,6 +10857,39 @@ static bool is_kfunc_arg_prog_aux(const struct btf *btf, const struct btf_param return __is_kfunc_ptr_arg_type(btf, arg, KF_ARG_PROG_AUX_ID); } +/* + * Injectable argument types are implicit kfunc arguments whose value is + * injected by the kernel at call time rather than received from the BPF + * program. Use name-based matching for initial detection to avoid false + * negatives when a module's BTF references the type via a different BTF ID + * than vmlinux's. Actual type compatibility is still validated by the + * caller with btf_types_are_same(). + */ +enum kfunc_inject_arg_type { + KF_INJECT_ARG_NONE = 0, + KF_INJECT_ARG_PROG_AUX, +}; + +static enum kfunc_inject_arg_type get_kfunc_arg_inject_type( + const struct btf *btf, const struct btf_param *arg) +{ + const struct btf_type *t; + u32 res_id; + + t = btf_type_skip_modifiers(btf, arg->type, NULL); + if (!t || !btf_type_is_ptr(t)) + return KF_INJECT_ARG_NONE; + + t = btf_type_skip_modifiers(btf, t->type, &res_id); + if (!t) + return KF_INJECT_ARG_NONE; + + if (strcmp(btf_type_name(btf, res_id), "bpf_prog_aux") == 0) + return KF_INJECT_ARG_PROG_AUX; + + return KF_INJECT_ARG_NONE; +} + /* * A kfunc with KF_IMPLICIT_ARGS has two prototypes in BTF: * - the _impl prototype with full arg list (meta->func_proto) @@ -11899,8 +11932,17 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_ u32 ref_id, type_size; bool is_ret_buf_sz = false; int kf_arg_type; - - if (is_kfunc_arg_prog_aux(btf, &args[i])) { + enum kfunc_inject_arg_type inject_type; + + inject_type = get_kfunc_arg_inject_type(btf, &args[i]); + switch (inject_type) { + case KF_INJECT_ARG_PROG_AUX: + /* Validate the arg type against vmlinux's definition */ + if (!is_kfunc_arg_prog_aux(btf, &args[i])) { + verbose(env, "arg#%d implicit argument type mismatch, " + "expected struct bpf_prog_aux *\n", i); + return -EINVAL; + } /* Reject repeated use bpf_prog_aux */ if (meta->arg_prog) { verifier_bug(env, "Only 1 prog->aux argument supported per-kfunc"); @@ -11914,6 +11956,8 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_ meta->arg_prog = true; cur_aux(env)->arg_prog = regno; continue; + default: + break; } if (is_kfunc_arg_ignore(btf, &args[i]) || is_kfunc_arg_implicit(meta, i)) -- 2.54.0 ^ permalink raw reply related [flat|nested] 35+ messages in thread
* Re: [PATCH bpf v2 1/2] bpf: Fix kfunc implicit arg inject type detection to prevent invalid pointer deref 2026-06-01 6:46 ` [PATCH bpf v2 1/2] bpf: Fix kfunc implicit arg inject type detection to prevent invalid pointer deref chenyuan_fl @ 2026-06-01 7:42 ` bot+bpf-ci 2026-06-01 19:32 ` Eduard Zingerman 2026-06-01 17:12 ` [PATCH bpf v2 1/2] bpf: Fix kfunc implicit arg inject type detection to prevent invalid pointer deref Yonghong Song 2026-06-01 21:36 ` Eduard Zingerman 2 siblings, 1 reply; 35+ messages in thread From: bot+bpf-ci @ 2026-06-01 7:42 UTC (permalink / raw) To: chenyuan_fl, chenyuan_fl Cc: andrii, ast, bpf, chenyuan, daniel, eddyz87, jolsa, linux-kernel, martin.lau, memxor, song, yonghong.song, ast, andrii, daniel, martin.lau, eddyz87, yonghong.song, clm, ihor.solodrai [-- Attachment #1: Type: text/plain, Size: 2875 bytes --] > bpf: Fix kfunc implicit arg inject type detection to prevent invalid pointer deref > > When a module kfunc declares an implicit struct bpf_prog_aux * argument, > the verifier must identify it so the kernel injects env->prog->aux into > the correct register at runtime. The original check used > is_kfunc_arg_prog_aux() which calls btf_types_are_same() to compare the > module BTF type against vmlinux. > > Root Cause > ---------- > > This issue was triggered by pahole 1.30 generating module BTF with > incorrect type information, which caused the kernel's distilled base > BTF deduplication for modules to fail. As a result, the module retained > its own copy of struct bpf_prog_aux with a different BTF ID than > vmlinux's definition. While pahole 1.31 fixed the BTF generation issue, > the kernel must be robust against such inconsistencies: a BTF mismatch > should result in a clean rejection, not a kernel crash or information > disclosure. > > When the distilled base dedup fails and btf_types_are_same() cannot > match the module's bpf_prog_aux type against vmlinux's, > is_kfunc_arg_prog_aux() returned false and the code fell through > silently without setting arg_prog. The kfunc then received whatever > value was in the argument register and dereferenced it as a > bpf_prog_aux pointer, leading to: > > BUG: kernel invalid pointer dereference, address: 00000000000009e2 > RIP: bpf_prog_get_assoc_struct_ops+0xa/0xc0 > RDI: 0x000000000000046d (stale register value) > > In the observed crash the stale value was the process PID, causing a > dereference within the unmapped NULL page. However, an attacker able > to control the register value -- for example by writing a BPF program > that explicitly sets R2 before calling a KF_IMPLICIT_ARGS kfunc -- > could redirect the dereference to arbitrary kernel memory, turning > this into an information disclosure. The fix ensures the verifier > either validates and injects the correct bpf_prog_aux pointer, or > rejects the program outright -- no silent fallthrough that could > be exploited. This looks like a fix for a real bug, but it does not carry a Fixes: tag. Should it point at the change that introduced the implicit prog_aux argument handling, is_kfunc_arg_prog_aux() and KF_ARG_PROG_AUX_ID? That code appears to come from "bpf: Support __magic prog_aux arguments for kfuncs" (Ihor Solodrai, lore message-id 20251029190113.3323406-5-ihor.solodrai@linux.dev). The merged SHA could not be resolved from this repository, so the tag would need to be filled in by hand, for example: Fixes: <sha> ("bpf: Support __magic prog_aux arguments for kfuncs") --- AI reviewed your patch. Please fix the bug or email reply why it's not a bug. See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md CI run summary: https://github.com/kernel-patches/bpf/actions/runs/26740141551 ^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH bpf v2 1/2] bpf: Fix kfunc implicit arg inject type detection to prevent invalid pointer deref 2026-06-01 7:42 ` bot+bpf-ci @ 2026-06-01 19:32 ` Eduard Zingerman 2026-06-02 8:58 ` [PATCH bpf v3 0/2] bpf: Fix kfunc implicit arg injection and add selftest chenyuan_fl 2026-06-02 9:38 ` [PATCH bpf v4 0/2] bpf: Fix kfunc implicit arg injection and add selftest chenyuan_fl 0 siblings, 2 replies; 35+ messages in thread From: Eduard Zingerman @ 2026-06-01 19:32 UTC (permalink / raw) To: bot+bpf-ci, chenyuan_fl Cc: andrii, ast, bpf, chenyuan, daniel, jolsa, linux-kernel, martin.lau, memxor, song, yonghong.song, martin.lau, clm, ihor.solodrai On Mon, 2026-06-01 at 07:42 +0000, bot+bpf-ci@kernel.org wrote: > > bpf: Fix kfunc implicit arg inject type detection to prevent invalid pointer deref > > > > When a module kfunc declares an implicit struct bpf_prog_aux * argument, > > the verifier must identify it so the kernel injects env->prog->aux into > > the correct register at runtime. The original check used > > is_kfunc_arg_prog_aux() which calls btf_types_are_same() to compare the > > module BTF type against vmlinux. > > > > Root Cause > > ---------- > > > > This issue was triggered by pahole 1.30 generating module BTF with > > incorrect type information, which caused the kernel's distilled base > > BTF deduplication for modules to fail. As a result, the module retained > > its own copy of struct bpf_prog_aux with a different BTF ID than > > vmlinux's definition. While pahole 1.31 fixed the BTF generation issue, > > the kernel must be robust against such inconsistencies: a BTF mismatch > > should result in a clean rejection, not a kernel crash or information > > disclosure. > > > > When the distilled base dedup fails and btf_types_are_same() cannot > > match the module's bpf_prog_aux type against vmlinux's, > > is_kfunc_arg_prog_aux() returned false and the code fell through > > silently without setting arg_prog. The kfunc then received whatever > > value was in the argument register and dereferenced it as a > > bpf_prog_aux pointer, leading to: > > > > BUG: kernel invalid pointer dereference, address: 00000000000009e2 > > RIP: bpf_prog_get_assoc_struct_ops+0xa/0xc0 > > RDI: 0x000000000000046d (stale register value) > > > > In the observed crash the stale value was the process PID, causing a > > dereference within the unmapped NULL page. However, an attacker able > > to control the register value -- for example by writing a BPF program > > that explicitly sets R2 before calling a KF_IMPLICIT_ARGS kfunc -- > > could redirect the dereference to arbitrary kernel memory, turning > > this into an information disclosure. The fix ensures the verifier > > either validates and injects the correct bpf_prog_aux pointer, or > > rejects the program outright -- no silent fallthrough that could > > be exploited. > > This looks like a fix for a real bug, but it does not carry a Fixes: > tag. Should it point at the change that introduced the implicit > prog_aux argument handling, is_kfunc_arg_prog_aux() and KF_ARG_PROG_AUX_ID? > > That code appears to come from "bpf: Support __magic prog_aux arguments > for kfuncs" (Ihor Solodrai, lore message-id > 20251029190113.3323406-5-ihor.solodrai@linux.dev). > > The merged SHA could not be resolved from this repository, so the tag > would need to be filled in by hand, for example: > > Fixes: <sha> ("bpf: Support __magic prog_aux arguments for kfuncs") > > > --- > AI reviewed your patch. Please fix the bug or email reply why it's not a bug. > See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md > > CI run summary: https://github.com/kernel-patches/bpf/actions/runs/26740141551 The robot (and b4 utility as well) is confused by the following formatting quirk: > ... > Fix > --- > ... The triple dash marks the start of the diff section of the patch. Hence, when I apply this series the commit message is truncated after this point. ^ permalink raw reply [flat|nested] 35+ messages in thread
* [PATCH bpf v3 0/2] bpf: Fix kfunc implicit arg injection and add selftest 2026-06-01 19:32 ` Eduard Zingerman @ 2026-06-02 8:58 ` chenyuan_fl 2026-06-02 8:58 ` [PATCH bpf v3 1/2] bpf: Fix kfunc implicit arg inject type detection to prevent invalid pointer deref chenyuan_fl 2026-06-02 8:58 ` [PATCH bpf v3 2/2] selftests/bpf: Add regression test for kfunc implicit arg injection chenyuan_fl 2026-06-02 9:38 ` [PATCH bpf v4 0/2] bpf: Fix kfunc implicit arg injection and add selftest chenyuan_fl 1 sibling, 2 replies; 35+ messages in thread From: chenyuan_fl @ 2026-06-02 8:58 UTC (permalink / raw) To: eddyz87, yonghong.song Cc: andrii, ast, bot+bpf-ci, bpf, chenyuan, chenyuan_fl, clm, daniel, ihor.solodrai, jolsa, linux-kernel, martin.lau, martin.lau, memxor, song From: Yuan Chen <chenyuan@kylinos.cn> v1: https://lore.kernel.org/bpf/20260407080900.551797-1-chenyuan_fl@163.com/ v1 made btf_types_are_same() cross-BTF aware by comparing kind, size and name. Alan Maguire pointed out that this would be too permissive since two different structs can share the same name and size. v2 used a name-based classifier (get_kfunc_arg_inject_type). v3 (this revision) splits the combined is_kfunc_arg_ignore || is_kfunc_arg_implicit check in check_kfunc_args(), so that an implicit argument reaching is_kfunc_arg_implicit() without any prior handler is rejected with -EFAULT instead of silently skipped (Eduard Zingerman). This prevents the silent fallthrough that occurred when module BTF was inconsistent with vmlinux (e.g. pahole 1.30 breaking distilled base dedup). Patch 2 adds a positive regression test for the injection path. Yonghong Song pointed out that with pahole 1.31 available in CI the test will always pass, so it cannot reproduce the original BTF mismatch scenario. The test still serves as a useful regression guard for the injection mechanism itself, however, and would catch any accidental breakage in the future. Thanks to everyone who reviewed and provided feedback on this series. Yuan Chen (2): bpf: Fix kfunc implicit arg inject type detection to prevent invalid pointer deref selftests/bpf: Add regression test for kfunc implicit arg injection kernel/bpf/verifier.c | 15 +++++++- .../bpf/prog_tests/test_struct_ops_assoc.c | 7 ++++ .../testing/selftests/bpf/progs/struct_ops_assoc.c | 40 ++++++++++++++++++++++ .../testing/selftests/bpf/test_kmods/bpf_testmod.c | 9 +++++ .../selftests/bpf/test_kmods/bpf_testmod_kfunc.h | 1 + 5 files changed, 71 insertions(+), 1 deletion(-) -- 2.47.2 ^ permalink raw reply [flat|nested] 35+ messages in thread
* [PATCH bpf v3 1/2] bpf: Fix kfunc implicit arg inject type detection to prevent invalid pointer deref 2026-06-02 8:58 ` [PATCH bpf v3 0/2] bpf: Fix kfunc implicit arg injection and add selftest chenyuan_fl @ 2026-06-02 8:58 ` chenyuan_fl 2026-06-02 9:23 ` sashiko-bot ` (2 more replies) 2026-06-02 8:58 ` [PATCH bpf v3 2/2] selftests/bpf: Add regression test for kfunc implicit arg injection chenyuan_fl 1 sibling, 3 replies; 35+ messages in thread From: chenyuan_fl @ 2026-06-02 8:58 UTC (permalink / raw) To: eddyz87, yonghong.song Cc: andrii, ast, bot+bpf-ci, bpf, chenyuan, chenyuan_fl, clm, daniel, ihor.solodrai, jolsa, linux-kernel, martin.lau, martin.lau, memxor, song From: Yuan Chen <chenyuan@kylinos.cn> When a module kfunc declares an implicit struct bpf_prog_aux * argument, the verifier must identify it so the kernel injects env->prog->aux into the correct register at runtime. The original check used is_kfunc_arg_prog_aux() which calls btf_types_are_same() to compare the module BTF type against vmlinux. Root Cause This issue was triggered by pahole 1.30 generating module BTF with incorrect type information, which caused the kernel's distilled base BTF deduplication for modules to fail. As a result, the module retained its own copy of struct bpf_prog_aux with a different BTF ID than vmlinux's definition. While pahole 1.31 fixed the BTF generation issue, the kernel must be robust against such inconsistencies: a BTF mismatch should result in a clean rejection, not a kernel crash or information disclosure. When the distilled base dedup fails and btf_types_are_same() cannot match the module's bpf_prog_aux type against vmlinux's, is_kfunc_arg_prog_aux() returned false and the code fell through silently without setting arg_prog. The kfunc then received whatever value was in the argument register and dereferenced it as a bpf_prog_aux pointer, leading to: BUG: kernel invalid pointer dereference, address: 00000000000009e2 RIP: bpf_prog_get_assoc_struct_ops+0xa/0xc0 RDI: 0x000000000000046d (stale register value) In the observed crash the stale value was the process PID, causing a dereference within the unmapped NULL page. However, an attacker able to control the register value -- for example by writing a BPF program that explicitly sets R2 before calling a KF_IMPLICIT_ARGS kfunc -- could redirect the dereference to arbitrary kernel memory, turning this into an information disclosure. The fix ensures the verifier either validates and injects the correct bpf_prog_aux pointer, or rejects the program outright -- no silent fallthrough that could be exploited. Crash Stack Trace PID: 1133 TASK: ffff8881057d3900 CPU: 3 COMMAND: "test_progs" #0 machine_kexec at ffffffff812f6e26 #1 __crash_kexec at ffffffff8145a788 #2 crash_kexec at ffffffff8145ac24 #3 oops_end at ffffffff812bb67c #4 page_fault_oops at ffffffff813053a1 #5 exc_page_fault at ffffffff828e60a1 #6 asm_exc_page_fault at ffffffff810012a6 [exception RIP: bpf_prog_get_assoc_struct_ops+10] RIP: ffffffff815c024a RSP: ffffc90001b57e48 RFLAGS: 00010283 RAX: ffff8881057d3900 RBX: ffffc90001b57e68 RCX: ffff8881057d3900 RDX: 0000607d4d1768b8 RSI: 000000000000046d RDI: 000000000000046d #7 bpf_kfunc_multi_st_ops_test_1_assoc at ffffffffc0013a85 [bpf_testmod] #8 bpf_trace_run2 at ffffffff814f8332 #9 __traceiter_sys_enter at ffffffff81415f45 #10 trace_syscall_enter at ffffffff81416735 #11 do_syscall_64 at ffffffff828e06a1 Fix Split the combined is_kfunc_arg_ignore() || is_kfunc_arg_implicit() check in check_kfunc_args() so that an implicit argument reaching is_kfunc_arg_implicit() without being handled by a prior handler is rejected with -EFAULT, instead of silently skipped. Recognized cases: - struct bpf_prog_aux * : is_kfunc_arg_prog_aux() - __ign suffix args : is_kfunc_arg_ignore() - list_push/rbtree_add : is_bpf_list_push_kfunc() / is_bpf_rbtree_add_kfunc() Suggested-by: Eduard Zingerman <eddyz87@gmail.com> Fixes: 64e1360524b9 ("bpf: Verifier support for KF_IMPLICIT_ARGS") Signed-off-by: Yuan Chen <chenyuan@kylinos.cn> --- kernel/bpf/verifier.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 8dd79b735a69..55c74d064e4e 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -11916,9 +11916,22 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_ continue; } - if (is_kfunc_arg_ignore(btf, &args[i]) || is_kfunc_arg_implicit(meta, i)) + if (is_kfunc_arg_ignore(btf, &args[i])) continue; + if (is_kfunc_arg_implicit(meta, i)) { + /* list_push / rbtree_add kfuncs have implicit args + * (e.g. 'off' parameter) handled during verification + * in bpf_fixup_kfunc_call(). Don't flag them. + */ + if (is_bpf_list_push_kfunc(meta->func_id) || + is_bpf_rbtree_add_kfunc(meta->func_id)) + continue; + verbose(env, "%s unrecognized implicit argument, possible BTF mismatch\n", + reg_arg_name(env, argno)); + return -EFAULT; + } + t = btf_type_skip_modifiers(btf, args[i].type, NULL); if (btf_type_is_scalar(t)) { -- 2.54.0 ^ permalink raw reply related [flat|nested] 35+ messages in thread
* Re: [PATCH bpf v3 1/2] bpf: Fix kfunc implicit arg inject type detection to prevent invalid pointer deref 2026-06-02 8:58 ` [PATCH bpf v3 1/2] bpf: Fix kfunc implicit arg inject type detection to prevent invalid pointer deref chenyuan_fl @ 2026-06-02 9:23 ` sashiko-bot 2026-06-02 9:44 ` bot+bpf-ci 2026-06-02 18:52 ` Ihor Solodrai 2 siblings, 0 replies; 35+ messages in thread From: sashiko-bot @ 2026-06-02 9:23 UTC (permalink / raw) To: chenyuan_fl; +Cc: bpf Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider: New issues: - [Low] The patch introduces a fatal compilation error by using an undeclared variable `argno` and an undefined function `reg_arg_name`. Pre-existing issues: - [Critical] Module BTF dedup failures cause special pointer types (like bpf_timer, bpf_list_head, bpf_spin_lock) to fall back to KF_ARG_PTR_TO_MEM, allowing BPF programs to pass attacker-controlled memory instead of valid map-backed objects. -- commit 198d635479b994ffecc7d0ff675e91259613ce61 Author: Yuan Chen <chenyuan@kylinos.cn> Subject: bpf: Fix kfunc implicit arg inject type detection to prevent invalid pointer deref When a module kfunc declares an implicit struct bpf_prog_aux * argument, the verifier must identify it so the kernel injects env->prog->aux into the correct register at runtime. This patch ensures the verifier either validates and injects the correct bpf_prog_aux pointer, or rejects the program outright if BTF type matching fails. > Root Cause > > This issue was triggered by pahole 1.30 generating module BTF with > incorrect type information, which caused the kernel's distilled base > BTF deduplication for modules to fail. [Severity: Critical] This is a pre-existing issue, but since module BTF dedup failures cause btf_types_are_same() to return false, does this also affect explicit special kfunc arguments? When __is_kfunc_ptr_arg_type() in kernel/bpf/verifier.c fails to match types like bpf_timer or bpf_list_head, the verifier falls through to the fallback logic in get_kfunc_ptr_arg_type(): kernel/bpf/verifier.c:get_kfunc_ptr_arg_type() { ... if (!btf_type_is_scalar(ref_t) && !__btf_type_is_scalar_struct(env, meta->btf, ref_t, 0) && (arg_mem_size ? !btf_type_is_void(ref_t) : 1)) { ... } return arg_mem_size ? KF_ARG_PTR_TO_MEM_SIZE : KF_ARG_PTR_TO_MEM; } Because structs like bpf_timer and bpf_list_head are composed solely of scalars, __btf_type_is_scalar_struct() evaluates to true, and they are categorized as KF_ARG_PTR_TO_MEM. Does this allow BPF programs to pass a pointer to attacker-controlled memory (like a stack buffer) to a module kfunc expecting a valid, map-backed kernel object? > diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c > index 7fb88e1cd7c4d..57e5fdf9f7e06 100644 > --- a/kernel/bpf/verifier.c > +++ b/kernel/bpf/verifier.c [ ... ] > @@ -12082,9 +12082,22 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_ > continue; > } > > - if (is_kfunc_arg_ignore(btf, &args[i]) || is_kfunc_arg_implicit(meta, i)) > + if (is_kfunc_arg_ignore(btf, &args[i])) > continue; > > + if (is_kfunc_arg_implicit(meta, i)) { > + /* list_push / rbtree_add kfuncs have implicit args > + * (e.g. 'off' parameter) handled during verification > + * in bpf_fixup_kfunc_call(). Don't flag them. > + */ > + if (is_bpf_list_push_kfunc(meta->func_id) || > + is_bpf_rbtree_add_kfunc(meta->func_id)) > + continue; > + verbose(env, "%s unrecognized implicit argument, possible BTF mismatch\n", > + reg_arg_name(env, argno)); [Severity: Low] Will this compile? It looks like argno is neither declared nor defined in check_kfunc_args(), and reg_arg_name() is undefined in the kernel source tree. Should this use regno instead of argno, and a different way to format the register name? > + return -EFAULT; > + } -- Sashiko AI review · https://sashiko.dev/#/patchset/20260602085827.2562222-1-chenyuan_fl@163.com?part=1 ^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH bpf v3 1/2] bpf: Fix kfunc implicit arg inject type detection to prevent invalid pointer deref 2026-06-02 8:58 ` [PATCH bpf v3 1/2] bpf: Fix kfunc implicit arg inject type detection to prevent invalid pointer deref chenyuan_fl 2026-06-02 9:23 ` sashiko-bot @ 2026-06-02 9:44 ` bot+bpf-ci 2026-06-02 18:52 ` Ihor Solodrai 2 siblings, 0 replies; 35+ messages in thread From: bot+bpf-ci @ 2026-06-02 9:44 UTC (permalink / raw) To: chenyuan_fl, eddyz87, yonghong.song Cc: andrii, ast, bot+bpf-ci, bpf, chenyuan, chenyuan_fl, clm, daniel, ihor.solodrai, jolsa, linux-kernel, martin.lau, martin.lau, memxor, song, ast, andrii, daniel, martin.lau, eddyz87, yonghong.song, clm, ihor.solodrai [-- Attachment #1: Type: text/plain, Size: 2463 bytes --] > diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c > index 7fb88e1cd7c4..57e5fdf9f7e0 100644 > --- a/kernel/bpf/verifier.c > +++ b/kernel/bpf/verifier.c > @@ -12082,9 +12082,22 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_ > continue; > } > > - if (is_kfunc_arg_ignore(btf, &args[i]) || is_kfunc_arg_implicit(meta, i)) > + if (is_kfunc_arg_ignore(btf, &args[i])) > continue; > > + if (is_kfunc_arg_implicit(meta, i)) { > + /* list_push / rbtree_add kfuncs have implicit args > + * (e.g. 'off' parameter) handled during verification > + * in bpf_fixup_kfunc_call(). Don't flag them. > + */ This isn't a bug, but in the v2 review Yonghong Song asked for a comment explaining that this code exists to work around the pahole 1.30 BTF mismatch, so it can be removed once that toolchain issue is no longer relevant: https://lore.kernel.org/bpf/7b3ba4a8-a785-4f70-879d-50e63d6de787@linux.dev/ "Also, please add some comments in the code to explain the reason of this code is due to pahole 1.30. So later on at some point, this code can be removed." The comment block here only documents the list_push/rbtree_add exception and does not record the pahole 1.30 rationale. Was that request meant to be addressed in this version? > + if (is_bpf_list_push_kfunc(meta->func_id) || > + is_bpf_rbtree_add_kfunc(meta->func_id)) > + continue; > + verbose(env, "%s unrecognized implicit argument, possible BTF mismatch\n", > + reg_arg_name(env, argno)); Where do reg_arg_name() and argno come from? Grepping the tree finds reg_arg_name and argno only on this new line; there is no definition anywhere else, and the enclosing for loop in check_kfunc_args() declares the register index as: u32 regno = i + 1; not argno. Does this compile? As written, reg_arg_name() looks like an implicit/undeclared function reference and argno an undeclared identifier. Should this use the existing regno instead, for example: verbose(env, "R%d unrecognized implicit argument, possible BTF mismatch\n", regno); > + return -EFAULT; > + } > + > t = btf_type_skip_modifiers(btf, args[i].type, NULL); > > if (btf_type_is_scalar(t)) { --- AI reviewed your patch. Please fix the bug or email reply why it's not a bug. See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md CI run summary: https://github.com/kernel-patches/bpf/actions/runs/26810234837 ^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH bpf v3 1/2] bpf: Fix kfunc implicit arg inject type detection to prevent invalid pointer deref 2026-06-02 8:58 ` [PATCH bpf v3 1/2] bpf: Fix kfunc implicit arg inject type detection to prevent invalid pointer deref chenyuan_fl 2026-06-02 9:23 ` sashiko-bot 2026-06-02 9:44 ` bot+bpf-ci @ 2026-06-02 18:52 ` Ihor Solodrai 2026-06-04 9:14 ` chenyuan 2 siblings, 1 reply; 35+ messages in thread From: Ihor Solodrai @ 2026-06-02 18:52 UTC (permalink / raw) To: chenyuan_fl, eddyz87, yonghong.song Cc: andrii, ast, bot+bpf-ci, bpf, chenyuan, clm, daniel, jolsa, linux-kernel, martin.lau, martin.lau, memxor, song On 6/2/26 1:58 AM, chenyuan_fl@163.com wrote: > From: Yuan Chen <chenyuan@kylinos.cn> > > When a module kfunc declares an implicit struct bpf_prog_aux * argument, > the verifier must identify it so the kernel injects env->prog->aux into > the correct register at runtime. The original check used > is_kfunc_arg_prog_aux() which calls btf_types_are_same() to compare the > module BTF type against vmlinux. > > Root Cause > > This issue was triggered by pahole 1.30 generating module BTF with > incorrect type information, which caused the kernel's distilled base > BTF deduplication for modules to fail. As a result, the module retained > its own copy of struct bpf_prog_aux with a different BTF ID than > vmlinux's definition. While pahole 1.31 fixed the BTF generation issue, Hi Yuan, Could you please elaborate on the "incorrect type information" generated by pahole 1.30? My understanding is, the symptom of the problem you're trying to fix is that module BTF with custom kfuncs with KF_IMPLICIT_ARGS ends up with a copy of an arg type, instead of referencing the BTF ID in kernel BTF. Do you know how this happens? Is it a bug in pahole or in resolve_btfids? I have a suspicion we might not need changes in the verifier to fix this. Might be wrong of course, would appreciate a bit more details. > the kernel must be robust against such inconsistencies: a BTF mismatch > should result in a clean rejection, not a kernel crash or information > disclosure. > > When the distilled base dedup fails and btf_types_are_same() cannot > match the module's bpf_prog_aux type against vmlinux's, Dedup happens in pahole, but distill_base is done in resolve_btfids. I think distill_base is supposed to remove the (copy of) target type from module BTF. Can you confirm that it doesn't? > is_kfunc_arg_prog_aux() returned false and the code fell through > silently without setting arg_prog. The kfunc then received whatever > value was in the argument register and dereferenced it as a > bpf_prog_aux pointer, leading to: > > BUG: kernel invalid pointer dereference, address: 00000000000009e2 > RIP: bpf_prog_get_assoc_struct_ops+0xa/0xc0 > RDI: 0x000000000000046d (stale register value) > > In the observed crash the stale value was the process PID, causing a > dereference within the unmapped NULL page. However, an attacker able > to control the register value -- for example by writing a BPF program > that explicitly sets R2 before calling a KF_IMPLICIT_ARGS kfunc -- > could redirect the dereference to arbitrary kernel memory, turning > this into an information disclosure. The fix ensures the verifier > either validates and injects the correct bpf_prog_aux pointer, or > rejects the program outright -- no silent fallthrough that could > be exploited. > > Crash Stack Trace > > PID: 1133 TASK: ffff8881057d3900 CPU: 3 COMMAND: "test_progs" > #0 machine_kexec at ffffffff812f6e26 > #1 __crash_kexec at ffffffff8145a788 > #2 crash_kexec at ffffffff8145ac24 > #3 oops_end at ffffffff812bb67c > #4 page_fault_oops at ffffffff813053a1 > #5 exc_page_fault at ffffffff828e60a1 > #6 asm_exc_page_fault at ffffffff810012a6 > [exception RIP: bpf_prog_get_assoc_struct_ops+10] > RIP: ffffffff815c024a RSP: ffffc90001b57e48 RFLAGS: 00010283 > RAX: ffff8881057d3900 RBX: ffffc90001b57e68 RCX: ffff8881057d3900 > RDX: 0000607d4d1768b8 RSI: 000000000000046d RDI: 000000000000046d > #7 bpf_kfunc_multi_st_ops_test_1_assoc at ffffffffc0013a85 [bpf_testmod] > #8 bpf_trace_run2 at ffffffff814f8332 > #9 __traceiter_sys_enter at ffffffff81415f45 > #10 trace_syscall_enter at ffffffff81416735 > #11 do_syscall_64 at ffffffff828e06a1 > > Fix > > Split the combined is_kfunc_arg_ignore() || is_kfunc_arg_implicit() > check in check_kfunc_args() so that an implicit argument reaching > is_kfunc_arg_implicit() without being handled by a prior handler is > rejected with -EFAULT, instead of silently skipped. Recognized cases: > > - struct bpf_prog_aux * : is_kfunc_arg_prog_aux() > - __ign suffix args : is_kfunc_arg_ignore() > - list_push/rbtree_add : is_bpf_list_push_kfunc() / is_bpf_rbtree_add_kfunc() > > Suggested-by: Eduard Zingerman <eddyz87@gmail.com> > Fixes: 64e1360524b9 ("bpf: Verifier support for KF_IMPLICIT_ARGS") > Signed-off-by: Yuan Chen <chenyuan@kylinos.cn> > --- > kernel/bpf/verifier.c | 15 ++++++++++++++- > 1 file changed, 14 insertions(+), 1 deletion(-) > > diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c > index 8dd79b735a69..55c74d064e4e 100644 > --- a/kernel/bpf/verifier.c > +++ b/kernel/bpf/verifier.c > @@ -11916,9 +11916,22 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_ > continue; > } > > - if (is_kfunc_arg_ignore(btf, &args[i]) || is_kfunc_arg_implicit(meta, i)) > + if (is_kfunc_arg_ignore(btf, &args[i])) > continue; > > + if (is_kfunc_arg_implicit(meta, i)) { > + /* list_push / rbtree_add kfuncs have implicit args > + * (e.g. 'off' parameter) handled during verification > + * in bpf_fixup_kfunc_call(). Don't flag them. > + */ > + if (is_bpf_list_push_kfunc(meta->func_id) || > + is_bpf_rbtree_add_kfunc(meta->func_id)) > + continue; > + verbose(env, "%s unrecognized implicit argument, possible BTF mismatch\n", > + reg_arg_name(env, argno)); > + return -EFAULT; > + } > + > t = btf_type_skip_modifiers(btf, args[i].type, NULL); > > if (btf_type_is_scalar(t)) { ^ permalink raw reply [flat|nested] 35+ messages in thread
* Re:Re: [PATCH bpf v3 1/2] bpf: Fix kfunc implicit arg inject type detection to prevent invalid pointer deref 2026-06-02 18:52 ` Ihor Solodrai @ 2026-06-04 9:14 ` chenyuan 2026-06-04 10:21 ` Alan Maguire 0 siblings, 1 reply; 35+ messages in thread From: chenyuan @ 2026-06-04 9:14 UTC (permalink / raw) To: Ihor Solodrai Cc: eddyz87, yonghong.song, andrii, ast, bot+bpf-ci, bpf, chenyuan, clm, daniel, jolsa, linux-kernel, martin.lau, martin.lau, memxor, song Hi Ihor: > Could you please elaborate on the "incorrect type information" generated by pahole 1.30? I extracted the .BTF and .BTF.base sections from the same bpf_testmod.ko built with pahole 1.30 vs 1.31 to compare: # pahole 1.30 — bpf_prog_aux in split BTF, NOT in base $ bpftool btf dump file btf_1_30_base.bin | grep bpf_prog_aux (empty) $ bpftool btf dump -B btf_1_30_base.bin file btf_1_30.bin | grep aux [3172] STRUCT 'bpf_prog_aux' size=2016 vlen=87 # pahole 1.31 — bpf_prog_aux in base BTF, NOT in split $ bpftool btf dump file btf_1_31_base.bin | grep bpf_prog_aux [10] STRUCT 'bpf_prog_aux' size=2016 vlen=0 $ bpftool btf dump -B btf_1_31_base.bin file btf_1_31.bin | grep aux (empty) pahole 1.30 places struct bpf_prog_aux in the module's split BTF (instead of the distilled base). pahole 1.31 correctly moves it to the base BTF. > Do you know how this happens? Is it a bug in pahole or in resolve_btfids? It's a pahole issue. pahole's BTF encoder decides which types go into .BTF.base (distilled base) vs .BTF (split). In 1.30, types referenced by kfunc declarations were not being correctly identified as vmlinux types for the distilled base, so they ended up as "new" types in the split BTF instead. > I have a suspicion we might not need changes in the verifier to fix this. The kernel's BTF dedup during module loading only operates on types in the distilled base (.BTF.base), merging them with vmlinux BTF. Types in the split BTF (.BTF) are treated as module-local and assigned fresh IDs — they are never matched against vmlinux. This means when a toolchain bug puts a vmlinux type into split BTF instead of the base , the kernel has no mechanism to detect or fix it. btf_types_are_same() compares pointers across two different BTF objects — always fails for this case. is_kfunc_arg_prog_aux() returns false, the argument is silently skipped, and the kfunc dereferences uninitialized register state. While pahole 1.31 fixes the root cause, the verifier change is a safety net: a crash becomes a clean rejection regardless of which toolchain component introduces a BTF mismatch. This is defense-in-depth, not a replacement for the toolchain fix. At 2026-06-03 02:52:39, "Ihor Solodrai" <ihor.solodrai@linux.dev> wrote: >On 6/2/26 1:58 AM, chenyuan_fl@163.com wrote: >> From: Yuan Chen <chenyuan@kylinos.cn> >> >> When a module kfunc declares an implicit struct bpf_prog_aux * argument, >> the verifier must identify it so the kernel injects env->prog->aux into >> the correct register at runtime. The original check used >> is_kfunc_arg_prog_aux() which calls btf_types_are_same() to compare the >> module BTF type against vmlinux. >> >> Root Cause >> >> This issue was triggered by pahole 1.30 generating module BTF with >> incorrect type information, which caused the kernel's distilled base >> BTF deduplication for modules to fail. As a result, the module retained >> its own copy of struct bpf_prog_aux with a different BTF ID than >> vmlinux's definition. While pahole 1.31 fixed the BTF generation issue, > >Hi Yuan, > >Could you please elaborate on the "incorrect type information" generated >by pahole 1.30? > >My understanding is, the symptom of the problem you're trying to fix is >that module BTF with custom kfuncs with KF_IMPLICIT_ARGS ends up with a >copy of an arg type, instead of referencing the BTF ID in kernel BTF. > >Do you know how this happens? Is it a bug in pahole or in resolve_btfids? > >I have a suspicion we might not need changes in the verifier to fix this. >Might be wrong of course, would appreciate a bit more details. > >> the kernel must be robust against such inconsistencies: a BTF mismatch >> should result in a clean rejection, not a kernel crash or information >> disclosure. >> >> When the distilled base dedup fails and btf_types_are_same() cannot >> match the module's bpf_prog_aux type against vmlinux's, > >Dedup happens in pahole, but distill_base is done in resolve_btfids. >I think distill_base is supposed to remove the (copy of) target type from >module BTF. Can you confirm that it doesn't? > >> is_kfunc_arg_prog_aux() returned false and the code fell through >> silently without setting arg_prog. The kfunc then received whatever >> value was in the argument register and dereferenced it as a >> bpf_prog_aux pointer, leading to: >> >> BUG: kernel invalid pointer dereference, address: 00000000000009e2 >> RIP: bpf_prog_get_assoc_struct_ops+0xa/0xc0 >> RDI: 0x000000000000046d (stale register value) >> >> In the observed crash the stale value was the process PID, causing a >> dereference within the unmapped NULL page. However, an attacker able >> to control the register value -- for example by writing a BPF program >> that explicitly sets R2 before calling a KF_IMPLICIT_ARGS kfunc -- >> could redirect the dereference to arbitrary kernel memory, turning >> this into an information disclosure. The fix ensures the verifier >> either validates and injects the correct bpf_prog_aux pointer, or >> rejects the program outright -- no silent fallthrough that could >> be exploited. >> >> Crash Stack Trace >> >> PID: 1133 TASK: ffff8881057d3900 CPU: 3 COMMAND: "test_progs" >> #0 machine_kexec at ffffffff812f6e26 >> #1 __crash_kexec at ffffffff8145a788 >> #2 crash_kexec at ffffffff8145ac24 >> #3 oops_end at ffffffff812bb67c >> #4 page_fault_oops at ffffffff813053a1 >> #5 exc_page_fault at ffffffff828e60a1 >> #6 asm_exc_page_fault at ffffffff810012a6 >> [exception RIP: bpf_prog_get_assoc_struct_ops+10] >> RIP: ffffffff815c024a RSP: ffffc90001b57e48 RFLAGS: 00010283 >> RAX: ffff8881057d3900 RBX: ffffc90001b57e68 RCX: ffff8881057d3900 >> RDX: 0000607d4d1768b8 RSI: 000000000000046d RDI: 000000000000046d >> #7 bpf_kfunc_multi_st_ops_test_1_assoc at ffffffffc0013a85 [bpf_testmod] >> #8 bpf_trace_run2 at ffffffff814f8332 >> #9 __traceiter_sys_enter at ffffffff81415f45 >> #10 trace_syscall_enter at ffffffff81416735 >> #11 do_syscall_64 at ffffffff828e06a1 >> >> Fix >> >> Split the combined is_kfunc_arg_ignore() || is_kfunc_arg_implicit() >> check in check_kfunc_args() so that an implicit argument reaching >> is_kfunc_arg_implicit() without being handled by a prior handler is >> rejected with -EFAULT, instead of silently skipped. Recognized cases: >> >> - struct bpf_prog_aux * : is_kfunc_arg_prog_aux() >> - __ign suffix args : is_kfunc_arg_ignore() >> - list_push/rbtree_add : is_bpf_list_push_kfunc() / is_bpf_rbtree_add_kfunc() >> >> Suggested-by: Eduard Zingerman <eddyz87@gmail.com> >> Fixes: 64e1360524b9 ("bpf: Verifier support for KF_IMPLICIT_ARGS") >> Signed-off-by: Yuan Chen <chenyuan@kylinos.cn> >> --- >> kernel/bpf/verifier.c | 15 ++++++++++++++- >> 1 file changed, 14 insertions(+), 1 deletion(-) >> >> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c >> index 8dd79b735a69..55c74d064e4e 100644 >> --- a/kernel/bpf/verifier.c >> +++ b/kernel/bpf/verifier.c >> @@ -11916,9 +11916,22 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_ >> continue; >> } >> >> - if (is_kfunc_arg_ignore(btf, &args[i]) || is_kfunc_arg_implicit(meta, i)) >> + if (is_kfunc_arg_ignore(btf, &args[i])) >> continue; >> >> + if (is_kfunc_arg_implicit(meta, i)) { >> + /* list_push / rbtree_add kfuncs have implicit args >> + * (e.g. 'off' parameter) handled during verification >> + * in bpf_fixup_kfunc_call(). Don't flag them. >> + */ >> + if (is_bpf_list_push_kfunc(meta->func_id) || >> + is_bpf_rbtree_add_kfunc(meta->func_id)) >> + continue; >> + verbose(env, "%s unrecognized implicit argument, possible BTF mismatch\n", >> + reg_arg_name(env, argno)); >> + return -EFAULT; >> + } >> + >> t = btf_type_skip_modifiers(btf, args[i].type, NULL); >> >> if (btf_type_is_scalar(t)) { ^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH bpf v3 1/2] bpf: Fix kfunc implicit arg inject type detection to prevent invalid pointer deref 2026-06-04 9:14 ` chenyuan @ 2026-06-04 10:21 ` Alan Maguire 0 siblings, 0 replies; 35+ messages in thread From: Alan Maguire @ 2026-06-04 10:21 UTC (permalink / raw) To: chenyuan, Ihor Solodrai Cc: eddyz87, yonghong.song, andrii, ast, bot+bpf-ci, bpf, chenyuan, clm, daniel, jolsa, linux-kernel, martin.lau, martin.lau, memxor, song On 04/06/2026 10:14, chenyuan wrote: > Hi Ihor: >> Could you please elaborate on the "incorrect type information" generated > by pahole 1.30? > I extracted the .BTF and .BTF.base sections from the same > bpf_testmod.ko built with pahole 1.30 vs 1.31 to compare: > > # pahole 1.30 — bpf_prog_aux in split BTF, NOT in base > $ bpftool btf dump file btf_1_30_base.bin | grep bpf_prog_aux > (empty) > $ bpftool btf dump -B btf_1_30_base.bin file btf_1_30.bin | grep aux > [3172] STRUCT 'bpf_prog_aux' size=2016 vlen=87 > > # pahole 1.31 — bpf_prog_aux in base BTF, NOT in split > $ bpftool btf dump file btf_1_31_base.bin | grep bpf_prog_aux > [10] STRUCT 'bpf_prog_aux' size=2016 vlen=0 > $ bpftool btf dump -B btf_1_31_base.bin file btf_1_31.bin | grep aux > (empty) > > pahole 1.30 places struct bpf_prog_aux in the module's split BTF > (instead of the distilled base). pahole 1.31 correctly moves it to > the base BTF. Would be worth digging a bit more here I think. To confirm is this the v1.31 release where it is fixed or the post-1.31 pahole (i.e. do you know the top commit for the fixed pahole version)? pahole uses the libbpf machinery to do BTF distillation in .BTF.base so if we know which libbpf sync has the fix that will be helpful. The most recent pahole HEAD is synced with libbpf v1.8, but I suspect the previous sync that occured just after v1.30 (so v1.31 had the fix but v1.30 didn't): 042d73962d35 ("pahole: Sync with libbpf mainline") is the one. From the commit log: "To pull in dedup fix in commit 8e64c387c942 ("libbpf: Add identical pointer detection to btf_dedup_is_equiv()") sync with latest libbpf." My suspicion is that since v1.30 didn't have that fix, dedup failed for struct bpf_prog_aux on pahole v1.30 so it wound up in split BTF and base. v1.31 had the fix so the module bpf_prog_aux was correctly flagged as a dup of the kernel one, therefore we did the distillation into .BTF.base correctly. Does this fit with your observations? Thanks! Alan ^ permalink raw reply [flat|nested] 35+ messages in thread
* [PATCH bpf v3 2/2] selftests/bpf: Add regression test for kfunc implicit arg injection 2026-06-02 8:58 ` [PATCH bpf v3 0/2] bpf: Fix kfunc implicit arg injection and add selftest chenyuan_fl 2026-06-02 8:58 ` [PATCH bpf v3 1/2] bpf: Fix kfunc implicit arg inject type detection to prevent invalid pointer deref chenyuan_fl @ 2026-06-02 8:58 ` chenyuan_fl 2026-06-02 9:31 ` sashiko-bot 2026-06-02 9:44 ` bot+bpf-ci 1 sibling, 2 replies; 35+ messages in thread From: chenyuan_fl @ 2026-06-02 8:58 UTC (permalink / raw) To: eddyz87, yonghong.song Cc: andrii, ast, bot+bpf-ci, bpf, chenyuan, chenyuan_fl, clm, daniel, ihor.solodrai, jolsa, linux-kernel, martin.lau, martin.lau, memxor, song From: Yuan Chen <chenyuan@kylinos.cn> The preceding patch fixes a silent fallthrough in check_kfunc_args() that could cause the verifier to skip bpf_prog_aux injection for KF_IMPLICIT_ARGS kfuncs when module BTF is inconsistent with vmlinux (e.g. pahole 1.30 breaking distilled base dedup). Add a positive regression test that verifies the injection path works correctly under normal conditions (pahole 1.31+). The test contaminates BPF R2 with a magic value 0xDEAD via inline assembly before calling a KF_IMPLICIT_ARGS kfunc associated with a struct_ops map. The kfunc validates that the kernel overwrote R2 with the real bpf_prog_aux pointer rather than leaving the stale value. The specific pahole 1.30 BTF mismatch scenario cannot be tested with CI (which uses pahole 1.31), but this test ensures the injection mechanism remains correct and does not regress. Signed-off-by: Yuan Chen <chenyuan@kylinos.cn> --- .../bpf/prog_tests/test_struct_ops_assoc.c | 7 ++++ .../selftests/bpf/progs/struct_ops_assoc.c | 40 +++++++++++++++++++ .../selftests/bpf/test_kmods/bpf_testmod.c | 9 +++++ .../bpf/test_kmods/bpf_testmod_kfunc.h | 1 + 4 files changed, 57 insertions(+) diff --git a/tools/testing/selftests/bpf/prog_tests/test_struct_ops_assoc.c b/tools/testing/selftests/bpf/prog_tests/test_struct_ops_assoc.c index 461ded722351..fb4d06dd6c4d 100644 --- a/tools/testing/selftests/bpf/prog_tests/test_struct_ops_assoc.c +++ b/tools/testing/selftests/bpf/prog_tests/test_struct_ops_assoc.c @@ -35,6 +35,10 @@ static void test_st_ops_assoc(void) skel->maps.st_ops_map_b, NULL); ASSERT_OK(err, "bpf_program__assoc_struct_ops(sys_enter_prog_b, st_ops_map_b)"); + err = bpf_program__assoc_struct_ops(skel->progs.sys_enter_prog_test_aux_inject, + skel->maps.st_ops_map_a, NULL); + ASSERT_OK(err, "bpf_program__assoc_struct_ops(sys_enter_prog_test_aux_inject, st_ops_map_a)"); + /* sys_enter_prog_a already associated with map_a */ err = bpf_program__assoc_struct_ops(skel->progs.sys_enter_prog_a, skel->maps.st_ops_map_b, NULL); @@ -52,6 +56,7 @@ static void test_st_ops_assoc(void) ASSERT_EQ(skel->bss->test_err_a, 0, "skel->bss->test_err_a"); ASSERT_EQ(skel->bss->test_err_b, 0, "skel->bss->test_err_b"); + ASSERT_EQ(skel->bss->test_err_inject, 0, "skel->bss->test_err_inject"); /* run syscall_prog that calls .test_1 and checks return */ err = bpf_prog_test_run_opts(bpf_program__fd(skel->progs.syscall_prog_a), NULL); @@ -62,6 +67,7 @@ static void test_st_ops_assoc(void) ASSERT_EQ(skel->bss->test_err_a, 0, "skel->bss->test_err_a"); ASSERT_EQ(skel->bss->test_err_b, 0, "skel->bss->test_err_b"); + ASSERT_EQ(skel->bss->test_err_inject, 0, "skel->bss->test_err_inject"); out: struct_ops_assoc__destroy(skel); @@ -97,6 +103,7 @@ static void test_st_ops_assoc_reuse(void) ASSERT_EQ(skel->bss->test_err_a, 0, "skel->bss->test_err_a"); ASSERT_EQ(skel->bss->test_err_b, 0, "skel->bss->test_err_b"); + ASSERT_EQ(skel->bss->test_err_inject, 0, "skel->bss->test_err_inject"); out: struct_ops_assoc_reuse__destroy(skel); diff --git a/tools/testing/selftests/bpf/progs/struct_ops_assoc.c b/tools/testing/selftests/bpf/progs/struct_ops_assoc.c index 68842e3f936b..ed0084453d56 100644 --- a/tools/testing/selftests/bpf/progs/struct_ops_assoc.c +++ b/tools/testing/selftests/bpf/progs/struct_ops_assoc.c @@ -103,3 +103,43 @@ SEC(".struct_ops.link") struct bpf_testmod_multi_st_ops st_ops_map_b = { .test_1 = (void *)test_1_b, }; + +/* Test for aux injection with stale register contamination. + * + * This test verifies that the kernel correctly injects the implicit + * bpf_prog_aux pointer for kfuncs with KF_IMPLICIT_ARGS. The program + * uses inline assembly to contaminate R2 with a known magic value + * before calling the kfunc: + * + * asm volatile("r2 = %[magic]" :: [magic] "ri"(0xDEAD) : "r2"); + * + * The kernel must inject env->prog->aux into R2, overriding the magic + * value. The kfunc compares the received aux pointer against 0xDEAD: + * + * - aux == 0xDEAD → kernel failed to inject → kfunc returns -EINVAL + * - aux != 0xDEAD → kernel correctly injected → kfunc returns marker + * + * 0xDEAD is chosen with bit 31 clear to avoid BPF ALU64 sign-extension + * when used as a 32-bit immediate. + */ +int test_err_inject; + +SEC("tp_btf/sys_enter") +int BPF_PROG(sys_enter_prog_test_aux_inject, struct pt_regs *regs, long id) +{ + struct task_struct *task; + int marker = 0x5A5A; + int ret; + + task = bpf_get_current_task_btf(); + if (!test_pid || task->pid != test_pid) + return 0; + + asm volatile("r2 = %[magic]" :: [magic] "ri"(0xDEAD) : "r2"); + + ret = bpf_kfunc_aux_inject_stale(marker); + if (ret != marker) + test_err_inject++; + + return 0; +} diff --git a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c index 0be918fe3021..8e979f0fa56d 100644 --- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c +++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c @@ -1315,6 +1315,7 @@ __bpf_kfunc int bpf_kfunc_multi_st_ops_test_1_assoc(struct st_ops_args *args, st __bpf_kfunc int bpf_kfunc_implicit_arg(int a, struct bpf_prog_aux *aux); __bpf_kfunc int bpf_kfunc_implicit_arg_legacy(int a, int b, struct bpf_prog_aux *aux); +__bpf_kfunc int bpf_kfunc_aux_inject_stale(int marker, struct bpf_prog_aux *aux); __bpf_kfunc int bpf_kfunc_implicit_arg_legacy_impl(int a, int b, struct bpf_prog_aux *aux); /* hook targets */ @@ -1399,6 +1400,7 @@ BTF_ID_FLAGS(func, bpf_kfunc_multi_st_ops_test_1_assoc, KF_IMPLICIT_ARGS) BTF_ID_FLAGS(func, bpf_kfunc_implicit_arg, KF_IMPLICIT_ARGS) BTF_ID_FLAGS(func, bpf_kfunc_implicit_arg_legacy, KF_IMPLICIT_ARGS) BTF_ID_FLAGS(func, bpf_kfunc_implicit_arg_legacy_impl) +BTF_ID_FLAGS(func, bpf_kfunc_aux_inject_stale, KF_IMPLICIT_ARGS) BTF_ID_FLAGS(func, bpf_kfunc_trigger_ctx_check) BTF_KFUNCS_END(bpf_testmod_check_kfunc_ids) @@ -1916,6 +1918,13 @@ int bpf_kfunc_implicit_arg_legacy_impl(int a, int b, struct bpf_prog_aux *aux) return bpf_kfunc_implicit_arg_legacy(a, b, aux); } +int bpf_kfunc_aux_inject_stale(int marker, struct bpf_prog_aux *aux) +{ + if ((unsigned long)aux == 0xDEAD) + return -EINVAL; + return marker; +} + static int multi_st_ops_reg(void *kdata, struct bpf_link *link) { struct bpf_testmod_multi_st_ops *st_ops = diff --git a/tools/testing/selftests/bpf/test_kmods/bpf_testmod_kfunc.h b/tools/testing/selftests/bpf/test_kmods/bpf_testmod_kfunc.h index 2edc36b66de9..c18791e96b21 100644 --- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod_kfunc.h +++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod_kfunc.h @@ -192,6 +192,7 @@ int *bpf_kfunc_ret_rcu_test_nostruct(int rdonly_buf_size) __ksym; #ifndef __KERNEL__ extern int bpf_kfunc_multi_st_ops_test_1(struct st_ops_args *args, u32 id) __weak __ksym; extern int bpf_kfunc_multi_st_ops_test_1_assoc(struct st_ops_args *args) __weak __ksym; +extern int bpf_kfunc_aux_inject_stale(int marker) __weak __ksym; #endif struct prog_test_member *bpf_kfunc_get_default_trusted_ptr_test(void) __ksym; -- 2.54.0 ^ permalink raw reply related [flat|nested] 35+ messages in thread
* Re: [PATCH bpf v3 2/2] selftests/bpf: Add regression test for kfunc implicit arg injection 2026-06-02 8:58 ` [PATCH bpf v3 2/2] selftests/bpf: Add regression test for kfunc implicit arg injection chenyuan_fl @ 2026-06-02 9:31 ` sashiko-bot 2026-06-02 9:44 ` bot+bpf-ci 1 sibling, 0 replies; 35+ messages in thread From: sashiko-bot @ 2026-06-02 9:31 UTC (permalink / raw) To: chenyuan_fl; +Cc: bpf Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider: - [Low] Multi-line comment does not follow the BPF subsystem comment style. - [Low] Build failure in selftests due to accessing a non-existent BSS field in the reused struct_ops skeleton. -- commit 70edd2df92a9670c07a307bbdf1081733e04e3dd Author: Yuan Chen <chenyuan@kylinos.cn> selftests/bpf: Add regression test for kfunc implicit arg injection This commit adds a regression test to verify that the BPF verifier correctly injects the bpf_prog_aux pointer for KF_IMPLICIT_ARGS kfuncs. It uses inline assembly to contaminate R2 before calling a kfunc to ensure the kernel properly overwrites it with the valid pointer. > diff --git a/tools/testing/selftests/bpf/prog_tests/test_struct_ops_assoc.c b/tools/testing/selftests/bpf/prog_tests/test_struct_ops_assoc.c > index 461ded7223515..fb4d06dd6c4dc 100644 > --- a/tools/testing/selftests/bpf/prog_tests/test_struct_ops_assoc.c > +++ b/tools/testing/selftests/bpf/prog_tests/test_struct_ops_assoc.c [ ... ] > @@ -97,6 +103,7 @@ static void test_st_ops_assoc_reuse(void) > > ASSERT_EQ(skel->bss->test_err_a, 0, "skel->bss->test_err_a"); > ASSERT_EQ(skel->bss->test_err_b, 0, "skel->bss->test_err_b"); > + ASSERT_EQ(skel->bss->test_err_inject, 0, "skel->bss->test_err_inject"); [Severity: Low] Will this cause a build failure? It looks like test_err_inject is defined in struct_ops_assoc.c, but this function is testing the skeleton for struct_ops_assoc_reuse.c. Since the variable isn't defined in the reused program's source, the generated struct_ops_assoc_reuse skeleton won't contain the test_err_inject BSS field, which would cause a compiler error when accessing skel->bss->test_err_inject here. > > out: > struct_ops_assoc_reuse__destroy(skel); > diff --git a/tools/testing/selftests/bpf/progs/struct_ops_assoc.c b/tools/testing/selftests/bpf/progs/struct_ops_assoc.c > index 68842e3f936b9..ed0084453d560 100644 > --- a/tools/testing/selftests/bpf/progs/struct_ops_assoc.c > +++ b/tools/testing/selftests/bpf/progs/struct_ops_assoc.c > @@ -103,3 +103,43 @@ SEC(".struct_ops.link") > struct bpf_testmod_multi_st_ops st_ops_map_b = { > .test_1 = (void *)test_1_b, > }; > + > +/* Test for aux injection with stale register contamination. > + * [Severity: Low] This isn't a bug, but does this violate the BPF subsystem multi-line comment style? The guidelines require the opening /* to be on its own line, rather than starting the text on the same line. > + * This test verifies that the kernel correctly injects the implicit > + * bpf_prog_aux pointer for kfuncs with KF_IMPLICIT_ARGS. The program > + * uses inline assembly to contaminate R2 with a known magic value > + * before calling the kfunc: -- Sashiko AI review · https://sashiko.dev/#/patchset/20260602085827.2562222-1-chenyuan_fl@163.com?part=2 ^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH bpf v3 2/2] selftests/bpf: Add regression test for kfunc implicit arg injection 2026-06-02 8:58 ` [PATCH bpf v3 2/2] selftests/bpf: Add regression test for kfunc implicit arg injection chenyuan_fl 2026-06-02 9:31 ` sashiko-bot @ 2026-06-02 9:44 ` bot+bpf-ci 1 sibling, 0 replies; 35+ messages in thread From: bot+bpf-ci @ 2026-06-02 9:44 UTC (permalink / raw) To: chenyuan_fl, eddyz87, yonghong.song Cc: andrii, ast, bot+bpf-ci, bpf, chenyuan, chenyuan_fl, clm, daniel, ihor.solodrai, jolsa, linux-kernel, martin.lau, martin.lau, memxor, song, ast, andrii, daniel, martin.lau, eddyz87, yonghong.song, clm, ihor.solodrai [-- Attachment #1: Type: text/plain, Size: 1554 bytes --] > diff --git a/tools/testing/selftests/bpf/progs/struct_ops_assoc.c b/tools/testing/selftests/bpf/progs/struct_ops_assoc.c > index 68842e3f936b..ed0084453d56 100644 > --- a/tools/testing/selftests/bpf/progs/struct_ops_assoc.c > +++ b/tools/testing/selftests/bpf/progs/struct_ops_assoc.c > @@ -103,3 +103,43 @@ SEC(".struct_ops.link") [ ... ] > +int test_err_inject; > + > +SEC("tp_btf/sys_enter") > +int BPF_PROG(sys_enter_prog_test_aux_inject, struct pt_regs *regs, long id) > +{ This was raised on v2 by Yonghong Song, and it looks like it may still be open: Not sure whether this patch makes sense or not. The CI has pahole 1.31 so the test will always succeed. https://lore.kernel.org/bpf/9cc9ce47-c45e-4878-ad3a-6e6967399ab9@linux.dev/ The underlying bug only manifests with pahole 1.30, where module BTF is inconsistent with vmlinux. Under CI's pahole 1.31 the injection always works, so does this test pass whether or not the fix is present? In v3 the only change was an added paragraph in the commit message acknowledging the pahole 1.30 scenario cannot be reproduced under CI, and no code change was made to gate the test on the bug. Yonghong has not replied to v3. Is there a way to make the test meaningfully exercise the regression rather than always succeeding under the CI toolchain? --- AI reviewed your patch. Please fix the bug or email reply why it's not a bug. See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md CI run summary: https://github.com/kernel-patches/bpf/actions/runs/26810234837 ^ permalink raw reply [flat|nested] 35+ messages in thread
* [PATCH bpf v4 0/2] bpf: Fix kfunc implicit arg injection and add selftest 2026-06-01 19:32 ` Eduard Zingerman 2026-06-02 8:58 ` [PATCH bpf v3 0/2] bpf: Fix kfunc implicit arg injection and add selftest chenyuan_fl @ 2026-06-02 9:38 ` chenyuan_fl 2026-06-02 9:38 ` [PATCH bpf v4 1/2] bpf: Fix kfunc implicit arg inject type detection to prevent invalid pointer deref chenyuan_fl 2026-06-02 9:38 ` [PATCH bpf v4 2/2] selftests/bpf: Add regression test for kfunc implicit arg injection chenyuan_fl 1 sibling, 2 replies; 35+ messages in thread From: chenyuan_fl @ 2026-06-02 9:38 UTC (permalink / raw) To: eddyz87, yonghong.song Cc: andrii, ast, bot+bpf-ci, bpf, chenyuan, chenyuan_fl, clm, daniel, ihor.solodrai, jolsa, linux-kernel, martin.lau, martin.lau, memxor, song From: Yuan Chen <chenyuan@kylinos.cn> v1: https://lore.kernel.org/bpf/20260407080900.551797-1-chenyuan_fl@163.com/ v1 made btf_types_are_same() cross-BTF aware by comparing kind, size and name. Alan Maguire pointed out that this would be too permissive since two different structs can share the same name and size. v2 used a name-based classifier (get_kfunc_arg_inject_type). v3 splits the combined is_kfunc_arg_ignore || is_kfunc_arg_implicit check in check_kfunc_args(), so that an implicit argument reaching is_kfunc_arg_implicit() without any prior handler is rejected with -EFAULT instead of silently skipped (Eduard Zingerman). This prevents the silent fallthrough that occurred when module BTF was inconsistent with vmlinux (e.g. pahole 1.30 breaking distilled base dedup). v4: fix a build error in the selftest (test_st_ops_assoc_reuse used the wrong skeleton for the new variable). Patch 2 adds a positive regression test for the injection path. Yonghong Song pointed out that with pahole 1.31 available in CI the test will always pass, so it cannot reproduce the original BTF mismatch scenario. The test still serves as a useful regression guard for the injection mechanism itself, however, and would catch any accidental breakage in the future. Thanks to everyone who reviewed and provided feedback on this series. Yuan Chen (2): bpf: Fix kfunc implicit arg inject type detection to prevent invalid pointer deref selftests/bpf: Add regression test for kfunc implicit arg injection kernel/bpf/verifier.c | 15 +++++++- .../bpf/prog_tests/test_struct_ops_assoc.c | 6 ++++ .../testing/selftests/bpf/progs/struct_ops_assoc.c | 40 ++++++++++++++++++++++ .../testing/selftests/bpf/test_kmods/bpf_testmod.c | 9 +++++ .../selftests/bpf/test_kmods/bpf_testmod_kfunc.h | 1 + 5 files changed, 70 insertions(+), 1 deletion(-) -- 2.47.2 ^ permalink raw reply [flat|nested] 35+ messages in thread
* [PATCH bpf v4 1/2] bpf: Fix kfunc implicit arg inject type detection to prevent invalid pointer deref 2026-06-02 9:38 ` [PATCH bpf v4 0/2] bpf: Fix kfunc implicit arg injection and add selftest chenyuan_fl @ 2026-06-02 9:38 ` chenyuan_fl 2026-06-02 9:58 ` sashiko-bot ` (2 more replies) 2026-06-02 9:38 ` [PATCH bpf v4 2/2] selftests/bpf: Add regression test for kfunc implicit arg injection chenyuan_fl 1 sibling, 3 replies; 35+ messages in thread From: chenyuan_fl @ 2026-06-02 9:38 UTC (permalink / raw) To: eddyz87, yonghong.song Cc: andrii, ast, bot+bpf-ci, bpf, chenyuan, chenyuan_fl, clm, daniel, ihor.solodrai, jolsa, linux-kernel, martin.lau, martin.lau, memxor, song From: Yuan Chen <chenyuan@kylinos.cn> When a module kfunc declares an implicit struct bpf_prog_aux * argument, the verifier must identify it so the kernel injects env->prog->aux into the correct register at runtime. The original check used is_kfunc_arg_prog_aux() which calls btf_types_are_same() to compare the module BTF type against vmlinux. Root Cause This issue was triggered by pahole 1.30 generating module BTF with incorrect type information, which caused the kernel's distilled base BTF deduplication for modules to fail. As a result, the module retained its own copy of struct bpf_prog_aux with a different BTF ID than vmlinux's definition. While pahole 1.31 fixed the BTF generation issue, the kernel must be robust against such inconsistencies: a BTF mismatch should result in a clean rejection, not a kernel crash or information disclosure. When the distilled base dedup fails and btf_types_are_same() cannot match the module's bpf_prog_aux type against vmlinux's, is_kfunc_arg_prog_aux() returned false and the code fell through silently without setting arg_prog. The kfunc then received whatever value was in the argument register and dereferenced it as a bpf_prog_aux pointer, leading to: BUG: kernel invalid pointer dereference, address: 00000000000009e2 RIP: bpf_prog_get_assoc_struct_ops+0xa/0xc0 RDI: 0x000000000000046d (stale register value) In the observed crash the stale value was the process PID, causing a dereference within the unmapped NULL page. However, an attacker able to control the register value -- for example by writing a BPF program that explicitly sets R2 before calling a KF_IMPLICIT_ARGS kfunc -- could redirect the dereference to arbitrary kernel memory, turning this into an information disclosure. The fix ensures the verifier either validates and injects the correct bpf_prog_aux pointer, or rejects the program outright -- no silent fallthrough that could be exploited. Crash Stack Trace PID: 1133 TASK: ffff8881057d3900 CPU: 3 COMMAND: "test_progs" #0 machine_kexec at ffffffff812f6e26 #1 __crash_kexec at ffffffff8145a788 #2 crash_kexec at ffffffff8145ac24 #3 oops_end at ffffffff812bb67c #4 page_fault_oops at ffffffff813053a1 #5 exc_page_fault at ffffffff828e60a1 #6 asm_exc_page_fault at ffffffff810012a6 [exception RIP: bpf_prog_get_assoc_struct_ops+10] RIP: ffffffff815c024a RSP: ffffc90001b57e48 RFLAGS: 00010283 RAX: ffff8881057d3900 RBX: ffffc90001b57e68 RCX: ffff8881057d3900 RDX: 0000607d4d1768b8 RSI: 000000000000046d RDI: 000000000000046d #7 bpf_kfunc_multi_st_ops_test_1_assoc at ffffffffc0013a85 [bpf_testmod] #8 bpf_trace_run2 at ffffffff814f8332 #9 __traceiter_sys_enter at ffffffff81415f45 #10 trace_syscall_enter at ffffffff81416735 #11 do_syscall_64 at ffffffff828e06a1 Fix Split the combined is_kfunc_arg_ignore() || is_kfunc_arg_implicit() check in check_kfunc_args() so that an implicit argument reaching is_kfunc_arg_implicit() without being handled by a prior handler is rejected with -EFAULT, instead of silently skipped. Recognized cases: - struct bpf_prog_aux * : is_kfunc_arg_prog_aux() - __ign suffix args : is_kfunc_arg_ignore() - list_push/rbtree_add : is_bpf_list_push_kfunc() / is_bpf_rbtree_add_kfunc() Suggested-by: Eduard Zingerman <eddyz87@gmail.com> Fixes: 64e1360524b9 ("bpf: Verifier support for KF_IMPLICIT_ARGS") Signed-off-by: Yuan Chen <chenyuan@kylinos.cn> --- kernel/bpf/verifier.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 8dd79b735a69..55c74d064e4e 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -11916,9 +11916,22 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_ continue; } - if (is_kfunc_arg_ignore(btf, &args[i]) || is_kfunc_arg_implicit(meta, i)) + if (is_kfunc_arg_ignore(btf, &args[i])) continue; + if (is_kfunc_arg_implicit(meta, i)) { + /* list_push / rbtree_add kfuncs have implicit args + * (e.g. 'off' parameter) handled during verification + * in bpf_fixup_kfunc_call(). Don't flag them. + */ + if (is_bpf_list_push_kfunc(meta->func_id) || + is_bpf_rbtree_add_kfunc(meta->func_id)) + continue; + verbose(env, "%s unrecognized implicit argument, possible BTF mismatch\n", + reg_arg_name(env, argno)); + return -EFAULT; + } + t = btf_type_skip_modifiers(btf, args[i].type, NULL); if (btf_type_is_scalar(t)) { -- 2.54.0 ^ permalink raw reply related [flat|nested] 35+ messages in thread
* Re: [PATCH bpf v4 1/2] bpf: Fix kfunc implicit arg inject type detection to prevent invalid pointer deref 2026-06-02 9:38 ` [PATCH bpf v4 1/2] bpf: Fix kfunc implicit arg inject type detection to prevent invalid pointer deref chenyuan_fl @ 2026-06-02 9:58 ` sashiko-bot 2026-06-02 10:42 ` bot+bpf-ci 2026-06-05 0:42 ` Eduard Zingerman 2 siblings, 0 replies; 35+ messages in thread From: sashiko-bot @ 2026-06-02 9:58 UTC (permalink / raw) To: chenyuan_fl; +Cc: bpf Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Low] The patch introduces undeclared identifiers `argno` and `reg_arg_name`, resulting in a fatal compilation error. -- commit af92ff0b79cc212b53a40ea48cae9a7de79d1a76 Author: Yuan Chen <chenyuan@kylinos.cn> bpf: Fix kfunc implicit arg inject type detection to prevent invalid pointer deref This commit fixes a verifier issue where unrecognized implicit kfunc arguments caused a silent fallthrough instead of being rejected. It updates the checks in check_kfunc_args() to explicitly return -EFAULT for unrecognized implicit arguments, preventing a potential invalid pointer dereference or information disclosure. > diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c > index 7fb88e1cd7c4d..57e5fdf9f7e06 100644 > --- a/kernel/bpf/verifier.c > +++ b/kernel/bpf/verifier.c [ ... ] > @@ -12082,9 +12082,22 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_ > continue; > } > > - if (is_kfunc_arg_ignore(btf, &args[i]) || is_kfunc_arg_implicit(meta, i)) > + if (is_kfunc_arg_ignore(btf, &args[i])) > continue; > > + if (is_kfunc_arg_implicit(meta, i)) { > + /* list_push / rbtree_add kfuncs have implicit args > + * (e.g. 'off' parameter) handled during verification > + * in bpf_fixup_kfunc_call(). Don't flag them. > + */ > + if (is_bpf_list_push_kfunc(meta->func_id) || > + is_bpf_rbtree_add_kfunc(meta->func_id)) > + continue; > + verbose(env, "%s unrecognized implicit argument, possible BTF mismatch\n", > + reg_arg_name(env, argno)); [Severity: Low] Will this compile cleanly? In check_kfunc_args(), it looks like argno is undeclared in this scope, as the local loop index appears to be i. Also, does reg_arg_name() exist? I am unable to locate a definition for it in the tree. Could this result in an implicit function declaration error during the build? > + return -EFAULT; > + } > + > t = btf_type_skip_modifiers(btf, args[i].type, NULL); > -- Sashiko AI review · https://sashiko.dev/#/patchset/20260602093836.2632714-1-chenyuan_fl@163.com?part=1 ^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH bpf v4 1/2] bpf: Fix kfunc implicit arg inject type detection to prevent invalid pointer deref 2026-06-02 9:38 ` [PATCH bpf v4 1/2] bpf: Fix kfunc implicit arg inject type detection to prevent invalid pointer deref chenyuan_fl 2026-06-02 9:58 ` sashiko-bot @ 2026-06-02 10:42 ` bot+bpf-ci 2026-06-05 0:42 ` Eduard Zingerman 2 siblings, 0 replies; 35+ messages in thread From: bot+bpf-ci @ 2026-06-02 10:42 UTC (permalink / raw) To: chenyuan_fl, eddyz87, yonghong.song Cc: andrii, ast, bot+bpf-ci, bpf, chenyuan, chenyuan_fl, clm, daniel, ihor.solodrai, jolsa, linux-kernel, martin.lau, martin.lau, memxor, song, ast, andrii, daniel, martin.lau, eddyz87, yonghong.song, clm, ihor.solodrai [-- Attachment #1: Type: text/plain, Size: 2732 bytes --] > diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c > index 7fb88e1cd7c4..57e5fdf9f7e0 100644 > --- a/kernel/bpf/verifier.c > +++ b/kernel/bpf/verifier.c > @@ -12082,9 +12082,22 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_ > continue; > } > > - if (is_kfunc_arg_ignore(btf, &args[i]) || is_kfunc_arg_implicit(meta, i)) > + if (is_kfunc_arg_ignore(btf, &args[i])) > continue; > > + if (is_kfunc_arg_implicit(meta, i)) { > + /* list_push / rbtree_add kfuncs have implicit args > + * (e.g. 'off' parameter) handled during verification > + * in bpf_fixup_kfunc_call(). Don't flag them. > + */ A small note here: the v2 review asked for a comment recording that this workaround exists because of the pahole 1.30 BTF mismatch, so the code can be removed once that toolchain issue is no longer relevant. The comment block here only documents the list_push/rbtree_add exception and does not mention the pahole 1.30 rationale. Should that be added? > + if (is_bpf_list_push_kfunc(meta->func_id) || > + is_bpf_rbtree_add_kfunc(meta->func_id)) > + continue; > + verbose(env, "%s unrecognized implicit argument, possible BTF mismatch\n", > + reg_arg_name(env, argno)); ^^^^^^^^^^^^^^^^^^^^^^^ Will this compile? I cannot find a declaration of argno or a definition of reg_arg_name() anywhere in the tree at this commit. The per-argument loop in check_kfunc_args() names its register index regno, not argno: kernel/bpf/verifier.c:check_kfunc_args() { ... for (i = 0; i < nargs; i++) { ... u32 regno = i + 1, ref_id, type_size; ... } } There is no argno in this function, and git grep for reg_arg_name at this commit returns only this call site, with no definition or declaration. That would give "argno undeclared" and an implicit declaration of reg_arg_name(), which is an error under -Werror=implicit-function-declaration, so kernel/bpf/verifier.c (obj-y, always built) would fail to build. The rest of this function uses regno with an R%d format, for example "R%d is not a scalar". Did you mean something like: verbose(env, "R%d unrecognized implicit argument, possible BTF mismatch\n", regno); The bpf-ci bot and sashiko-bot both raised this on v3 and v4, and it still reads reg_arg_name(env, argno) verbatim here: https://lore.kernel.org/bpf/20260602085827.2562222-2-chenyuan_fl@163.com/ > + return -EFAULT; > + } > + --- AI reviewed your patch. Please fix the bug or email reply why it's not a bug. See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md CI run summary: https://github.com/kernel-patches/bpf/actions/runs/26812478701 ^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH bpf v4 1/2] bpf: Fix kfunc implicit arg inject type detection to prevent invalid pointer deref 2026-06-02 9:38 ` [PATCH bpf v4 1/2] bpf: Fix kfunc implicit arg inject type detection to prevent invalid pointer deref chenyuan_fl 2026-06-02 9:58 ` sashiko-bot 2026-06-02 10:42 ` bot+bpf-ci @ 2026-06-05 0:42 ` Eduard Zingerman 2 siblings, 0 replies; 35+ messages in thread From: Eduard Zingerman @ 2026-06-05 0:42 UTC (permalink / raw) To: chenyuan_fl, yonghong.song Cc: andrii, ast, bot+bpf-ci, bpf, chenyuan, clm, daniel, ihor.solodrai, jolsa, linux-kernel, martin.lau, martin.lau, memxor, song On Tue, 2026-06-02 at 17:38 +0800, chenyuan_fl@163.com wrote: [...] > diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c > index 8dd79b735a69..55c74d064e4e 100644 > --- a/kernel/bpf/verifier.c > +++ b/kernel/bpf/verifier.c > @@ -11916,9 +11916,22 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_ > continue; > } > > - if (is_kfunc_arg_ignore(btf, &args[i]) || is_kfunc_arg_implicit(meta, i)) > + if (is_kfunc_arg_ignore(btf, &args[i])) > continue; > > + if (is_kfunc_arg_implicit(meta, i)) { > + /* list_push / rbtree_add kfuncs have implicit args > + * (e.g. 'off' parameter) handled during verification > + * in bpf_fixup_kfunc_call(). Don't flag them. > + */ > + if (is_bpf_list_push_kfunc(meta->func_id) || > + is_bpf_rbtree_add_kfunc(meta->func_id)) I took a second look at the verifier.c:bpf_fixup_kfunc_call() and it appears it handles more functions with implicit args. The following predicates are used there: - is_bpf_obj_new_kfunc - is_bpf_percpu_obj_new_kfunc - is_bpf_obj_drop_kfunc - is_bpf_percpu_obj_drop_kfunc - is_bpf_refcount_acquire_kfunc - is_bpf_list_push_kfunc - is_bpf_rbtree_add_kfunc Could you please extend this check? > + continue; > + verbose(env, "%s unrecognized implicit argument, possible BTF mismatch\n", > + reg_arg_name(env, argno)); > + return -EFAULT; > + } > + > t = btf_type_skip_modifiers(btf, args[i].type, NULL); > > if (btf_type_is_scalar(t)) { ^ permalink raw reply [flat|nested] 35+ messages in thread
* [PATCH bpf v4 2/2] selftests/bpf: Add regression test for kfunc implicit arg injection 2026-06-02 9:38 ` [PATCH bpf v4 0/2] bpf: Fix kfunc implicit arg injection and add selftest chenyuan_fl 2026-06-02 9:38 ` [PATCH bpf v4 1/2] bpf: Fix kfunc implicit arg inject type detection to prevent invalid pointer deref chenyuan_fl @ 2026-06-02 9:38 ` chenyuan_fl 2026-06-02 10:06 ` sashiko-bot ` (4 more replies) 1 sibling, 5 replies; 35+ messages in thread From: chenyuan_fl @ 2026-06-02 9:38 UTC (permalink / raw) To: eddyz87, yonghong.song Cc: andrii, ast, bot+bpf-ci, bpf, chenyuan, chenyuan_fl, clm, daniel, ihor.solodrai, jolsa, linux-kernel, martin.lau, martin.lau, memxor, song From: Yuan Chen <chenyuan@kylinos.cn> The preceding patch fixes a silent fallthrough in check_kfunc_args() that could cause the verifier to skip bpf_prog_aux injection for KF_IMPLICIT_ARGS kfuncs when module BTF is inconsistent with vmlinux (e.g. pahole 1.30 breaking distilled base dedup). Add a positive regression test that verifies the injection path works correctly under normal conditions (pahole 1.31+). The test contaminates BPF R2 with a magic value 0xDEAD via inline assembly before calling a KF_IMPLICIT_ARGS kfunc associated with a struct_ops map. The kfunc validates that the kernel overwrote R2 with the real bpf_prog_aux pointer rather than leaving the stale value. The specific pahole 1.30 BTF mismatch scenario cannot be tested with CI (which uses pahole 1.31), but this test ensures the injection mechanism remains correct and does not regress. Signed-off-by: Yuan Chen <chenyuan@kylinos.cn> --- .../bpf/prog_tests/test_struct_ops_assoc.c | 6 +++ .../selftests/bpf/progs/struct_ops_assoc.c | 40 +++++++++++++++++++ .../selftests/bpf/test_kmods/bpf_testmod.c | 9 +++++ .../bpf/test_kmods/bpf_testmod_kfunc.h | 1 + 4 files changed, 56 insertions(+) diff --git a/tools/testing/selftests/bpf/prog_tests/test_struct_ops_assoc.c b/tools/testing/selftests/bpf/prog_tests/test_struct_ops_assoc.c index 461ded722351..192fd3166f38 100644 --- a/tools/testing/selftests/bpf/prog_tests/test_struct_ops_assoc.c +++ b/tools/testing/selftests/bpf/prog_tests/test_struct_ops_assoc.c @@ -35,6 +35,10 @@ static void test_st_ops_assoc(void) skel->maps.st_ops_map_b, NULL); ASSERT_OK(err, "bpf_program__assoc_struct_ops(sys_enter_prog_b, st_ops_map_b)"); + err = bpf_program__assoc_struct_ops(skel->progs.sys_enter_prog_test_aux_inject, + skel->maps.st_ops_map_a, NULL); + ASSERT_OK(err, "bpf_program__assoc_struct_ops(sys_enter_prog_test_aux_inject, st_ops_map_a)"); + /* sys_enter_prog_a already associated with map_a */ err = bpf_program__assoc_struct_ops(skel->progs.sys_enter_prog_a, skel->maps.st_ops_map_b, NULL); @@ -52,6 +56,7 @@ static void test_st_ops_assoc(void) ASSERT_EQ(skel->bss->test_err_a, 0, "skel->bss->test_err_a"); ASSERT_EQ(skel->bss->test_err_b, 0, "skel->bss->test_err_b"); + ASSERT_EQ(skel->bss->test_err_inject, 0, "skel->bss->test_err_inject"); /* run syscall_prog that calls .test_1 and checks return */ err = bpf_prog_test_run_opts(bpf_program__fd(skel->progs.syscall_prog_a), NULL); @@ -62,6 +67,7 @@ static void test_st_ops_assoc(void) ASSERT_EQ(skel->bss->test_err_a, 0, "skel->bss->test_err_a"); ASSERT_EQ(skel->bss->test_err_b, 0, "skel->bss->test_err_b"); + ASSERT_EQ(skel->bss->test_err_inject, 0, "skel->bss->test_err_inject"); out: struct_ops_assoc__destroy(skel); diff --git a/tools/testing/selftests/bpf/progs/struct_ops_assoc.c b/tools/testing/selftests/bpf/progs/struct_ops_assoc.c index 68842e3f936b..ed0084453d56 100644 --- a/tools/testing/selftests/bpf/progs/struct_ops_assoc.c +++ b/tools/testing/selftests/bpf/progs/struct_ops_assoc.c @@ -103,3 +103,43 @@ SEC(".struct_ops.link") struct bpf_testmod_multi_st_ops st_ops_map_b = { .test_1 = (void *)test_1_b, }; + +/* Test for aux injection with stale register contamination. + * + * This test verifies that the kernel correctly injects the implicit + * bpf_prog_aux pointer for kfuncs with KF_IMPLICIT_ARGS. The program + * uses inline assembly to contaminate R2 with a known magic value + * before calling the kfunc: + * + * asm volatile("r2 = %[magic]" :: [magic] "ri"(0xDEAD) : "r2"); + * + * The kernel must inject env->prog->aux into R2, overriding the magic + * value. The kfunc compares the received aux pointer against 0xDEAD: + * + * - aux == 0xDEAD → kernel failed to inject → kfunc returns -EINVAL + * - aux != 0xDEAD → kernel correctly injected → kfunc returns marker + * + * 0xDEAD is chosen with bit 31 clear to avoid BPF ALU64 sign-extension + * when used as a 32-bit immediate. + */ +int test_err_inject; + +SEC("tp_btf/sys_enter") +int BPF_PROG(sys_enter_prog_test_aux_inject, struct pt_regs *regs, long id) +{ + struct task_struct *task; + int marker = 0x5A5A; + int ret; + + task = bpf_get_current_task_btf(); + if (!test_pid || task->pid != test_pid) + return 0; + + asm volatile("r2 = %[magic]" :: [magic] "ri"(0xDEAD) : "r2"); + + ret = bpf_kfunc_aux_inject_stale(marker); + if (ret != marker) + test_err_inject++; + + return 0; +} diff --git a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c index 0be918fe3021..8e979f0fa56d 100644 --- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c +++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c @@ -1315,6 +1315,7 @@ __bpf_kfunc int bpf_kfunc_multi_st_ops_test_1_assoc(struct st_ops_args *args, st __bpf_kfunc int bpf_kfunc_implicit_arg(int a, struct bpf_prog_aux *aux); __bpf_kfunc int bpf_kfunc_implicit_arg_legacy(int a, int b, struct bpf_prog_aux *aux); +__bpf_kfunc int bpf_kfunc_aux_inject_stale(int marker, struct bpf_prog_aux *aux); __bpf_kfunc int bpf_kfunc_implicit_arg_legacy_impl(int a, int b, struct bpf_prog_aux *aux); /* hook targets */ @@ -1399,6 +1400,7 @@ BTF_ID_FLAGS(func, bpf_kfunc_multi_st_ops_test_1_assoc, KF_IMPLICIT_ARGS) BTF_ID_FLAGS(func, bpf_kfunc_implicit_arg, KF_IMPLICIT_ARGS) BTF_ID_FLAGS(func, bpf_kfunc_implicit_arg_legacy, KF_IMPLICIT_ARGS) BTF_ID_FLAGS(func, bpf_kfunc_implicit_arg_legacy_impl) +BTF_ID_FLAGS(func, bpf_kfunc_aux_inject_stale, KF_IMPLICIT_ARGS) BTF_ID_FLAGS(func, bpf_kfunc_trigger_ctx_check) BTF_KFUNCS_END(bpf_testmod_check_kfunc_ids) @@ -1916,6 +1918,13 @@ int bpf_kfunc_implicit_arg_legacy_impl(int a, int b, struct bpf_prog_aux *aux) return bpf_kfunc_implicit_arg_legacy(a, b, aux); } +int bpf_kfunc_aux_inject_stale(int marker, struct bpf_prog_aux *aux) +{ + if ((unsigned long)aux == 0xDEAD) + return -EINVAL; + return marker; +} + static int multi_st_ops_reg(void *kdata, struct bpf_link *link) { struct bpf_testmod_multi_st_ops *st_ops = diff --git a/tools/testing/selftests/bpf/test_kmods/bpf_testmod_kfunc.h b/tools/testing/selftests/bpf/test_kmods/bpf_testmod_kfunc.h index 2edc36b66de9..c18791e96b21 100644 --- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod_kfunc.h +++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod_kfunc.h @@ -192,6 +192,7 @@ int *bpf_kfunc_ret_rcu_test_nostruct(int rdonly_buf_size) __ksym; #ifndef __KERNEL__ extern int bpf_kfunc_multi_st_ops_test_1(struct st_ops_args *args, u32 id) __weak __ksym; extern int bpf_kfunc_multi_st_ops_test_1_assoc(struct st_ops_args *args) __weak __ksym; +extern int bpf_kfunc_aux_inject_stale(int marker) __weak __ksym; #endif struct prog_test_member *bpf_kfunc_get_default_trusted_ptr_test(void) __ksym; -- 2.54.0 ^ permalink raw reply related [flat|nested] 35+ messages in thread
* Re: [PATCH bpf v4 2/2] selftests/bpf: Add regression test for kfunc implicit arg injection 2026-06-02 9:38 ` [PATCH bpf v4 2/2] selftests/bpf: Add regression test for kfunc implicit arg injection chenyuan_fl @ 2026-06-02 10:06 ` sashiko-bot 2026-06-02 10:27 ` bot+bpf-ci ` (3 subsequent siblings) 4 siblings, 0 replies; 35+ messages in thread From: sashiko-bot @ 2026-06-02 10:06 UTC (permalink / raw) To: chenyuan_fl; +Cc: bpf Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Low] The multi-line comment in `struct_ops_assoc.c` violates the strict BPF subsystem comment style guidelines. -- commit 0131da78b5d91ab9b0a63c8ffd845ab004048c55 Author: Yuan Chen <chenyuan@kylinos.cn> selftests/bpf: Add regression test for kfunc implicit arg injection This commit adds a positive regression test to verify that the injection path for KF_IMPLICIT_ARGS kfuncs works correctly. It uses inline assembly to contaminate BPF R2 before calling a kfunc associated with a struct_ops map. > diff --git a/tools/testing/selftests/bpf/progs/struct_ops_assoc.c b/tools/testing/selftests/bpf/progs/struct_ops_assoc.c > index 68842e3f936b9..ed0084453d560 100644 > --- a/tools/testing/selftests/bpf/progs/struct_ops_assoc.c > +++ b/tools/testing/selftests/bpf/progs/struct_ops_assoc.c > @@ -103,3 +103,43 @@ SEC(".struct_ops.link") > struct bpf_testmod_multi_st_ops st_ops_map_b = { > .test_1 = (void *)test_1_b, > }; > + > +/* Test for aux injection with stale register contamination. > + * > + * This test verifies that the kernel correctly injects the implicit [Severity: Low] Could this comment be updated to follow the BPF subsystem style? Multi-line comments in this subsystem should have the opening /* on its own line, with the text starting on the following line. > + * bpf_prog_aux pointer for kfuncs with KF_IMPLICIT_ARGS. The program > + * uses inline assembly to contaminate R2 with a known magic value > + * before calling the kfunc: -- Sashiko AI review · https://sashiko.dev/#/patchset/20260602093836.2632714-1-chenyuan_fl@163.com?part=2 ^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH bpf v4 2/2] selftests/bpf: Add regression test for kfunc implicit arg injection 2026-06-02 9:38 ` [PATCH bpf v4 2/2] selftests/bpf: Add regression test for kfunc implicit arg injection chenyuan_fl 2026-06-02 10:06 ` sashiko-bot @ 2026-06-02 10:27 ` bot+bpf-ci 2026-06-02 17:36 ` kernel test robot ` (2 subsequent siblings) 4 siblings, 0 replies; 35+ messages in thread From: bot+bpf-ci @ 2026-06-02 10:27 UTC (permalink / raw) To: chenyuan_fl, eddyz87, yonghong.song Cc: andrii, ast, bot+bpf-ci, bpf, chenyuan, chenyuan_fl, clm, daniel, ihor.solodrai, jolsa, linux-kernel, martin.lau, martin.lau, memxor, song, ast, andrii, daniel, martin.lau, eddyz87, yonghong.song, clm, ihor.solodrai [-- Attachment #1: Type: text/plain, Size: 3733 bytes --] > diff --git a/tools/testing/selftests/bpf/prog_tests/test_struct_ops_assoc.c b/tools/testing/selftests/bpf/prog_tests/test_struct_ops_assoc.c > index 461ded722351..192fd3166f38 100644 > --- a/tools/testing/selftests/bpf/prog_tests/test_struct_ops_assoc.c > +++ b/tools/testing/selftests/bpf/prog_tests/test_struct_ops_assoc.c [ ... ] > diff --git a/tools/testing/selftests/bpf/progs/struct_ops_assoc.c b/tools/testing/selftests/bpf/progs/struct_ops_assoc.c > index 68842e3f936b..ed0084453d56 100644 > --- a/tools/testing/selftests/bpf/progs/struct_ops_assoc.c > +++ b/tools/testing/selftests/bpf/progs/struct_ops_assoc.c > @@ -103,3 +103,43 @@ SEC(".struct_ops.link") > struct bpf_testmod_multi_st_ops st_ops_map_b = { > .test_1 = (void *)test_1_b, > }; > + > +/* Test for aux injection with stale register contamination. > + * > + * This test verifies that the kernel correctly injects the implicit > + * bpf_prog_aux pointer for kfuncs with KF_IMPLICIT_ARGS. The program > + * uses inline assembly to contaminate R2 with a known magic value > + * before calling the kfunc: > + * > + * asm volatile("r2 = %[magic]" :: [magic] "ri"(0xDEAD) : "r2"); > + * > + * The kernel must inject env->prog->aux into R2, overriding the magic > + * value. The kfunc compares the received aux pointer against 0xDEAD: > + * > + * - aux == 0xDEAD -> kernel failed to inject -> kfunc returns -EINVAL > + * - aux != 0xDEAD -> kernel correctly injected -> kfunc returns marker > + * > + * 0xDEAD is chosen with bit 31 clear to avoid BPF ALU64 sign-extension > + * when used as a 32-bit immediate. > + */ > +int test_err_inject; > + > +SEC("tp_btf/sys_enter") > +int BPF_PROG(sys_enter_prog_test_aux_inject, struct pt_regs *regs, long id) > +{ > + struct task_struct *task; > + int marker = 0x5A5A; > + int ret; > + > + task = bpf_get_current_task_btf(); > + if (!test_pid || task->pid != test_pid) > + return 0; > + > + asm volatile("r2 = %[magic]" :: [magic] "ri"(0xDEAD) : "r2"); > + > + ret = bpf_kfunc_aux_inject_stale(marker); > + if (ret != marker) > + test_err_inject++; > + > + return 0; > +} In v2, Yonghong Song questioned whether this selftest makes sense, given that CI has pahole 1.31 so the test will always succeed: https://lore.kernel.org/bpf/9cc9ce47-c45e-4878-ad3a-6e6967399ab9@linux.dev/ The commit message now acknowledges that "the specific pahole 1.30 BTF mismatch scenario cannot be tested with CI", but it's unclear what value this test provides if it cannot actually verify the bug condition. Could you explain the rationale for including a test that validates the injection mechanism works under normal conditions but cannot reproduce the actual pahole 1.30 BTF inconsistency scenario the bug fix addresses? [ ... ] Unaddressed review comment from the preceding patch The issue below applies to the preceding patch (kernel/bpf/verifier.c) rather than this selftest, but remains unaddressed in the patch series: In v2, Yonghong Song requested that code comments should explain the fix is due to pahole 1.30 BTF generation issues, so the code can be removed later when that toolchain is no longer relevant: https://lore.kernel.org/bpf/7b3ba4a8-a785-4f70-879d-50e63d6de787@linux.dev/ The commit message in the preceding patch describes the pahole 1.30 issue extensively, but the in-code documentation was never added in v3 or v4. Should this documentation be added to check_kfunc_args() or is_kfunc_arg_implicit() to explain the temporary nature of this fix? --- AI reviewed your patch. Please fix the bug or email reply why it's not a bug. See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md CI run summary: https://github.com/kernel-patches/bpf/actions/runs/26812478701 ^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH bpf v4 2/2] selftests/bpf: Add regression test for kfunc implicit arg injection 2026-06-02 9:38 ` [PATCH bpf v4 2/2] selftests/bpf: Add regression test for kfunc implicit arg injection chenyuan_fl 2026-06-02 10:06 ` sashiko-bot 2026-06-02 10:27 ` bot+bpf-ci @ 2026-06-02 17:36 ` kernel test robot 2026-06-02 18:37 ` kernel test robot 2026-06-05 1:29 ` Eduard Zingerman 4 siblings, 0 replies; 35+ messages in thread From: kernel test robot @ 2026-06-02 17:36 UTC (permalink / raw) To: chenyuan_fl, eddyz87, yonghong.song Cc: llvm, oe-kbuild-all, andrii, ast, bot+bpf-ci, bpf, chenyuan, chenyuan_fl, clm, daniel, ihor.solodrai, jolsa, linux-kernel, martin.lau, martin.lau, memxor, song Hi Yuan, kernel test robot noticed the following build errors: [auto build test ERROR on bpf/master] url: https://github.com/intel-lab-lkp/linux/commits/chenyuan_fl-163-com/bpf-Fix-kfunc-implicit-arg-inject-type-detection-to-prevent-invalid-pointer-deref/20260602-175420 base: https://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf.git master patch link: https://lore.kernel.org/r/20260602093836.2632714-3-chenyuan_fl%40163.com patch subject: [PATCH bpf v4 2/2] selftests/bpf: Add regression test for kfunc implicit arg injection config: x86_64-kexec (https://download.01.org/0day-ci/archive/20260602/202606021934.SXop4khA-lkp@intel.com/config) compiler: clang version 20.1.8 (https://github.com/llvm/llvm-project 87f0227cb60147a26a1eeb4fb06e3b505e9c7261) reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260602/202606021934.SXop4khA-lkp@intel.com/reproduce) If you fix the issue in a separate patch/commit (i.e. not just a new version of the same patch/commit), kindly add following tags | Reported-by: kernel test robot <lkp@intel.com> | Closes: https://lore.kernel.org/oe-kbuild-all/202606021934.SXop4khA-lkp@intel.com/ All errors (new ones prefixed by >>): >> kernel/bpf/verifier.c:12097:5: error: call to undeclared function 'reg_arg_name'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration] 12097 | reg_arg_name(env, argno)); | ^ >> kernel/bpf/verifier.c:12097:23: error: use of undeclared identifier 'argno' 12097 | reg_arg_name(env, argno)); | ^ 2 errors generated. vim +/reg_arg_name +12097 kernel/bpf/verifier.c 12044 12045 static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_arg_meta *meta, 12046 int insn_idx) 12047 { 12048 const char *func_name = meta->func_name, *ref_tname; 12049 const struct btf *btf = meta->btf; 12050 const struct btf_param *args; 12051 struct btf_record *rec; 12052 u32 i, nargs; 12053 int ret; 12054 12055 args = (const struct btf_param *)(meta->func_proto + 1); 12056 nargs = btf_type_vlen(meta->func_proto); 12057 if (nargs > MAX_BPF_FUNC_REG_ARGS) { 12058 verbose(env, "Function %s has %d > %d args\n", func_name, nargs, 12059 MAX_BPF_FUNC_REG_ARGS); 12060 return -EINVAL; 12061 } 12062 12063 /* Check that BTF function arguments match actual types that the 12064 * verifier sees. 12065 */ 12066 for (i = 0; i < nargs; i++) { 12067 struct bpf_reg_state *regs = cur_regs(env), *reg = ®s[i + 1]; 12068 const struct btf_type *t, *ref_t, *resolve_ret; 12069 enum bpf_arg_type arg_type = ARG_DONTCARE; 12070 u32 regno = i + 1, ref_id, type_size; 12071 bool is_ret_buf_sz = false; 12072 int kf_arg_type; 12073 12074 if (is_kfunc_arg_prog_aux(btf, &args[i])) { 12075 /* Reject repeated use bpf_prog_aux */ 12076 if (meta->arg_prog) { 12077 verifier_bug(env, "Only 1 prog->aux argument supported per-kfunc"); 12078 return -EFAULT; 12079 } 12080 meta->arg_prog = true; 12081 cur_aux(env)->arg_prog = regno; 12082 continue; 12083 } 12084 12085 if (is_kfunc_arg_ignore(btf, &args[i])) 12086 continue; 12087 12088 if (is_kfunc_arg_implicit(meta, i)) { 12089 /* list_push / rbtree_add kfuncs have implicit args 12090 * (e.g. 'off' parameter) handled during verification 12091 * in bpf_fixup_kfunc_call(). Don't flag them. 12092 */ 12093 if (is_bpf_list_push_kfunc(meta->func_id) || 12094 is_bpf_rbtree_add_kfunc(meta->func_id)) 12095 continue; 12096 verbose(env, "%s unrecognized implicit argument, possible BTF mismatch\n", 12097 reg_arg_name(env, argno)); 12098 return -EFAULT; 12099 } 12100 12101 t = btf_type_skip_modifiers(btf, args[i].type, NULL); 12102 12103 if (btf_type_is_scalar(t)) { 12104 if (reg->type != SCALAR_VALUE) { 12105 verbose(env, "R%d is not a scalar\n", regno); 12106 return -EINVAL; 12107 } 12108 12109 if (is_kfunc_arg_constant(meta->btf, &args[i])) { 12110 if (meta->arg_constant.found) { 12111 verifier_bug(env, "only one constant argument permitted"); 12112 return -EFAULT; 12113 } 12114 if (!tnum_is_const(reg->var_off)) { 12115 verbose(env, "R%d must be a known constant\n", regno); 12116 return -EINVAL; 12117 } 12118 ret = mark_chain_precision(env, regno); 12119 if (ret < 0) 12120 return ret; 12121 meta->arg_constant.found = true; 12122 meta->arg_constant.value = reg->var_off.value; 12123 } else if (is_kfunc_arg_scalar_with_name(btf, &args[i], "rdonly_buf_size")) { 12124 meta->r0_rdonly = true; 12125 is_ret_buf_sz = true; 12126 } else if (is_kfunc_arg_scalar_with_name(btf, &args[i], "rdwr_buf_size")) { 12127 is_ret_buf_sz = true; 12128 } 12129 12130 if (is_ret_buf_sz) { 12131 if (meta->r0_size) { 12132 verbose(env, "2 or more rdonly/rdwr_buf_size parameters for kfunc"); 12133 return -EINVAL; 12134 } 12135 12136 if (!tnum_is_const(reg->var_off)) { 12137 verbose(env, "R%d is not a const\n", regno); 12138 return -EINVAL; 12139 } 12140 12141 meta->r0_size = reg->var_off.value; 12142 ret = mark_chain_precision(env, regno); 12143 if (ret) 12144 return ret; 12145 } 12146 continue; 12147 } 12148 12149 if (!btf_type_is_ptr(t)) { 12150 verbose(env, "Unrecognized arg#%d type %s\n", i, btf_type_str(t)); 12151 return -EINVAL; 12152 } 12153 12154 if ((bpf_register_is_null(reg) || type_may_be_null(reg->type)) && 12155 !is_kfunc_arg_nullable(meta->btf, &args[i])) { 12156 verbose(env, "Possibly NULL pointer passed to trusted arg%d\n", i); 12157 return -EACCES; 12158 } 12159 12160 if (reg->ref_obj_id) { 12161 if (is_kfunc_release(meta) && meta->ref_obj_id) { 12162 verifier_bug(env, "more than one arg with ref_obj_id R%d %u %u", 12163 regno, reg->ref_obj_id, 12164 meta->ref_obj_id); 12165 return -EFAULT; 12166 } 12167 meta->ref_obj_id = reg->ref_obj_id; 12168 if (is_kfunc_release(meta)) 12169 meta->release_regno = regno; 12170 } 12171 12172 ref_t = btf_type_skip_modifiers(btf, t->type, &ref_id); 12173 ref_tname = btf_name_by_offset(btf, ref_t->name_off); 12174 12175 kf_arg_type = get_kfunc_ptr_arg_type(env, meta, t, ref_t, ref_tname, args, i, nargs); 12176 if (kf_arg_type < 0) 12177 return kf_arg_type; 12178 12179 switch (kf_arg_type) { 12180 case KF_ARG_PTR_TO_NULL: 12181 continue; 12182 case KF_ARG_PTR_TO_MAP: 12183 if (!reg->map_ptr) { 12184 verbose(env, "pointer in R%d isn't map pointer\n", regno); 12185 return -EINVAL; 12186 } 12187 if (meta->map.ptr && (reg->map_ptr->record->wq_off >= 0 || 12188 reg->map_ptr->record->task_work_off >= 0)) { 12189 /* Use map_uid (which is unique id of inner map) to reject: 12190 * inner_map1 = bpf_map_lookup_elem(outer_map, key1) 12191 * inner_map2 = bpf_map_lookup_elem(outer_map, key2) 12192 * if (inner_map1 && inner_map2) { 12193 * wq = bpf_map_lookup_elem(inner_map1); 12194 * if (wq) 12195 * // mismatch would have been allowed 12196 * bpf_wq_init(wq, inner_map2); 12197 * } 12198 * 12199 * Comparing map_ptr is enough to distinguish normal and outer maps. 12200 */ 12201 if (meta->map.ptr != reg->map_ptr || 12202 meta->map.uid != reg->map_uid) { 12203 if (reg->map_ptr->record->task_work_off >= 0) { 12204 verbose(env, 12205 "bpf_task_work pointer in R2 map_uid=%d doesn't match map pointer in R3 map_uid=%d\n", 12206 meta->map.uid, reg->map_uid); 12207 return -EINVAL; 12208 } 12209 verbose(env, 12210 "workqueue pointer in R1 map_uid=%d doesn't match map pointer in R2 map_uid=%d\n", 12211 meta->map.uid, reg->map_uid); 12212 return -EINVAL; 12213 } 12214 } 12215 meta->map.ptr = reg->map_ptr; 12216 meta->map.uid = reg->map_uid; 12217 fallthrough; 12218 case KF_ARG_PTR_TO_ALLOC_BTF_ID: 12219 case KF_ARG_PTR_TO_BTF_ID: 12220 if (!is_trusted_reg(reg)) { 12221 if (!is_kfunc_rcu(meta)) { 12222 verbose(env, "R%d must be referenced or trusted\n", regno); 12223 return -EINVAL; 12224 } 12225 if (!is_rcu_reg(reg)) { 12226 verbose(env, "R%d must be a rcu pointer\n", regno); 12227 return -EINVAL; 12228 } 12229 } 12230 fallthrough; 12231 case KF_ARG_PTR_TO_DYNPTR: 12232 case KF_ARG_PTR_TO_ITER: 12233 case KF_ARG_PTR_TO_LIST_HEAD: 12234 case KF_ARG_PTR_TO_LIST_NODE: 12235 case KF_ARG_PTR_TO_RB_ROOT: 12236 case KF_ARG_PTR_TO_RB_NODE: 12237 case KF_ARG_PTR_TO_MEM: 12238 case KF_ARG_PTR_TO_MEM_SIZE: 12239 case KF_ARG_PTR_TO_CALLBACK: 12240 case KF_ARG_PTR_TO_REFCOUNTED_KPTR: 12241 case KF_ARG_PTR_TO_CONST_STR: 12242 case KF_ARG_PTR_TO_WORKQUEUE: 12243 case KF_ARG_PTR_TO_TIMER: 12244 case KF_ARG_PTR_TO_TASK_WORK: 12245 case KF_ARG_PTR_TO_IRQ_FLAG: 12246 case KF_ARG_PTR_TO_RES_SPIN_LOCK: 12247 break; 12248 case KF_ARG_PTR_TO_CTX: 12249 arg_type = ARG_PTR_TO_CTX; 12250 break; 12251 default: 12252 verifier_bug(env, "unknown kfunc arg type %d", kf_arg_type); 12253 return -EFAULT; 12254 } 12255 12256 if (is_kfunc_release(meta) && reg->ref_obj_id) 12257 arg_type |= OBJ_RELEASE; 12258 ret = check_func_arg_reg_off(env, reg, regno, arg_type); 12259 if (ret < 0) 12260 return ret; 12261 12262 switch (kf_arg_type) { 12263 case KF_ARG_PTR_TO_CTX: 12264 if (reg->type != PTR_TO_CTX) { 12265 verbose(env, "arg#%d expected pointer to ctx, but got %s\n", 12266 i, reg_type_str(env, reg->type)); 12267 return -EINVAL; 12268 } 12269 12270 if (meta->func_id == special_kfunc_list[KF_bpf_cast_to_kern_ctx]) { 12271 ret = get_kern_ctx_btf_id(&env->log, resolve_prog_type(env->prog)); 12272 if (ret < 0) 12273 return -EINVAL; 12274 meta->ret_btf_id = ret; 12275 } 12276 break; 12277 case KF_ARG_PTR_TO_ALLOC_BTF_ID: 12278 if (reg->type == (PTR_TO_BTF_ID | MEM_ALLOC)) { 12279 if (!is_bpf_obj_drop_kfunc(meta->func_id)) { 12280 verbose(env, "arg#%d expected for bpf_obj_drop()\n", i); 12281 return -EINVAL; 12282 } 12283 } else if (reg->type == (PTR_TO_BTF_ID | MEM_ALLOC | MEM_PERCPU)) { 12284 if (!is_bpf_percpu_obj_drop_kfunc(meta->func_id)) { 12285 verbose(env, "arg#%d expected for bpf_percpu_obj_drop()\n", i); 12286 return -EINVAL; 12287 } 12288 } else { 12289 verbose(env, "arg#%d expected pointer to allocated object\n", i); 12290 return -EINVAL; 12291 } 12292 if (!reg->ref_obj_id) { 12293 verbose(env, "allocated object must be referenced\n"); 12294 return -EINVAL; 12295 } 12296 if (meta->btf == btf_vmlinux) { 12297 meta->arg_btf = reg->btf; 12298 meta->arg_btf_id = reg->btf_id; 12299 } 12300 break; 12301 case KF_ARG_PTR_TO_DYNPTR: 12302 { 12303 enum bpf_arg_type dynptr_arg_type = ARG_PTR_TO_DYNPTR; 12304 int clone_ref_obj_id = 0; 12305 12306 if (reg->type == CONST_PTR_TO_DYNPTR) 12307 dynptr_arg_type |= MEM_RDONLY; 12308 12309 if (is_kfunc_arg_uninit(btf, &args[i])) 12310 dynptr_arg_type |= MEM_UNINIT; 12311 12312 if (meta->func_id == special_kfunc_list[KF_bpf_dynptr_from_skb]) { 12313 dynptr_arg_type |= DYNPTR_TYPE_SKB; 12314 } else if (meta->func_id == special_kfunc_list[KF_bpf_dynptr_from_xdp]) { 12315 dynptr_arg_type |= DYNPTR_TYPE_XDP; 12316 } else if (meta->func_id == special_kfunc_list[KF_bpf_dynptr_from_skb_meta]) { 12317 dynptr_arg_type |= DYNPTR_TYPE_SKB_META; 12318 } else if (meta->func_id == special_kfunc_list[KF_bpf_dynptr_from_file]) { 12319 dynptr_arg_type |= DYNPTR_TYPE_FILE; 12320 } else if (meta->func_id == special_kfunc_list[KF_bpf_dynptr_file_discard]) { 12321 dynptr_arg_type |= DYNPTR_TYPE_FILE; 12322 meta->release_regno = regno; 12323 } else if (meta->func_id == special_kfunc_list[KF_bpf_dynptr_clone] && 12324 (dynptr_arg_type & MEM_UNINIT)) { 12325 enum bpf_dynptr_type parent_type = meta->initialized_dynptr.type; 12326 12327 if (parent_type == BPF_DYNPTR_TYPE_INVALID) { 12328 verifier_bug(env, "no dynptr type for parent of clone"); 12329 return -EFAULT; 12330 } 12331 12332 dynptr_arg_type |= (unsigned int)get_dynptr_type_flag(parent_type); 12333 clone_ref_obj_id = meta->initialized_dynptr.ref_obj_id; 12334 if (dynptr_type_refcounted(parent_type) && !clone_ref_obj_id) { 12335 verifier_bug(env, "missing ref obj id for parent of clone"); 12336 return -EFAULT; 12337 } 12338 } 12339 12340 ret = process_dynptr_func(env, regno, insn_idx, dynptr_arg_type, clone_ref_obj_id); 12341 if (ret < 0) 12342 return ret; 12343 12344 if (!(dynptr_arg_type & MEM_UNINIT)) { 12345 int id = dynptr_id(env, reg); 12346 12347 if (id < 0) { 12348 verifier_bug(env, "failed to obtain dynptr id"); 12349 return id; 12350 } 12351 meta->initialized_dynptr.id = id; 12352 meta->initialized_dynptr.type = dynptr_get_type(env, reg); 12353 meta->initialized_dynptr.ref_obj_id = dynptr_ref_obj_id(env, reg); 12354 } 12355 12356 break; 12357 } 12358 case KF_ARG_PTR_TO_ITER: 12359 if (meta->func_id == special_kfunc_list[KF_bpf_iter_css_task_new]) { 12360 if (!check_css_task_iter_allowlist(env)) { 12361 verbose(env, "css_task_iter is only allowed in bpf_lsm, bpf_iter and sleepable progs\n"); 12362 return -EINVAL; 12363 } 12364 } 12365 ret = process_iter_arg(env, regno, insn_idx, meta); 12366 if (ret < 0) 12367 return ret; 12368 break; 12369 case KF_ARG_PTR_TO_LIST_HEAD: 12370 if (reg->type != PTR_TO_MAP_VALUE && 12371 reg->type != (PTR_TO_BTF_ID | MEM_ALLOC)) { 12372 verbose(env, "arg#%d expected pointer to map value or allocated object\n", i); 12373 return -EINVAL; 12374 } 12375 if (reg->type == (PTR_TO_BTF_ID | MEM_ALLOC) && !reg->ref_obj_id) { 12376 verbose(env, "allocated object must be referenced\n"); 12377 return -EINVAL; 12378 } 12379 ret = process_kf_arg_ptr_to_list_head(env, reg, regno, meta); 12380 if (ret < 0) 12381 return ret; 12382 break; 12383 case KF_ARG_PTR_TO_RB_ROOT: 12384 if (reg->type != PTR_TO_MAP_VALUE && 12385 reg->type != (PTR_TO_BTF_ID | MEM_ALLOC)) { 12386 verbose(env, "arg#%d expected pointer to map value or allocated object\n", i); 12387 return -EINVAL; 12388 } 12389 if (reg->type == (PTR_TO_BTF_ID | MEM_ALLOC) && !reg->ref_obj_id) { 12390 verbose(env, "allocated object must be referenced\n"); 12391 return -EINVAL; 12392 } 12393 ret = process_kf_arg_ptr_to_rbtree_root(env, reg, regno, meta); 12394 if (ret < 0) 12395 return ret; 12396 break; 12397 case KF_ARG_PTR_TO_LIST_NODE: 12398 if (reg->type != (PTR_TO_BTF_ID | MEM_ALLOC)) { 12399 verbose(env, "arg#%d expected pointer to allocated object\n", i); 12400 return -EINVAL; 12401 } 12402 if (!reg->ref_obj_id) { 12403 verbose(env, "allocated object must be referenced\n"); 12404 return -EINVAL; 12405 } 12406 ret = process_kf_arg_ptr_to_list_node(env, reg, regno, meta); 12407 if (ret < 0) 12408 return ret; 12409 break; 12410 case KF_ARG_PTR_TO_RB_NODE: 12411 if (is_bpf_rbtree_add_kfunc(meta->func_id)) { 12412 if (reg->type != (PTR_TO_BTF_ID | MEM_ALLOC)) { 12413 verbose(env, "arg#%d expected pointer to allocated object\n", i); 12414 return -EINVAL; 12415 } 12416 if (!reg->ref_obj_id) { 12417 verbose(env, "allocated object must be referenced\n"); 12418 return -EINVAL; 12419 } 12420 } else { 12421 if (!type_is_non_owning_ref(reg->type) && !reg->ref_obj_id) { 12422 verbose(env, "%s can only take non-owning or refcounted bpf_rb_node pointer\n", func_name); 12423 return -EINVAL; 12424 } 12425 if (in_rbtree_lock_required_cb(env)) { 12426 verbose(env, "%s not allowed in rbtree cb\n", func_name); 12427 return -EINVAL; 12428 } 12429 } 12430 12431 ret = process_kf_arg_ptr_to_rbtree_node(env, reg, regno, meta); 12432 if (ret < 0) 12433 return ret; 12434 break; 12435 case KF_ARG_PTR_TO_MAP: 12436 /* If argument has '__map' suffix expect 'struct bpf_map *' */ 12437 ref_id = *reg2btf_ids[CONST_PTR_TO_MAP]; 12438 ref_t = btf_type_by_id(btf_vmlinux, ref_id); 12439 ref_tname = btf_name_by_offset(btf, ref_t->name_off); 12440 fallthrough; 12441 case KF_ARG_PTR_TO_BTF_ID: 12442 /* Only base_type is checked, further checks are done here */ 12443 if ((base_type(reg->type) != PTR_TO_BTF_ID || 12444 (bpf_type_has_unsafe_modifiers(reg->type) && !is_rcu_reg(reg))) && 12445 !reg2btf_ids[base_type(reg->type)]) { 12446 verbose(env, "arg#%d is %s ", i, reg_type_str(env, reg->type)); 12447 verbose(env, "expected %s or socket\n", 12448 reg_type_str(env, base_type(reg->type) | 12449 (type_flag(reg->type) & BPF_REG_TRUSTED_MODIFIERS))); 12450 return -EINVAL; 12451 } 12452 ret = process_kf_arg_ptr_to_btf_id(env, reg, ref_t, ref_tname, ref_id, meta, i); 12453 if (ret < 0) 12454 return ret; 12455 break; 12456 case KF_ARG_PTR_TO_MEM: 12457 resolve_ret = btf_resolve_size(btf, ref_t, &type_size); 12458 if (IS_ERR(resolve_ret)) { 12459 verbose(env, "arg#%d reference type('%s %s') size cannot be determined: %ld\n", 12460 i, btf_type_str(ref_t), ref_tname, PTR_ERR(resolve_ret)); 12461 return -EINVAL; 12462 } 12463 ret = check_mem_reg(env, reg, regno, type_size); 12464 if (ret < 0) 12465 return ret; 12466 break; 12467 case KF_ARG_PTR_TO_MEM_SIZE: 12468 { 12469 struct bpf_reg_state *buff_reg = ®s[regno]; 12470 const struct btf_param *buff_arg = &args[i]; 12471 struct bpf_reg_state *size_reg = ®s[regno + 1]; 12472 const struct btf_param *size_arg = &args[i + 1]; 12473 12474 if (!bpf_register_is_null(buff_reg) || !is_kfunc_arg_nullable(meta->btf, buff_arg)) { 12475 ret = check_kfunc_mem_size_reg(env, size_reg, regno + 1); 12476 if (ret < 0) { 12477 verbose(env, "arg#%d arg#%d memory, len pair leads to invalid memory access\n", i, i + 1); 12478 return ret; 12479 } 12480 } 12481 12482 if (is_kfunc_arg_const_mem_size(meta->btf, size_arg, size_reg)) { 12483 if (meta->arg_constant.found) { 12484 verifier_bug(env, "only one constant argument permitted"); 12485 return -EFAULT; 12486 } 12487 if (!tnum_is_const(size_reg->var_off)) { 12488 verbose(env, "R%d must be a known constant\n", regno + 1); 12489 return -EINVAL; 12490 } 12491 meta->arg_constant.found = true; 12492 meta->arg_constant.value = size_reg->var_off.value; 12493 } 12494 12495 /* Skip next '__sz' or '__szk' argument */ 12496 i++; 12497 break; 12498 } 12499 case KF_ARG_PTR_TO_CALLBACK: 12500 if (reg->type != PTR_TO_FUNC) { 12501 verbose(env, "arg%d expected pointer to func\n", i); 12502 return -EINVAL; 12503 } 12504 meta->subprogno = reg->subprogno; 12505 break; 12506 case KF_ARG_PTR_TO_REFCOUNTED_KPTR: 12507 if (!type_is_ptr_alloc_obj(reg->type)) { 12508 verbose(env, "arg#%d is neither owning or non-owning ref\n", i); 12509 return -EINVAL; 12510 } 12511 if (!type_is_non_owning_ref(reg->type)) 12512 meta->arg_owning_ref = true; 12513 12514 rec = reg_btf_record(reg); 12515 if (!rec) { 12516 verifier_bug(env, "Couldn't find btf_record"); 12517 return -EFAULT; 12518 } 12519 12520 if (rec->refcount_off < 0) { 12521 verbose(env, "arg#%d doesn't point to a type with bpf_refcount field\n", i); 12522 return -EINVAL; 12523 } 12524 12525 meta->arg_btf = reg->btf; 12526 meta->arg_btf_id = reg->btf_id; 12527 break; 12528 case KF_ARG_PTR_TO_CONST_STR: 12529 if (reg->type != PTR_TO_MAP_VALUE) { 12530 verbose(env, "arg#%d doesn't point to a const string\n", i); 12531 return -EINVAL; 12532 } 12533 ret = check_reg_const_str(env, reg, regno); 12534 if (ret) 12535 return ret; 12536 break; 12537 case KF_ARG_PTR_TO_WORKQUEUE: 12538 if (reg->type != PTR_TO_MAP_VALUE) { 12539 verbose(env, "arg#%d doesn't point to a map value\n", i); 12540 return -EINVAL; 12541 } 12542 ret = check_map_field_pointer(env, regno, BPF_WORKQUEUE, &meta->map); 12543 if (ret < 0) 12544 return ret; 12545 break; 12546 case KF_ARG_PTR_TO_TIMER: 12547 if (reg->type != PTR_TO_MAP_VALUE) { 12548 verbose(env, "arg#%d doesn't point to a map value\n", i); 12549 return -EINVAL; 12550 } 12551 ret = process_timer_kfunc(env, regno, meta); 12552 if (ret < 0) 12553 return ret; 12554 break; 12555 case KF_ARG_PTR_TO_TASK_WORK: 12556 if (reg->type != PTR_TO_MAP_VALUE) { 12557 verbose(env, "arg#%d doesn't point to a map value\n", i); 12558 return -EINVAL; 12559 } 12560 ret = check_map_field_pointer(env, regno, BPF_TASK_WORK, &meta->map); 12561 if (ret < 0) 12562 return ret; 12563 break; 12564 case KF_ARG_PTR_TO_IRQ_FLAG: 12565 if (reg->type != PTR_TO_STACK) { 12566 verbose(env, "arg#%d doesn't point to an irq flag on stack\n", i); 12567 return -EINVAL; 12568 } 12569 ret = process_irq_flag(env, regno, meta); 12570 if (ret < 0) 12571 return ret; 12572 break; 12573 case KF_ARG_PTR_TO_RES_SPIN_LOCK: 12574 { 12575 int flags = PROCESS_RES_LOCK; 12576 12577 if (reg->type != PTR_TO_MAP_VALUE && reg->type != (PTR_TO_BTF_ID | MEM_ALLOC)) { 12578 verbose(env, "arg#%d doesn't point to map value or allocated object\n", i); 12579 return -EINVAL; 12580 } 12581 12582 if (!is_bpf_res_spin_lock_kfunc(meta->func_id)) 12583 return -EFAULT; 12584 if (meta->func_id == special_kfunc_list[KF_bpf_res_spin_lock] || 12585 meta->func_id == special_kfunc_list[KF_bpf_res_spin_lock_irqsave]) 12586 flags |= PROCESS_SPIN_LOCK; 12587 if (meta->func_id == special_kfunc_list[KF_bpf_res_spin_lock_irqsave] || 12588 meta->func_id == special_kfunc_list[KF_bpf_res_spin_unlock_irqrestore]) 12589 flags |= PROCESS_LOCK_IRQ; 12590 ret = process_spin_lock(env, regno, flags); 12591 if (ret < 0) 12592 return ret; 12593 break; 12594 } 12595 } 12596 } 12597 12598 if (is_kfunc_release(meta) && !meta->release_regno) { 12599 verbose(env, "release kernel function %s expects refcounted PTR_TO_BTF_ID\n", 12600 func_name); 12601 return -EINVAL; 12602 } 12603 12604 return 0; 12605 } 12606 -- 0-DAY CI Kernel Test Service https://github.com/intel/lkp-tests/wiki ^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH bpf v4 2/2] selftests/bpf: Add regression test for kfunc implicit arg injection 2026-06-02 9:38 ` [PATCH bpf v4 2/2] selftests/bpf: Add regression test for kfunc implicit arg injection chenyuan_fl ` (2 preceding siblings ...) 2026-06-02 17:36 ` kernel test robot @ 2026-06-02 18:37 ` kernel test robot 2026-06-05 1:29 ` Eduard Zingerman 4 siblings, 0 replies; 35+ messages in thread From: kernel test robot @ 2026-06-02 18:37 UTC (permalink / raw) To: chenyuan_fl, eddyz87, yonghong.song Cc: oe-kbuild-all, andrii, ast, bot+bpf-ci, bpf, chenyuan, chenyuan_fl, clm, daniel, ihor.solodrai, jolsa, linux-kernel, martin.lau, martin.lau, memxor, song Hi Yuan, kernel test robot noticed the following build errors: [auto build test ERROR on bpf/master] url: https://github.com/intel-lab-lkp/linux/commits/chenyuan_fl-163-com/bpf-Fix-kfunc-implicit-arg-inject-type-detection-to-prevent-invalid-pointer-deref/20260602-175420 base: https://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf.git master patch link: https://lore.kernel.org/r/20260602093836.2632714-3-chenyuan_fl%40163.com patch subject: [PATCH bpf v4 2/2] selftests/bpf: Add regression test for kfunc implicit arg injection config: x86_64-rhel-9.4 (https://download.01.org/0day-ci/archive/20260602/202606022025.lDSZsxCa-lkp@intel.com/config) compiler: gcc-14 (Debian 14.2.0-19) 14.2.0 reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260602/202606022025.lDSZsxCa-lkp@intel.com/reproduce) If you fix the issue in a separate patch/commit (i.e. not just a new version of the same patch/commit), kindly add following tags | Reported-by: kernel test robot <lkp@intel.com> | Closes: https://lore.kernel.org/oe-kbuild-all/202606022025.lDSZsxCa-lkp@intel.com/ All errors (new ones prefixed by >>): kernel/bpf/verifier.c: In function 'check_kfunc_args': >> kernel/bpf/verifier.c:12097:33: error: implicit declaration of function 'reg_arg_name' [-Wimplicit-function-declaration] 12097 | reg_arg_name(env, argno)); | ^~~~~~~~~~~~ >> kernel/bpf/verifier.c:12097:51: error: 'argno' undeclared (first use in this function) 12097 | reg_arg_name(env, argno)); | ^~~~~ kernel/bpf/verifier.c:12097:51: note: each undeclared identifier is reported only once for each function it appears in vim +/reg_arg_name +12097 kernel/bpf/verifier.c 12044 12045 static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_arg_meta *meta, 12046 int insn_idx) 12047 { 12048 const char *func_name = meta->func_name, *ref_tname; 12049 const struct btf *btf = meta->btf; 12050 const struct btf_param *args; 12051 struct btf_record *rec; 12052 u32 i, nargs; 12053 int ret; 12054 12055 args = (const struct btf_param *)(meta->func_proto + 1); 12056 nargs = btf_type_vlen(meta->func_proto); 12057 if (nargs > MAX_BPF_FUNC_REG_ARGS) { 12058 verbose(env, "Function %s has %d > %d args\n", func_name, nargs, 12059 MAX_BPF_FUNC_REG_ARGS); 12060 return -EINVAL; 12061 } 12062 12063 /* Check that BTF function arguments match actual types that the 12064 * verifier sees. 12065 */ 12066 for (i = 0; i < nargs; i++) { 12067 struct bpf_reg_state *regs = cur_regs(env), *reg = ®s[i + 1]; 12068 const struct btf_type *t, *ref_t, *resolve_ret; 12069 enum bpf_arg_type arg_type = ARG_DONTCARE; 12070 u32 regno = i + 1, ref_id, type_size; 12071 bool is_ret_buf_sz = false; 12072 int kf_arg_type; 12073 12074 if (is_kfunc_arg_prog_aux(btf, &args[i])) { 12075 /* Reject repeated use bpf_prog_aux */ 12076 if (meta->arg_prog) { 12077 verifier_bug(env, "Only 1 prog->aux argument supported per-kfunc"); 12078 return -EFAULT; 12079 } 12080 meta->arg_prog = true; 12081 cur_aux(env)->arg_prog = regno; 12082 continue; 12083 } 12084 12085 if (is_kfunc_arg_ignore(btf, &args[i])) 12086 continue; 12087 12088 if (is_kfunc_arg_implicit(meta, i)) { 12089 /* list_push / rbtree_add kfuncs have implicit args 12090 * (e.g. 'off' parameter) handled during verification 12091 * in bpf_fixup_kfunc_call(). Don't flag them. 12092 */ 12093 if (is_bpf_list_push_kfunc(meta->func_id) || 12094 is_bpf_rbtree_add_kfunc(meta->func_id)) 12095 continue; 12096 verbose(env, "%s unrecognized implicit argument, possible BTF mismatch\n", 12097 reg_arg_name(env, argno)); 12098 return -EFAULT; 12099 } 12100 12101 t = btf_type_skip_modifiers(btf, args[i].type, NULL); 12102 12103 if (btf_type_is_scalar(t)) { 12104 if (reg->type != SCALAR_VALUE) { 12105 verbose(env, "R%d is not a scalar\n", regno); 12106 return -EINVAL; 12107 } 12108 12109 if (is_kfunc_arg_constant(meta->btf, &args[i])) { 12110 if (meta->arg_constant.found) { 12111 verifier_bug(env, "only one constant argument permitted"); 12112 return -EFAULT; 12113 } 12114 if (!tnum_is_const(reg->var_off)) { 12115 verbose(env, "R%d must be a known constant\n", regno); 12116 return -EINVAL; 12117 } 12118 ret = mark_chain_precision(env, regno); 12119 if (ret < 0) 12120 return ret; 12121 meta->arg_constant.found = true; 12122 meta->arg_constant.value = reg->var_off.value; 12123 } else if (is_kfunc_arg_scalar_with_name(btf, &args[i], "rdonly_buf_size")) { 12124 meta->r0_rdonly = true; 12125 is_ret_buf_sz = true; 12126 } else if (is_kfunc_arg_scalar_with_name(btf, &args[i], "rdwr_buf_size")) { 12127 is_ret_buf_sz = true; 12128 } 12129 12130 if (is_ret_buf_sz) { 12131 if (meta->r0_size) { 12132 verbose(env, "2 or more rdonly/rdwr_buf_size parameters for kfunc"); 12133 return -EINVAL; 12134 } 12135 12136 if (!tnum_is_const(reg->var_off)) { 12137 verbose(env, "R%d is not a const\n", regno); 12138 return -EINVAL; 12139 } 12140 12141 meta->r0_size = reg->var_off.value; 12142 ret = mark_chain_precision(env, regno); 12143 if (ret) 12144 return ret; 12145 } 12146 continue; 12147 } 12148 12149 if (!btf_type_is_ptr(t)) { 12150 verbose(env, "Unrecognized arg#%d type %s\n", i, btf_type_str(t)); 12151 return -EINVAL; 12152 } 12153 12154 if ((bpf_register_is_null(reg) || type_may_be_null(reg->type)) && 12155 !is_kfunc_arg_nullable(meta->btf, &args[i])) { 12156 verbose(env, "Possibly NULL pointer passed to trusted arg%d\n", i); 12157 return -EACCES; 12158 } 12159 12160 if (reg->ref_obj_id) { 12161 if (is_kfunc_release(meta) && meta->ref_obj_id) { 12162 verifier_bug(env, "more than one arg with ref_obj_id R%d %u %u", 12163 regno, reg->ref_obj_id, 12164 meta->ref_obj_id); 12165 return -EFAULT; 12166 } 12167 meta->ref_obj_id = reg->ref_obj_id; 12168 if (is_kfunc_release(meta)) 12169 meta->release_regno = regno; 12170 } 12171 12172 ref_t = btf_type_skip_modifiers(btf, t->type, &ref_id); 12173 ref_tname = btf_name_by_offset(btf, ref_t->name_off); 12174 12175 kf_arg_type = get_kfunc_ptr_arg_type(env, meta, t, ref_t, ref_tname, args, i, nargs); 12176 if (kf_arg_type < 0) 12177 return kf_arg_type; 12178 12179 switch (kf_arg_type) { 12180 case KF_ARG_PTR_TO_NULL: 12181 continue; 12182 case KF_ARG_PTR_TO_MAP: 12183 if (!reg->map_ptr) { 12184 verbose(env, "pointer in R%d isn't map pointer\n", regno); 12185 return -EINVAL; 12186 } 12187 if (meta->map.ptr && (reg->map_ptr->record->wq_off >= 0 || 12188 reg->map_ptr->record->task_work_off >= 0)) { 12189 /* Use map_uid (which is unique id of inner map) to reject: 12190 * inner_map1 = bpf_map_lookup_elem(outer_map, key1) 12191 * inner_map2 = bpf_map_lookup_elem(outer_map, key2) 12192 * if (inner_map1 && inner_map2) { 12193 * wq = bpf_map_lookup_elem(inner_map1); 12194 * if (wq) 12195 * // mismatch would have been allowed 12196 * bpf_wq_init(wq, inner_map2); 12197 * } 12198 * 12199 * Comparing map_ptr is enough to distinguish normal and outer maps. 12200 */ 12201 if (meta->map.ptr != reg->map_ptr || 12202 meta->map.uid != reg->map_uid) { 12203 if (reg->map_ptr->record->task_work_off >= 0) { 12204 verbose(env, 12205 "bpf_task_work pointer in R2 map_uid=%d doesn't match map pointer in R3 map_uid=%d\n", 12206 meta->map.uid, reg->map_uid); 12207 return -EINVAL; 12208 } 12209 verbose(env, 12210 "workqueue pointer in R1 map_uid=%d doesn't match map pointer in R2 map_uid=%d\n", 12211 meta->map.uid, reg->map_uid); 12212 return -EINVAL; 12213 } 12214 } 12215 meta->map.ptr = reg->map_ptr; 12216 meta->map.uid = reg->map_uid; 12217 fallthrough; 12218 case KF_ARG_PTR_TO_ALLOC_BTF_ID: 12219 case KF_ARG_PTR_TO_BTF_ID: 12220 if (!is_trusted_reg(reg)) { 12221 if (!is_kfunc_rcu(meta)) { 12222 verbose(env, "R%d must be referenced or trusted\n", regno); 12223 return -EINVAL; 12224 } 12225 if (!is_rcu_reg(reg)) { 12226 verbose(env, "R%d must be a rcu pointer\n", regno); 12227 return -EINVAL; 12228 } 12229 } 12230 fallthrough; 12231 case KF_ARG_PTR_TO_DYNPTR: 12232 case KF_ARG_PTR_TO_ITER: 12233 case KF_ARG_PTR_TO_LIST_HEAD: 12234 case KF_ARG_PTR_TO_LIST_NODE: 12235 case KF_ARG_PTR_TO_RB_ROOT: 12236 case KF_ARG_PTR_TO_RB_NODE: 12237 case KF_ARG_PTR_TO_MEM: 12238 case KF_ARG_PTR_TO_MEM_SIZE: 12239 case KF_ARG_PTR_TO_CALLBACK: 12240 case KF_ARG_PTR_TO_REFCOUNTED_KPTR: 12241 case KF_ARG_PTR_TO_CONST_STR: 12242 case KF_ARG_PTR_TO_WORKQUEUE: 12243 case KF_ARG_PTR_TO_TIMER: 12244 case KF_ARG_PTR_TO_TASK_WORK: 12245 case KF_ARG_PTR_TO_IRQ_FLAG: 12246 case KF_ARG_PTR_TO_RES_SPIN_LOCK: 12247 break; 12248 case KF_ARG_PTR_TO_CTX: 12249 arg_type = ARG_PTR_TO_CTX; 12250 break; 12251 default: 12252 verifier_bug(env, "unknown kfunc arg type %d", kf_arg_type); 12253 return -EFAULT; 12254 } 12255 12256 if (is_kfunc_release(meta) && reg->ref_obj_id) 12257 arg_type |= OBJ_RELEASE; 12258 ret = check_func_arg_reg_off(env, reg, regno, arg_type); 12259 if (ret < 0) 12260 return ret; 12261 12262 switch (kf_arg_type) { 12263 case KF_ARG_PTR_TO_CTX: 12264 if (reg->type != PTR_TO_CTX) { 12265 verbose(env, "arg#%d expected pointer to ctx, but got %s\n", 12266 i, reg_type_str(env, reg->type)); 12267 return -EINVAL; 12268 } 12269 12270 if (meta->func_id == special_kfunc_list[KF_bpf_cast_to_kern_ctx]) { 12271 ret = get_kern_ctx_btf_id(&env->log, resolve_prog_type(env->prog)); 12272 if (ret < 0) 12273 return -EINVAL; 12274 meta->ret_btf_id = ret; 12275 } 12276 break; 12277 case KF_ARG_PTR_TO_ALLOC_BTF_ID: 12278 if (reg->type == (PTR_TO_BTF_ID | MEM_ALLOC)) { 12279 if (!is_bpf_obj_drop_kfunc(meta->func_id)) { 12280 verbose(env, "arg#%d expected for bpf_obj_drop()\n", i); 12281 return -EINVAL; 12282 } 12283 } else if (reg->type == (PTR_TO_BTF_ID | MEM_ALLOC | MEM_PERCPU)) { 12284 if (!is_bpf_percpu_obj_drop_kfunc(meta->func_id)) { 12285 verbose(env, "arg#%d expected for bpf_percpu_obj_drop()\n", i); 12286 return -EINVAL; 12287 } 12288 } else { 12289 verbose(env, "arg#%d expected pointer to allocated object\n", i); 12290 return -EINVAL; 12291 } 12292 if (!reg->ref_obj_id) { 12293 verbose(env, "allocated object must be referenced\n"); 12294 return -EINVAL; 12295 } 12296 if (meta->btf == btf_vmlinux) { 12297 meta->arg_btf = reg->btf; 12298 meta->arg_btf_id = reg->btf_id; 12299 } 12300 break; 12301 case KF_ARG_PTR_TO_DYNPTR: 12302 { 12303 enum bpf_arg_type dynptr_arg_type = ARG_PTR_TO_DYNPTR; 12304 int clone_ref_obj_id = 0; 12305 12306 if (reg->type == CONST_PTR_TO_DYNPTR) 12307 dynptr_arg_type |= MEM_RDONLY; 12308 12309 if (is_kfunc_arg_uninit(btf, &args[i])) 12310 dynptr_arg_type |= MEM_UNINIT; 12311 12312 if (meta->func_id == special_kfunc_list[KF_bpf_dynptr_from_skb]) { 12313 dynptr_arg_type |= DYNPTR_TYPE_SKB; 12314 } else if (meta->func_id == special_kfunc_list[KF_bpf_dynptr_from_xdp]) { 12315 dynptr_arg_type |= DYNPTR_TYPE_XDP; 12316 } else if (meta->func_id == special_kfunc_list[KF_bpf_dynptr_from_skb_meta]) { 12317 dynptr_arg_type |= DYNPTR_TYPE_SKB_META; 12318 } else if (meta->func_id == special_kfunc_list[KF_bpf_dynptr_from_file]) { 12319 dynptr_arg_type |= DYNPTR_TYPE_FILE; 12320 } else if (meta->func_id == special_kfunc_list[KF_bpf_dynptr_file_discard]) { 12321 dynptr_arg_type |= DYNPTR_TYPE_FILE; 12322 meta->release_regno = regno; 12323 } else if (meta->func_id == special_kfunc_list[KF_bpf_dynptr_clone] && 12324 (dynptr_arg_type & MEM_UNINIT)) { 12325 enum bpf_dynptr_type parent_type = meta->initialized_dynptr.type; 12326 12327 if (parent_type == BPF_DYNPTR_TYPE_INVALID) { 12328 verifier_bug(env, "no dynptr type for parent of clone"); 12329 return -EFAULT; 12330 } 12331 12332 dynptr_arg_type |= (unsigned int)get_dynptr_type_flag(parent_type); 12333 clone_ref_obj_id = meta->initialized_dynptr.ref_obj_id; 12334 if (dynptr_type_refcounted(parent_type) && !clone_ref_obj_id) { 12335 verifier_bug(env, "missing ref obj id for parent of clone"); 12336 return -EFAULT; 12337 } 12338 } 12339 12340 ret = process_dynptr_func(env, regno, insn_idx, dynptr_arg_type, clone_ref_obj_id); 12341 if (ret < 0) 12342 return ret; 12343 12344 if (!(dynptr_arg_type & MEM_UNINIT)) { 12345 int id = dynptr_id(env, reg); 12346 12347 if (id < 0) { 12348 verifier_bug(env, "failed to obtain dynptr id"); 12349 return id; 12350 } 12351 meta->initialized_dynptr.id = id; 12352 meta->initialized_dynptr.type = dynptr_get_type(env, reg); 12353 meta->initialized_dynptr.ref_obj_id = dynptr_ref_obj_id(env, reg); 12354 } 12355 12356 break; 12357 } 12358 case KF_ARG_PTR_TO_ITER: 12359 if (meta->func_id == special_kfunc_list[KF_bpf_iter_css_task_new]) { 12360 if (!check_css_task_iter_allowlist(env)) { 12361 verbose(env, "css_task_iter is only allowed in bpf_lsm, bpf_iter and sleepable progs\n"); 12362 return -EINVAL; 12363 } 12364 } 12365 ret = process_iter_arg(env, regno, insn_idx, meta); 12366 if (ret < 0) 12367 return ret; 12368 break; 12369 case KF_ARG_PTR_TO_LIST_HEAD: 12370 if (reg->type != PTR_TO_MAP_VALUE && 12371 reg->type != (PTR_TO_BTF_ID | MEM_ALLOC)) { 12372 verbose(env, "arg#%d expected pointer to map value or allocated object\n", i); 12373 return -EINVAL; 12374 } 12375 if (reg->type == (PTR_TO_BTF_ID | MEM_ALLOC) && !reg->ref_obj_id) { 12376 verbose(env, "allocated object must be referenced\n"); 12377 return -EINVAL; 12378 } 12379 ret = process_kf_arg_ptr_to_list_head(env, reg, regno, meta); 12380 if (ret < 0) 12381 return ret; 12382 break; 12383 case KF_ARG_PTR_TO_RB_ROOT: 12384 if (reg->type != PTR_TO_MAP_VALUE && 12385 reg->type != (PTR_TO_BTF_ID | MEM_ALLOC)) { 12386 verbose(env, "arg#%d expected pointer to map value or allocated object\n", i); 12387 return -EINVAL; 12388 } 12389 if (reg->type == (PTR_TO_BTF_ID | MEM_ALLOC) && !reg->ref_obj_id) { 12390 verbose(env, "allocated object must be referenced\n"); 12391 return -EINVAL; 12392 } 12393 ret = process_kf_arg_ptr_to_rbtree_root(env, reg, regno, meta); 12394 if (ret < 0) 12395 return ret; 12396 break; 12397 case KF_ARG_PTR_TO_LIST_NODE: 12398 if (reg->type != (PTR_TO_BTF_ID | MEM_ALLOC)) { 12399 verbose(env, "arg#%d expected pointer to allocated object\n", i); 12400 return -EINVAL; 12401 } 12402 if (!reg->ref_obj_id) { 12403 verbose(env, "allocated object must be referenced\n"); 12404 return -EINVAL; 12405 } 12406 ret = process_kf_arg_ptr_to_list_node(env, reg, regno, meta); 12407 if (ret < 0) 12408 return ret; 12409 break; 12410 case KF_ARG_PTR_TO_RB_NODE: 12411 if (is_bpf_rbtree_add_kfunc(meta->func_id)) { 12412 if (reg->type != (PTR_TO_BTF_ID | MEM_ALLOC)) { 12413 verbose(env, "arg#%d expected pointer to allocated object\n", i); 12414 return -EINVAL; 12415 } 12416 if (!reg->ref_obj_id) { 12417 verbose(env, "allocated object must be referenced\n"); 12418 return -EINVAL; 12419 } 12420 } else { 12421 if (!type_is_non_owning_ref(reg->type) && !reg->ref_obj_id) { 12422 verbose(env, "%s can only take non-owning or refcounted bpf_rb_node pointer\n", func_name); 12423 return -EINVAL; 12424 } 12425 if (in_rbtree_lock_required_cb(env)) { 12426 verbose(env, "%s not allowed in rbtree cb\n", func_name); 12427 return -EINVAL; 12428 } 12429 } 12430 12431 ret = process_kf_arg_ptr_to_rbtree_node(env, reg, regno, meta); 12432 if (ret < 0) 12433 return ret; 12434 break; 12435 case KF_ARG_PTR_TO_MAP: 12436 /* If argument has '__map' suffix expect 'struct bpf_map *' */ 12437 ref_id = *reg2btf_ids[CONST_PTR_TO_MAP]; 12438 ref_t = btf_type_by_id(btf_vmlinux, ref_id); 12439 ref_tname = btf_name_by_offset(btf, ref_t->name_off); 12440 fallthrough; 12441 case KF_ARG_PTR_TO_BTF_ID: 12442 /* Only base_type is checked, further checks are done here */ 12443 if ((base_type(reg->type) != PTR_TO_BTF_ID || 12444 (bpf_type_has_unsafe_modifiers(reg->type) && !is_rcu_reg(reg))) && 12445 !reg2btf_ids[base_type(reg->type)]) { 12446 verbose(env, "arg#%d is %s ", i, reg_type_str(env, reg->type)); 12447 verbose(env, "expected %s or socket\n", 12448 reg_type_str(env, base_type(reg->type) | 12449 (type_flag(reg->type) & BPF_REG_TRUSTED_MODIFIERS))); 12450 return -EINVAL; 12451 } 12452 ret = process_kf_arg_ptr_to_btf_id(env, reg, ref_t, ref_tname, ref_id, meta, i); 12453 if (ret < 0) 12454 return ret; 12455 break; 12456 case KF_ARG_PTR_TO_MEM: 12457 resolve_ret = btf_resolve_size(btf, ref_t, &type_size); 12458 if (IS_ERR(resolve_ret)) { 12459 verbose(env, "arg#%d reference type('%s %s') size cannot be determined: %ld\n", 12460 i, btf_type_str(ref_t), ref_tname, PTR_ERR(resolve_ret)); 12461 return -EINVAL; 12462 } 12463 ret = check_mem_reg(env, reg, regno, type_size); 12464 if (ret < 0) 12465 return ret; 12466 break; 12467 case KF_ARG_PTR_TO_MEM_SIZE: 12468 { 12469 struct bpf_reg_state *buff_reg = ®s[regno]; 12470 const struct btf_param *buff_arg = &args[i]; 12471 struct bpf_reg_state *size_reg = ®s[regno + 1]; 12472 const struct btf_param *size_arg = &args[i + 1]; 12473 12474 if (!bpf_register_is_null(buff_reg) || !is_kfunc_arg_nullable(meta->btf, buff_arg)) { 12475 ret = check_kfunc_mem_size_reg(env, size_reg, regno + 1); 12476 if (ret < 0) { 12477 verbose(env, "arg#%d arg#%d memory, len pair leads to invalid memory access\n", i, i + 1); 12478 return ret; 12479 } 12480 } 12481 12482 if (is_kfunc_arg_const_mem_size(meta->btf, size_arg, size_reg)) { 12483 if (meta->arg_constant.found) { 12484 verifier_bug(env, "only one constant argument permitted"); 12485 return -EFAULT; 12486 } 12487 if (!tnum_is_const(size_reg->var_off)) { 12488 verbose(env, "R%d must be a known constant\n", regno + 1); 12489 return -EINVAL; 12490 } 12491 meta->arg_constant.found = true; 12492 meta->arg_constant.value = size_reg->var_off.value; 12493 } 12494 12495 /* Skip next '__sz' or '__szk' argument */ 12496 i++; 12497 break; 12498 } 12499 case KF_ARG_PTR_TO_CALLBACK: 12500 if (reg->type != PTR_TO_FUNC) { 12501 verbose(env, "arg%d expected pointer to func\n", i); 12502 return -EINVAL; 12503 } 12504 meta->subprogno = reg->subprogno; 12505 break; 12506 case KF_ARG_PTR_TO_REFCOUNTED_KPTR: 12507 if (!type_is_ptr_alloc_obj(reg->type)) { 12508 verbose(env, "arg#%d is neither owning or non-owning ref\n", i); 12509 return -EINVAL; 12510 } 12511 if (!type_is_non_owning_ref(reg->type)) 12512 meta->arg_owning_ref = true; 12513 12514 rec = reg_btf_record(reg); 12515 if (!rec) { 12516 verifier_bug(env, "Couldn't find btf_record"); 12517 return -EFAULT; 12518 } 12519 12520 if (rec->refcount_off < 0) { 12521 verbose(env, "arg#%d doesn't point to a type with bpf_refcount field\n", i); 12522 return -EINVAL; 12523 } 12524 12525 meta->arg_btf = reg->btf; 12526 meta->arg_btf_id = reg->btf_id; 12527 break; 12528 case KF_ARG_PTR_TO_CONST_STR: 12529 if (reg->type != PTR_TO_MAP_VALUE) { 12530 verbose(env, "arg#%d doesn't point to a const string\n", i); 12531 return -EINVAL; 12532 } 12533 ret = check_reg_const_str(env, reg, regno); 12534 if (ret) 12535 return ret; 12536 break; 12537 case KF_ARG_PTR_TO_WORKQUEUE: 12538 if (reg->type != PTR_TO_MAP_VALUE) { 12539 verbose(env, "arg#%d doesn't point to a map value\n", i); 12540 return -EINVAL; 12541 } 12542 ret = check_map_field_pointer(env, regno, BPF_WORKQUEUE, &meta->map); 12543 if (ret < 0) 12544 return ret; 12545 break; 12546 case KF_ARG_PTR_TO_TIMER: 12547 if (reg->type != PTR_TO_MAP_VALUE) { 12548 verbose(env, "arg#%d doesn't point to a map value\n", i); 12549 return -EINVAL; 12550 } 12551 ret = process_timer_kfunc(env, regno, meta); 12552 if (ret < 0) 12553 return ret; 12554 break; 12555 case KF_ARG_PTR_TO_TASK_WORK: 12556 if (reg->type != PTR_TO_MAP_VALUE) { 12557 verbose(env, "arg#%d doesn't point to a map value\n", i); 12558 return -EINVAL; 12559 } 12560 ret = check_map_field_pointer(env, regno, BPF_TASK_WORK, &meta->map); 12561 if (ret < 0) 12562 return ret; 12563 break; 12564 case KF_ARG_PTR_TO_IRQ_FLAG: 12565 if (reg->type != PTR_TO_STACK) { 12566 verbose(env, "arg#%d doesn't point to an irq flag on stack\n", i); 12567 return -EINVAL; 12568 } 12569 ret = process_irq_flag(env, regno, meta); 12570 if (ret < 0) 12571 return ret; 12572 break; 12573 case KF_ARG_PTR_TO_RES_SPIN_LOCK: 12574 { 12575 int flags = PROCESS_RES_LOCK; 12576 12577 if (reg->type != PTR_TO_MAP_VALUE && reg->type != (PTR_TO_BTF_ID | MEM_ALLOC)) { 12578 verbose(env, "arg#%d doesn't point to map value or allocated object\n", i); 12579 return -EINVAL; 12580 } 12581 12582 if (!is_bpf_res_spin_lock_kfunc(meta->func_id)) 12583 return -EFAULT; 12584 if (meta->func_id == special_kfunc_list[KF_bpf_res_spin_lock] || 12585 meta->func_id == special_kfunc_list[KF_bpf_res_spin_lock_irqsave]) 12586 flags |= PROCESS_SPIN_LOCK; 12587 if (meta->func_id == special_kfunc_list[KF_bpf_res_spin_lock_irqsave] || 12588 meta->func_id == special_kfunc_list[KF_bpf_res_spin_unlock_irqrestore]) 12589 flags |= PROCESS_LOCK_IRQ; 12590 ret = process_spin_lock(env, regno, flags); 12591 if (ret < 0) 12592 return ret; 12593 break; 12594 } 12595 } 12596 } 12597 12598 if (is_kfunc_release(meta) && !meta->release_regno) { 12599 verbose(env, "release kernel function %s expects refcounted PTR_TO_BTF_ID\n", 12600 func_name); 12601 return -EINVAL; 12602 } 12603 12604 return 0; 12605 } 12606 -- 0-DAY CI Kernel Test Service https://github.com/intel/lkp-tests/wiki ^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH bpf v4 2/2] selftests/bpf: Add regression test for kfunc implicit arg injection 2026-06-02 9:38 ` [PATCH bpf v4 2/2] selftests/bpf: Add regression test for kfunc implicit arg injection chenyuan_fl ` (3 preceding siblings ...) 2026-06-02 18:37 ` kernel test robot @ 2026-06-05 1:29 ` Eduard Zingerman 4 siblings, 0 replies; 35+ messages in thread From: Eduard Zingerman @ 2026-06-05 1:29 UTC (permalink / raw) To: chenyuan_fl, yonghong.song Cc: andrii, ast, bot+bpf-ci, bpf, chenyuan, clm, daniel, ihor.solodrai, jolsa, linux-kernel, martin.lau, martin.lau, memxor, song On Tue, 2026-06-02 at 17:38 +0800, chenyuan_fl@163.com wrote: > From: Yuan Chen <chenyuan@kylinos.cn> > > The preceding patch fixes a silent fallthrough in check_kfunc_args() > that could cause the verifier to skip bpf_prog_aux injection for > KF_IMPLICIT_ARGS kfuncs when module BTF is inconsistent with vmlinux > (e.g. pahole 1.30 breaking distilled base dedup). > > Add a positive regression test that verifies the injection path works > correctly under normal conditions (pahole 1.31+). The test contaminates > BPF R2 with a magic value 0xDEAD via inline assembly before calling a > KF_IMPLICIT_ARGS kfunc associated with a struct_ops map. The kfunc > validates that the kernel overwrote R2 with the real bpf_prog_aux > pointer rather than leaving the stale value. > > The specific pahole 1.30 BTF mismatch scenario cannot be tested with > CI (which uses pahole 1.31), but this test ensures the injection > mechanism remains correct and does not regress. > > Signed-off-by: Yuan Chen <chenyuan@kylinos.cn> > --- I think that such test makes sense, it allows to verify if implicit struct bpf_prog_aux parameter works as expected when kfunc is defined in a module. However, I'd suggest to hijack bpf_testmod.c:bpf_kfunc_implicit_arg() and verify a field like aux->name. Hence making sure that bpf_prog_aux is passed correctly. E.g.: int bpf_kfunc_implicit_arg(int a, struct bpf_prog_aux *aux) { if (strcmp(aux->name, "...expected name...") == 0 && a > 0) return a; return -EINVAL; } Thus keeping the changes to a minimum. [...] ^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH bpf v2 1/2] bpf: Fix kfunc implicit arg inject type detection to prevent invalid pointer deref 2026-06-01 6:46 ` [PATCH bpf v2 1/2] bpf: Fix kfunc implicit arg inject type detection to prevent invalid pointer deref chenyuan_fl 2026-06-01 7:42 ` bot+bpf-ci @ 2026-06-01 17:12 ` Yonghong Song 2026-06-01 21:36 ` Eduard Zingerman 2 siblings, 0 replies; 35+ messages in thread From: Yonghong Song @ 2026-06-01 17:12 UTC (permalink / raw) To: chenyuan_fl Cc: andrii, ast, bpf, chenyuan, daniel, eddyz87, jolsa, linux-kernel, martin.lau, memxor, song On 5/31/26 11:46 PM, chenyuan_fl@163.com wrote: > From: Yuan Chen <chenyuan@kylinos.cn> > > When a module kfunc declares an implicit struct bpf_prog_aux * argument, > the verifier must identify it so the kernel injects env->prog->aux into > the correct register at runtime. The original check used > is_kfunc_arg_prog_aux() which calls btf_types_are_same() to compare the > module BTF type against vmlinux. > > Root Cause > ---------- > > This issue was triggered by pahole 1.30 generating module BTF with > incorrect type information, which caused the kernel's distilled base > BTF deduplication for modules to fail. As a result, the module retained > its own copy of struct bpf_prog_aux with a different BTF ID than > vmlinux's definition. While pahole 1.31 fixed the BTF generation issue, > the kernel must be robust against such inconsistencies: a BTF mismatch > should result in a clean rejection, not a kernel crash or information > disclosure. > > When the distilled base dedup fails and btf_types_are_same() cannot > match the module's bpf_prog_aux type against vmlinux's, > is_kfunc_arg_prog_aux() returned false and the code fell through > silently without setting arg_prog. The kfunc then received whatever > value was in the argument register and dereferenced it as a > bpf_prog_aux pointer, leading to: > > BUG: kernel invalid pointer dereference, address: 00000000000009e2 > RIP: bpf_prog_get_assoc_struct_ops+0xa/0xc0 > RDI: 0x000000000000046d (stale register value) > > In the observed crash the stale value was the process PID, causing a > dereference within the unmapped NULL page. However, an attacker able > to control the register value -- for example by writing a BPF program > that explicitly sets R2 before calling a KF_IMPLICIT_ARGS kfunc -- > could redirect the dereference to arbitrary kernel memory, turning > this into an information disclosure. The fix ensures the verifier > either validates and injects the correct bpf_prog_aux pointer, or > rejects the program outright -- no silent fallthrough that could > be exploited. > > Crash Stack Trace > ----------------- > > PID: 1133 TASK: ffff8881057d3900 CPU: 3 COMMAND: "test_progs" > #0 machine_kexec at ffffffff812f6e26 > #1 __crash_kexec at ffffffff8145a788 > #2 crash_kexec at ffffffff8145ac24 > #3 oops_end at ffffffff812bb67c > #4 page_fault_oops at ffffffff813053a1 > #5 exc_page_fault at ffffffff828e60a1 > #6 asm_exc_page_fault at ffffffff810012a6 > [exception RIP: bpf_prog_get_assoc_struct_ops+10] > RIP: ffffffff815c024a RSP: ffffc90001b57e48 RFLAGS: 00010283 > RAX: ffff8881057d3900 RBX: ffffc90001b57e68 RCX: ffff8881057d3900 > RDX: 0000607d4d1768b8 RSI: 000000000000046d RDI: 000000000000046d > #7 bpf_kfunc_multi_st_ops_test_1_assoc at ffffffffc0013a85 [bpf_testmod] > #8 bpf_trace_run2 at ffffffff814f8332 > #9 __traceiter_sys_enter at ffffffff81415f45 > #10 trace_syscall_enter at ffffffff81416735 > #11 do_syscall_64 at ffffffff828e06a1 > > Fix > --- > > Introduce a two-layer argument-injection detection: > > 1. get_kfunc_arg_inject_type() -- lightweight name-based classification > of injectable types (currently only KF_INJECT_ARG_PROG_AUX). This > ensures we recognize injection candidates regardless of BTF type IDs. > > 2. is_kfunc_arg_prog_aux() -- strict type validation within the inject > case; if validation fails the program is rejected with -EINVAL instead > of silently bypassing injection setup. > > This design ensures that BTF inconsistencies result in a clean verification > failure instead of a crash or a potential information disclosure, and the > approach is extensible for future injection types. > > Fixes: 64e1360524b9 ("bpf: Verifier support for KF_IMPLICIT_ARGS") > Signed-off-by: Yuan Chen <chenyuan@kylinos.cn> > --- > kernel/bpf/verifier.c | 48 +++++++++++++++++++++++++++++++++++++++++-- > 1 file changed, 46 insertions(+), 2 deletions(-) > > diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c > index 8dd79b735a69..928b6c42a4bf 100644 > --- a/kernel/bpf/verifier.c > +++ b/kernel/bpf/verifier.c > @@ -10857,6 +10857,39 @@ static bool is_kfunc_arg_prog_aux(const struct btf *btf, const struct btf_param > return __is_kfunc_ptr_arg_type(btf, arg, KF_ARG_PROG_AUX_ID); > } > > +/* > + * Injectable argument types are implicit kfunc arguments whose value is > + * injected by the kernel at call time rather than received from the BPF > + * program. Use name-based matching for initial detection to avoid false > + * negatives when a module's BTF references the type via a different BTF ID > + * than vmlinux's. Actual type compatibility is still validated by the > + * caller with btf_types_are_same(). > + */ > +enum kfunc_inject_arg_type { > + KF_INJECT_ARG_NONE = 0, > + KF_INJECT_ARG_PROG_AUX, > +}; > + > +static enum kfunc_inject_arg_type get_kfunc_arg_inject_type( > + const struct btf *btf, const struct btf_param *arg) > +{ > + const struct btf_type *t; > + u32 res_id; > + > + t = btf_type_skip_modifiers(btf, arg->type, NULL); > + if (!t || !btf_type_is_ptr(t)) > + return KF_INJECT_ARG_NONE; > + > + t = btf_type_skip_modifiers(btf, t->type, &res_id); > + if (!t) > + return KF_INJECT_ARG_NONE; > + > + if (strcmp(btf_type_name(btf, res_id), "bpf_prog_aux") == 0) > + return KF_INJECT_ARG_PROG_AUX; > + > + return KF_INJECT_ARG_NONE; > +} > + > /* > * A kfunc with KF_IMPLICIT_ARGS has two prototypes in BTF: > * - the _impl prototype with full arg list (meta->func_proto) > @@ -11899,8 +11932,17 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_ > u32 ref_id, type_size; > bool is_ret_buf_sz = false; > int kf_arg_type; > - > - if (is_kfunc_arg_prog_aux(btf, &args[i])) { > + enum kfunc_inject_arg_type inject_type; > + > + inject_type = get_kfunc_arg_inject_type(btf, &args[i]); > + switch (inject_type) { > + case KF_INJECT_ARG_PROG_AUX: > + /* Validate the arg type against vmlinux's definition */ > + if (!is_kfunc_arg_prog_aux(btf, &args[i])) { > + verbose(env, "arg#%d implicit argument type mismatch, " > + "expected struct bpf_prog_aux *\n", i); Change the above verbose(env, ...) to verbose(env, "%s implicit argument type mismatch, expected struct bpf_prog_aux *\n", reg_arg_name(env, argno)); Please do not break strings in two lines. > + return -EINVAL; > + } > /* Reject repeated use bpf_prog_aux */ > if (meta->arg_prog) { > verifier_bug(env, "Only 1 prog->aux argument supported per-kfunc"); > @@ -11914,6 +11956,8 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_ > meta->arg_prog = true; > cur_aux(env)->arg_prog = regno; > continue; > + default: > + break; > } > > if (is_kfunc_arg_ignore(btf, &args[i]) || is_kfunc_arg_implicit(meta, i)) I think we can do some checking in is_kfunc_arg_implicit(meta, i) to validate the result of is_kfunc_arg_prog_aux(). This will make code simpler. Also, please add some comments in the code to explain the reason of this code is due to pahole 1.30. So later on at some point, this code can be removed. ^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH bpf v2 1/2] bpf: Fix kfunc implicit arg inject type detection to prevent invalid pointer deref 2026-06-01 6:46 ` [PATCH bpf v2 1/2] bpf: Fix kfunc implicit arg inject type detection to prevent invalid pointer deref chenyuan_fl 2026-06-01 7:42 ` bot+bpf-ci 2026-06-01 17:12 ` [PATCH bpf v2 1/2] bpf: Fix kfunc implicit arg inject type detection to prevent invalid pointer deref Yonghong Song @ 2026-06-01 21:36 ` Eduard Zingerman 2 siblings, 0 replies; 35+ messages in thread From: Eduard Zingerman @ 2026-06-01 21:36 UTC (permalink / raw) To: chenyuan_fl Cc: andrii, ast, bpf, chenyuan, daniel, jolsa, linux-kernel, martin.lau, memxor, song, yonghong.song On Mon, 2026-06-01 at 14:46 +0800, chenyuan_fl@163.com wrote: [...] > @@ -11899,8 +11932,17 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_ > u32 ref_id, type_size; > bool is_ret_buf_sz = false; > int kf_arg_type; > - > - if (is_kfunc_arg_prog_aux(btf, &args[i])) { Thank you for figuring out this corner case. I don't think I like matching the type by name here, though. Wdyt about a somewhat more generic solution described below? bpf_fixup_kfunc_call() lists all the cases when verifier injects an implicit argument: - functions with KF_IMPLICIT_ARGS flag and a parameter of type bpf_prog_aux. - functions with KF_IMPLICIT_ARGS allowed by is_bpf_list_push_kfunc() / is_bpf_rbtree_add_kfunc(). Meaning that current check_kfunc_arg() code can be changed as follows: if (is_kfunc_arg_prog_aux(btf, &args[i])) { ... unmodified ... continue; } if (is_bpf_list_push_kfunc(...) || is_bpf_rbtree_add_kfunc(...)) { ... continue; } if (is_kfunc_arg_implicit(meta, i)) { ... report error ... return -EFAULT; } Thus catching all the cases. > + enum kfunc_inject_arg_type inject_type; > + > + inject_type = get_kfunc_arg_inject_type(btf, &args[i]); > + switch (inject_type) { > + case KF_INJECT_ARG_PROG_AUX: > + /* Validate the arg type against vmlinux's definition */ > + if (!is_kfunc_arg_prog_aux(btf, &args[i])) { > + verbose(env, "arg#%d implicit argument type mismatch, " > + "expected struct bpf_prog_aux *\n", i); > + return -EINVAL; > + } > /* Reject repeated use bpf_prog_aux */ > if (meta->arg_prog) { > verifier_bug(env, "Only 1 prog->aux argument supported per-kfunc"); [...] ^ permalink raw reply [flat|nested] 35+ messages in thread
* [PATCH bpf v2 2/2] selftests/bpf: Add regression test for kfunc implicit arg injection with stale register 2026-06-01 6:46 ` [PATCH bpf v2 0/2] bpf: Fix kfunc implicit arg injection and add selftest chenyuan_fl 2026-06-01 6:46 ` [PATCH bpf v2 1/2] bpf: Fix kfunc implicit arg inject type detection to prevent invalid pointer deref chenyuan_fl @ 2026-06-01 6:46 ` chenyuan_fl 2026-06-01 7:08 ` sashiko-bot 2026-06-01 17:17 ` Yonghong Song 1 sibling, 2 replies; 35+ messages in thread From: chenyuan_fl @ 2026-06-01 6:46 UTC (permalink / raw) To: chenyuan_fl Cc: andrii, ast, bpf, chenyuan, daniel, eddyz87, jolsa, linux-kernel, martin.lau, memxor, song, yonghong.song From: Yuan Chen <chenyuan@kylinos.cn> The preceding patch fixes a bug where the BPF verifier failed to detect an implicit struct bpf_prog_aux * argument in a module kfunc, causing the kernel to dereference a stale register value as a pointer and crash. Add a selftest that deliberately contaminates BPF R2 (the register slot for the 2nd kfunc argument, which maps to the implicit bpf_prog_aux *) with a known magic value 0xDEAD via inline assembly, then calls a KF_IMPLICIT_ARGS kfunc that validates whether injection occurred: - If the kernel correctly injects env->prog->aux into R2, the kfunc receives a real bpf_prog_aux pointer (≠ 0xDEAD) and returns the caller-supplied marker value. - If injection is skipped (the original bug), the kfunc receives the stale 0xDEAD value and returns -EINVAL, which the BPF program detects as a test failure. The magic value 0xDEAD is chosen with bit 31 clear to avoid BPF ALU64 sign-extension when used as a 32-bit immediate, ensuring the comparison in the kfunc matches the actual register value. The kfunc is associated with a struct_ops map to exercise the exact call path (struct_ops → kfunc with KF_IMPLICIT_ARGS → bpf_prog_aux dereference) that triggered the original crash. Signed-off-by: Yuan Chen <chenyuan@kylinos.cn> --- .../bpf/prog_tests/test_struct_ops_assoc.c | 5 +++ .../selftests/bpf/progs/struct_ops_assoc.c | 40 +++++++++++++++++++ .../selftests/bpf/test_kmods/bpf_testmod.c | 9 +++++ .../bpf/test_kmods/bpf_testmod_kfunc.h | 1 + 4 files changed, 55 insertions(+) diff --git a/tools/testing/selftests/bpf/prog_tests/test_struct_ops_assoc.c b/tools/testing/selftests/bpf/prog_tests/test_struct_ops_assoc.c index 461ded722351..123bd2c7a292 100644 --- a/tools/testing/selftests/bpf/prog_tests/test_struct_ops_assoc.c +++ b/tools/testing/selftests/bpf/prog_tests/test_struct_ops_assoc.c @@ -35,6 +35,10 @@ static void test_st_ops_assoc(void) skel->maps.st_ops_map_b, NULL); ASSERT_OK(err, "bpf_program__assoc_struct_ops(sys_enter_prog_b, st_ops_map_b)"); + err = bpf_program__assoc_struct_ops(skel->progs.sys_enter_prog_test_aux_inject, + skel->maps.st_ops_map_a, NULL); + ASSERT_OK(err, "bpf_program__assoc_struct_ops(sys_enter_prog_test_aux_inject, st_ops_map_a)"); + /* sys_enter_prog_a already associated with map_a */ err = bpf_program__assoc_struct_ops(skel->progs.sys_enter_prog_a, skel->maps.st_ops_map_b, NULL); @@ -52,6 +56,7 @@ static void test_st_ops_assoc(void) ASSERT_EQ(skel->bss->test_err_a, 0, "skel->bss->test_err_a"); ASSERT_EQ(skel->bss->test_err_b, 0, "skel->bss->test_err_b"); + ASSERT_EQ(skel->bss->test_err_inject, 0, "skel->bss->test_err_inject"); /* run syscall_prog that calls .test_1 and checks return */ err = bpf_prog_test_run_opts(bpf_program__fd(skel->progs.syscall_prog_a), NULL); diff --git a/tools/testing/selftests/bpf/progs/struct_ops_assoc.c b/tools/testing/selftests/bpf/progs/struct_ops_assoc.c index 68842e3f936b..dd322d43ff5e 100644 --- a/tools/testing/selftests/bpf/progs/struct_ops_assoc.c +++ b/tools/testing/selftests/bpf/progs/struct_ops_assoc.c @@ -103,3 +103,43 @@ SEC(".struct_ops.link") struct bpf_testmod_multi_st_ops st_ops_map_b = { .test_1 = (void *)test_1_b, }; + +/* Test for aux injection with stale register contamination. + * + * This test reproduces the scenario where the BPF verifier fails to + * inject the implicit bpf_prog_aux pointer for kfuncs with + * KF_IMPLICIT_ARGS. The program uses inline assembly to explicitly + * set R2 (the register for the 2nd kfunc argument, which maps to + * the implicit bpf_prog_aux *) to a known magic value (0xDEAD, + * chosen with bit 31 clear to avoid BPF ALU64 sign-extension): + * + * asm volatile("r2 = %[magic]" :: [magic] "ri"(0xDEAD) : "r2"); + * + * Then bpf_kfunc_aux_inject_stale() is called. The kernel verifier + * should inject the real bpf_prog_aux into R2, overriding the magic + * value. The kfunc compares aux against 0xDEAD: + * + * - aux == 0xDEAD → kernel failed to inject aux → test fails + * - aux != 0xDEAD → kernel correctly injected aux → test passes + */ +int test_err_inject; + +SEC("tp_btf/sys_enter") +int BPF_PROG(sys_enter_prog_test_aux_inject, struct pt_regs *regs, long id) +{ + struct task_struct *task; + int marker = 0x5A5A; + int ret; + + task = bpf_get_current_task_btf(); + if (!test_pid || task->pid != test_pid) + return 0; + + asm volatile("r2 = %[magic]" :: [magic] "ri"(0xDEAD) : "r2"); + + ret = bpf_kfunc_aux_inject_stale(marker); + if (ret != marker) + test_err_inject++; + + return 0; +} diff --git a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c index 0be918fe3021..da95a6de3bbf 100644 --- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c +++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c @@ -1316,6 +1316,7 @@ __bpf_kfunc int bpf_kfunc_multi_st_ops_test_1_assoc(struct st_ops_args *args, st __bpf_kfunc int bpf_kfunc_implicit_arg(int a, struct bpf_prog_aux *aux); __bpf_kfunc int bpf_kfunc_implicit_arg_legacy(int a, int b, struct bpf_prog_aux *aux); __bpf_kfunc int bpf_kfunc_implicit_arg_legacy_impl(int a, int b, struct bpf_prog_aux *aux); +__bpf_kfunc int bpf_kfunc_aux_inject_stale(int marker, struct bpf_prog_aux *aux); /* hook targets */ noinline void bpf_testmod_test_hardirq_fn(void) { barrier(); } @@ -1399,6 +1400,7 @@ BTF_ID_FLAGS(func, bpf_kfunc_multi_st_ops_test_1_assoc, KF_IMPLICIT_ARGS) BTF_ID_FLAGS(func, bpf_kfunc_implicit_arg, KF_IMPLICIT_ARGS) BTF_ID_FLAGS(func, bpf_kfunc_implicit_arg_legacy, KF_IMPLICIT_ARGS) BTF_ID_FLAGS(func, bpf_kfunc_implicit_arg_legacy_impl) +BTF_ID_FLAGS(func, bpf_kfunc_aux_inject_stale, KF_IMPLICIT_ARGS) BTF_ID_FLAGS(func, bpf_kfunc_trigger_ctx_check) BTF_KFUNCS_END(bpf_testmod_check_kfunc_ids) @@ -1916,6 +1918,13 @@ int bpf_kfunc_implicit_arg_legacy_impl(int a, int b, struct bpf_prog_aux *aux) return bpf_kfunc_implicit_arg_legacy(a, b, aux); } +int bpf_kfunc_aux_inject_stale(int marker, struct bpf_prog_aux *aux) +{ + if ((unsigned long)aux == 0xDEAD) + return -EINVAL; + return marker; +} + static int multi_st_ops_reg(void *kdata, struct bpf_link *link) { struct bpf_testmod_multi_st_ops *st_ops = diff --git a/tools/testing/selftests/bpf/test_kmods/bpf_testmod_kfunc.h b/tools/testing/selftests/bpf/test_kmods/bpf_testmod_kfunc.h index 2edc36b66de9..c18791e96b21 100644 --- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod_kfunc.h +++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod_kfunc.h @@ -192,6 +192,7 @@ int *bpf_kfunc_ret_rcu_test_nostruct(int rdonly_buf_size) __ksym; #ifndef __KERNEL__ extern int bpf_kfunc_multi_st_ops_test_1(struct st_ops_args *args, u32 id) __weak __ksym; extern int bpf_kfunc_multi_st_ops_test_1_assoc(struct st_ops_args *args) __weak __ksym; +extern int bpf_kfunc_aux_inject_stale(int marker) __weak __ksym; #endif struct prog_test_member *bpf_kfunc_get_default_trusted_ptr_test(void) __ksym; -- 2.54.0 ^ permalink raw reply related [flat|nested] 35+ messages in thread
* Re: [PATCH bpf v2 2/2] selftests/bpf: Add regression test for kfunc implicit arg injection with stale register 2026-06-01 6:46 ` [PATCH bpf v2 2/2] selftests/bpf: Add regression test for kfunc implicit arg injection with stale register chenyuan_fl @ 2026-06-01 7:08 ` sashiko-bot 2026-06-01 17:17 ` Yonghong Song 1 sibling, 0 replies; 35+ messages in thread From: sashiko-bot @ 2026-06-01 7:08 UTC (permalink / raw) To: chenyuan_fl; +Cc: bpf Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Low] The newly added multi-line comment documenting the aux injection test violates the BPF subsystem comment style guidelines by placing text on the same line as the opening `/*`. -- commit 1ef24f7e0744d3c963c7f542a86739e51e7b0b41 Author: Yuan Chen <chenyuan@kylinos.cn> selftests/bpf: Add regression test for kfunc implicit arg injection with stale register This commit adds a selftest to verify that the BPF verifier correctly injects the implicit bpf_prog_aux pointer for KF_IMPLICIT_ARGS kfuncs. It explicitly contaminates the register mapping to the second argument with a magic value to ensure the kernel overwrites it with the real pointer. > diff --git a/tools/testing/selftests/bpf/progs/struct_ops_assoc.c b/tools/testing/selftests/bpf/progs/struct_ops_assoc.c > index 68842e3f936b9..dd322d43ff5e4 100644 > --- a/tools/testing/selftests/bpf/progs/struct_ops_assoc.c > +++ b/tools/testing/selftests/bpf/progs/struct_ops_assoc.c > @@ -103,3 +103,43 @@ SEC(".struct_ops.link") > struct bpf_testmod_multi_st_ops st_ops_map_b = { > .test_1 = (void *)test_1_b, > }; > + > +/* Test for aux injection with stale register contamination. > + * > + * This test reproduces the scenario where the BPF verifier fails to [Severity: Low] This isn't a bug, but does this multi-line comment follow the preferred BPF subsystem comment style? The guidelines require multi-line comments to have the opening /* on its own line, rather than having text on the same line as the opening marker. [ ... ] -- Sashiko AI review · https://sashiko.dev/#/patchset/20260601064611.807637-1-chenyuan_fl@163.com?part=2 ^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH bpf v2 2/2] selftests/bpf: Add regression test for kfunc implicit arg injection with stale register 2026-06-01 6:46 ` [PATCH bpf v2 2/2] selftests/bpf: Add regression test for kfunc implicit arg injection with stale register chenyuan_fl 2026-06-01 7:08 ` sashiko-bot @ 2026-06-01 17:17 ` Yonghong Song 1 sibling, 0 replies; 35+ messages in thread From: Yonghong Song @ 2026-06-01 17:17 UTC (permalink / raw) To: chenyuan_fl Cc: andrii, ast, bpf, chenyuan, daniel, eddyz87, jolsa, linux-kernel, martin.lau, memxor, song On 5/31/26 11:46 PM, chenyuan_fl@163.com wrote: > From: Yuan Chen <chenyuan@kylinos.cn> > > The preceding patch fixes a bug where the BPF verifier failed to detect > an implicit struct bpf_prog_aux * argument in a module kfunc, causing > the kernel to dereference a stale register value as a pointer and crash. > > Add a selftest that deliberately contaminates BPF R2 (the register slot > for the 2nd kfunc argument, which maps to the implicit bpf_prog_aux *) > with a known magic value 0xDEAD via inline assembly, then calls a > KF_IMPLICIT_ARGS kfunc that validates whether injection occurred: > > - If the kernel correctly injects env->prog->aux into R2, the kfunc > receives a real bpf_prog_aux pointer (≠ 0xDEAD) and returns the > caller-supplied marker value. > > - If injection is skipped (the original bug), the kfunc receives the > stale 0xDEAD value and returns -EINVAL, which the BPF program > detects as a test failure. > > The magic value 0xDEAD is chosen with bit 31 clear to avoid BPF ALU64 > sign-extension when used as a 32-bit immediate, ensuring the comparison > in the kfunc matches the actual register value. The kfunc is associated > with a struct_ops map to exercise the exact call path (struct_ops → > kfunc with KF_IMPLICIT_ARGS → bpf_prog_aux dereference) that triggered > the original crash. Not sure whether this patch makes sense or not. The CI has pahole 1.31 so the test will always succeed. > > Signed-off-by: Yuan Chen <chenyuan@kylinos.cn> > --- > .../bpf/prog_tests/test_struct_ops_assoc.c | 5 +++ > .../selftests/bpf/progs/struct_ops_assoc.c | 40 +++++++++++++++++++ > .../selftests/bpf/test_kmods/bpf_testmod.c | 9 +++++ > .../bpf/test_kmods/bpf_testmod_kfunc.h | 1 + > 4 files changed, 55 insertions(+) > > diff --git a/tools/testing/selftests/bpf/prog_tests/test_struct_ops_assoc.c b/tools/testing/selftests/bpf/prog_tests/test_struct_ops_assoc.c > index 461ded722351..123bd2c7a292 100644 > --- a/tools/testing/selftests/bpf/prog_tests/test_struct_ops_assoc.c > +++ b/tools/testing/selftests/bpf/prog_tests/test_struct_ops_assoc.c > @@ -35,6 +35,10 @@ static void test_st_ops_assoc(void) > skel->maps.st_ops_map_b, NULL); > ASSERT_OK(err, "bpf_program__assoc_struct_ops(sys_enter_prog_b, st_ops_map_b)"); > > + err = bpf_program__assoc_struct_ops(skel->progs.sys_enter_prog_test_aux_inject, > + skel->maps.st_ops_map_a, NULL); > + ASSERT_OK(err, "bpf_program__assoc_struct_ops(sys_enter_prog_test_aux_inject, st_ops_map_a)"); > + > /* sys_enter_prog_a already associated with map_a */ > err = bpf_program__assoc_struct_ops(skel->progs.sys_enter_prog_a, > skel->maps.st_ops_map_b, NULL); > @@ -52,6 +56,7 @@ static void test_st_ops_assoc(void) > > ASSERT_EQ(skel->bss->test_err_a, 0, "skel->bss->test_err_a"); > ASSERT_EQ(skel->bss->test_err_b, 0, "skel->bss->test_err_b"); > + ASSERT_EQ(skel->bss->test_err_inject, 0, "skel->bss->test_err_inject"); > > /* run syscall_prog that calls .test_1 and checks return */ > err = bpf_prog_test_run_opts(bpf_program__fd(skel->progs.syscall_prog_a), NULL); > diff --git a/tools/testing/selftests/bpf/progs/struct_ops_assoc.c b/tools/testing/selftests/bpf/progs/struct_ops_assoc.c > index 68842e3f936b..dd322d43ff5e 100644 > --- a/tools/testing/selftests/bpf/progs/struct_ops_assoc.c > +++ b/tools/testing/selftests/bpf/progs/struct_ops_assoc.c > @@ -103,3 +103,43 @@ SEC(".struct_ops.link") > struct bpf_testmod_multi_st_ops st_ops_map_b = { > .test_1 = (void *)test_1_b, > }; > + > +/* Test for aux injection with stale register contamination. > + * > + * This test reproduces the scenario where the BPF verifier fails to > + * inject the implicit bpf_prog_aux pointer for kfuncs with > + * KF_IMPLICIT_ARGS. The program uses inline assembly to explicitly > + * set R2 (the register for the 2nd kfunc argument, which maps to > + * the implicit bpf_prog_aux *) to a known magic value (0xDEAD, > + * chosen with bit 31 clear to avoid BPF ALU64 sign-extension): > + * > + * asm volatile("r2 = %[magic]" :: [magic] "ri"(0xDEAD) : "r2"); > + * > + * Then bpf_kfunc_aux_inject_stale() is called. The kernel verifier > + * should inject the real bpf_prog_aux into R2, overriding the magic > + * value. The kfunc compares aux against 0xDEAD: > + * > + * - aux == 0xDEAD → kernel failed to inject aux → test fails > + * - aux != 0xDEAD → kernel correctly injected aux → test passes > + */ > +int test_err_inject; > + > +SEC("tp_btf/sys_enter") > +int BPF_PROG(sys_enter_prog_test_aux_inject, struct pt_regs *regs, long id) > +{ > + struct task_struct *task; > + int marker = 0x5A5A; > + int ret; > + > + task = bpf_get_current_task_btf(); > + if (!test_pid || task->pid != test_pid) > + return 0; > + > + asm volatile("r2 = %[magic]" :: [magic] "ri"(0xDEAD) : "r2"); > + > + ret = bpf_kfunc_aux_inject_stale(marker); > + if (ret != marker) > + test_err_inject++; > + > + return 0; > +} > diff --git a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c > index 0be918fe3021..da95a6de3bbf 100644 > --- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c > +++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c > @@ -1316,6 +1316,7 @@ __bpf_kfunc int bpf_kfunc_multi_st_ops_test_1_assoc(struct st_ops_args *args, st > __bpf_kfunc int bpf_kfunc_implicit_arg(int a, struct bpf_prog_aux *aux); > __bpf_kfunc int bpf_kfunc_implicit_arg_legacy(int a, int b, struct bpf_prog_aux *aux); > __bpf_kfunc int bpf_kfunc_implicit_arg_legacy_impl(int a, int b, struct bpf_prog_aux *aux); > +__bpf_kfunc int bpf_kfunc_aux_inject_stale(int marker, struct bpf_prog_aux *aux); > > /* hook targets */ > noinline void bpf_testmod_test_hardirq_fn(void) { barrier(); } > @@ -1399,6 +1400,7 @@ BTF_ID_FLAGS(func, bpf_kfunc_multi_st_ops_test_1_assoc, KF_IMPLICIT_ARGS) > BTF_ID_FLAGS(func, bpf_kfunc_implicit_arg, KF_IMPLICIT_ARGS) > BTF_ID_FLAGS(func, bpf_kfunc_implicit_arg_legacy, KF_IMPLICIT_ARGS) > BTF_ID_FLAGS(func, bpf_kfunc_implicit_arg_legacy_impl) > +BTF_ID_FLAGS(func, bpf_kfunc_aux_inject_stale, KF_IMPLICIT_ARGS) > BTF_ID_FLAGS(func, bpf_kfunc_trigger_ctx_check) > BTF_KFUNCS_END(bpf_testmod_check_kfunc_ids) > > @@ -1916,6 +1918,13 @@ int bpf_kfunc_implicit_arg_legacy_impl(int a, int b, struct bpf_prog_aux *aux) > return bpf_kfunc_implicit_arg_legacy(a, b, aux); > } > > +int bpf_kfunc_aux_inject_stale(int marker, struct bpf_prog_aux *aux) > +{ > + if ((unsigned long)aux == 0xDEAD) > + return -EINVAL; > + return marker; > +} > + > static int multi_st_ops_reg(void *kdata, struct bpf_link *link) > { > struct bpf_testmod_multi_st_ops *st_ops = > diff --git a/tools/testing/selftests/bpf/test_kmods/bpf_testmod_kfunc.h b/tools/testing/selftests/bpf/test_kmods/bpf_testmod_kfunc.h > index 2edc36b66de9..c18791e96b21 100644 > --- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod_kfunc.h > +++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod_kfunc.h > @@ -192,6 +192,7 @@ int *bpf_kfunc_ret_rcu_test_nostruct(int rdonly_buf_size) __ksym; > #ifndef __KERNEL__ > extern int bpf_kfunc_multi_st_ops_test_1(struct st_ops_args *args, u32 id) __weak __ksym; > extern int bpf_kfunc_multi_st_ops_test_1_assoc(struct st_ops_args *args) __weak __ksym; > +extern int bpf_kfunc_aux_inject_stale(int marker) __weak __ksym; > #endif > > struct prog_test_member *bpf_kfunc_get_default_trusted_ptr_test(void) __ksym; ^ permalink raw reply [flat|nested] 35+ messages in thread
end of thread, other threads:[~2026-06-05 1:29 UTC | newest] Thread overview: 35+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-04-07 8:09 [PATCH] bpf: fix btf_types_are_same for cross-BTF type comparison chenyuan_fl 2026-04-07 8:58 ` Leon Hwang 2026-04-07 9:01 ` bot+bpf-ci 2026-04-07 11:19 ` Alan Maguire 2026-05-15 18:27 ` Ihor Solodrai 2026-06-01 6:46 ` [PATCH bpf v2 0/2] bpf: Fix kfunc implicit arg injection and add selftest chenyuan_fl 2026-06-01 6:46 ` [PATCH bpf v2 1/2] bpf: Fix kfunc implicit arg inject type detection to prevent invalid pointer deref chenyuan_fl 2026-06-01 7:42 ` bot+bpf-ci 2026-06-01 19:32 ` Eduard Zingerman 2026-06-02 8:58 ` [PATCH bpf v3 0/2] bpf: Fix kfunc implicit arg injection and add selftest chenyuan_fl 2026-06-02 8:58 ` [PATCH bpf v3 1/2] bpf: Fix kfunc implicit arg inject type detection to prevent invalid pointer deref chenyuan_fl 2026-06-02 9:23 ` sashiko-bot 2026-06-02 9:44 ` bot+bpf-ci 2026-06-02 18:52 ` Ihor Solodrai 2026-06-04 9:14 ` chenyuan 2026-06-04 10:21 ` Alan Maguire 2026-06-02 8:58 ` [PATCH bpf v3 2/2] selftests/bpf: Add regression test for kfunc implicit arg injection chenyuan_fl 2026-06-02 9:31 ` sashiko-bot 2026-06-02 9:44 ` bot+bpf-ci 2026-06-02 9:38 ` [PATCH bpf v4 0/2] bpf: Fix kfunc implicit arg injection and add selftest chenyuan_fl 2026-06-02 9:38 ` [PATCH bpf v4 1/2] bpf: Fix kfunc implicit arg inject type detection to prevent invalid pointer deref chenyuan_fl 2026-06-02 9:58 ` sashiko-bot 2026-06-02 10:42 ` bot+bpf-ci 2026-06-05 0:42 ` Eduard Zingerman 2026-06-02 9:38 ` [PATCH bpf v4 2/2] selftests/bpf: Add regression test for kfunc implicit arg injection chenyuan_fl 2026-06-02 10:06 ` sashiko-bot 2026-06-02 10:27 ` bot+bpf-ci 2026-06-02 17:36 ` kernel test robot 2026-06-02 18:37 ` kernel test robot 2026-06-05 1:29 ` Eduard Zingerman 2026-06-01 17:12 ` [PATCH bpf v2 1/2] bpf: Fix kfunc implicit arg inject type detection to prevent invalid pointer deref Yonghong Song 2026-06-01 21:36 ` Eduard Zingerman 2026-06-01 6:46 ` [PATCH bpf v2 2/2] selftests/bpf: Add regression test for kfunc implicit arg injection with stale register chenyuan_fl 2026-06-01 7:08 ` sashiko-bot 2026-06-01 17:17 ` Yonghong Song
This is an external index of several public inboxes, see mirroring instructions on how to clone and mirror all data and code used by this external index.