[PATCH bpf v4 2/2] selftests/bpf: Add regression test for kfunc implicit arg injection
From: chenyuan_fl
Date: Tue Jun 02 2026 - 05:40:05 EST
From: Yuan Chen <chenyuan@xxxxxxxxxx>
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@xxxxxxxxxx>
---
.../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