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
Based on kernel commit 36aad21d50d1:

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

  1. 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.

  2. 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);
    
  3. How the Bug is Triggered:

    • If an application using an SQPOLL ring receives a fatal signal (such as SIGKILL), the signal is delivered group-wide to both the user process and the SQPOLL thread.
    • In io_sq_thread(), io_sqd_handle_event() detects the pending signal via get_signal(&ksig) and returns true, causing io_sq_thread() to exit its main loop.
    • io_sq_thread() executes line 441: set_bit(IO_SQ_THREAD_SHOULD_STOP, &sqd->state), marks sqd->thread = NULL, releases its free_refs reference, and terminates.
    • Subsequently, during task teardown and file descriptor closure, io_ring_exit_work() runs on the iou_exit workqueue to destroy the io_ring_ctx.
    • io_ring_ctx_free() calls io_sq_thread_finish(), which drops the ring's reference to sqd via io_put_sq_data(sqd).
    • In io_put_sq_data(), refcount_dec_and_test(&sqd->refs) returns true and invokes io_sq_thread_stop(sqd).
    • Inside io_sq_thread_stop(), line 88 evaluates:
      WARN_ON_ONCE(test_bit(IO_SQ_THREAD_SHOULD_STOP, &sqd->state));
      
    • Since IO_SQ_THREAD_SHOULD_STOP was already set by io_sq_thread() when it exited, the check evaluates to true and fires the warning splat.
▶ ▼ ✨ AI Jobs (2)
ID Workflow Result Correct Bug Created Started Finished Revision
554ecb63 repro-c Reproduced: ✅ ❓ WARNING in io_put_sq_data 2026/11/04 06:10 2026/11/04 06:10 2026/11/04 07:01 5af5b6c6
c2b14d00 assessment-security DenialOfService: ❌ Exploitable: ❌ FilesystemTrigger: ❌ FuzzingArtifact: ❌ MemoryCorruption: ❌ NetworkNeedsSetup: ❌ NetworkTrigger: ❌ PeripheralTrigger: ❌ RemoteTrigger: ❌ Unprivileged: ✅ UserNamespace: ✅ VMGuestTrigger: ❌ VMHostTrigger: ❌ ❓ WARNING in io_put_sq_data 2026/11/01 06:25 2026/11/01 06:25 2026/11/01 07:14 25a0ae84
▶ ▼ Discussions (1)
Title Replies (including bot) Last reply
[syzbot] [io-uring?] WARNING in io_put_sq_data 0 (1) 2026/11/05 06:31
▶ ▼ Last patch testing requests (1)
Created Duration User Patch Repo Result
2026/11/04 07:01 13m retest repro git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git 05441d697f7f9a927264f4058cbbc6a03dfcac9d report job log log

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>

Crashes (1):
Time Kernel Commit Syzkaller Config Log Report Syz repro C repro VM info Assets (help?) Manager Title
2026/11/01 06:25 upstream 36aad21d50d1 25a0ae84 .config console log report info [disk image (non-bootable)] [vmlinux] [kernel image] ci-qemu-upstream-386 WARNING in io_put_sq_data
* Struck through repros no longer work on HEAD.