BPF List
 help / color / mirror / Atom feed
From: Kumar Kartikeya Dwivedi <memxor@gmail.com>
To: bpf@vger.kernel.org
Cc: Alexei Starovoitov <ast@kernel.org>,
	Andrii Nakryiko <andrii@kernel.org>,
	Daniel Borkmann <daniel@iogearbox.net>,
	Eduard Zingerman <eddyz87@gmail.com>,
	Emil Tsalapatis <emil@etsalapatis.com>,
	Ihor Solodrai <ihor.solodrai@linux.dev>,
	Tejun Heo <tj@kernel.org>,
	kkd@meta.com, kernel-team@meta.com
Subject: [PATCH bpf-next v4 08/16] selftests/bpf: Test __nullable pointer members
Date: Mon, 19 Oct 2026 05:23:48 +0200	[thread overview]
Message-ID: <20261019032401.2302823-9-memxor@gmail.com> (raw)
In-Reply-To: <20261019032401.2302823-1-memxor@gmail.com>

Add a struct_ops member to bpf_testmod whose argument struct carries two
task pointers, one plain and one __nullable, both made trusted through a
__safe_trusted alias of the struct in the module BTF, and a kfunc that
calls the member once with the nullable pointer unset and once set.

The callback acquires the plain pointer as is and the nullable one only
after a check, driven through the kfunc from a syscall program. The
failure case passes the nullable pointer to bpf_task_acquire() unchecked
and is rejected as possibly NULL.

More members cover the other kinds of pointer: a per-CPU one, which
only the per-CPU helpers consume and which keeps the flag, the struct
itself through a plain pointer and through an untrusted one out of a
__kptr_untrusted map value that is never set, and a pointer to a scalar,
which are probed and lose it. The callback uses each after a check; a
failure test passes the per-CPU pointer unchecked, and three programs
read the probed ones unchecked and load. bpf_testmod_ops3 gets the base
helpers for the per-CPU helpers.

Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
---
 .../bpf/prog_tests/test_struct_ops_nullable.c | 62 ++++++++++++++
 .../selftests/bpf/progs/struct_ops_nullable.c | 82 +++++++++++++++++++
 .../bpf/progs/struct_ops_nullable_fail.c      | 28 +++++++
 .../progs/struct_ops_nullable_fail__percpu.c  | 28 +++++++
 .../progs/struct_ops_nullable_probed__plain.c | 26 ++++++
 .../struct_ops_nullable_probed__scalar.c      | 26 ++++++
 .../struct_ops_nullable_probed__untrusted.c   | 45 ++++++++++
 .../selftests/bpf/test_kmods/bpf_testmod.c    | 43 ++++++++++
 .../selftests/bpf/test_kmods/bpf_testmod.h    | 34 ++++++++
 .../bpf/test_kmods/bpf_testmod_kfunc.h        |  1 +
 tools/testing/selftests/bpf/testing_helpers.c | 45 ++++++++++
 tools/testing/selftests/bpf/testing_helpers.h |  7 ++
 12 files changed, 427 insertions(+)
 create mode 100644 tools/testing/selftests/bpf/prog_tests/test_struct_ops_nullable.c
 create mode 100644 tools/testing/selftests/bpf/progs/struct_ops_nullable.c
 create mode 100644 tools/testing/selftests/bpf/progs/struct_ops_nullable_fail.c
 create mode 100644 tools/testing/selftests/bpf/progs/struct_ops_nullable_fail__percpu.c
 create mode 100644 tools/testing/selftests/bpf/progs/struct_ops_nullable_probed__plain.c
 create mode 100644 tools/testing/selftests/bpf/progs/struct_ops_nullable_probed__scalar.c
 create mode 100644 tools/testing/selftests/bpf/progs/struct_ops_nullable_probed__untrusted.c

