CVE-2021-46942 - How a Linux Kernel io_uring Bug Could Hang Your Shared SQPOLL Threads
In 2021, a critical bug surfaced in the Linux kernel impacting the io_uring subsystem—specifically, for applications leveraging shared SQPOLL threads. This bug, registered as CVE-2021-46942, could cause the cancellation process for io_uring requests to hang indefinitely. If you've ever seen processes like iou-sqp-4294 stuck in an uninterruptible state for minutes (or more), you've seen the result.
In this article, we’ll explain the vulnerability, review kernel logs, walk through the core code, and outline how the patch fixed this tricky problem. All in plain English, so you don’t have to be a kernel wizard to follow along.
What’s SQPOLL in io_uring?
io_uring brings high-performance, async I/O to Linux. For even more performance, you can dedicate a kernel thread called SQPOLL to actively submit user operations without always needing syscalls.
To save resources, multiple io_uring contexts (let’s call them "ctxs") can share a single SQPOLL thread. But, what happens when you try to clean these up or cancel outstanding requests? Well, as you’ll see, things used to go very wrong...
The Vulnerability: Hangs During Shared SQPOLL Cancellation
When a process or someone with root permissions tries to tear down io_uring resources—maybe because an app is stopping or updating—the kernel runs io_uring_cancel_sqpoll() to cancel all remaining requests tied to each ctx, one by one. The intent is to cancel all work associated to the given io_uring context.
But the function doesn’t just look at the current ctx:
It counts inflight requests _per kernel task_, so it sees _more_ requests than those for your specific ctx and expects those requests to appear (as if from interrupts or other events). If they never appear—for instance, if they’re for a different ctx—the thread sleeps forever.
Relevant Kernel Log Example
[ 736.982891] INFO: task iou-sqp-4294:4295 blocked for more than 122 seconds.
[ 736.982897] Call Trace:
[ 736.982901] schedule+x68/xe
[ 736.982903] io_uring_cancel_sqpoll+xdb/x110
[ 736.982908] io_sqpoll_cancel_cb+x24/x30
[ 736.982911] io_run_task_work_head+x28/x50
[ 736.982913] io_sq_thread+x4e3/x720
Notice how the SQPOLL thread is stuck in schedule(), unable to make progress.
Here's a simplified version of the problematic code (from before the patch)
void io_uring_cancel_sqpoll(struct io_ring_ctx *ctx)
{
// ...setup code...
while (atomic_read(&ctx->inflight) > )
schedule(); // Wait until inflight drops to zero
// ...cleanup...
}
But when sharing SQPOLL, ctx->inflight could count requests not belonging to this context, leading to the thread waiting for requests that _will never complete_. Result: permanent hang.
The Fix: Cancel All Shared Contexts Atomically
The commit (see original patch) changes cancellation to target all contexts sharing the SQPOLL thread at once. The solution avoids removing a context from the list until _after_ the cancellation callback has run; otherwise, cancellation code could never locate the right context(s) in the shared SQPOLL list, causing deadlocks.
The result: no more unexpected sleep; shared counters are correctly decremented, and all remaining requests from all sharing contexts are canceled together, allowing graceful teardown.
Here’s a simplified fix illustration
// Instead of one ctx, iterate all sharing same SQPOLL:
list_for_each_entry(shared_ctx, &sqpoll->ctx_list, list)
cancel_inflight_requests(shared_ctx);
schedule(); // Now waits for ALL to clear
Exploiting the Bug
No fancy exploit is needed to trigger the vulnerability—simply use io_uring with shared SQPOLL, then try to cancel or tear down when in-flight requests are present. The kernel thread(s) will hang, leading to resource exhaustion, potential application deadlock, and possibly denial of service.
Proof of concept:
Run several io_uring-using processes with shared SQPOLL, perform heavy I/O, then terminate abruptly. Kernel gets stuck, and system may need a hard reset.
Who Is Affected?
- Linux kernel users with io_uring and SQPOLL, especially in multi-tenanted or high-performance environments.
- Kernel versions: Prior to kernel v5.14-rc7 / v5.13.13 (see upstream release notes).
References
- io_uring Documentation
- Upstream kernel patch commit
- CVE Report on cve.mitre.org
- Linux kernel mailing list discussion
Conclusion
If you’re running io_uring with shared SQPOLL on your Linux systems, patch _now_!
Deadlocks in kernel threads are no joke—they can freeze applications, force reboots, and cause data loss. Upgrading to a fixed kernel version removes this risk and ensures your async I/O stacks remain as bulletproof as possible.
Timeline
Published on: 02/27/2024 19:04:06 UTC
Last modified on: 04/10/2024 19:56:14 UTC