* [PATCH bpf-next v4 0/4] bpf: Fix a struct_ops use-after-free behind a flaky assoc_in_timer test
@ 2026-10-16 2:59 Qiliang Yuan
2026-10-16 2:59 ` [PATCH bpf-next v4 1/4] bpf: Protect the struct_ops association with a spinlock Qiliang Yuan
` (3 more replies)
0 siblings, 4 replies; 5+ messages in thread
From: Qiliang Yuan @ 2026-10-16 2:59 UTC (permalink / raw)
To: Andrii Nakryiko, Eduard Zingerman, Ihor Solodrai,
Alexei Starovoitov, Daniel Borkmann, Kumar Kartikeya Dwivedi,
Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa,
Emil Tsalapatis, Shuah Khan, Amery Hung
Cc: bpf, linux-kselftest, linux-kernel, Qiliang Yuan
st_ops_assoc_in_timer_no_uref drops every reference to a struct_ops map
and expects a timer callback of its prog, 500ms later, to find no
struct_ops associated anymore. It fails now and then while test_progs
runs other tests in parallel.
Since commit 5db69b0fbdc8 ("bpf: Make struct_ops tasks_rcu grace period
optional"), a struct_ops map clears the association only after the RCU
grace period of bpf_map_put(), so nothing waits for a prog that reads
the association in between. A timer callback can then return into a
freed trampoline, and the kernel hits an int3 Oops.
Patch 1 lets the association be cleared in any context, and patch 2
clears it in map_free_pre_rcu(), so that the grace period bpf_map_put()
already waits for follows it. Patch 3 adds a test that catches a map
freed under a running .test_1. Patch 4 has the timer callback of
st_ops_assoc_in_timer_no_uref retry until the association is gone.
Signed-off-by: Qiliang Yuan <odys.yuan@gmail.com>
---
V3 -> V4:
- Clear the association in map_free_pre_rcu() instead of deferring the
free of the map (Amery Hung)
- Add patch 1, replacing st_ops_assoc_mutex with a spinlock, since
map_free_pre_rcu() may run in softirq
- Stop st_ops_assoc_in_timer_free and _no_uref when running syscall_prog
fails, instead of waiting for the timer forever (Sashiko)
- Point Fixes of patch 4 at 0e841d19263ab, which added the test, as the
delay left after patch 2 comes from freeing syscall_prog
V2 -> V3:
- Wait for an RCU tasks trace grace period in patch 1 for every map, so
that progs reading the association under RCU tasks trace, e.g. from a
bpf_wq callback, are waited for too (CI bot)
V1 -> V2:
- Add patch 1, fixing the use-after-free behind the flaky test (CI bot)
- Add patch 2, a test for the use-after-free
- Point Fixes of patch 3 at 5db69b0fbdc8 (CI bot)
- Move the restarting of the timer into patch 2, which needs it too,
so patch 3 only turns it on for st_ops_assoc_in_timer_no_uref
- Rebase onto current bpf-next
v3: https://lore.kernel.org/r/20261015-selftests-bpf-struct-ops-assoc-timer-v3-0-efe643d4b2a3@gmail.com
v2: https://lore.kernel.org/r/20261010-selftests-bpf-struct-ops-assoc-timer-v2-0-d7a43896dbf1@gmail.com
v1: https://lore.kernel.org/r/20261009-selftests-bpf-struct-ops-assoc-timer-v1-1-9a97a9b3d1db@gmail.com
---
Qiliang Yuan (4):
bpf: Protect the struct_ops association with a spinlock
bpf: Dissociate struct_ops progs before the grace period of the map
selftests/bpf: Test freeing a struct_ops map under a running prog
selftests/bpf: Wait for the struct_ops map to be freed in assoc_in_timer
include/linux/bpf.h | 2 +-
kernel/bpf/bpf_struct_ops.c | 28 ++++++++++--------
kernel/bpf/core.c | 3 +-
.../bpf/prog_tests/test_struct_ops_assoc.c | 34 ++++++++++++++++++----
.../bpf/progs/struct_ops_assoc_in_timer.c | 16 ++++++++++
5 files changed, 63 insertions(+), 20 deletions(-)
---
base-commit: 15b578b1715d9a318c804350d98af87c203672bc
change-id: 20261009-selftests-bpf-struct-ops-assoc-timer-5eb0295ec300
Best regards,
--
Qiliang Yuan <odys.yuan@gmail.com>
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH bpf-next v4 1/4] bpf: Protect the struct_ops association with a spinlock
2026-10-16 2:59 [PATCH bpf-next v4 0/4] bpf: Fix a struct_ops use-after-free behind a flaky assoc_in_timer test Qiliang Yuan
@ 2026-10-16 2:59 ` Qiliang Yuan
2026-10-16 2:59 ` [PATCH bpf-next v4 2/4] bpf: Dissociate struct_ops progs before the grace period of the map Qiliang Yuan
` (2 subsequent siblings)
3 siblings, 0 replies; 5+ messages in thread
From: Qiliang Yuan @ 2026-10-16 2:59 UTC (permalink / raw)
To: Andrii Nakryiko, Eduard Zingerman, Ihor Solodrai,
Alexei Starovoitov, Daniel Borkmann, Kumar Kartikeya Dwivedi,
Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa,
Emil Tsalapatis, Shuah Khan, Amery Hung
Cc: bpf, linux-kselftest, linux-kernel, Qiliang Yuan
bpf_prog_assoc_struct_ops() and bpf_prog_disassoc_struct_ops() update
prog->aux->st_ops_assoc under st_ops_assoc_mutex, and the latter puts
the map of a non-struct_ops prog while holding it.
A struct_ops map is about to dissociate its progs from
map_free_pre_rcu(), which bpf_map_put() calls in whatever context drops
the last reference, including softirq, e.g. bpf_struct_ops_put() from
tcp_cleanup_congestion_control(). A mutex can't be taken there, and the
put of the last reference under the lock of one prog would take the
lock of other progs.
Replace the mutex with a spinlock taken with interrupts disabled, and
put the map after dropping it.
Signed-off-by: Qiliang Yuan <odys.yuan@gmail.com>
---
include/linux/bpf.h | 2 +-
kernel/bpf/bpf_struct_ops.c | 19 ++++++++++---------
kernel/bpf/core.c | 3 +--
3 files changed, 12 insertions(+), 12 deletions(-)
diff --git a/include/linux/bpf.h b/include/linux/bpf.h
index 54144372281cb..2a51acd34f9a9 100644
--- a/include/linux/bpf.h
+++ b/include/linux/bpf.h
@@ -1960,7 +1960,7 @@ struct bpf_prog_aux {
struct rcu_head rcu;
};
struct bpf_stream *stream[2];
- struct mutex st_ops_assoc_mutex;
+ spinlock_t st_ops_assoc_lock; /* protects st_ops_assoc */
struct bpf_map __rcu *st_ops_assoc;
};
diff --git a/kernel/bpf/bpf_struct_ops.c b/kernel/bpf/bpf_struct_ops.c
index 1178acd72296e..a1ae30b145a03 100644
--- a/kernel/bpf/bpf_struct_ops.c
+++ b/kernel/bpf/bpf_struct_ops.c
@@ -1541,10 +1541,10 @@ int bpf_prog_assoc_struct_ops(struct bpf_prog *prog, struct bpf_map *map)
{
struct bpf_map *st_ops_assoc;
- guard(mutex)(&prog->aux->st_ops_assoc_mutex);
+ guard(spinlock_irqsave)(&prog->aux->st_ops_assoc_lock);
st_ops_assoc = rcu_dereference_protected(prog->aux->st_ops_assoc,
- lockdep_is_held(&prog->aux->st_ops_assoc_mutex));
+ lockdep_is_held(&prog->aux->st_ops_assoc_lock));
if (st_ops_assoc && st_ops_assoc == map)
return 0;
@@ -1569,19 +1569,20 @@ int bpf_prog_assoc_struct_ops(struct bpf_prog *prog, struct bpf_map *map)
void bpf_prog_disassoc_struct_ops(struct bpf_prog *prog)
{
+ struct bpf_prog_aux *aux = prog->aux;
struct bpf_map *st_ops_assoc;
- guard(mutex)(&prog->aux->st_ops_assoc_mutex);
+ scoped_guard(spinlock_irqsave, &aux->st_ops_assoc_lock) {
+ st_ops_assoc = rcu_dereference_protected(aux->st_ops_assoc,
+ lockdep_is_held(&aux->st_ops_assoc_lock));
+ if (!st_ops_assoc || st_ops_assoc == BPF_PTR_POISON)
+ return;
- st_ops_assoc = rcu_dereference_protected(prog->aux->st_ops_assoc,
- lockdep_is_held(&prog->aux->st_ops_assoc_mutex));
- if (!st_ops_assoc || st_ops_assoc == BPF_PTR_POISON)
- return;
+ RCU_INIT_POINTER(aux->st_ops_assoc, NULL);
+ }
if (prog->type != BPF_PROG_TYPE_STRUCT_OPS)
bpf_map_put(st_ops_assoc);
-
- RCU_INIT_POINTER(prog->aux->st_ops_assoc, NULL);
}
/*
diff --git a/kernel/bpf/core.c b/kernel/bpf/core.c
index 05c89396119ad..fd4e4dc4acc8c 100644
--- a/kernel/bpf/core.c
+++ b/kernel/bpf/core.c
@@ -141,7 +141,7 @@ struct bpf_prog *bpf_prog_alloc_no_stats(unsigned int size, gfp_t gfp_extra_flag
mutex_init(&fp->aux->used_maps_mutex);
mutex_init(&fp->aux->ext_mutex);
mutex_init(&fp->aux->dst_mutex);
- mutex_init(&fp->aux->st_ops_assoc_mutex);
+ spin_lock_init(&fp->aux->st_ops_assoc_lock);
return fp;
}
@@ -291,7 +291,6 @@ void __bpf_prog_free(struct bpf_prog *fp)
#endif
mutex_destroy(&fp->aux->used_maps_mutex);
mutex_destroy(&fp->aux->dst_mutex);
- mutex_destroy(&fp->aux->st_ops_assoc_mutex);
kfree(fp->aux->poke_tab);
kfree(fp->aux);
}
--
2.43.0
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH bpf-next v4 2/4] bpf: Dissociate struct_ops progs before the grace period of the map
2026-10-16 2:59 [PATCH bpf-next v4 0/4] bpf: Fix a struct_ops use-after-free behind a flaky assoc_in_timer test Qiliang Yuan
2026-10-16 2:59 ` [PATCH bpf-next v4 1/4] bpf: Protect the struct_ops association with a spinlock Qiliang Yuan
@ 2026-10-16 2:59 ` Qiliang Yuan
2026-10-16 2:59 ` [PATCH bpf-next v4 3/4] selftests/bpf: Test freeing a struct_ops map under a running prog Qiliang Yuan
2026-10-16 2:59 ` [PATCH bpf-next v4 4/4] selftests/bpf: Wait for the struct_ops map to be freed in assoc_in_timer Qiliang Yuan
3 siblings, 0 replies; 5+ messages in thread
From: Qiliang Yuan @ 2026-10-16 2:59 UTC (permalink / raw)
To: Andrii Nakryiko, Eduard Zingerman, Ihor Solodrai,
Alexei Starovoitov, Daniel Borkmann, Kumar Kartikeya Dwivedi,
Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa,
Emil Tsalapatis, Shuah Khan, Amery Hung
Cc: bpf, linux-kselftest, linux-kernel, Qiliang Yuan
A struct_ops prog is associated with its map without a reference on the
map, and finds the struct_ops through bpf_prog_get_assoc_struct_ops()
under RCU. For a struct_ops map, bpf_map_put() waits for an RCU grace
period before it calls bpf_struct_ops_map_free(), which clears the
association and then frees the trampoline and the kdata right away.
Nothing waits for a prog that reads the association after that grace
period started and before the association is cleared. A timer callback
of a struct_ops prog that calls test_1 of bpf_testmod's multi_st_ops
through the association while the map is freed returns into the freed
trampoline, whose image is filled with int3. RAX holds MAP_MAGIC (1234),
which test_1 has just returned:
Oops: int3: 0000 [#1] SMP KASAN NOPTI
CPU: 2 UID: 0 PID: 31 Comm: ksoftirqd/2 ...
RIP: 0010:0xffffffffc043114d
Code: cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc <cc> cc cc cc
RAX: 00000000000004d2 ...
Call Trace:
bpf_prog_ec591f4f9d13f364_timer_cb+0x96/0x1e2
bpf_timer_cb+0x15d/0x240
__hrtimer_run_queues+0x264/0x5a0
hrtimer_run_softirq+0x1a7/0x3b0
handle_softirqs+0x18c/0x5b0
Dissociate the progs in map_free_pre_rcu(), so that the grace period of
bpf_map_put() follows it, and make that an RCU tasks trace grace period
for every struct_ops map. It then also waits for the progs that read
the association under RCU tasks trace, e.g. from a bpf_wq callback or a
sleepable prog, as a tasks trace grace period implies an RCU one.
The st_ops_assoc_in_timer_free subtest of test_progs reproduces it. Its
timer callback keeps calling test_1 through the association while the
map is freed, and test_1 spins in bpf_loop() when called from there:
$ cd tools/testing/selftests/bpf
$ make test_progs
$ ./test_progs -t struct_ops_assoc/st_ops_assoc_in_timer_free
On kernels of the same bpf-next commit with KASAN enabled, x86_64 VM
with 32 vCPUs, before and after this patch:
before after
outcome Oops (int3) on run 1 100 of 100 runs passed, no Oops
Fixes: 5db69b0fbdc8 ("bpf: Make struct_ops tasks_rcu grace period optional")
Signed-off-by: Qiliang Yuan <odys.yuan@gmail.com>
---
kernel/bpf/bpf_struct_ops.c | 9 ++++++---
1 file changed, 6 insertions(+), 3 deletions(-)
diff --git a/kernel/bpf/bpf_struct_ops.c b/kernel/bpf/bpf_struct_ops.c
index a1ae30b145a03..7d1a8514ba341 100644
--- a/kernel/bpf/bpf_struct_ops.c
+++ b/kernel/bpf/bpf_struct_ops.c
@@ -1030,6 +1030,7 @@ static void bpf_struct_ops_map_free_pre_rcu(struct bpf_map *map)
struct bpf_struct_ops_map *st_map = (struct bpf_struct_ops_map *)map;
bpf_struct_ops_map_del_ksyms(st_map);
+ bpf_struct_ops_map_dissoc_progs(st_map);
}
static void bpf_struct_ops_map_free(struct bpf_map *map)
@@ -1045,8 +1046,6 @@ static void bpf_struct_ops_map_free(struct bpf_map *map)
if (btf_is_module(st_map->btf))
module_put(st_map->st_ops_desc->st_ops->owner);
- bpf_struct_ops_map_dissoc_progs(st_map);
-
if (tasks_rcu && IS_ENABLED(CONFIG_TASKS_RCU))
synchronize_rcu_tasks();
@@ -1162,7 +1161,11 @@ static struct bpf_map *bpf_struct_ops_map_alloc(union bpf_attr *attr)
mutex_init(&st_map->lock);
bpf_map_init_from_attr(map, attr);
- map->free_after_mult_rcu_gp = st_ops_desc->st_ops->free_after_mult_rcu_gp;
+ /*
+ * A prog dissociated in map_free_pre_rcu() may still run the struct_ops
+ * from a bpf_wq callback or a sleepable prog, under RCU tasks trace.
+ */
+ map->free_after_mult_rcu_gp = true;
map->free_after_rcu_gp = true;
return map;
--
2.43.0
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH bpf-next v4 3/4] selftests/bpf: Test freeing a struct_ops map under a running prog
2026-10-16 2:59 [PATCH bpf-next v4 0/4] bpf: Fix a struct_ops use-after-free behind a flaky assoc_in_timer test Qiliang Yuan
2026-10-16 2:59 ` [PATCH bpf-next v4 1/4] bpf: Protect the struct_ops association with a spinlock Qiliang Yuan
2026-10-16 2:59 ` [PATCH bpf-next v4 2/4] bpf: Dissociate struct_ops progs before the grace period of the map Qiliang Yuan
@ 2026-10-16 2:59 ` Qiliang Yuan
2026-10-16 2:59 ` [PATCH bpf-next v4 4/4] selftests/bpf: Wait for the struct_ops map to be freed in assoc_in_timer Qiliang Yuan
3 siblings, 0 replies; 5+ messages in thread
From: Qiliang Yuan @ 2026-10-16 2:59 UTC (permalink / raw)
To: Andrii Nakryiko, Eduard Zingerman, Ihor Solodrai,
Alexei Starovoitov, Daniel Borkmann, Kumar Kartikeya Dwivedi,
Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa,
Emil Tsalapatis, Shuah Khan, Amery Hung
Cc: bpf, linux-kselftest, linux-kernel, Qiliang Yuan
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, which
stops when running syscall_prog fails instead of waiting for a timer
that never starts.
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, median 0.27 s
Signed-off-by: Qiliang Yuan <odys.yuan@gmail.com>
---
.../bpf/prog_tests/test_struct_ops_assoc.c | 34 ++++++++++++++++++----
.../bpf/progs/struct_ops_assoc_in_timer.c | 16 ++++++++++
2 files changed, 45 insertions(+), 5 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..9d56e8cb5675a 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,12 +158,18 @@ 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");
+ if (!ASSERT_OK(err, "bpf_prog_test_run_opts")) {
+ bpf_link__destroy(link);
+ goto out;
+ }
/* Detach and close struct_ops map to cause it to be freed */
bpf_link__destroy(link);
@@ -178,6 +185,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 +210,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
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH bpf-next v4 4/4] selftests/bpf: Wait for the struct_ops map to be freed in assoc_in_timer
2026-10-16 2:59 [PATCH bpf-next v4 0/4] bpf: Fix a struct_ops use-after-free behind a flaky assoc_in_timer test Qiliang Yuan
` (2 preceding siblings ...)
2026-10-16 2:59 ` [PATCH bpf-next v4 3/4] selftests/bpf: Test freeing a struct_ops map under a running prog Qiliang Yuan
@ 2026-10-16 2:59 ` Qiliang Yuan
3 siblings, 0 replies; 5+ messages in thread
From: Qiliang Yuan @ 2026-10-16 2:59 UTC (permalink / raw)
To: Andrii Nakryiko, Eduard Zingerman, Ihor Solodrai,
Alexei Starovoitov, Daniel Borkmann, Kumar Kartikeya Dwivedi,
Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa,
Emil Tsalapatis, Shuah Khan, Amery Hung
Cc: bpf, linux-kselftest, linux-kernel, Qiliang Yuan
st_ops_assoc_in_timer_no_uref runs .test_1, which starts a timer to
call it again 500ms later, drops every reference to the struct_ops map,
and expects the timer callback to find no struct_ops associated with
its program anymore.
The association goes away only when the last reference to the map is
dropped. syscall_prog holds one through its own association and drops
it only when it is freed, in a work item after an RCU tasks trace grace
period. With other tests running in parallel, that can take longer than
500ms, and the callback still reaches the struct_ops and gets MAP_MAGIC
instead of -1.
Have the timer callback restart the timer until the map is gone, as it
does for st_ops_assoc_in_timer_free. Calling the struct_ops through the
association until then is safe, as the map is freed only after a grace
period that follows clearing the association.
st_ops_assoc_in_timer_no_uref run 100 times with test_progs -j32 running
in the background, on a KASAN kernel with the fix, x86_64 VM with 32
vCPUs, before and after this patch:
before after
outcome 1 of 100 failed (MAP_MAGIC) 0 of 100 failed (MAP_MAGIC)
Fixes: 0e841d19263a ("selftests/bpf: Test getting associated struct_ops in timer callback")
Signed-off-by: Qiliang Yuan <odys.yuan@gmail.com>
---
tools/testing/selftests/bpf/prog_tests/test_struct_ops_assoc.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
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 9d56e8cb5675a..435653cb7c908 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
@@ -187,7 +187,7 @@ static void run_st_ops_assoc_in_timer_no_uref(int timer_ns, int wait_map_free,
static void test_st_ops_assoc_in_timer_no_uref(void)
{
- run_st_ops_assoc_in_timer_no_uref(500000000, 0, 0);
+ run_st_ops_assoc_in_timer_no_uref(500000000, 1, 0);
}
/*
--
2.43.0
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-10-16 2:59 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-16 2:59 [PATCH bpf-next v4 0/4] bpf: Fix a struct_ops use-after-free behind a flaky assoc_in_timer test Qiliang Yuan
2026-10-16 2:59 ` [PATCH bpf-next v4 1/4] bpf: Protect the struct_ops association with a spinlock Qiliang Yuan
2026-10-16 2:59 ` [PATCH bpf-next v4 2/4] bpf: Dissociate struct_ops progs before the grace period of the map Qiliang Yuan
2026-10-16 2:59 ` [PATCH bpf-next v4 3/4] selftests/bpf: Test freeing a struct_ops map under a running prog Qiliang Yuan
2026-10-16 2:59 ` [PATCH bpf-next v4 4/4] selftests/bpf: Wait for the struct_ops map to be freed in assoc_in_timer Qiliang Yuan
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®