]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
selftests/bpf: Test referenced kptr arguments of struct_ops programs
authorAmery Hung <amery.hung@bytedance.com>
Mon, 17 Feb 2025 19:06:38 +0000 (11:06 -0800)
committerAlexei Starovoitov <ast@kernel.org>
Tue, 18 Feb 2025 02:47:27 +0000 (18:47 -0800)
Test referenced kptr acquired through struct_ops argument tagged with
"__ref". The success case checks whether 1) a reference to the correct
type is acquired, and 2) the referenced kptr argument can be accessed in
multiple paths as long as it hasn't been released. In the fail cases,
we first confirm that a referenced kptr acquried through a struct_ops
argument is not allowed to be leaked. Then, we make sure this new
referenced kptr acquiring mechanism does not accidentally allow referenced
kptrs to flow into global subprograms through their arguments.

Signed-off-by: Amery Hung <amery.hung@bytedance.com>
Acked-by: Eduard Zingerman <eddyz87@gmail.com>
Acked-by: Martin KaFai Lau <martin.lau@kernel.org>
Link: https://lore.kernel.org/r/20250217190640.1748177-4-ameryhung@gmail.com
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
tools/testing/selftests/bpf/prog_tests/test_struct_ops_refcounted.c [new file with mode: 0644]
tools/testing/selftests/bpf/progs/struct_ops_refcounted.c [new file with mode: 0644]
tools/testing/selftests/bpf/progs/struct_ops_refcounted_fail__global_subprog.c [new file with mode: 0644]
tools/testing/selftests/bpf/progs/struct_ops_refcounted_fail__ref_leak.c [new file with mode: 0644]
tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
tools/testing/selftests/bpf/test_kmods/bpf_testmod.h

