CVE-2026-46274

io-wq: check that the predecessor is hashed in io_wq_remove_pending()

High · published June 8, 2026

CVSS v3.1
7.8
EPSS
0%
Percentile
2.9
In the wild
Unconfirmed
What it is

In the Linux kernel, the following vulnerability has been resolved:

io-wq: check that the predecessor is hashed in io_wq_remove_pending()

io_wq_remove_pending() needs to fix up wq->hash_tail[] if the cancelled

work was the tail of its hash bucket. When doing this, it checks whether

the preceding entry in acct->work_list has the same hash value, but

never checks that the predecessor is hashed at all. io_get_work_hash()

is simply atomic_read(&work->flags) >> IO_WQ_HASH_SHIFT, and the hash

bits are never set for non-hashed work, so it returns 0. Thus, when a

hashed bucket-0 work is cancelled while a non-hashed work is its list

predecessor, the check spuriously passes and a pointer to the non-hashed

io_kiocb is stored in wq->hash_tail[0].

Because non-hashed work is dequeued via the fast path in

io_get_next_work(), which never touches hash_tail[], the stale pointer

is never cleared. Therefore, after the non-hashed io_kiocb completes and

is freed back to req_cachep, wq->hash_tail[0] is a dangling pointer. The

io_wq is per-task (tctx->io_wq) and survives ring open/close, so the

dangling pointer persists for the lifetime of the task; the next hashed

bucket-0 enqueue dereferences it in io_wq_insert_work() and

wq_list_add_after() writes through freed memory.

Add the missing io_wq_is_hashed() check so a non-hashed predecessor

never inherits a hash_tail[] slot.

The record
Technical detail
CVSS v3.1
7.8 · HIGH
Vector
CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
CVSS v4.0
Not supplied
EPSS
0.00138 · 2.9th percentile
Published
2026-06-08T14:30Z
EPSS history
Timeline
  • 08 JUN 14:30Z
    io-wq: check that the predecessor is hashed in io_wq_remove_pending()
    cvelistv5