* [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; 56+ 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] 56+ 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; 56+ 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] 56+ 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; 56+ 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] 56+ 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; 56+ 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] 56+ 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; 56+ 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] 56+ 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; 56+ 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] 56+ 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; 56+ 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] 56+ 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; 56+ 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] 56+ 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; 56+ 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] 56+ 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; 56+ 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] 56+ 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; 56+ 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] 56+ 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; 56+ 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] 56+ 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; 56+ 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] 56+ 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; 56+ 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] 56+ 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; 56+ 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] 56+ 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; 56+ 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] 56+ 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; 56+ 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] 56+ 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; 56+ 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] 56+ 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; 56+ 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] 56+ 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
` (2 more replies)
1 sibling, 3 replies; 56+ 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] 56+ 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
2026-06-08 14:26 ` [PATCH bpf v5 0/2] bpf: Fix kfunc implicit arg injection and add selftest chenyuan_fl
2 siblings, 3 replies; 56+ 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] 56+ 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)
2026-06-08 14:26 ` [PATCH bpf v5 0/2] bpf: Fix kfunc implicit arg injection and add selftest chenyuan_fl
2 siblings, 5 replies; 56+ 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] 56+ 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; 56+ 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] 56+ 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; 56+ 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] 56+ 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; 56+ 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] 56+ 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; 56+ 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] 56+ 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; 56+ 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] 56+ 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; 56+ 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] 56+ 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; 56+ 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] 56+ 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; 56+ 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] 56+ 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; 56+ 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] 56+ 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; 56+ 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] 56+ 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
2026-06-09 0:46 ` Ihor Solodrai
0 siblings, 1 reply; 56+ 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] 56+ 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; 56+ 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] 56+ 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; 56+ 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] 56+ messages in thread
* [PATCH bpf v5 0/2] bpf: Fix kfunc implicit arg injection and add selftest
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 ` [PATCH bpf v4 2/2] selftests/bpf: Add regression test for kfunc implicit arg injection chenyuan_fl
@ 2026-06-08 14:26 ` chenyuan_fl
2026-06-08 14:26 ` [PATCH bpf v5 1/2] bpf: Fix kfunc implicit arg inject type detection to prevent invalid pointer deref chenyuan_fl
` (3 more replies)
2 siblings, 4 replies; 56+ messages in thread
From: chenyuan_fl @ 2026-06-08 14:26 UTC (permalink / raw)
To: chenyuan_fl
Cc: andrii, ast, bot+bpf-ci, bpf, chenyuan, clm, daniel, eddyz87,
ihor.solodrai, jolsa, linux-kernel, martin.lau, martin.lau,
memxor, song, yonghong.song
From: Yuan Chen <chenyuan@kylinos.cn>
v4: https://lore.kernel.org/bpf/20260602093836.2632714-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.
v5 changes:
- Patch 1: extend the kfunc allow-list in check_kfunc_args() to cover
all kfuncs with implicit args handled in bpf_fixup_kfunc_call():
obj_new, percpu_obj_new, obj_drop, percpu_obj_drop,
refcount_acquire, list_push, rbtree_add (Eduard Zingerman).
- Patch 2: simplify the selftest by verifying aux->name in
bpf_kfunc_implicit_arg() instead of adding a separate struct_ops
test program with inline assembly (Eduard Zingerman). This is
a single-file change exercised by the existing kfunc_implicit_args
selftest.
Yuan Chen (2):
bpf: Fix kfunc implicit arg inject type detection to prevent invalid
pointer deref
selftests/bpf: strengthen bpf_kfunc_implicit_arg to verify aux injection
kernel/bpf/verifier.c | 19 +++++++++++++++--
.../testing/selftests/bpf/test_kmods/bpf_testmod.c | 6 +++++-
2 files changed, 22 insertions(+), 3 deletions(-)
--
2.54.0
^ permalink raw reply [flat|nested] 56+ messages in thread
* [PATCH bpf v5 1/2] bpf: Fix kfunc implicit arg inject type detection to prevent invalid pointer deref
2026-06-08 14:26 ` [PATCH bpf v5 0/2] bpf: Fix kfunc implicit arg injection and add selftest chenyuan_fl
@ 2026-06-08 14:26 ` chenyuan_fl
2026-06-08 14:47 ` sashiko-bot
` (3 more replies)
2026-06-08 14:26 ` [PATCH bpf v5 2/2] selftests/bpf: strengthen bpf_kfunc_implicit_arg to verify aux injection chenyuan_fl
` (2 subsequent siblings)
3 siblings, 4 replies; 56+ messages in thread
From: chenyuan_fl @ 2026-06-08 14:26 UTC (permalink / raw)
To: chenyuan_fl
Cc: andrii, ast, bot+bpf-ci, bpf, chenyuan, clm, daniel, eddyz87,
ihor.solodrai, jolsa, linux-kernel, martin.lau, 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
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. Existing implicit
args in bpf_fixup_kfunc_call() (obj_new, percpu_obj_new, obj_drop,
percpu_obj_drop, refcount_acquire, list_push, rbtree_add) are
explicitly allowed.
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 | 20 +++++++++++++++++++-
1 file changed, 19 insertions(+), 1 deletion(-)
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 8ed484cb1a8a..91aaed7a5eeb 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -11885,9 +11885,27 @@ 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)) {
+ /* kfuncs with implicit args (e.g. 'off' parameter)
+ * handled during verification in bpf_fixup_kfunc_call():
+ * obj_new, percpu_obj_new, obj_drop, percpu_obj_drop,
+ * refcount_acquire, list_push, rbtree_add. Don't flag them. */
+ if (is_bpf_obj_new_kfunc(meta->func_id) ||
+ is_bpf_percpu_obj_new_kfunc(meta->func_id) ||
+ is_bpf_obj_drop_kfunc(meta->func_id) ||
+ is_bpf_percpu_obj_drop_kfunc(meta->func_id) ||
+ is_bpf_refcount_acquire_kfunc(meta->func_id) ||
+ 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] 56+ messages in thread
* [PATCH bpf v5 2/2] selftests/bpf: strengthen bpf_kfunc_implicit_arg to verify aux injection
2026-06-08 14:26 ` [PATCH bpf v5 0/2] bpf: Fix kfunc implicit arg injection and add selftest chenyuan_fl
2026-06-08 14:26 ` [PATCH bpf v5 1/2] bpf: Fix kfunc implicit arg inject type detection to prevent invalid pointer deref chenyuan_fl
@ 2026-06-08 14:26 ` chenyuan_fl
2026-06-08 14:53 ` sashiko-bot
` (2 more replies)
2026-06-08 19:58 ` [PATCH bpf v5 0/2] bpf: Fix kfunc implicit arg injection and add selftest Alexei Starovoitov
2026-06-09 12:52 ` [PATCH bpf v6 " chenyuan_fl
3 siblings, 3 replies; 56+ messages in thread
From: chenyuan_fl @ 2026-06-08 14:26 UTC (permalink / raw)
To: chenyuan_fl
Cc: andrii, ast, bot+bpf-ci, bpf, chenyuan, clm, daniel, eddyz87,
ihor.solodrai, jolsa, linux-kernel, martin.lau, martin.lau,
memxor, song, yonghong.song
From: Yuan Chen <chenyuan@kylinos.cn>
Verify that the KF_IMPLICIT_ARGS injection path correctly passes
the bpf_prog_aux pointer by checking aux->name in
bpf_kfunc_implicit_arg() for the expected program name prefix.
If the verifier incorrectly skipped injection (as could happen
with pahole 1.30's BTF mismatch), the stale register would not
contain a valid aux pointer and the name check would fail.
This is a positive test exercised by the existing kfunc_implicit_args
selftest, which calls bpf_kfunc_implicit_arg(5) and expects a return
value of 5.
Signed-off-by: Yuan Chen <chenyuan@kylinos.cn>
---
tools/testing/selftests/bpf/test_kmods/bpf_testmod.c | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
index 30f1cd23093c..624d57a5c79a 100644
--- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
+++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
@@ -1906,7 +1906,11 @@ int bpf_kfunc_multi_st_ops_test_1_assoc(struct st_ops_args *args, struct bpf_pro
int bpf_kfunc_implicit_arg(int a, struct bpf_prog_aux *aux)
{
- if (aux && a > 0)
+ /* Verify the kernel injected the correct bpf_prog_aux pointer
+ * rather than leaving a stale register value. */
+ if (!aux || strncmp(aux->name, "test_kfunc", sizeof("test_kfunc") - 1))
+ return -EINVAL;
+ if (a > 0)
return a;
return -EINVAL;
}
--
2.54.0
^ permalink raw reply related [flat|nested] 56+ messages in thread
* Re: [PATCH bpf v5 1/2] bpf: Fix kfunc implicit arg inject type detection to prevent invalid pointer deref
2026-06-08 14:26 ` [PATCH bpf v5 1/2] bpf: Fix kfunc implicit arg inject type detection to prevent invalid pointer deref chenyuan_fl
@ 2026-06-08 14:47 ` sashiko-bot
2026-06-08 14:52 ` bot+bpf-ci
` (2 subsequent siblings)
3 siblings, 0 replies; 56+ messages in thread
From: sashiko-bot @ 2026-06-08 14:47 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 references to an undeclared variable `argno` and an undefined function `reg_arg_name`, causing an unconditional build failure.
--
commit 07d82a6816de40e50bec7a27a6fdfd4ab3af2c6d
Author: Yuan Chen <chenyuan@kylinos.cn>
bpf: Fix kfunc implicit arg inject type detection to prevent invalid pointer deref
This patch modifies the verifier's check_kfunc_args() to explicitly reject
unrecognized implicit kfunc arguments with -EFAULT instead of silently skipping
them. It ensures that BTF mismatches result in a clean rejection rather than
leading to a kernel crash or information disclosure.
> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> index 7fb88e1cd7c4d..7e5b68c31c349 100644
> --- a/kernel/bpf/verifier.c
> +++ b/kernel/bpf/verifier.c
> @@ -12082,9 +12082,27 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_
[ ... ]
> + if (is_kfunc_arg_implicit(meta, i)) {
> + /* kfuncs with implicit args (e.g. 'off' parameter)
> + * handled during verification in bpf_fixup_kfunc_call():
> + * obj_new, percpu_obj_new, obj_drop, percpu_obj_drop,
> + * refcount_acquire, list_push, rbtree_add. Don't flag them. */
> + if (is_bpf_obj_new_kfunc(meta->func_id) ||
> + is_bpf_percpu_obj_new_kfunc(meta->func_id) ||
> + is_bpf_obj_drop_kfunc(meta->func_id) ||
> + is_bpf_percpu_obj_drop_kfunc(meta->func_id) ||
> + is_bpf_refcount_acquire_kfunc(meta->func_id) ||
> + 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 successfully? It looks like argno is not declared in this
scope (perhaps the intended variable was i or regno?), and the function
reg_arg_name() does not appear to be defined in the kernel tree. Could this
cause an unconditional build failure?
> + return -EFAULT;
> + }
> +
> t = btf_type_skip_modifiers(btf, args[i].type, NULL);
>
> if (btf_type_is_scalar(t)) {
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260608142618.3064380-1-chenyuan_fl@163.com?part=1
^ permalink raw reply [flat|nested] 56+ messages in thread
* Re: [PATCH bpf v5 1/2] bpf: Fix kfunc implicit arg inject type detection to prevent invalid pointer deref
2026-06-08 14:26 ` [PATCH bpf v5 1/2] bpf: Fix kfunc implicit arg inject type detection to prevent invalid pointer deref chenyuan_fl
2026-06-08 14:47 ` sashiko-bot
@ 2026-06-08 14:52 ` bot+bpf-ci
2026-06-08 17:28 ` Eduard Zingerman
2026-06-09 0:54 ` Ihor Solodrai
3 siblings, 0 replies; 56+ messages in thread
From: bot+bpf-ci @ 2026-06-08 14:52 UTC (permalink / raw)
To: chenyuan_fl, chenyuan_fl
Cc: andrii, ast, bot+bpf-ci, bpf, chenyuan, clm, daniel, eddyz87,
ihor.solodrai, jolsa, linux-kernel, martin.lau, martin.lau,
memxor, song, yonghong.song, ast, andrii, daniel, martin.lau,
eddyz87, yonghong.song, clm, ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 2690 bytes --]
> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> index 7fb88e1cd7c4..7e5b68c31c34 100644
> --- a/kernel/bpf/verifier.c
> +++ b/kernel/bpf/verifier.c
> @@ -12082,9 +12082,27 @@ 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)) {
> + /* kfuncs with implicit args (e.g. 'off' parameter)
> + * handled during verification in bpf_fixup_kfunc_call():
> + * obj_new, percpu_obj_new, obj_drop, percpu_obj_drop,
> + * refcount_acquire, list_push, rbtree_add. Don't flag them. */
> + if (is_bpf_obj_new_kfunc(meta->func_id) ||
> + is_bpf_percpu_obj_new_kfunc(meta->func_id) ||
> + is_bpf_obj_drop_kfunc(meta->func_id) ||
> + is_bpf_percpu_obj_drop_kfunc(meta->func_id) ||
> + is_bpf_refcount_acquire_kfunc(meta->func_id) ||
> + 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));
Does this reference an undeclared 'argno'? The for-loop in
check_kfunc_args() declares only 'i' and 'regno = i + 1', and there is no
'argno' in scope here. The only 'argno' identifiers in this file are
parameters of get_kfunc_ptr_arg_type() and process_kf_arg_ptr_to_btf_id(),
neither of which is visible inside check_kfunc_args().
Would this fail to compile with "error: 'argno' undeclared (first use in
this function)" and break the build of kernel/bpf/verifier.c?
There is also a type concern. reg_arg_name()'s second parameter has type
argno_t, which is a struct:
typedef struct argno { int argno; } argno_t;
so substituting the in-scope u32 'regno' would be a type mismatch rather
than a fix. The other diagnostics in this function pass the u32 'regno'
directly, for example:
verbose(env, "R%d is not a scalar\n", regno);
Was 'regno' (with a matching format) the intended argument here, or is an
argno_t value needed?
The range b75a2e8e..687e2f6c was checked and no later commit corrects
this, so the build break does not appear to be fixed later in the series.
> + 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/27144762590
^ permalink raw reply [flat|nested] 56+ messages in thread
* Re: [PATCH bpf v5 2/2] selftests/bpf: strengthen bpf_kfunc_implicit_arg to verify aux injection
2026-06-08 14:26 ` [PATCH bpf v5 2/2] selftests/bpf: strengthen bpf_kfunc_implicit_arg to verify aux injection chenyuan_fl
@ 2026-06-08 14:53 ` sashiko-bot
2026-06-08 17:34 ` Eduard Zingerman
2026-06-09 0:59 ` Ihor Solodrai
2 siblings, 0 replies; 56+ messages in thread
From: sashiko-bot @ 2026-06-08 14:53 UTC (permalink / raw)
To: chenyuan_fl; +Cc: bpf
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Medium] Unsafe memory access in `bpf_kfunc_implicit_arg` leading to kernel panic on verifier injection failure
- [Low] BPF subsystem multi-line comment style violation
--
commit 7fe0e7a112c19898e04e79eb1d5d25a017d9fcd2
Author: Yuan Chen <chenyuan@kylinos.cn>
selftests/bpf: strengthen bpf_kfunc_implicit_arg to verify aux injection
This commit updates bpf_kfunc_implicit_arg to verify that the kernel correctly
injected the bpf_prog_aux pointer. It checks the name field of the aux pointer
to ensure it matches the expected prefix, attempting to catch cases where the
verifier leaves a stale register value.
> diff --git a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
> --- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
> +++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
[ ... ]
> @@ -1827,7 +1827,11 @@ int bpf_kfunc_multi_st_ops_test_1_assoc(struct st_ops_args *args, struct bpf_pro
>
> int bpf_kfunc_implicit_arg(int a, struct bpf_prog_aux *aux)
> {
> - if (aux && a > 0)
> + /* Verify the kernel injected the correct bpf_prog_aux pointer
> + * rather than leaving a stale register value. */
[Severity: Low]
This isn't a bug, but does this multi-line comment follow the preferred BPF
subsystem comment style?
The BPF subsystem guidelines require multi-line comments to have the
opening /* on its own line:
/*
* Verify the kernel injected the correct bpf_prog_aux pointer
* rather than leaving a stale register value.
*/
> + if (!aux || strncmp(aux->name, "test_kfunc", sizeof("test_kfunc") - 1))
[Severity: Medium]
If the verifier skips injection and leaves a stale, non-NULL register
value in the aux variable, won't dereferencing aux->name cause a kernel
panic?
The commit message mentions that in cases like a BTF mismatch, the stale
register would not contain a valid aux pointer. If aux is a garbage pointer
rather than NULL, the !aux check will evaluate to false, and the strncmp
call will attempt to read from unmapped or invalid memory.
Could this use something like copy_from_kernel_nofault() or similar safe
memory access to gracefully fail the test instead of crashing the host kernel?
> + return -EINVAL;
> + if (a > 0)
> return a;
> return -EINVAL;
> }
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260608142618.3064380-1-chenyuan_fl@163.com?part=2
^ permalink raw reply [flat|nested] 56+ messages in thread
* Re: [PATCH bpf v5 1/2] bpf: Fix kfunc implicit arg inject type detection to prevent invalid pointer deref
2026-06-08 14:26 ` [PATCH bpf v5 1/2] bpf: Fix kfunc implicit arg inject type detection to prevent invalid pointer deref chenyuan_fl
2026-06-08 14:47 ` sashiko-bot
2026-06-08 14:52 ` bot+bpf-ci
@ 2026-06-08 17:28 ` Eduard Zingerman
2026-06-09 0:54 ` Ihor Solodrai
3 siblings, 0 replies; 56+ messages in thread
From: Eduard Zingerman @ 2026-06-08 17:28 UTC (permalink / raw)
To: chenyuan_fl
Cc: andrii, ast, bot+bpf-ci, bpf, chenyuan, clm, daniel,
ihor.solodrai, jolsa, linux-kernel, martin.lau, martin.lau,
memxor, song, yonghong.song
On Mon, 2026-06-08 at 22:26 +0800, chenyuan_fl@163.com wrote:
> From: Yuan Chen <chenyuan@kylinos.cn>
[...]
> 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. Existing implicit
> args in bpf_fixup_kfunc_call() (obj_new, percpu_obj_new, obj_drop,
> percpu_obj_drop, refcount_acquire, list_push, rbtree_add) are
> explicitly allowed.
>
> Suggested-by: Eduard Zingerman <eddyz87@gmail.com>
> Fixes: 64e1360524b9 ("bpf: Verifier support for KF_IMPLICIT_ARGS")
> Signed-off-by: Yuan Chen <chenyuan@kylinos.cn>
> ---
Acked-by: Eduard Zingerman <eddyz87@gmail.com>
> kernel/bpf/verifier.c | 20 +++++++++++++++++++-
> 1 file changed, 19 insertions(+), 1 deletion(-)
>
> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> index 8ed484cb1a8a..91aaed7a5eeb 100644
> --- a/kernel/bpf/verifier.c
> +++ b/kernel/bpf/verifier.c
> @@ -11885,9 +11885,27 @@ 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)) {
> + /* kfuncs with implicit args (e.g. 'off' parameter)
> + * handled during verification in bpf_fixup_kfunc_call():
> + * obj_new, percpu_obj_new, obj_drop, percpu_obj_drop,
> + * refcount_acquire, list_push, rbtree_add. Don't flag them. */
Nit: comment does not follow formatting rules and the two lines
listing relevant kfuncs are redundant.
/*
* This an exhaustive list of the non-bpf_prog_aux implicit args kfuncs,
* which are handled post-verification by bpf_fixup_kfunc_call().
*/
> + if (is_bpf_obj_new_kfunc(meta->func_id) ||
> + is_bpf_percpu_obj_new_kfunc(meta->func_id) ||
> + is_bpf_obj_drop_kfunc(meta->func_id) ||
> + is_bpf_percpu_obj_drop_kfunc(meta->func_id) ||
> + is_bpf_refcount_acquire_kfunc(meta->func_id) ||
> + 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] 56+ messages in thread
* Re: [PATCH bpf v5 2/2] selftests/bpf: strengthen bpf_kfunc_implicit_arg to verify aux injection
2026-06-08 14:26 ` [PATCH bpf v5 2/2] selftests/bpf: strengthen bpf_kfunc_implicit_arg to verify aux injection chenyuan_fl
2026-06-08 14:53 ` sashiko-bot
@ 2026-06-08 17:34 ` Eduard Zingerman
2026-06-09 0:59 ` Ihor Solodrai
2 siblings, 0 replies; 56+ messages in thread
From: Eduard Zingerman @ 2026-06-08 17:34 UTC (permalink / raw)
To: chenyuan_fl
Cc: andrii, ast, bot+bpf-ci, bpf, chenyuan, clm, daniel,
ihor.solodrai, jolsa, linux-kernel, martin.lau, martin.lau,
memxor, song, yonghong.song
On Mon, 2026-06-08 at 22:26 +0800, chenyuan_fl@163.com wrote:
> From: Yuan Chen <chenyuan@kylinos.cn>
>
> Verify that the KF_IMPLICIT_ARGS injection path correctly passes
> the bpf_prog_aux pointer by checking aux->name in
> bpf_kfunc_implicit_arg() for the expected program name prefix.
> If the verifier incorrectly skipped injection (as could happen
> with pahole 1.30's BTF mismatch), the stale register would not
> contain a valid aux pointer and the name check would fail.
>
> This is a positive test exercised by the existing kfunc_implicit_args
> selftest, which calls bpf_kfunc_implicit_arg(5) and expects a return
> value of 5.
>
> Signed-off-by: Yuan Chen <chenyuan@kylinos.cn>
> ---
Acked-by: Eduard Zingerman <eddyz87@gmail.com>
[...]
^ permalink raw reply [flat|nested] 56+ messages in thread
* Re: [PATCH bpf v5 0/2] bpf: Fix kfunc implicit arg injection and add selftest
2026-06-08 14:26 ` [PATCH bpf v5 0/2] bpf: Fix kfunc implicit arg injection and add selftest chenyuan_fl
2026-06-08 14:26 ` [PATCH bpf v5 1/2] bpf: Fix kfunc implicit arg inject type detection to prevent invalid pointer deref chenyuan_fl
2026-06-08 14:26 ` [PATCH bpf v5 2/2] selftests/bpf: strengthen bpf_kfunc_implicit_arg to verify aux injection chenyuan_fl
@ 2026-06-08 19:58 ` Alexei Starovoitov
2026-06-09 12:52 ` [PATCH bpf v6 " chenyuan_fl
3 siblings, 0 replies; 56+ messages in thread
From: Alexei Starovoitov @ 2026-06-08 19:58 UTC (permalink / raw)
To: chenyuan_fl
Cc: andrii, ast, bot+bpf-ci, bpf, chenyuan, clm, daniel, eddyz87,
ihor.solodrai, jolsa, linux-kernel, martin.lau, martin.lau,
memxor, song, yonghong.song
On Mon Jun 8, 2026 at 7:26 AM PDT, chenyuan_fl wrote:
> From: Yuan Chen <chenyuan@kylinos.cn>
>
> v4: https://lore.kernel.org/bpf/20260602093836.2632714-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.
>
> v5 changes:
> - Patch 1: extend the kfunc allow-list in check_kfunc_args() to cover
> all kfuncs with implicit args handled in bpf_fixup_kfunc_call():
> obj_new, percpu_obj_new, obj_drop, percpu_obj_drop,
> refcount_acquire, list_push, rbtree_add (Eduard Zingerman).
> - Patch 2: simplify the selftest by verifying aux->name in
> bpf_kfunc_implicit_arg() instead of adding a separate struct_ops
> test program with inline assembly (Eduard Zingerman). This is
> a single-file change exercised by the existing kfunc_implicit_args
> selftest.
Pls address feedback from Eduard and respin with [PATCH bpf-next v6] subject.
pw-bot: cr
^ permalink raw reply [flat|nested] 56+ 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 10:21 ` Alan Maguire
@ 2026-06-09 0:46 ` Ihor Solodrai
0 siblings, 0 replies; 56+ messages in thread
From: Ihor Solodrai @ 2026-06-09 0:46 UTC (permalink / raw)
To: Alan Maguire, chenyuan
Cc: eddyz87, yonghong.song, andrii, ast, bot+bpf-ci, bpf, chenyuan,
clm, daniel, jolsa, linux-kernel, martin.lau, martin.lau, memxor,
song
On 6/4/26 3:21 AM, Alan Maguire wrote:
> 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?
Hi everyone.
I've been scratching my head trying to understand exactly what may
cause the divergence that Yuan reported. It took me and the bots a
while, but I think I figured it out.
TL;DR is that compilers may produce multiple DW_TAG_pointer_type DIEs
in DWARF for the same field/arg if they differ for example in address
space. The libbpf commit 8e64c387c942 ("libbpf: Add identical pointer
detection to btf_dedup_is_equiv()"), that Alan referenced looks like
the right fix for this for the BTF generation.
Since the fix is already there, the recommendation for people running
into this is to update their libbpf to at least 1.6:
# in https://github.com/libbpf/libbpf
$ git tag --contains 5ee9fbf7d769fb4e84881e0a073d6259b8555880
v1.6.0
v1.6.1
v1.6.2
v1.6.3
v1.7.0
Now some details. Consider a code example below.
base.c:
struct aux {
int __seg_gs *pcpu_a;
int __seg_gs *pcpu_b;
};
int base_use(struct aux *aux)
{
return aux != 0;
}
module.c:
struct aux {
int __seg_gs *pcpu_a;
int __seg_fs *pcpu_b; // <-- NOTE: a different attribute
};
int kfunc_pick_a(struct aux *aux)
{
// TYPEOF_UNQUAL macro is referenced in Alan's fix (8e64c387c942)
// as a trigger for the dedup bug
__typeof_unqual__(*aux->pcpu_a) *plain_from_typeof_unqual;
plain_from_typeof_unqual = (int *)(unsigned long)aux->pcpu_a;
return plain_from_typeof_unqual != 0;
}
If you compile module.c, you can see in the DWARF there are two
pointer DIEs for int * that only differ in DW_AT_address_class:
0x00000040: DW_TAG_member
DW_AT_name ("pcpu_a")
DW_AT_type (0x00000057 "int *")
0x0000004b: DW_TAG_member
DW_AT_name ("pcpu_b")
DW_AT_type (0x00000064 "int *")
0x00000057: DW_TAG_pointer_type
DW_AT_address_class (0x02)
0x00000064: DW_TAG_pointer_type
DW_AT_address_class (0x01)
DW_AT_name ("plain_from_typeof_unqual")
DW_AT_type (0x000000b4 "int *")
Then when you use pahole (without the libbpf fix) to create base BTF
from base.c, and a split BTF for module.c, you can see struct aux
still exists in the split BTF, and the kfunc_pick_a() prototype
referencing it:
[7] STRUCT 'aux' size=16 vlen=2
'pcpu_a' type_id=2 bits_offset=0
'pcpu_b' type_id=2 bits_offset=64
[8] PTR '(anon)' type_id=7
[9] FUNC_PROTO '(anon)' ret_type_id=3 vlen=1
'aux' type_id=8
[10] FUNC 'kfunc_pick_a' type_id=9 linkage=static
The reason for this is that pahole generates a ref type for each
DW_TAG_pointer_type [1], so there are two BTF IDs for int * in this
example. This then makes the struct aux BTF referenced from the module
and from the base "different", exposing the dedup bug.
The DW_AT_address_class is never even loaded and is not taken into
account anywhere in pahole. But that seems fine, I suspect there are
other examples of valid DWARF where the difference is not relevant to
BTF dedup.
[1] https://git.kernel.org/pub/scm/devel/pahole/pahole.git/tree/btf_encoder.c?h=v1.31#n1647
>
> Thanks!
>
> Alan
^ permalink raw reply [flat|nested] 56+ messages in thread
* Re: [PATCH bpf v5 1/2] bpf: Fix kfunc implicit arg inject type detection to prevent invalid pointer deref
2026-06-08 14:26 ` [PATCH bpf v5 1/2] bpf: Fix kfunc implicit arg inject type detection to prevent invalid pointer deref chenyuan_fl
` (2 preceding siblings ...)
2026-06-08 17:28 ` Eduard Zingerman
@ 2026-06-09 0:54 ` Ihor Solodrai
2026-06-09 1:00 ` Eduard Zingerman
3 siblings, 1 reply; 56+ messages in thread
From: Ihor Solodrai @ 2026-06-09 0:54 UTC (permalink / raw)
To: chenyuan_fl
Cc: andrii, ast, bot+bpf-ci, bpf, chenyuan, clm, daniel, eddyz87,
jolsa, linux-kernel, martin.lau, martin.lau, memxor, song,
yonghong.song
On 6/8/26 7:26 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,
> 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. Existing implicit
> args in bpf_fixup_kfunc_call() (obj_new, percpu_obj_new, obj_drop,
> percpu_obj_drop, refcount_acquire, list_push, rbtree_add) are
> explicitly allowed.
>
> 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 | 20 +++++++++++++++++++-
> 1 file changed, 19 insertions(+), 1 deletion(-)
>
> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> index 8ed484cb1a8a..91aaed7a5eeb 100644
> --- a/kernel/bpf/verifier.c
> +++ b/kernel/bpf/verifier.c
> @@ -11885,9 +11885,27 @@ 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)) {
> + /* kfuncs with implicit args (e.g. 'off' parameter)
> + * handled during verification in bpf_fixup_kfunc_call():
> + * obj_new, percpu_obj_new, obj_drop, percpu_obj_drop,
> + * refcount_acquire, list_push, rbtree_add. Don't flag them. */
> + if (is_bpf_obj_new_kfunc(meta->func_id) ||
> + is_bpf_percpu_obj_new_kfunc(meta->func_id) ||
> + is_bpf_obj_drop_kfunc(meta->func_id) ||
> + is_bpf_percpu_obj_drop_kfunc(meta->func_id) ||
> + is_bpf_refcount_acquire_kfunc(meta->func_id) ||
> + is_bpf_list_push_kfunc(meta->func_id) ||
> + is_bpf_rbtree_add_kfunc(meta->func_id))
Is the goal here to have a nice error message?
I think this will fail for other functions like bpf_wq_set_callback().
For a proper check, the list must include every single kfunc with KF_IMPLICIT_ARGS, no?
If we go this route, then the list of flagged kfuncs can be collected automatically.
I'm not sure we actually want to do this.
> + 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] 56+ messages in thread
* Re: [PATCH bpf v5 2/2] selftests/bpf: strengthen bpf_kfunc_implicit_arg to verify aux injection
2026-06-08 14:26 ` [PATCH bpf v5 2/2] selftests/bpf: strengthen bpf_kfunc_implicit_arg to verify aux injection chenyuan_fl
2026-06-08 14:53 ` sashiko-bot
2026-06-08 17:34 ` Eduard Zingerman
@ 2026-06-09 0:59 ` Ihor Solodrai
2 siblings, 0 replies; 56+ messages in thread
From: Ihor Solodrai @ 2026-06-09 0:59 UTC (permalink / raw)
To: chenyuan_fl
Cc: andrii, ast, bot+bpf-ci, bpf, chenyuan, clm, daniel, eddyz87,
jolsa, linux-kernel, martin.lau, martin.lau, memxor, song,
yonghong.song
On 6/8/26 7:26 AM, chenyuan_fl@163.com wrote:
> From: Yuan Chen <chenyuan@kylinos.cn>
>
> Verify that the KF_IMPLICIT_ARGS injection path correctly passes
> the bpf_prog_aux pointer by checking aux->name in
> bpf_kfunc_implicit_arg() for the expected program name prefix.
> If the verifier incorrectly skipped injection (as could happen
> with pahole 1.30's BTF mismatch), the stale register would not
> contain a valid aux pointer and the name check would fail.
>
> This is a positive test exercised by the existing kfunc_implicit_args
> selftest, which calls bpf_kfunc_implicit_arg(5) and expects a return
> value of 5.
>
> Signed-off-by: Yuan Chen <chenyuan@kylinos.cn>
Acked-by: Ihor Solodrai <ihor.solodrai@linux.dev>
> ---
> tools/testing/selftests/bpf/test_kmods/bpf_testmod.c | 6 +++++-
> 1 file changed, 5 insertions(+), 1 deletion(-)
>
> diff --git a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
> index 30f1cd23093c..624d57a5c79a 100644
> --- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
> +++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
> @@ -1906,7 +1906,11 @@ int bpf_kfunc_multi_st_ops_test_1_assoc(struct st_ops_args *args, struct bpf_pro
>
> int bpf_kfunc_implicit_arg(int a, struct bpf_prog_aux *aux)
> {
> - if (aux && a > 0)
> + /* Verify the kernel injected the correct bpf_prog_aux pointer
> + * rather than leaving a stale register value. */
> + if (!aux || strncmp(aux->name, "test_kfunc", sizeof("test_kfunc") - 1))
> + return -EINVAL;
> + if (a > 0)
> return a;
> return -EINVAL;
> }
^ permalink raw reply [flat|nested] 56+ messages in thread
* Re: [PATCH bpf v5 1/2] bpf: Fix kfunc implicit arg inject type detection to prevent invalid pointer deref
2026-06-09 0:54 ` Ihor Solodrai
@ 2026-06-09 1:00 ` Eduard Zingerman
2026-06-09 1:18 ` Ihor Solodrai
0 siblings, 1 reply; 56+ messages in thread
From: Eduard Zingerman @ 2026-06-09 1:00 UTC (permalink / raw)
To: Ihor Solodrai, chenyuan_fl
Cc: andrii, ast, bot+bpf-ci, bpf, chenyuan, clm, daniel, jolsa,
linux-kernel, martin.lau, martin.lau, memxor, song, yonghong.song
On Mon, 2026-06-08 at 17:54 -0700, Ihor Solodrai wrote:
> On 6/8/26 7:26 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,
> > 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. Existing implicit
> > args in bpf_fixup_kfunc_call() (obj_new, percpu_obj_new, obj_drop,
> > percpu_obj_drop, refcount_acquire, list_push, rbtree_add) are
> > explicitly allowed.
> >
> > 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 | 20 +++++++++++++++++++-
> > 1 file changed, 19 insertions(+), 1 deletion(-)
> >
> > diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> > index 8ed484cb1a8a..91aaed7a5eeb 100644
> > --- a/kernel/bpf/verifier.c
> > +++ b/kernel/bpf/verifier.c
> > @@ -11885,9 +11885,27 @@ 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)) {
> > + /* kfuncs with implicit args (e.g. 'off' parameter)
> > + * handled during verification in bpf_fixup_kfunc_call():
> > + * obj_new, percpu_obj_new, obj_drop, percpu_obj_drop,
> > + * refcount_acquire, list_push, rbtree_add. Don't flag them. */
> > + if (is_bpf_obj_new_kfunc(meta->func_id) ||
> > + is_bpf_percpu_obj_new_kfunc(meta->func_id) ||
> > + is_bpf_obj_drop_kfunc(meta->func_id) ||
> > + is_bpf_percpu_obj_drop_kfunc(meta->func_id) ||
> > + is_bpf_refcount_acquire_kfunc(meta->func_id) ||
> > + is_bpf_list_push_kfunc(meta->func_id) ||
> > + is_bpf_rbtree_add_kfunc(meta->func_id))
>
> Is the goal here to have a nice error message?
>
> I think this will fail for other functions like bpf_wq_set_callback().
> For a proper check, the list must include every single kfunc with KF_IMPLICIT_ARGS, no?
>
> If we go this route, then the list of flagged kfuncs can be collected automatically.
> I'm not sure we actually want to do this.
The calls to functions with implicit arguments are patched by
bpf_fixup_kfunc_call(). As far as I understand, this function:
- handles functions with implicit bpf_prog_aux generically
- handles the functions listed above on a case-by-case basis.
The goal is not to have a nice error message, but to prevent runtime
from reading garbage from a register.
>
> > + 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] 56+ messages in thread
* Re: [PATCH bpf v5 1/2] bpf: Fix kfunc implicit arg inject type detection to prevent invalid pointer deref
2026-06-09 1:00 ` Eduard Zingerman
@ 2026-06-09 1:18 ` Ihor Solodrai
2026-06-09 6:20 ` Eduard Zingerman
0 siblings, 1 reply; 56+ messages in thread
From: Ihor Solodrai @ 2026-06-09 1:18 UTC (permalink / raw)
To: Eduard Zingerman, chenyuan_fl
Cc: andrii, ast, bot+bpf-ci, bpf, chenyuan, clm, daniel, jolsa,
linux-kernel, martin.lau, martin.lau, memxor, song, yonghong.song
On 6/8/26 6:00 PM, Eduard Zingerman wrote:
> On Mon, 2026-06-08 at 17:54 -0700, Ihor Solodrai wrote:
>> On 6/8/26 7:26 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,
>>> 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. Existing implicit
>>> args in bpf_fixup_kfunc_call() (obj_new, percpu_obj_new, obj_drop,
>>> percpu_obj_drop, refcount_acquire, list_push, rbtree_add) are
>>> explicitly allowed.
>>>
>>> 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 | 20 +++++++++++++++++++-
>>> 1 file changed, 19 insertions(+), 1 deletion(-)
>>>
>>> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
>>> index 8ed484cb1a8a..91aaed7a5eeb 100644
>>> --- a/kernel/bpf/verifier.c
>>> +++ b/kernel/bpf/verifier.c
>>> @@ -11885,9 +11885,27 @@ 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)) {
>>> + /* kfuncs with implicit args (e.g. 'off' parameter)
>>> + * handled during verification in bpf_fixup_kfunc_call():
>>> + * obj_new, percpu_obj_new, obj_drop, percpu_obj_drop,
>>> + * refcount_acquire, list_push, rbtree_add. Don't flag them. */
>>> + if (is_bpf_obj_new_kfunc(meta->func_id) ||
>>> + is_bpf_percpu_obj_new_kfunc(meta->func_id) ||
>>> + is_bpf_obj_drop_kfunc(meta->func_id) ||
>>> + is_bpf_percpu_obj_drop_kfunc(meta->func_id) ||
>>> + is_bpf_refcount_acquire_kfunc(meta->func_id) ||
>>> + is_bpf_list_push_kfunc(meta->func_id) ||
>>> + is_bpf_rbtree_add_kfunc(meta->func_id))
>>
>> Is the goal here to have a nice error message?
>>
>> I think this will fail for other functions like bpf_wq_set_callback().
>> For a proper check, the list must include every single kfunc with KF_IMPLICIT_ARGS, no?
>>
>> If we go this route, then the list of flagged kfuncs can be collected automatically.
>> I'm not sure we actually want to do this.
>
> The calls to functions with implicit arguments are patched by
> bpf_fixup_kfunc_call(). As far as I understand, this function:
> - handles functions with implicit bpf_prog_aux generically
> - handles the functions listed above on a case-by-case basis.
>
> The goal is not to have a nice error message, but to prevent runtime
> from reading garbage from a register.
In check_kfunc_args() we only needed to know whether the arg is implicit,
independent of its type, which is_kfunc_arg_implicit() already does. To skip it.
For vmlinux kernel kfuncs this should be enough, I think.
To properly harden against reading garbage for a *module* kfunc, I think the
verifier would have to check for the specific BTF type, e.g. "are we patching a
struct bpf_prog_aux pointer?".
It could be done immediately before patching, but at that point the verification is
complete, right?..
Other than that we'd have to somehow pass through from check_kfunc_args() to
bpf_fixup_kfunc_call() information like "arg 1 of this kfunc can be patched to prog_aux" etc.
We sort of do that already with the meta->arg_prog = true
In a module one can define arbitrary kfuncs and add KF_IMPLICIT_ARGS to them, so
proper hardening needs to be generic.
I think this boils down to whether we want to error-check module kfuncs or not.
Not sure what the verifier strategy is here, but my understanding is that in general
a custom module can easily make kernel read garbage, and it's not a kernel's problem.
>
>>
>>> + 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] 56+ messages in thread
* Re: [PATCH bpf v5 1/2] bpf: Fix kfunc implicit arg inject type detection to prevent invalid pointer deref
2026-06-09 1:18 ` Ihor Solodrai
@ 2026-06-09 6:20 ` Eduard Zingerman
0 siblings, 0 replies; 56+ messages in thread
From: Eduard Zingerman @ 2026-06-09 6:20 UTC (permalink / raw)
To: Ihor Solodrai, chenyuan_fl
Cc: andrii, ast, bot+bpf-ci, bpf, chenyuan, clm, daniel, jolsa,
linux-kernel, martin.lau, martin.lau, memxor, song, yonghong.song
On Mon, 2026-06-08 at 18:18 -0700, Ihor Solodrai wrote:
[...]
> > > > @@ -11885,9 +11885,27 @@ 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)) {
> > > > + /* kfuncs with implicit args (e.g. 'off' parameter)
> > > > + * handled during verification in bpf_fixup_kfunc_call():
> > > > + * obj_new, percpu_obj_new, obj_drop, percpu_obj_drop,
> > > > + * refcount_acquire, list_push, rbtree_add. Don't flag them. */
> > > > + if (is_bpf_obj_new_kfunc(meta->func_id) ||
> > > > + is_bpf_percpu_obj_new_kfunc(meta->func_id) ||
> > > > + is_bpf_obj_drop_kfunc(meta->func_id) ||
> > > > + is_bpf_percpu_obj_drop_kfunc(meta->func_id) ||
> > > > + is_bpf_refcount_acquire_kfunc(meta->func_id) ||
> > > > + is_bpf_list_push_kfunc(meta->func_id) ||
> > > > + is_bpf_rbtree_add_kfunc(meta->func_id))
> > >
> > > Is the goal here to have a nice error message?
> > >
> > > I think this will fail for other functions like bpf_wq_set_callback().
> > > For a proper check, the list must include every single kfunc with KF_IMPLICIT_ARGS, no?
> > >
> > > If we go this route, then the list of flagged kfuncs can be collected automatically.
> > > I'm not sure we actually want to do this.
> >
> > The calls to functions with implicit arguments are patched by
> > bpf_fixup_kfunc_call(). As far as I understand, this function:
> > - handles functions with implicit bpf_prog_aux generically
> > - handles the functions listed above on a case-by-case basis.
> >
> > The goal is not to have a nice error message, but to prevent runtime
> > from reading garbage from a register.
>
> In check_kfunc_args() we only needed to know whether the arg is implicit,
> independent of its type, which is_kfunc_arg_implicit() already does. To skip it.
>
> For vmlinux kernel kfuncs this should be enough, I think.
>
> To properly harden against reading garbage for a *module* kfunc, I think the
> verifier would have to check for the specific BTF type, e.g. "are we patching a
> struct bpf_prog_aux pointer?".
The only way for a module kfunc can have implicit args is to have
bpf_prog_aux parameter and an implicit args flag.
There are no module-specific callbacks to do custom implicit args patching.
The actual arguments patching is done by verifier.c:bpf_fixup_kfunc_call().
It has the following structure:
int bpf_fixup_kfunc_call(...)
{
...
if (is_bpf_obj_new_kfunc(desc->func_id) || is_bpf_percpu_obj_new_kfunc(desc->func_id)) {
... patch args ...
} else if (is_bpf_obj_drop_kfunc(desc->func_id) ||
is_bpf_percpu_obj_drop_kfunc(desc->func_id) ||
is_bpf_refcount_acquire_kfunc(desc->func_id)) {
... patch args ...
} else if (is_bpf_list_push_kfunc(desc->func_id) ||
is_bpf_rbtree_add_kfunc(desc->func_id)) {
... patch args ...
} else if (desc->func_id == special_kfunc_list[KF_bpf_cast_to_kern_ctx] ||
desc->func_id == special_kfunc_list[KF_bpf_rdonly_cast]) {
...
} else if (desc->func_id == special_kfunc_list[KF_bpf_session_is_return] && ...) {
...
} else if (desc->func_id == special_kfunc_list[KF_bpf_session_cookie] && ...) {
...
}
if (env->insn_aux_data[insn_idx].arg_prog) {
u32 regno = env->insn_aux_data[insn_idx].arg_prog;
... patch bpf_prog_aux arg in `regno` ...
}
return 0;
}
Also consider how check_kfunc_args() looks w/o this patch:
static int check_kfunc_args(...)
{
...
for (i = 0; i < nargs; i++) {
...
if (is_kfunc_arg_prog_aux(btf, &args[i])) {
... set env->insn_aux_data[insn_idx].arg_prog ...
}
if (is_kfunc_arg_ignore(btf, &args[i]) || is_kfunc_arg_implicit(meta, i))
continue;
...
}
return 0;
}
The `is_kfunc_arg_prog_aux(btf, &args[i])` might return false in case of bogus BTF.
In such a case `insn_aux_data[insn_idx].arg_prog` won't be set and no patching
would happen in check_kfunc_args(). The kfunc call would remain and the kfunc would
read whatever happens to be in the should-have-been patched register.
So this patch modifies check_kfunc_args() to error out if function is
marked with implicit args flag, but neither of known good implicit
args cases is present.
> It could be done immediately before patching, but at that point the verification is
> complete, right?..
Actually, yes. The following should be a legit fix as well (I think):
diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h
index b27352d72b4f..6d6f4ccf2021 100644
--- a/include/linux/bpf_verifier.h
+++ b/include/linux/bpf_verifier.h
@@ -1606,6 +1606,7 @@ enum bpf_reg_arg_type {
struct bpf_kfunc_desc {
struct btf_func_model func_model;
u32 func_id;
+ u32 flags;
s32 imm;
u16 offset;
unsigned long addr;
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 02239c56801b..6e5f5a14a29c 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -2752,6 +2752,7 @@ int bpf_add_kfunc_call(struct bpf_verifier_env *env, u32 func_id, u16 offset)
desc = &tab->descs[tab->nr_descs++];
desc->func_id = func_id;
+ desc->flags = kfunc.flags ? *kfunc.flags : 0;
desc->offset = offset;
desc->addr = addr;
desc->func_model = func_model;
@@ -19753,9 +19754,7 @@ int bpf_fixup_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
insn_buf[4] = BPF_ALU64_REG(BPF_SUB, BPF_REG_0, BPF_REG_1);
insn_buf[5] = BPF_ALU64_IMM(BPF_NEG, BPF_REG_0, 0);
*cnt = 6;
- }
-
- if (env->insn_aux_data[insn_idx].arg_prog) {
+ } else if (env->insn_aux_data[insn_idx].arg_prog) {
u32 regno = env->insn_aux_data[insn_idx].arg_prog;
struct bpf_insn ld_addrs[2] = { BPF_LD_IMM64(regno, (long)env->prog->aux) };
int idx = *cnt;
@@ -19764,6 +19763,10 @@ int bpf_fixup_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
insn_buf[idx++] = ld_addrs[1];
insn_buf[idx++] = *insn;
*cnt = idx;
+ } else if (desc->flags & KF_IMPLICIT_ARGS) {
+ verbose(env, "don't know how to patch kfunc call at instruction %d, possible BTF mismatch, kfunc is marked with KF_IMPLICIT_ARGS\n",
+ insn_idx);
+ return -EFAULT;
}
return 0;
}
> Other than that we'd have to somehow pass through from check_kfunc_args() to
> bpf_fixup_kfunc_call() information like "arg 1 of this kfunc can be patched to prog_aux" etc.
> We sort of do that already with the meta->arg_prog = true
>
> In a module one can define arbitrary kfuncs and add KF_IMPLICIT_ARGS to them, so
> proper hardening needs to be generic.
>
> I think this boils down to whether we want to error-check module kfuncs or not.
> Not sure what the verifier strategy is here, but my understanding is that in general
> a custom module can easily make kernel read garbage, and it's not a kernel's problem.
^ permalink raw reply [flat|nested] 56+ messages in thread
* [PATCH bpf v6 0/2] bpf: Fix kfunc implicit arg injection and add selftest
2026-06-08 14:26 ` [PATCH bpf v5 0/2] bpf: Fix kfunc implicit arg injection and add selftest chenyuan_fl
` (2 preceding siblings ...)
2026-06-08 19:58 ` [PATCH bpf v5 0/2] bpf: Fix kfunc implicit arg injection and add selftest Alexei Starovoitov
@ 2026-06-09 12:52 ` chenyuan_fl
2026-06-09 12:52 ` [PATCH bpf v6 1/2] bpf: Fix kfunc implicit arg inject type detection to prevent invalid pointer deref chenyuan_fl
2026-06-09 12:52 ` [PATCH bpf v6 2/2] selftests/bpf: strengthen bpf_kfunc_implicit_arg to verify aux injection chenyuan_fl
3 siblings, 2 replies; 56+ messages in thread
From: chenyuan_fl @ 2026-06-09 12:52 UTC (permalink / raw)
To: chenyuan_fl
Cc: andrii, ast, bot+bpf-ci, bpf, chenyuan, clm, daniel, eddyz87,
ihor.solodrai, jolsa, linux-kernel, martin.lau, martin.lau,
memxor, song, yonghong.song
From: Yuan Chen <chenyuan@kylinos.cn>
v5: https://lore.kernel.org/bpf/20260608142618.3064380-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.
v5 changes:
- Patch 1: extend the kfunc allow-list to cover all KF_IMPLICIT_ARGS
kfuncs handled in bpf_fixup_kfunc_call(): obj_new, percpu_obj_new,
obj_drop, percpu_obj_drop, refcount_acquire, list_push, rbtree_add.
- Patch 2: simplify the selftest by verifying aux->name in
bpf_kfunc_implicit_arg() instead of adding a separate struct_ops
test program with inline assembly.
v6 changes:
- Patch 1: fix comment formatting to follow kernel style (Eduard
Zingerman).
Yuan Chen (2):
bpf: Fix kfunc implicit arg inject type detection to prevent invalid
pointer deref
selftests/bpf: strengthen bpf_kfunc_implicit_arg to verify aux injection
kernel/bpf/verifier.c | 20 +++++++++++++++++++-
.../testing/selftests/bpf/test_kmods/bpf_testmod.c | 6 +++++-
2 files changed, 24 insertions(+), 2 deletions(-)
--
2.54.0
^ permalink raw reply [flat|nested] 56+ messages in thread
* [PATCH bpf v6 1/2] bpf: Fix kfunc implicit arg inject type detection to prevent invalid pointer deref
2026-06-09 12:52 ` [PATCH bpf v6 " chenyuan_fl
@ 2026-06-09 12:52 ` chenyuan_fl
2026-06-09 13:08 ` sashiko-bot
2026-06-09 13:32 ` bot+bpf-ci
2026-06-09 12:52 ` [PATCH bpf v6 2/2] selftests/bpf: strengthen bpf_kfunc_implicit_arg to verify aux injection chenyuan_fl
1 sibling, 2 replies; 56+ messages in thread
From: chenyuan_fl @ 2026-06-09 12:52 UTC (permalink / raw)
To: chenyuan_fl
Cc: andrii, ast, bot+bpf-ci, bpf, chenyuan, clm, daniel, eddyz87,
ihor.solodrai, jolsa, linux-kernel, martin.lau, 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
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. Existing implicit
args in bpf_fixup_kfunc_call() (obj_new, percpu_obj_new, obj_drop,
percpu_obj_drop, refcount_acquire, list_push, rbtree_add) are
explicitly allowed.
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 | 21 ++++++++++++++++++++-
1 file changed, 20 insertions(+), 1 deletion(-)
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 8ed484cb1a8a..0d598a419008 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -11885,9 +11885,28 @@ 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)) {
+ /*
+ * This is an exhaustive list of the non-bpf_prog_aux
+ * implicit args kfuncs, which are handled
+ * post-verification by bpf_fixup_kfunc_call().
+ */
+ if (is_bpf_obj_new_kfunc(meta->func_id) ||
+ is_bpf_percpu_obj_new_kfunc(meta->func_id) ||
+ is_bpf_obj_drop_kfunc(meta->func_id) ||
+ is_bpf_percpu_obj_drop_kfunc(meta->func_id) ||
+ is_bpf_refcount_acquire_kfunc(meta->func_id) ||
+ 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] 56+ messages in thread
* [PATCH bpf v6 2/2] selftests/bpf: strengthen bpf_kfunc_implicit_arg to verify aux injection
2026-06-09 12:52 ` [PATCH bpf v6 " chenyuan_fl
2026-06-09 12:52 ` [PATCH bpf v6 1/2] bpf: Fix kfunc implicit arg inject type detection to prevent invalid pointer deref chenyuan_fl
@ 2026-06-09 12:52 ` chenyuan_fl
2026-06-09 12:59 ` sashiko-bot
1 sibling, 1 reply; 56+ messages in thread
From: chenyuan_fl @ 2026-06-09 12:52 UTC (permalink / raw)
To: chenyuan_fl
Cc: andrii, ast, bot+bpf-ci, bpf, chenyuan, clm, daniel, eddyz87,
ihor.solodrai, jolsa, linux-kernel, martin.lau, martin.lau,
memxor, song, yonghong.song
From: Yuan Chen <chenyuan@kylinos.cn>
Verify that the KF_IMPLICIT_ARGS injection path correctly passes
the bpf_prog_aux pointer by checking aux->name in
bpf_kfunc_implicit_arg() for the expected program name prefix.
If the verifier incorrectly skipped injection (as could happen
with pahole 1.30's BTF mismatch), the stale register would not
contain a valid aux pointer and the name check would fail.
This is a positive test exercised by the existing kfunc_implicit_args
selftest, which calls bpf_kfunc_implicit_arg(5) and expects a return
value of 5.
Signed-off-by: Yuan Chen <chenyuan@kylinos.cn>
---
tools/testing/selftests/bpf/test_kmods/bpf_testmod.c | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
index 30f1cd23093c..624d57a5c79a 100644
--- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
+++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
@@ -1906,7 +1906,11 @@ int bpf_kfunc_multi_st_ops_test_1_assoc(struct st_ops_args *args, struct bpf_pro
int bpf_kfunc_implicit_arg(int a, struct bpf_prog_aux *aux)
{
- if (aux && a > 0)
+ /* Verify the kernel injected the correct bpf_prog_aux pointer
+ * rather than leaving a stale register value. */
+ if (!aux || strncmp(aux->name, "test_kfunc", sizeof("test_kfunc") - 1))
+ return -EINVAL;
+ if (a > 0)
return a;
return -EINVAL;
}
--
2.54.0
^ permalink raw reply related [flat|nested] 56+ messages in thread
* Re: [PATCH bpf v6 2/2] selftests/bpf: strengthen bpf_kfunc_implicit_arg to verify aux injection
2026-06-09 12:52 ` [PATCH bpf v6 2/2] selftests/bpf: strengthen bpf_kfunc_implicit_arg to verify aux injection chenyuan_fl
@ 2026-06-09 12:59 ` sashiko-bot
0 siblings, 0 replies; 56+ messages in thread
From: sashiko-bot @ 2026-06-09 12:59 UTC (permalink / raw)
To: chenyuan_fl; +Cc: bpf
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] Dereferencing a potentially stale or uninitialized `aux` pointer in `strncmp` will trigger a kernel panic (Oops) instead of gracefully returning a test failure.
- [Low] The multi-line comment does not follow the required BPF subsystem style.
--
commit 9fe1fbc629675a37bbb1c47b4e9b422cd2edf717
Author: Yuan Chen <chenyuan@kylinos.cn>
selftests/bpf: strengthen bpf_kfunc_implicit_arg to verify aux injection
This commit updates bpf_kfunc_implicit_arg() to verify that the
KF_IMPLICIT_ARGS injection path correctly passes the bpf_prog_aux pointer.
It does this by comparing the name field of the aux pointer to ensure it
matches the expected prefix.
> diff --git a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
> --- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
> +++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
[ ... ]
> @@ -1827,7 +1827,11 @@ int bpf_kfunc_multi_st_ops_test_1_assoc(struct st_ops_args *args, struct bpf_pro
>
> int bpf_kfunc_implicit_arg(int a, struct bpf_prog_aux *aux)
> {
> - if (aux && a > 0)
> + /* Verify the kernel injected the correct bpf_prog_aux pointer
> + * rather than leaving a stale register value. */
[Severity: Low]
This isn't a bug, but does this multi-line comment follow the expected
BPF subsystem style? The preferred kernel style typically places the opening
/* on its own line before the text begins.
> + if (!aux || strncmp(aux->name, "test_kfunc", sizeof("test_kfunc") - 1))
[Severity: High]
If the verifier incorrectly skips injection and leaves a stale register
value in the aux pointer, as described in the commit message, wouldn't calling
strncmp() on aux->name trigger a kernel page fault?
Since the intention here is to catch a stale register containing an arbitrary
unmapped or uninitialized memory address, can we safely dereference it directly
in strncmp() without using a fault-safe memory access method?
> + return -EINVAL;
> + if (a > 0)
> return a;
> return -EINVAL;
> }
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260609125214.3096351-1-chenyuan_fl@163.com?part=2
^ permalink raw reply [flat|nested] 56+ messages in thread
* Re: [PATCH bpf v6 1/2] bpf: Fix kfunc implicit arg inject type detection to prevent invalid pointer deref
2026-06-09 12:52 ` [PATCH bpf v6 1/2] bpf: Fix kfunc implicit arg inject type detection to prevent invalid pointer deref chenyuan_fl
@ 2026-06-09 13:08 ` sashiko-bot
2026-06-09 13:32 ` bot+bpf-ci
1 sibling, 0 replies; 56+ messages in thread
From: sashiko-bot @ 2026-06-09 13: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 patch introduces a hard compilation error by referencing an undeclared variable `argno` and an undefined function `reg_arg_name()`.
--
commit 73eb7a1ebaf3a1d6b87a679580f3e6dc96801fe5
Author: Yuan Chen <chenyuan@kylinos.cn>
bpf: Fix kfunc implicit arg inject type detection to prevent invalid pointer deref
This commit fixes a vulnerability where an implicit bpf_prog_aux argument
could be bypassed due to BTF dedup failures, leading to arbitrary kernel
memory dereference. It ensures the verifier strictly validates and either
injects the correct pointer or explicitly rejects the program.
> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> --- a/kernel/bpf/verifier.c
> +++ b/kernel/bpf/verifier.c
> @@ -12082,9 +12082,28 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_
[ ... ]
> + if (is_kfunc_arg_implicit(meta, i)) {
> + /*
> + * This is an exhaustive list of the non-bpf_prog_aux
> + * implicit args kfuncs, which are handled
> + * post-verification by bpf_fixup_kfunc_call().
> + */
> + if (is_bpf_obj_new_kfunc(meta->func_id) ||
> + is_bpf_percpu_obj_new_kfunc(meta->func_id) ||
> + is_bpf_obj_drop_kfunc(meta->func_id) ||
> + is_bpf_percpu_obj_drop_kfunc(meta->func_id) ||
> + is_bpf_refcount_acquire_kfunc(meta->func_id) ||
> + 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]
Does this code compile? It looks like it introduces a build error by
referencing argno, which is not defined in this scope.
Also, is reg_arg_name() defined anywhere? It doesn't appear to be declared
or defined in the kernel tree.
> + return -EFAULT;
> + }
> +
> t = btf_type_skip_modifiers(btf, args[i].type, NULL);
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260609125214.3096351-1-chenyuan_fl@163.com?part=1
^ permalink raw reply [flat|nested] 56+ messages in thread
* Re: [PATCH bpf v6 1/2] bpf: Fix kfunc implicit arg inject type detection to prevent invalid pointer deref
2026-06-09 12:52 ` [PATCH bpf v6 1/2] bpf: Fix kfunc implicit arg inject type detection to prevent invalid pointer deref chenyuan_fl
2026-06-09 13:08 ` sashiko-bot
@ 2026-06-09 13:32 ` bot+bpf-ci
1 sibling, 0 replies; 56+ messages in thread
From: bot+bpf-ci @ 2026-06-09 13:32 UTC (permalink / raw)
To: chenyuan_fl, chenyuan_fl
Cc: andrii, ast, bot+bpf-ci, bpf, chenyuan, clm, daniel, eddyz87,
ihor.solodrai, jolsa, linux-kernel, martin.lau, martin.lau,
memxor, song, yonghong.song, ast, andrii, daniel, martin.lau,
eddyz87, yonghong.song, clm, ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 3116 bytes --]
> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> index 7fb88e1cd7c4..806c86388984 100644
> --- a/kernel/bpf/verifier.c
> +++ b/kernel/bpf/verifier.c
> @@ -12082,9 +12082,28 @@ 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)) {
> + /*
> + * This is an exhaustive list of the non-bpf_prog_aux
> + * implicit args kfuncs, which are handled
> + * post-verification by bpf_fixup_kfunc_call().
> + */
> + if (is_bpf_obj_new_kfunc(meta->func_id) ||
> + is_bpf_percpu_obj_new_kfunc(meta->func_id) ||
> + is_bpf_obj_drop_kfunc(meta->func_id) ||
> + is_bpf_percpu_obj_drop_kfunc(meta->func_id) ||
> + is_bpf_refcount_acquire_kfunc(meta->func_id) ||
> + 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;
> + }
> +
Does this code compile? The call to reg_arg_name(env, argno) references
two symbols that don't exist at this commit. The function reg_arg_name()
is not defined in the tree: searching kernel/bpf/verifier.c finds the
identifier only once at this call site, and a tree-wide search yields no
definition. The variable argno is also undeclared: check_kfunc_args()
declares 'u32 regno = i + 1, ref_id, type_size;' but has no argno local.
Both trigger build failures (implicit declaration of function and
undeclared variable). The intended register is likely regno, matching the
pattern used elsewhere in this function:
verbose(env, "R%d ...", regno);
Looking at the comment by Ihor Solodrai on Mon, 8 Jun 2026 17:54:36 -0700
(https://lore.kernel.org/bpf/eab4e479-2023-49b8-9915-1c8ef2356837@linux.dev/),
the list of allowed kfuncs is incomplete. It omits bpf_wq_set_callback,
bpf_stream_vprintk, bpf_stream_print_stack, bpf_task_work_schedule_signal,
and bpf_task_work_schedule_resume, all of which have KF_IMPLICIT_ARGS and
would trigger the new error path incorrectly.
Eduard Zingerman's response on Mon, 08 Jun 2026 23:20:36 -0700
(https://lore.kernel.org/bpf/0d63a8d68bf0778d6e931ed27892a5bcb151295d.camel@gmail.com/)
suggested an alternative fix: instead of enumerating kfuncs in the
verifier, check in bpf_fixup_kfunc_call() whether an implicit arg has the
KF_IMPLICIT_ARGS flag but wasn't filled by an earlier handler. This would
catch the BTF mismatch case without needing to maintain a list and without
breaking valid kfuncs.
The comment claims the list is exhaustive, but it's missing at least five
kfuncs. Should this use Eduard's suggested approach in bpf_fixup_kfunc_call()
instead?
---
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/27208267613
^ permalink raw reply [flat|nested] 56+ messages in thread
end of thread, other threads:[~2026-06-09 13:32 UTC | newest]
Thread overview: 56+ 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-09 0:46 ` Ihor Solodrai
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-08 14:26 ` [PATCH bpf v5 0/2] bpf: Fix kfunc implicit arg injection and add selftest chenyuan_fl
2026-06-08 14:26 ` [PATCH bpf v5 1/2] bpf: Fix kfunc implicit arg inject type detection to prevent invalid pointer deref chenyuan_fl
2026-06-08 14:47 ` sashiko-bot
2026-06-08 14:52 ` bot+bpf-ci
2026-06-08 17:28 ` Eduard Zingerman
2026-06-09 0:54 ` Ihor Solodrai
2026-06-09 1:00 ` Eduard Zingerman
2026-06-09 1:18 ` Ihor Solodrai
2026-06-09 6:20 ` Eduard Zingerman
2026-06-08 14:26 ` [PATCH bpf v5 2/2] selftests/bpf: strengthen bpf_kfunc_implicit_arg to verify aux injection chenyuan_fl
2026-06-08 14:53 ` sashiko-bot
2026-06-08 17:34 ` Eduard Zingerman
2026-06-09 0:59 ` Ihor Solodrai
2026-06-08 19:58 ` [PATCH bpf v5 0/2] bpf: Fix kfunc implicit arg injection and add selftest Alexei Starovoitov
2026-06-09 12:52 ` [PATCH bpf v6 " chenyuan_fl
2026-06-09 12:52 ` [PATCH bpf v6 1/2] bpf: Fix kfunc implicit arg inject type detection to prevent invalid pointer deref chenyuan_fl
2026-06-09 13:08 ` sashiko-bot
2026-06-09 13:32 ` bot+bpf-ci
2026-06-09 12:52 ` [PATCH bpf v6 2/2] selftests/bpf: strengthen bpf_kfunc_implicit_arg to verify aux injection chenyuan_fl
2026-06-09 12:59 ` sashiko-bot
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.