diff options
| author | Yun Lu <luyun@kylinos.cn> | 2026-10-12 14:19:12 +0800 |
|---|---|---|
| committer | Alexei Starovoitov <ast@kernel.org> | 2026-10-16 00:39:48 +0000 |
| commit | c961f7b41a108313a4705baac7b27d2174cc8035 (patch) | |
| tree | 290f1be5a5fc12bbf041393aeae203d389a26ed3 /tools/testing | |
| parent | 70024d04ea27a92132eb83b12911dc8de2bfad4b (diff) | |
bpf: Fix NULL task dereference in task work cancellation
bpf_task_work_irq() publishes ctx->work with task_work_add() before
changing the context from SCHEDULING to SCHEDULED. The target task can
run the callback on another CPU before the handler reaches its final
state transition.
The handler then reloads ctx->task for cancellation if its final cmpxchg
observes FREED. By that point the callback or context destruction may
already have cleared ctx->task, causing task_work_cancel() to dereference
a NULL task pointer.
One concrete interleaving is:
1. CPU 0 changes PENDING to SCHEDULING and successfully queues
ctx->work with task_work_add(), but has not attempted the final
state cmpxchg.
2. The target task on CPU 1 runs the callback. It changes SCHEDULING
to RUNNING, executes the BPF subprogram, resets ctx->task,
publishes STANDBY and drops the callback's context reference.
3. CPU 2 deletes the map value. It changes STANDBY to FREED and drops
the map's context reference without queuing asynchronous
cancellation.
4. CPU 0 resumes. Its SCHEDULING-to-SCHEDULED cmpxchg observes FREED,
and the cancellation attempt passes NULL to task_work_cancel().
If deletion publishes FREED before the callback claims RUNNING, the
callback takes its FREED exit and drops its context reference. Context
destruction can then clear ctx->task before CPU 0 attempts cancellation.
A controlled reproducer with a delay before the final cmpxchg triggered
the following fault on a v7.3-rc3 based x86-64 KVM guest:
BUG: kernel NULL pointer dereference, address: 0000000000000890
RIP: 0010:task_work_cancel+0xd/0xa0
RDI: 0000000000000000
CR2: 0000000000000890
Call Trace:
<IRQ>
bpf_task_work_irq+0x95/0x100
irq_work_run_list+0x4f/0x90
irq_work_run+0x18/0x50
</IRQ>
0x890 is the task_struct::task_works offset in that build, read through
the NULL task argument.
Capture ctx->task before task_work_add() publishes the callback and use
that pointer for the handler's cancellation attempt. The round still
holds its task reference when the pointer is captured. Releasing that
reference uses bpf_task_release(), which calls put_task_struct_rcu_user(),
so the handler's existing RCU read-side section keeps the captured task
alive even if ctx->task is subsequently cleared.
The asynchronous cancellation path reads ctx->task directly, which is
safe there: queuing it requires deletion to observe SCHEDULED, and that
xchg wins against the callback's transition to RUNNING, so the callback
takes its FREED exit without resetting ctx->task, while the map
reference transferred to the cancellation handler prevents context
destruction until cancellation completes.
The handler-tail and asynchronous cancellation attempts are mutually
exclusive: asynchronous cancellation is queued only if deletion observes
SCHEDULED after the scheduling handler's final cmpxchg has succeeded.
If that cmpxchg instead observes FREED, the scheduling handler attempts
cancellation with the captured pointer.
Fixes: 38aa7003e369 ("bpf: task work scheduling kfuncs")
Reported-by: Jackie Liu <liuyun01@kylinos.cn>
Suggested-by: Mykyta Yatsenko <mykyta.yatsenko5@gmail.com>
Signed-off-by: Yun Lu <luyun@kylinos.cn>
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
Link: https://patch.msgid.link/20261012061915.231254-2-luyun_611@163.com
Diffstat (limited to 'tools/testing')
0 files changed, 0 insertions, 0 deletions