diff --git a/tools/testing/selftests/bpf/prog_tests/test_struct_ops_nullable.c b/tools/testing/selftests/bpf/prog_tests/test_struct_ops_nullable.c
new file mode 100644
index 000000000000..028f070f2e5f
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/test_struct_ops_nullable.c
@@ -0,0 +1,62 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
+#include <test_progs.h>
+
+#include "struct_ops_nullable.skel.h"
+#include "struct_ops_nullable_fail.skel.h"
+#include "struct_ops_nullable_fail__percpu.skel.h"
+#include "struct_ops_nullable_probed__plain.skel.h"
+#include "struct_ops_nullable_probed__scalar.skel.h"
+#include "struct_ops_nullable_probed__untrusted.skel.h"
+
+/*
+ * Attach a callback that gets a trusted and a trusted __nullable task pointer
+ * through its argument struct and drive it through the
+ * bpf_testmod_ops3_call_test_nullable() kfunc.
+ */
+static void nullable_member(void)
+{
+	LIBBPF_OPTS(bpf_test_run_opts, topts);
+	struct struct_ops_nullable *skel;
+	struct bpf_link *link;
+	int err;
+
+	skel = struct_ops_nullable__open_and_load();
+	if (!ASSERT_OK_PTR(skel, "struct_ops_nullable__open_and_load"))
+		return;
+
+	link = bpf_map__attach_struct_ops(skel->maps.testmod_nullable);
+	if (!ASSERT_OK_PTR(link, "attach_struct_ops"))
+		goto out;
+
+	err = bpf_prog_test_run_opts(bpf_program__fd(skel->progs.trigger), &topts);
+	ASSERT_OK(err, "test_run");
+	ASSERT_EQ(topts.retval, 0, "trigger_retval");
+
+	bpf_link__destroy(link);
+out:
+	struct_ops_nullable__destroy(skel);
+}
+
+/* Serialized because it attaches the singleton bpf_testmod_ops3. */
+void serial_test_struct_ops_nullable(void)
+{
+	int has_tag;
+
+	/* the tag reaches the module BTF only from a toolchain with btf_type_tag support */
+	has_tag = testmod_member_has_type_tag("bpf_testmod_nullable_args", "task_or_null", "nullable");
+	if (!ASSERT_GE(has_tag, 0, "testmod_btf"))
+		return;
+	if (!has_tag) {
+		printf("%s:SKIP: no nullable type tag in the bpf_testmod BTF\n", __func__);
+		test__skip();
+		return;
+	}
+	if (test__start_subtest("nullable_member"))
+		nullable_member();
+	RUN_TESTS(struct_ops_nullable_fail);
+	RUN_TESTS(struct_ops_nullable_fail__percpu);
+	RUN_TESTS(struct_ops_nullable_probed__plain);
+	RUN_TESTS(struct_ops_nullable_probed__scalar);
+	RUN_TESTS(struct_ops_nullable_probed__untrusted);
+}
diff --git a/tools/testing/selftests/bpf/progs/struct_ops_nullable.c b/tools/testing/selftests/bpf/progs/struct_ops_nullable.c
new file mode 100644
index 000000000000..929969e1e012
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/struct_ops_nullable.c
@@ -0,0 +1,82 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
+
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+#include "../test_kmods/bpf_testmod.h"
+#include "../test_kmods/bpf_testmod_kfunc.h"
+
+char _license[] SEC("license") = "GPL";
+
+/* an untrusted pointer to the argument struct, never set, for the verifier */
+struct untrusted_val {
+	struct bpf_testmod_nullable_args __kptr_untrusted *args;
+};
+
+struct {
+	__uint(type, BPF_MAP_TYPE_ARRAY);
+	__uint(max_entries, 1);
+	__type(key, int);
+	__type(value, struct untrusted_val);
+} untrusted_map SEC(".maps");
+
+SEC("struct_ops/test_nullable")
+int BPF_PROG(test_nullable_cb, struct bpf_testmod_nullable_args *args)
+{
+	struct bpf_testmod_nullable_args *u;
+	struct untrusted_val *v;
+	struct task_struct *t;
+	int ret = 0, *i, zero = 0;
+
+	/* trusted, so good for a kfunc as is */
+	t = bpf_task_acquire(args->task);
+	if (!t)
+		return -1;
+	bpf_task_release(t);
+
+	/* trusted but __nullable, so a check has to come first */
+	if (args->task_or_null) {
+		t = bpf_task_acquire(args->task_or_null);
+		if (!t)
+			return -1;
+		bpf_task_release(t);
+		ret = 1;
+	}
+	/* per-CPU and __nullable: a check has to come first, per_cpu_ptr() has none */
+	if (args->pcpu_or_null) {
+		struct bpf_testmod_pcpu *v = bpf_this_cpu_ptr(args->pcpu_or_null);
+
+		if (v->val != 0x5ca1ab1e)
+			return -1;
+	}
+	/* through a plain pointer the loads are probed, __nullable still asks for a check */
+	t = args->self->task_or_null;
+	if (t && t->pid != args->task->pid)
+		return -1;
+	/* a pointer to a scalar walks as untrusted memory, __nullable applies all the same */
+	i = args->int_or_null;
+	if (i && *i != 7)
+		return -1;
+	/* through an untrusted pointer the member is probed too, and stays nullable */
+	v = bpf_map_lookup_elem(&untrusted_map, &zero);
+	u = v ? v->args : NULL;
+	if (u) {
+		t = u->task_or_null;
+		if (t && t->pid != args->task->pid)
+			return -1;
+	}
+	return ret;
+}
+
+SEC(".struct_ops.link")
+struct bpf_testmod_ops3 testmod_nullable = {
+	.test_nullable = (void *)test_nullable_cb,
+};
+
+SEC("syscall")
+int trigger(void *ctx)
+{
+	/* one call without task_or_null, one with */
+	return bpf_testmod_ops3_call_test_nullable() != 2;
+}
diff --git a/tools/testing/selftests/bpf/progs/struct_ops_nullable_fail.c b/tools/testing/selftests/bpf/progs/struct_ops_nullable_fail.c
new file mode 100644
index 000000000000..c15b3a8c8d39
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/struct_ops_nullable_fail.c
@@ -0,0 +1,28 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
+
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+#include "bpf_misc.h"
+#include "../test_kmods/bpf_testmod.h"
+
+char _license[] SEC("license") = "GPL";
+
+/* The member is trusted, but __nullable, so it needs a check before the kfunc. */
+SEC("struct_ops/test_nullable")
+__failure __msg("Possibly NULL pointer passed to trusted")
+int BPF_PROG(unchecked_nullable, struct bpf_testmod_nullable_args *args)
+{
+	struct task_struct *t;
+
+	t = bpf_task_acquire(args->task_or_null);
+	if (t)
+		bpf_task_release(t);
+	return 0;
+}
+
+SEC(".struct_ops.link")
+struct bpf_testmod_ops3 testmod_nullable_fail = {
+	.test_nullable = (void *)unchecked_nullable,
+};
diff --git a/tools/testing/selftests/bpf/progs/struct_ops_nullable_fail__percpu.c b/tools/testing/selftests/bpf/progs/struct_ops_nullable_fail__percpu.c
new file mode 100644
index 000000000000..652015189162
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/struct_ops_nullable_fail__percpu.c
@@ -0,0 +1,28 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
+
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+#include "bpf_misc.h"
+#include "../test_kmods/bpf_testmod.h"
+
+char _license[] SEC("license") = "GPL";
+
+/*
+ * The member is per-CPU and __nullable: per_cpu_ptr() shifts NULL like any
+ * other pointer, so a check has to come before the helper.
+ */
+SEC("struct_ops/test_nullable")
+__failure __msg("Possibly NULL pointer passed to trusted R1")
+int BPF_PROG(unchecked_percpu, struct bpf_testmod_nullable_args *args)
+{
+	struct bpf_testmod_pcpu *v = bpf_this_cpu_ptr(args->pcpu_or_null);
+
+	return v->val;
+}
+
+SEC(".struct_ops.link")
+struct bpf_testmod_ops3 testmod_nullable_fail = {
+	.test_nullable = (void *)unchecked_percpu,
+};
diff --git a/tools/testing/selftests/bpf/progs/struct_ops_nullable_probed__plain.c b/tools/testing/selftests/bpf/progs/struct_ops_nullable_probed__plain.c
new file mode 100644
index 000000000000..c45f6d34f7d2
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/struct_ops_nullable_probed__plain.c
@@ -0,0 +1,26 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
+
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+#include "bpf_misc.h"
+#include "../test_kmods/bpf_testmod.h"
+
+char _license[] SEC("license") = "GPL";
+
+/*
+ * Through a plain pointer the member is probed, which tolerates NULL, so the
+ * tag asks for no check there, as the BTF_TYPE_SAFE_*_OR_NULL lists never did.
+ */
+SEC("struct_ops/test_nullable")
+__success
+int BPF_PROG(unchecked_plain, struct bpf_testmod_nullable_args *args)
+{
+	return args->self->task_or_null->pid;
+}
+
+SEC(".struct_ops.link")
+struct bpf_testmod_ops3 testmod_nullable_probed = {
+	.test_nullable = (void *)unchecked_plain,
+};
diff --git a/tools/testing/selftests/bpf/progs/struct_ops_nullable_probed__scalar.c b/tools/testing/selftests/bpf/progs/struct_ops_nullable_probed__scalar.c
new file mode 100644
index 000000000000..ff768746b0da
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/struct_ops_nullable_probed__scalar.c
@@ -0,0 +1,26 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
+
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+#include "bpf_misc.h"
+#include "../test_kmods/bpf_testmod.h"
+
+char _license[] SEC("license") = "GPL";
+
+/*
+ * A pointer to a scalar walks as untrusted memory, which is probed, so the
+ * tag asks for no check there.
+ */
+SEC("struct_ops/test_nullable")
+__success
+int BPF_PROG(unchecked_scalar, struct bpf_testmod_nullable_args *args)
+{
+	return *args->int_or_null;
+}
+
+SEC(".struct_ops.link")
+struct bpf_testmod_ops3 testmod_nullable_probed = {
+	.test_nullable = (void *)unchecked_scalar,
+};
diff --git a/tools/testing/selftests/bpf/progs/struct_ops_nullable_probed__untrusted.c b/tools/testing/selftests/bpf/progs/struct_ops_nullable_probed__untrusted.c
new file mode 100644
index 000000000000..961999139841
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/struct_ops_nullable_probed__untrusted.c
@@ -0,0 +1,45 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
+
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+#include "bpf_misc.h"
+#include "../test_kmods/bpf_testmod.h"
+
+char _license[] SEC("license") = "GPL";
+
+struct untrusted_val {
+	struct bpf_testmod_nullable_args __kptr_untrusted *args;
+};
+
+struct {
+	__uint(type, BPF_MAP_TYPE_ARRAY);
+	__uint(max_entries, 1);
+	__type(key, int);
+	__type(value, struct untrusted_val);
+} untrusted_map SEC(".maps");
+
+/*
+ * Through an untrusted pointer the member is probed, which tolerates NULL, so
+ * the tag asks for no check there.
+ */
+SEC("struct_ops/test_nullable")
+__success
+int BPF_PROG(unchecked_untrusted, struct bpf_testmod_nullable_args *args)
+{
+	struct bpf_testmod_nullable_args *u;
+	struct untrusted_val *v;
+	int zero = 0;
+
+	v = bpf_map_lookup_elem(&untrusted_map, &zero);
+	u = v ? v->args : NULL;
+	if (!u)
+		return 0;
+	return u->task_or_null->pid;
+}
+
+SEC(".struct_ops.link")
+struct bpf_testmod_ops3 testmod_nullable_probed = {
+	.test_nullable = (void *)unchecked_untrusted,
+};
diff --git a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
index 613abd27be5d..37d5824810d1 100644
--- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
+++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
@@ -506,6 +506,11 @@ static int bpf_testmod_ops3__test_arena_multislot(struct bpf_testmod_arena_pair
 	return 0;
 }
 