diff --git a/tools/testing/selftests/bpf/prog_tests/test_struct_ops_refcounted.c b/tools/testing/selftests/bpf/prog_tests/test_struct_ops_refcounted.c
new file mode 100644 (file)
index 0000000..e290a2f
--- /dev/null
@@ -0,0 +1,12 @@
+#include <test_progs.h>
+
+#include "struct_ops_refcounted.skel.h"
+#include "struct_ops_refcounted_fail__ref_leak.skel.h"
+#include "struct_ops_refcounted_fail__global_subprog.skel.h"
+
+void test_struct_ops_refcounted(void)
+{
+       RUN_TESTS(struct_ops_refcounted);
+       RUN_TESTS(struct_ops_refcounted_fail__ref_leak);
+       RUN_TESTS(struct_ops_refcounted_fail__global_subprog);
+}
diff --git a/tools/testing/selftests/bpf/progs/struct_ops_refcounted.c b/tools/testing/selftests/bpf/progs/struct_ops_refcounted.c
new file mode 100644 (file)
index 0000000..76dcb60
--- /dev/null
@@ -0,0 +1,31 @@
+#include <vmlinux.h>
+#include <bpf/bpf_tracing.h>
+#include "../test_kmods/bpf_testmod.h"
+#include "bpf_misc.h"
+
+char _license[] SEC("license") = "GPL";
+
+__attribute__((nomerge)) extern void bpf_task_release(struct task_struct *p) __ksym;
+
+/* This is a test BPF program that uses struct_ops to access a referenced
+ * kptr argument. This is a test for the verifier to ensure that it
+ * 1) recongnizes the task as a referenced object (i.e., ref_obj_id > 0), and
+ * 2) the same reference can be acquired from multiple paths as long as it
+ *    has not been released.
+ */
+SEC("struct_ops/test_refcounted")
+int BPF_PROG(refcounted, int dummy, struct task_struct *task)
+{
+       if (dummy == 1)
+               bpf_task_release(task);
+       else
+               bpf_task_release(task);
+       return 0;
+}
+
+SEC(".struct_ops.link")
+struct bpf_testmod_ops testmod_refcounted = {
+       .test_refcounted = (void *)refcounted,
+};
+
+
diff --git a/tools/testing/selftests/bpf/progs/struct_ops_refcounted_fail__global_subprog.c b/tools/testing/selftests/bpf/progs/struct_ops_refcounted_fail__global_subprog.c
new file mode 100644 (file)
index 0000000..ae074aa
--- /dev/null
@@ -0,0 +1,39 @@
+#include <vmlinux.h>
+#include <bpf/bpf_tracing.h>
+#include "../test_kmods/bpf_testmod.h"
+#include "bpf_misc.h"
+
+char _license[] SEC("license") = "GPL";
+
+extern void bpf_task_release(struct task_struct *p) __ksym;
+
+__noinline int subprog_release(__u64 *ctx __arg_ctx)
+{
+       struct task_struct *task = (struct task_struct *)ctx[1];
+       int dummy = (int)ctx[0];
+
+       bpf_task_release(task);
+
+       return dummy + 1;
+}
+
+/* Test that the verifier rejects a program that contains a global
+ * subprogram with referenced kptr arguments
+ */
+SEC("struct_ops/test_refcounted")
+__failure __log_level(2)
+__msg("Validating subprog_release() func#1...")
+__msg("invalid bpf_context access off=8. Reference may already be released")
+int refcounted_fail__global_subprog(unsigned long long *ctx)
+{
+       struct task_struct *task = (struct task_struct *)ctx[1];
+
+       bpf_task_release(task);
+
+       return subprog_release(ctx);
+}
+
+SEC(".struct_ops.link")
+struct bpf_testmod_ops testmod_ref_acquire = {
+       .test_refcounted = (void *)refcounted_fail__global_subprog,
+};
diff --git a/tools/testing/selftests/bpf/progs/struct_ops_refcounted_fail__ref_leak.c b/tools/testing/selftests/bpf/progs/struct_ops_refcounted_fail__ref_leak.c
new file mode 100644 (file)
index 0000000..e945b1a
--- /dev/null
@@ -0,0 +1,22 @@
+#include <vmlinux.h>
+#include <bpf/bpf_tracing.h>
+#include "../test_kmods/bpf_testmod.h"
+#include "bpf_misc.h"
+
+char _license[] SEC("license") = "GPL";
+
+/* Test that the verifier rejects a program that acquires a referenced
+ * kptr through context without releasing the reference
+ */
+SEC("struct_ops/test_refcounted")
+__failure __msg("Unreleased reference id=1 alloc_insn=0")
+int BPF_PROG(refcounted_fail__ref_leak, int dummy,
+            struct task_struct *task)
+{
+       return 0;
+}
+
+SEC(".struct_ops.link")
+struct bpf_testmod_ops testmod_ref_acquire = {
+       .test_refcounted = (void *)refcounted_fail__ref_leak,
+};
index cc9dde507aba557ab36bfde5cd793cbe6739edcc..802cbd87103516cc6222d7c1199510d64b4c53e4 100644 (file)
@@ -1176,10 +1176,17 @@ static int bpf_testmod_ops__test_maybe_null(int dummy,
        return 0;
 }
 
+static int bpf_testmod_ops__test_refcounted(int dummy,
+                                           struct task_struct *task__ref)
+{
+       return 0;
+}
+
 static struct bpf_testmod_ops __bpf_testmod_ops = {
        .test_1 = bpf_testmod_test_1,
        .test_2 = bpf_testmod_test_2,
        .test_maybe_null = bpf_testmod_ops__test_maybe_null,
+       .test_refcounted = bpf_testmod_ops__test_refcounted,
 };
 
 struct bpf_struct_ops bpf_bpf_testmod_ops = {
index 356803d1c10e81746b516173f08fb9b3bcb09f26..c57b2f9dab104f6198cbc34c94422c6a2873da19 100644 (file)
@@ -36,6 +36,8 @@ struct bpf_testmod_ops {
        /* Used to test nullable arguments. */
        int (*test_maybe_null)(int dummy, struct task_struct *task);
        int (*unsupported_ops)(void);
+       /* Used to test ref_acquired arguments. */
+       int (*test_refcounted)(int dummy, struct task_struct *task);
 
        /* The following fields are used to test shadow copies. */
        char onebyte;