From: Yun Lu bpf_task_work_cancel_and_free() clears the value's context slot before marking the context FREED. A concurrent deletion can observe the empty slot and return while the detached context can still enter RUNNING: uref cleanup delete target task xchg(slot, NULL) gets C unlink E cancel sees NULL return C -> RUNNING xchg(C->state, FREED) A preallocated HASH uref walk racing with deletion allows this missed cancellation. It does not itself free the element, but deletion fails to prevent the old context from starting a callback. Mark FREED before conditionally clearing the slot. A callback that has not already entered RUNNING can no longer start once C is detached, and the compare-and-exchange does not remove a replacement context. Unlike xchg(), reading the slot does not acquire exclusive ownership of C. Hold regular RCU while competing cancellers can detach C and drop its reference. Preserve the old xchg()'s unlink-to-slot ordering with a full barrier, including when the slot is NULL. Only the first FREED transition drops or transfers the map-owned reference; later cancellers may help detach C without disposing of that reference again. Fixes: 38aa7003e369 ("bpf: task work scheduling kfuncs") Signed-off-by: Yun Lu --- kernel/bpf/helpers.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/kernel/bpf/helpers.c b/kernel/bpf/helpers.c index 6788489a2608..281fedabf7b0 100644 --- a/kernel/bpf/helpers.c +++ b/kernel/bpf/helpers.c @@ -5089,11 +5089,20 @@ void bpf_task_work_cancel_and_free(void *val) struct bpf_task_work_ctx *ctx; enum bpf_task_work_state state; - ctx = xchg(&twk->ctx, NULL); + /* Another canceller can release the map-owned context reference. */ + guard(rcu)(); + /* Preserve unlink-to-slot ordering, including the NULL case. */ + smp_mb(); + ctx = READ_ONCE(twk->ctx); if (!ctx) return; + /* Stop callbacks before making the context invisible to deletion. */ state = xchg(&ctx->state, BPF_TW_FREED); + (void)cmpxchg(&twk->ctx, ctx, NULL); + /* Only the first FREED transition owns the map's reference. */ + if (state == BPF_TW_FREED) + return; if (state == BPF_TW_SCHEDULED) { /* run in irq_work to avoid locks in NMI */ init_irq_work(&ctx->irq_work, bpf_task_work_cancel_scheduled); -- 2.43.0