+static int bpf_testmod_ops3__test_nullable(struct bpf_testmod_nullable_args *args)
+{
+	return 0;
+}
+
 static struct bpf_testmod_ops3 __bpf_testmod_ops3 = {
 	.test_1 = bpf_testmod_test_3,
 	.test_2 = bpf_testmod_test_4,
@@ -513,6 +518,7 @@ static struct bpf_testmod_ops3 __bpf_testmod_ops3 = {
 	.test_arena_nullable = bpf_testmod_ops3__test_arena_nullable,
 	.test_arena_stack = bpf_testmod_ops3__test_arena_stack,
 	.test_arena_multislot = bpf_testmod_ops3__test_arena_multislot,
+	.test_nullable = bpf_testmod_ops3__test_nullable,
 };
 
 static void bpf_testmod_test_struct_ops3(void)
@@ -553,6 +559,41 @@ __bpf_kfunc int bpf_testmod_ops3_call_test_arena_multislot(u64 *ptr__arena)
 	return st_ops3->test_arena_multislot(p, ptr__arena);
 }
 
+/*
+ * The members of struct bpf_testmod_nullable_args are trusted the way the
+ * verifier's BTF_TYPE_SAFE_TRUSTED() lists vmlinux members: through a struct
+ * of the same name with the __safe_trusted suffix in the BTF of the module.
+ */
+struct bpf_testmod_nullable_args__safe_trusted {
+	struct task_struct *task;
+	struct task_struct *task_or_null;
+};
+
+static DEFINE_PER_CPU(struct bpf_testmod_pcpu, bpf_testmod_nullable_pcpu);
+
+/*
+ * Call test_nullable() with the __nullable members unset, then set. The
+ * callback returns 1 when it found a task in @task_or_null, so the expected
+ * result is 2.
+ */
+__bpf_kfunc int bpf_testmod_ops3_call_test_nullable(void)
+{
+	struct bpf_testmod_nullable_args args = { .task = current };
+	int seven = 7, ret;
+
+	BTF_TYPE_EMIT(struct bpf_testmod_nullable_args__safe_trusted);
+
+	if (!st_ops3 || !st_ops3->test_nullable)
+		return -EOPNOTSUPP;
+	args.self = &args;
+	ret = st_ops3->test_nullable(&args);
+	args.task_or_null = current;
+	this_cpu_write(bpf_testmod_nullable_pcpu.val, 0x5ca1ab1e);
+	args.pcpu_or_null = &bpf_testmod_nullable_pcpu;
+	args.int_or_null = &seven;
+	return ret | st_ops3->test_nullable(&args) << 1;
+}
+
 struct bpf_testmod_btf_type_tag_1 {
 	int a;
 };
