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. This deletion does not itself free the element. Mark FREED before clearing the slot. Only the first FREED transition clears the slot and drops or transfers its map-owned context reference. Until that canceller clears the slot, no replacement can be published, so a plain slot store cannot overwrite a new context. Reading the slot no longer acquires exclusive ownership of C. Protect that access with regular RCU while another canceller can release C, and preserve unlink-to-slot ordering with a full barrier, including when the slot is NULL. Fixes: 38aa7003e369 ("bpf: task work scheduling kfuncs") Signed-off-by: Yun Lu --- kernel/bpf/helpers.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/kernel/bpf/helpers.c b/kernel/bpf/helpers.c index 937a0d7d6839..4b252ca61166 100644 --- a/kernel/bpf/helpers.c +++ b/kernel/bpf/helpers.c @@ -5174,11 +5174,21 @@ 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)(); + /* Order a preceding unlink before observing an empty slot. */ + 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); + /* Only the first FREED transition detaches ctx and owns the map's ref. */ + if (state == BPF_TW_FREED) + return; + WRITE_ONCE(twk->ctx, NULL); + 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