[PATCH bpf-next 2/2] selftests/bpf: Cover IA32 narrow kfunc returns
From: Yiyang Chen
Date: Thu Aug 13 2026 - 08:02:10 EST
Teach test_loader to select IA32-specific cases and add unsigned bool and
signed int kfunc-return tests. Each case consumes the high half of R0 after
the call and checks the required zero or sign extension.
Signed-off-by: Yiyang Chen <chenyy23@xxxxxxxxxxxxxxxxxxxxx>
---
tools/testing/selftests/bpf/prog_tests/verifier.c | 2 +
tools/testing/selftests/bpf/progs/bpf_misc.h | 1 +
.../selftests/bpf/progs/verifier_kfunc_return.c | 84 ++++++++++++++++++++++
tools/testing/selftests/bpf/test_loader.c | 13 ++--
4 files changed, 96 insertions(+), 4 deletions(-)
diff --git a/tools/testing/selftests/bpf/prog_tests/verifier.c b/tools/testing/selftests/bpf/prog_tests/verifier.c
index 8113fea7ba86c..4638e0a45db6e 100644
--- a/tools/testing/selftests/bpf/prog_tests/verifier.c
+++ b/tools/testing/selftests/bpf/prog_tests/verifier.c
@@ -127,6 +127,7 @@
#include "verifier_set_retval.skel.h"
#include "verifier_lsm.skel.h"
#include "verifier_jit_inline.skel.h"
+#include "verifier_kfunc_return.skel.h"
#include "irq.skel.h"
#include "verifier_ctx_ptr_param.skel.h"
#include "verifier_zext.skel.h"
@@ -289,6 +290,7 @@ void test_verifier_lsm(void) { RUN(verifier_lsm); }
void test_irq(void) { RUN(irq); }
void test_verifier_mtu(void) { RUN(verifier_mtu); }
void test_verifier_jit_inline(void) { RUN(verifier_jit_inline); }
+void test_verifier_kfunc_return(void) { RUN(verifier_kfunc_return); }
void test_verifier_ctx_ptr_param(void) { RUN(verifier_ctx_ptr_param); }
void test_verifier_zext(void) { RUN_TESTS(verifier_zext); }
diff --git a/tools/testing/selftests/bpf/progs/bpf_misc.h b/tools/testing/selftests/bpf/progs/bpf_misc.h
index 5eacf1b432521..1498da04597af 100644
--- a/tools/testing/selftests/bpf/progs/bpf_misc.h
+++ b/tools/testing/selftests/bpf/progs/bpf_misc.h
@@ -155,6 +155,7 @@
#define __btf_func_path(path) __test_tag("test_btf_func_path=" path)
#define __arch(arch) __test_tag("test_arch=" arch)
#define __arch_x86_64 __arch("X86_64")
+#define __arch_x86_32 __arch("X86_32")
#define __arch_arm64 __arch("ARM64")
#define __arch_riscv64 __arch("RISCV64")
#define __arch_s390x __arch("s390x")
diff --git a/tools/testing/selftests/bpf/progs/verifier_kfunc_return.c b/tools/testing/selftests/bpf/progs/verifier_kfunc_return.c
new file mode 100644
index 0000000000000..52a94daf03922
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/verifier_kfunc_return.c
@@ -0,0 +1,84 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#define BPF_NO_KFUNC_PROTOTYPES
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+#include "bpf_misc.h"
+
+extern int bpf_strcmp(const char *s1__ign, const char *s2__ign) __ksym;
+extern bool bpf_dynptr_is_null(const struct bpf_dynptr *p) __ksym;
+
+struct {
+ __uint(type, BPF_MAP_TYPE_ARRAY);
+ __uint(max_entries, 1);
+ __type(key, __u32);
+ __type(value, __u64);
+} kfunc_return_map SEC(".maps");
+
+static const char string_a[] = "a";
+static const char string_b[] = "b";
+
+void __kfunc_btf_root(void)
+{
+ struct bpf_dynptr ptr = {};
+
+ bpf_strcmp(string_a, string_b);
+ bpf_dynptr_is_null(&ptr);
+}
+
+SEC("syscall")
+__flag(BPF_F_SLEEPABLE)
+__success __retval(0)
+__arch_x86_32
+__naked void kfunc_bool_return_zero_extended(void)
+{
+ asm volatile (" \
+ r0 = 0; \
+ *(u32 *)(r10 - 4) = r0; \
+ r2 = r10; \
+ r2 += -4; \
+ r1 = %[kfunc_return_map] ll; \
+ call %[bpf_map_lookup_elem]; \
+ if r0 == 0 goto 1f; \
+ r1 = r0; \
+ r2 = 8; \
+ r3 = 0; \
+ r4 = r10; \
+ r4 += -24; \
+ call %[bpf_dynptr_from_mem]; \
+ if r0 != 0 goto 1f; \
+ r0 = 0x100000000 ll; \
+ r1 = r10; \
+ r1 += -24; \
+ call bpf_dynptr_is_null; \
+ r0 >>= 32; \
+ exit; \
+1: r0 = 2; \
+ exit; \
+" :
+ : __imm_addr(kfunc_return_map),
+ __imm(bpf_map_lookup_elem),
+ __imm(bpf_dynptr_from_mem)
+ : __clobber_all);
+}
+
+SEC("syscall")
+__flag(BPF_F_SLEEPABLE)
+__success __retval(-1)
+__arch_x86_32
+__naked void kfunc_int_return_sign_extended(void)
+{
+ asm volatile (" \
+ r0 = 0; \
+ r1 = %[string_a] ll; \
+ r2 = %[string_b] ll; \
+ call bpf_strcmp; \
+ r0 >>= 32; \
+ exit; \
+" :
+ : __imm_addr(string_a),
+ __imm_addr(string_b)
+ : __clobber_all);
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/test_loader.c b/tools/testing/selftests/bpf/test_loader.c
index 07807757b518d..8a9f4a31a1bb1 100644
--- a/tools/testing/selftests/bpf/test_loader.c
+++ b/tools/testing/selftests/bpf/test_loader.c
@@ -374,16 +374,19 @@ static const char **collect_decl_tags(struct btf *btf, int id, int *cnt)
enum arch {
ARCH_UNKNOWN = 0x1,
ARCH_X86_64 = 0x2,
- ARCH_ARM64 = 0x4,
- ARCH_RISCV64 = 0x8,
- ARCH_S390X = 0x10,
- ARCH_LOONGARCH = 0x20,
+ ARCH_X86_32 = 0x4,
+ ARCH_ARM64 = 0x8,
+ ARCH_RISCV64 = 0x10,
+ ARCH_S390X = 0x20,
+ ARCH_LOONGARCH = 0x40,
};
static int get_current_arch(void)
{
#if defined(__x86_64__)
return ARCH_X86_64;
+#elif defined(__i386__)
+ return ARCH_X86_32;
#elif defined(__aarch64__)
return ARCH_ARM64;
#elif defined(__riscv) && __riscv_xlen == 64
@@ -577,6 +580,8 @@ static int parse_test_spec(struct test_loader *tester,
} else if ((val = str_has_pfx(s, "test_arch="))) {
if (strcmp(val, "X86_64") == 0) {
arch = ARCH_X86_64;
+ } else if (strcmp(val, "X86_32") == 0) {
+ arch = ARCH_X86_32;
} else if (strcmp(val, "ARM64") == 0) {
arch = ARCH_ARM64;
} else if (strcmp(val, "RISCV64") == 0) {
--
2.43.0