@@ -980,6 +1021,7 @@ BTF_ID_FLAGS(func, bpf_testmod_ops3_call_test_arena)
 BTF_ID_FLAGS(func, bpf_testmod_ops3_call_test_arena_nullable)
 BTF_ID_FLAGS(func, bpf_testmod_ops3_call_test_arena_stack)
 BTF_ID_FLAGS(func, bpf_testmod_ops3_call_test_arena_multislot)
+BTF_ID_FLAGS(func, bpf_testmod_ops3_call_test_nullable)
 BTF_ID_FLAGS(func, bpf_kfunc_get_default_trusted_ptr_test);
 BTF_ID_FLAGS(func, bpf_kfunc_put_default_trusted_ptr_test);
 BTF_ID_FLAGS(func, bpf_kfunc_kasan_poison)
@@ -1956,6 +1998,7 @@ static const struct bpf_verifier_ops bpf_testmod_verifier_ops = {
 };
 
 static const struct bpf_verifier_ops bpf_testmod_verifier_ops3 = {
+	.get_func_proto	 = bpf_base_func_proto,
 	.is_valid_access = bpf_testmod_ops_is_valid_access,
 };
 
diff --git a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.h b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.h
index 2b12f81bf748..1a67564a6b8d 100644
--- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.h
+++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.h
@@ -114,6 +114,38 @@ struct bpf_testmod_arena_pair {
 	u64 b;
 };
 
