syzbot
WARNING in io_put_sq_data
Status:
upstream: reported on 2026/11/05 06:31
Subsystems:
io-uring
Labels:
prio:low
[Documentation on labels]
Reported-by: syzbot+9f32706bfd7c4ff81434@syzkaller.appspotmail.com
First crash: 4d01h, last: 4d01h
✨ AI-generated root cause summary:
In io_uring/sqpoll.c, io_sq_thread() sets the IO_SQ_THREAD_SHOULD_STOP bit on sqd->state when the SQPOLL thread terminates (e.g., due to a fatal signal) to prevent new rings from attaching during cancelation. However, io_sq_thread_stop() still contains an assertion WARN_ON_ONCE(test_bit(IO_SQ_THREAD_SHOULD_STOP, &sqd->state)) under the earlier invariant that only io_sq_thread_stop() ever sets this bit when rings release their references. When the ring is subsequently torn down during process cleanup, io_put_sq_data() calls io_sq_thread_stop() on the already-stopped sqd, tripping the warning.
▶
▼
✨ Full AI root cause analysis
Root Cause Analysis
1. Relevant Subsystem, Functions, and State
- Source file:
io_uring/sqpoll.c
- Functions involved:
io_sq_thread(), io_sq_thread_stop(), io_put_sq_data(), io_sq_thread_finish(), io_sq_offload_create()
- Structures and fields:
struct io_sq_data::state, IO_SQ_THREAD_SHOULD_STOP, struct io_sq_data::thread, struct io_sq_data::free_refs, struct io_sq_data::refs
2. Technical Mechanism
-
Previous Invariant and Design:
In io_uring/sqpoll.c, struct io_sq_data coordinates the SQPOLL kernel thread shared by one or more io_ring_ctx instances. It maintains reference counts: sqd->refs counts how many rings reference the sqd, while sqd->free_refs counts holders (one for all rings collectively, one for the thread itself).
Historically, io_sq_thread_stop(struct io_sq_data *sqd) was the sole place where the IO_SQ_THREAD_SHOULD_STOP flag on sqd->state was set:
void io_sq_thread_stop(struct io_sq_data *sqd)
{
struct task_struct *tsk;
WARN_ON_ONCE(test_bit(IO_SQ_THREAD_SHOULD_STOP, &sqd->state));
set_bit(IO_SQ_THREAD_SHOULD_STOP, &sqd->state);
mutex_lock(&sqd->lock);
tsk = sqpoll_task_locked(sqd);
if (tsk) {
WARN_ON_ONCE(tsk == current);
wake_up_process(tsk);
}
mutex_unlock(&sqd->lock);
}
Because io_sq_thread_stop() was only called once when the last ring dropped its reference in io_put_sq_data() (if (refcount_dec_and_test(&sqd->refs))), the assertion WARN_ON_ONCE(test_bit(IO_SQ_THREAD_SHOULD_STOP, &sqd->state)) checked that io_sq_thread_stop() had not been invoked before for this sqd.
-
The Introduction of the Conflict:
To fix a race condition where a new ring attaches (IORING_SETUP_ATTACH_WQ) to an SQPOLL thread while that thread is exiting and canceling requests, commit 22d9db9fd64a ("io_uring/sqpoll: don't attach to a thread that is canceling") added logic to set IO_SQ_THREAD_SHOULD_STOP directly in io_sq_thread() when the thread breaks out of its work loop:
/*
* Cancelation drops sqd->lock while it waits, and a ring could attach
* to a thread that is on its way out. A fatal signal doesn't set the
* bit, so set it here for io_sq_offload_create() to see.
*/
set_bit(IO_SQ_THREAD_SHOULD_STOP, &sqd->state);
io_uring_cancel_generic(true, sqd);
rcu_assign_pointer(sqd->thread, NULL);
put_task_struct(current);
...
err_out:
io_sq_data_release(sqd);
do_exit(0);
-
How the Bug is Triggered:
▶
▼
Last patch testing requests (1)
Sample crash report:
------------[ cut here ]------------
test_bit(IO_SQ_THREAD_SHOULD_STOP, &sqd->state)
WARNING: io_uring/sqpoll.c:88 at io_sq_thread_stop+0x18b/0x1c0 io_uring/sqpoll.c:88, CPU#2: kworker/u33:12/1172
Modules linked in:
CPU: 2 UID: 0 PID: 1172 Comm: kworker/u33:12 Not tainted syzkaller #0 PREEMPT(full)
Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
Workqueue: iou_exit io_ring_exit_work
RIP: 001qemu-system-x86_64: warning: 9p: degraded performance: a reasonable high msize should be chosen on client/guest side (chosen msize is <= 8192). See https://wiki.qemu.org/Documentation/9psetup#msize for details.
Code: 48 c7 c2 c0 cc 80 8c be 25 00 00 00 48 c7 c7 20 cd 80 8c c6 05 29 56 22 0d 01 e8 f0 3f b8 fc e9 12 ff ff ff e8 36 a9 dd fc 90 <0f> 0b 90 e9 c8 fe ff ff e8 28 a9 dd fc 90 0f 0b 90 e9 3c ff ff ff
RSP: 0000:ffffc90006627a28 EFLAGS: 00010293
RAX: 0000000000000000 RBX: ffff888052edb400 RCX: 0000000000000000
RDX: ffff888022a90000 RSI: ffffffff852ffcea RDI: 0000000000000001
RBP: 0000000000000001 R08: 0000000000000000 R09: 0000000000000001
R10: ffff888052edb51f R11: 0000000000000000 R12: ffff888052edb518
R13: 0000000000000000 R14: ffff88806ebfe4e0 R15: ffff888052edafc0
FS: 0000000000000000(0000) GS:ffff888095481000(0000) knlGS:0000000000000000
CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 0000000057f2b470 CR3: 000000004f385000 CR4: 0000000000352ef0
Call Trace:
<TASK>
io_put_sq_data io_uring/sqpoll.c:105 [inline]
io_put_sq_data+0xb0/0x100 io_uring/sqpoll.c:100
io_sq_thread_finish+0x252/0x320 io_uring/sqpoll.c:130
io_ring_ctx_free io_uring/io_uring.c:2322 [inline]
io_ring_exit_work+0x40a/0x9f5 io_uring/io_uring.c:2658
process_one_work+0xac7/0x1b10 kernel/workqueue.c:3495
process_scheduled_works kernel/workqueue.c:3578 [inline]
worker_thread+0x5ef/0xe50 kernel/workqueue.c:3659
kthread+0x373/0x450 kernel/kthread.c:436
ret_from_fork+0x730/0xd60 arch/x86/kernel/process.c:158
ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
</TASK>
* Struck through repros no longer work on HEAD.