]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
bpf: Check for running wq callback when freeing bpf_async_cb
authorKumar Kartikeya Dwivedi <memxor@gmail.com>
Thu, 5 Feb 2026 00:38:52 +0000 (16:38 -0800)
committerAlexei Starovoitov <ast@kernel.org>
Thu, 5 Feb 2026 02:14:26 +0000 (18:14 -0800)
When freeing a bpf_async_cb in bpf_async_cb_rcu_tasks_trace_free(), in
case the wq callback is not scheduled, doing cancel_work() currently
returns false and leads to retry of RCU tasks trace grace period. If the
callback is never scheduled, we keep retrying indefinitely and don't put
the prog reference.

Since the only race we care about here is against a potentially running
wq callback in the first grace period, it should finish by the second
grace period, hence check work_busy() result to detect presence of
running wq callback if it's not pending, otherwise free the object
immediately without retrying.

Reasoning behind the check and its correctness with racing wq callback
invocation: cancel_work is supposed to be synchronized, hence calling it
first and getting false would mean that work is definitely not pending,
at this point, either the work is not scheduled at all or already
running, or we race and it already finished by the time we checked for
it using work_busy(). In case it is running, we synchronize using
pool->lock to check the current work running there, if we match, it
means we extend the wait by another grace period using retry = true,
otherwise either the work already finished running or was never
scheduled, so we can free the bpf_async_cb right away.

Fixes: 1bfbc267ec91 ("bpf: Enable bpf_timer and bpf_wq in any context")
Reported-by: Alexei Starovoitov <ast@kernel.org>
Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
Link: https://lore.kernel.org/r/20260205003853.527571-2-memxor@gmail.com
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
kernel/bpf/helpers.c

index 01052f8664eb92d1f20e6f6bd0155a3a70e73b34..b7aec34540c2894bd064a6d22756cc62e9a89cc6 100644 (file)
@@ -1257,7 +1257,7 @@ static void bpf_async_cb_rcu_tasks_trace_free(struct rcu_head *rcu)
                        retry = true;
                break;
        case BPF_ASYNC_TYPE_WQ:
-               if (!cancel_work(&w->work))
+               if (!cancel_work(&w->work) && work_busy(&w->work))
                        retry = true;
                break;
        }