+/*
+ * The qualifiers are BTF type tags for the verifier. The module gets them
+ * from bpf_compiler_types.h, user space and BPF programs need them empty.
+ */
+#ifdef __KERNEL__
+#include <linux/bpf_compiler_types.h>
+#endif
+#ifndef __nullable
+#define __nullable
+#endif
+#ifndef __percpu
+#define __percpu
+#endif
+
+struct bpf_testmod_pcpu {
+	int val;
+};
+
+/* Arguments of test_nullable(), trusted through the __safe_trusted alias in the module */
+struct bpf_testmod_nullable_args {
+	/* never NULL */
+	struct task_struct *task;
+	/* NULL on the first call, set on the second */
+	struct task_struct __nullable *task_or_null;
+	/* NULL on the first call, a per-CPU variable on the second */
+	struct bpf_testmod_pcpu __percpu __nullable *pcpu_or_null;
+	/* NULL on the first call, set on the second */
+	int __nullable *int_or_null;
+	/* this struct again, through a plain pointer, which the verifier probes */
+	struct bpf_testmod_nullable_args *self;
+};
+
 struct bpf_testmod_ops3 {
 	int (*test_1)(void);
 	int (*test_2)(void);
@@ -125,6 +157,8 @@ struct bpf_testmod_ops3 {
 				u64 g, u64 h, u64 *ptr);
 	/* a multi-slot leading arg, so @ptr is not at the slot its arg index suggests */
 	int (*test_arena_multislot)(struct bpf_testmod_arena_pair p, u64 *ptr);
+	/* Used to test __nullable pointer members. */
+	int (*test_nullable)(struct bpf_testmod_nullable_args *args);
 };
 
 struct st_ops_args {
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 8f2f8b8a26e3..1e7b1f1e5148 100644
--- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod_kfunc.h
+++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod_kfunc.h
@@ -224,6 +224,7 @@ int bpf_testmod_ops3_call_test_arena(__u64 *ptr__arena) __ksym;
 int bpf_testmod_ops3_call_test_arena_nullable(__u64 *ptr__arena__nullable) __ksym;
 int bpf_testmod_ops3_call_test_arena_stack(__u64 *ptr__arena) __ksym;
 int bpf_testmod_ops3_call_test_arena_multislot(__u64 *ptr__arena) __ksym;
+int bpf_testmod_ops3_call_test_nullable(void) __ksym;
 
 __u64 bpf_kfunc_call_test1(struct sock *sk, __u32 a, __u64 b,
 				__u32 c, __u64 d) __ksym;
diff --git a/tools/testing/selftests/bpf/testing_helpers.c b/tools/testing/selftests/bpf/testing_helpers.c
index 202c40e536b2..1e3e31ac36eb 100644
--- a/tools/testing/selftests/bpf/testing_helpers.c
+++ b/tools/testing/selftests/bpf/testing_helpers.c
@@ -8,6 +8,7 @@
 #include <sys/mman.h>
 #include <alloca.h>
 #include <bpf/bpf.h>
+#include <bpf/btf.h>
 #include <bpf/libbpf.h>
 #include "disasm.h"
 #include "test_progs.h"
@@ -644,3 +645,47 @@ int compare_text_to_expected(const char *actual, const char *expected)
 	}
 	return -1;
 }
