st_ops_assoc_in_timer_no_uref drops every reference to the struct_ops
map while a timer callback of its prog calls .test_1 through the
association, and checks that the callback ends up with no struct_ops.

The timer first fires 500ms later and .test_1 returns right away, so the
callback hardly ever runs .test_1 while the map is freed, and the test
can't catch a map that is freed under a running .test_1.

Add st_ops_assoc_in_timer_free, which starts the timer right away,
restarts it with no delay until the association is gone, and makes
.test_1 spin in bpf_loop() when the timer callback calls it. Share the
steps of st_ops_assoc_in_timer_no_uref with it through a helper.

st_ops_assoc_in_timer_free run in a loop on kernels of the same bpf-next
commit with KASAN enabled, x86_64 VM with 32 vCPUs, without and with
the previous fix:

              without the fix           with the fix
  outcome     Oops (int3) on run 1      100 of 100 passed, 0.28 s each

Signed-off-by: Qiliang Yuan <[email protected]>
---
 .../bpf/prog_tests/test_struct_ops_assoc.c         | 29 +++++++++++++++++++---
 .../bpf/progs/struct_ops_assoc_in_timer.c          | 16 ++++++++++++
 2 files changed, 41 insertions(+), 4 deletions(-)

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..987117091c676 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
@@ -136,7 +136,8 @@ static void test_st_ops_assoc_in_timer(void)
        struct_ops_assoc_in_timer__destroy(skel);
 }
 
-static void test_st_ops_assoc_in_timer_no_uref(void)
+static void run_st_ops_assoc_in_timer_no_uref(int timer_ns, int wait_map_free,
+                                             int spin_loops)
 {
        struct struct_ops_assoc_in_timer *skel = NULL;
        struct bpf_link *link;
@@ -157,10 +158,13 @@ static void test_st_ops_assoc_in_timer_no_uref(void)
        /*
         * Run .test_1 by calling kfunc 
bpf_kfunc_multi_st_ops_test_1_prog_arg() and checks
         * the return value. .test_1 will also schedule timer_cb that runs 
.test_1 again.
-        * timer_cb will run 500ms after syscall_prog runs, when the user space 
no longer
-        * holds a reference to st_ops_map.
+        * timer_cb will run timer_ns after syscall_prog runs and, with 
wait_map_free, every
+        * timer_ns after that until the map is freed. With 500ms, it first 
runs when the
+        * user space no longer holds a reference to st_ops_map.
         */
-       skel->bss->timer_ns = 500000000;
+       skel->bss->timer_ns = timer_ns;
+       skel->bss->wait_map_free = wait_map_free;
+       skel->bss->spin_loops = spin_loops;
        err = bpf_prog_test_run_opts(bpf_program__fd(skel->progs.syscall_prog), 
NULL);
        ASSERT_OK(err, "bpf_prog_test_run_opts");
 
@@ -178,6 +182,21 @@ static void test_st_ops_assoc_in_timer_no_uref(void)
        struct_ops_assoc_in_timer__destroy(skel);
 }
 
+static void test_st_ops_assoc_in_timer_no_uref(void)
+{
+       run_st_ops_assoc_in_timer_no_uref(500000000, 0, 0);
+}
+
+/*
+ * Keep calling .test_1 through the association from the timer callback, and
+ * stay in it for a while each time, while the map is being freed. The map
+ * must not be freed under a running .test_1.
+ */
+static void test_st_ops_assoc_in_timer_free(void)
+{
+       run_st_ops_assoc_in_timer_no_uref(0, 1, 1 << 20);
+}
+
 void test_struct_ops_assoc(void)
 {
        if (test__start_subtest("st_ops_assoc"))
@@ -188,4 +207,6 @@ void test_struct_ops_assoc(void)
                test_st_ops_assoc_in_timer();
        if (test__start_subtest("st_ops_assoc_in_timer_no_uref"))
                test_st_ops_assoc_in_timer_no_uref();
+       if (test__start_subtest("st_ops_assoc_in_timer_free"))
+               test_st_ops_assoc_in_timer_free();
 }
diff --git a/tools/testing/selftests/bpf/progs/struct_ops_assoc_in_timer.c 
b/tools/testing/selftests/bpf/progs/struct_ops_assoc_in_timer.c
index 0bed49e9f2170..47a44c8e8b114 100644
--- a/tools/testing/selftests/bpf/progs/struct_ops_assoc_in_timer.c
+++ b/tools/testing/selftests/bpf/progs/struct_ops_assoc_in_timer.c
@@ -25,6 +25,13 @@ int test_err;
 int timer_ns;
 int timer_test_1_ret;
 int timer_cb_run;
+int wait_map_free;
+int spin_loops;
+
+static int spin(u32 i, void *ctx)
+{
+       return 0;
+}
 
 __noinline static int timer_cb(void *map, int *key, struct bpf_timer *timer)
 {
@@ -34,6 +41,12 @@ __noinline static int timer_cb(void *map, int *key, struct 
bpf_timer *timer)
        timer_test_1_ret = bpf_kfunc_multi_st_ops_test_1_assoc(&args);
        recur--;
 
+       /* Try again later until the map is freed */
+       if (wait_map_free && timer_test_1_ret != -1) {
+               bpf_timer_start(timer, timer_ns, 0);
+               return 0;
+       }
+
        timer_cb_run++;
 
        return 0;
@@ -53,6 +66,9 @@ int BPF_PROG(test_1, struct st_ops_args *args)
                bpf_timer_init(timer, &array_map, 1);
                bpf_timer_set_callback(timer, timer_cb);
                bpf_timer_start(timer, timer_ns, 0);
+       } else if (spin_loops) {
+               /* Stay in the trampoline while the map may be freed */
+               bpf_loop(spin_loops, spin, NULL, 0);
        }
 
        return MAP_MAGIC;

-- 
2.43.0


Reply via email to