+
+int testmod_member_has_type_tag(const char *struct_name, const char *member, const char *tag)
+{
+	struct btf *vmlinux_btf, *module_btf = NULL;
+	const struct btf_member *m;
+	const struct btf_type *t;
+	int err = 0, id, i;
+
+	vmlinux_btf = btf__load_vmlinux_btf();
+	if (!vmlinux_btf)
+		return -errno;
+	module_btf = btf__load_module_btf("bpf_testmod", vmlinux_btf);
+	if (!module_btf) {
+		err = -errno;
+		goto out;
+	}
+	id = btf__find_by_name_kind_own(module_btf, struct_name, BTF_KIND_STRUCT);
+	if (id < 0) {
+		err = id;
+		goto out;
+	}
+	t = btf__type_by_id(module_btf, id);
+	err = -ENOENT;
+	for (i = 0, m = btf_members(t); i < btf_vlen(t); i++, m++) {
+		if (strcmp(btf__name_by_offset(module_btf, m->name_off), member))
+			continue;
+		err = 0;
+		/* the tag sits on the pointee, behind the pointer and other modifiers */
+		for (id = m->type; id; id = t->type) {
+			t = btf__type_by_id(module_btf, id);
+			if (btf_is_type_tag(t) && !strcmp(btf__name_by_offset(module_btf, t->name_off), tag)) {
+				err = 1;
+				break;
+			}
+			if (!btf_is_ptr(t) && !btf_is_mod(t) && !btf_is_typedef(t))
+				break;
+		}
+		break;
+	}
+out:
+	btf__free(module_btf);
+	btf__free(vmlinux_btf);
+	return err;
+}
diff --git a/tools/testing/selftests/bpf/testing_helpers.h b/tools/testing/selftests/bpf/testing_helpers.h
index d5083fb92ccd..36a75158495b 100644
--- a/tools/testing/selftests/bpf/testing_helpers.h
+++ b/tools/testing/selftests/bpf/testing_helpers.h
@@ -67,4 +67,11 @@ int stack_mprotect(void);
 /* Runs diff(1) on mismatch */
 int compare_text_to_expected(const char *actual, const char *expected);
 
+/*
+ * Whether @member of struct @struct_name in the BTF of bpf_testmod carries
+ * the BTF type tag @tag: 1 if so, 0 if not, negative on a BTF error or when
+ * the struct or the member is missing.
+ */
+int testmod_member_has_type_tag(const char *struct_name, const char *member, const char *tag);
+
 #endif /* __TESTING_HELPERS_H */
-- 
2.53.0-Meta


  parent reply	other threads:[~2026-10-19  3:24 UTC|newest]

Thread overview: 19+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-19  3:23 [PATCH bpf-next v4 00/16] Fix some arena paper cuts Kumar Kartikeya Dwivedi
2026-10-19  3:23 ` [PATCH bpf-next v4 01/16] bpf: Check exclusive maps once the program digest is final Kumar Kartikeya Dwivedi
2026-10-19  3:46   ` sashiko-bot
2026-10-19  3:23 ` [PATCH bpf-next v4 02/16] selftests/bpf: Bind an exclusive map through a continuous fd_array Kumar Kartikeya Dwivedi
2026-10-19  3:23 ` [PATCH bpf-next v4 03/16] libbpf: Keep the fd_array fill index out of bpf_program__clone() Kumar Kartikeya Dwivedi
2026-10-19  3:23 ` [PATCH bpf-next v4 04/16] selftests/bpf: Clone a program that calls a module kfunc Kumar Kartikeya Dwivedi
2026-10-19  3:31   ` sashiko-bot
2026-10-19  3:23 ` [PATCH bpf-next v4 05/16] bpf: Add the __arena and __nullable BTF type tags Kumar Kartikeya Dwivedi
2026-10-19  3:23 ` [PATCH bpf-next v4 06/16] bpf: Recognize __nullable on pointer members of kernel structs Kumar Kartikeya Dwivedi
2026-10-19  3:23 ` [PATCH bpf-next v4 07/16] bpf: Turn loads of __arena pointer members into arena pointers Kumar Kartikeya Dwivedi
2026-10-19  3:23 ` Kumar Kartikeya Dwivedi [this message]
2026-10-19  3:23 ` [PATCH bpf-next v4 09/16] selftests/bpf: Test __arena pointer members Kumar Kartikeya Dwivedi
2026-10-19  3:23 ` [PATCH bpf-next v4 10/16] selftests/bpf: Move the no-arena tests out of objects with an arena map Kumar Kartikeya Dwivedi
2026-10-19  3:23 ` [PATCH bpf-next v4 11/16] libbpf: Bind the arena of an object to every program in it Kumar Kartikeya Dwivedi
2026-10-19  3:23 ` [PATCH bpf-next v4 12/16] selftests/bpf: Test arena binding by libbpf Kumar Kartikeya Dwivedi
2026-10-19  3:23 ` [PATCH bpf-next v4 13/16] bpf: Set up the arena base only in functions that use it Kumar Kartikeya Dwivedi
2026-10-19  3:23 ` [PATCH bpf-next v4 14/16] libbpf: Render the arena type tag with its address space Kumar Kartikeya Dwivedi
2026-10-19  3:23 ` [PATCH bpf-next v4 15/16] selftests/bpf: Test the address space rendering of the arena type tag Kumar Kartikeya Dwivedi
2026-10-19  3:23 ` [PATCH bpf-next v4 16/16] selftests/bpf: Test an arena kfunc argument passed from a subprogram Kumar Kartikeya Dwivedi

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261019032401.2302823-9-memxor@gmail.com \
    --to=memxor@gmail.com \
    --cc=andrii@kernel.org \
    --cc=ast@kernel.org \
    --cc=bpf@vger.kernel.org \
    --cc=daniel@iogearbox.net \
    --cc=eddyz87@gmail.com \
    --cc=emil@etsalapatis.com \
    --cc=ihor.solodrai@linux.dev \
    --cc=kernel-team@meta.com \
    --cc=kkd@meta.com \
    --cc=tj@kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox