CVE-2026-46274
Description
Linux kernel io_wq_remove_pending() mishandles hashed vs non-hashed work, leading to a use-after-free vulnerability.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Linux kernel io_wq_remove_pending() mishandles hashed vs non-hashed work, leading to a use-after-free vulnerability.
Vulnerability
The Linux kernel's io_wq_remove_pending() function incorrectly handles the cleanup of hashed work queues. Specifically, it fails to verify that a predecessor in a work list is actually hashed before updating the hash_tail pointer. This can occur when hashed bucket-0 work is cancelled and its predecessor is non-hashed. The function io_get_work_hash() returns 0 for non-hashed work, causing the check to pass spuriously. This results in wq->hash_tail[0] being updated with a pointer to non-hashed io_kiocb work.
Exploitation
An attacker can trigger this vulnerability by cancelling hashed bucket-0 work when a non-hashed work item precedes it in the list. The io_wq is task-specific and persists across ring operations. After the non-hashed io_kiocb completes and is freed, wq->hash_tail[0] becomes a dangling pointer. The next time hashed bucket-0 work is enqueued, io_wq_insert_work() and wq_list_add_after() will dereference this stale pointer.
Impact
Dereferencing the dangling pointer leads to a use-after-free vulnerability. This can result in a crash or potentially arbitrary code execution within the context of the affected task, depending on the state of the freed memory and subsequent operations.
Mitigation
This vulnerability has been resolved in the Linux kernel. The fix involves adding a check for io_wq_is_hashed() to ensure that a non-hashed predecessor does not incorrectly inherit a hash_tail[] slot. The specific commit addressing this issue is d6bda9df0c0a3080804181464d5c0f4d78a4e769 [1].
AI Insight generated on Jun 8, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected products
1Patches
10d6a2d7b04b5aio-wq: check that the predecessor is hashed in io_wq_remove_pending()
1 file changed · +2 −2
io_uring/io-wq.c+2 −2 modifieddiff --git a/io_uring/io-wq.c b/io_uring/io-wq.c index 7a9f94a0ce6f2..8cc7b47d30894 100644 --- a/io_uring/io-wq.c +++ b/io_uring/io-wq.c @@ -1124,7 +1124,8 @@ static inline void io_wq_remove_pending(struct io_wq *wq, if (io_wq_is_hashed(work) && work == wq->hash_tail[hash]) { if (prev) prev_work = container_of(prev, struct io_wq_work, list); - if (prev_work && io_get_work_hash(prev_work) == hash) + if (prev_work && io_wq_is_hashed(prev_work) && + io_get_work_hash(prev_work) == hash) wq->hash_tail[hash] = prev_work; else wq->hash_tail[hash] = NULL; -- cgit 1.3-korg
5a20ebf0c81bio-wq: check that the predecessor is hashed in io_wq_remove_pending()
1 file changed · +2 −2
io_uring/io-wq.c+2 −2 modifieddiff --git a/io_uring/io-wq.c b/io_uring/io-wq.c index faa00f163e236..0aa32ec6de630 100644 --- a/io_uring/io-wq.c +++ b/io_uring/io-wq.c @@ -1044,7 +1044,8 @@ static inline void io_wq_remove_pending(struct io_wq *wq, if (io_wq_is_hashed(work) && work == wq->hash_tail[hash]) { if (prev) prev_work = container_of(prev, struct io_wq_work, list); - if (prev_work && io_get_work_hash(prev_work) == hash) + if (prev_work && io_wq_is_hashed(prev_work) && + io_get_work_hash(prev_work) == hash) wq->hash_tail[hash] = prev_work; else wq->hash_tail[hash] = NULL; -- cgit 1.3-korg
d6bda9df0c0aio-wq: check that the predecessor is hashed in io_wq_remove_pending()
1 file changed · +2 −2
io_uring/io-wq.c+2 −2 modifieddiff --git a/io_uring/io-wq.c b/io_uring/io-wq.c index c848a5018d126..d8123dc5d742c 100644 --- a/io_uring/io-wq.c +++ b/io_uring/io-wq.c @@ -1046,7 +1046,8 @@ static inline void io_wq_remove_pending(struct io_wq *wq, if (io_wq_is_hashed(work) && work == wq->hash_tail[hash]) { if (prev) prev_work = container_of(prev, struct io_wq_work, list); - if (prev_work && io_get_work_hash(prev_work) == hash) + if (prev_work && io_wq_is_hashed(prev_work) && + io_get_work_hash(prev_work) == hash) wq->hash_tail[hash] = prev_work; else wq->hash_tail[hash] = NULL; -- cgit 1.3-korg
252c5051dba9io-wq: check that the predecessor is hashed in io_wq_remove_pending()
1 file changed · +2 −2
io_uring/io-wq.c+2 −2 modifieddiff --git a/io_uring/io-wq.c b/io_uring/io-wq.c index 49a9c914b4e97..bd47d32f71c25 100644 --- a/io_uring/io-wq.c +++ b/io_uring/io-wq.c @@ -1125,7 +1125,8 @@ static inline void io_wq_remove_pending(struct io_wq *wq, if (io_wq_is_hashed(work) && work == wq->hash_tail[hash]) { if (prev) prev_work = container_of(prev, struct io_wq_work, list); - if (prev_work && io_get_work_hash(prev_work) == hash) + if (prev_work && io_wq_is_hashed(prev_work) && + io_get_work_hash(prev_work) == hash) wq->hash_tail[hash] = prev_work; else wq->hash_tail[hash] = NULL; -- cgit 1.3-korg
d376c131af7cio-wq: check that the predecessor is hashed in io_wq_remove_pending()
1 file changed · +2 −2
io_uring/io-wq.c+2 −2 modifieddiff --git a/io_uring/io-wq.c b/io_uring/io-wq.c index 7a9f94a0ce6f2..8cc7b47d30894 100644 --- a/io_uring/io-wq.c +++ b/io_uring/io-wq.c @@ -1124,7 +1124,8 @@ static inline void io_wq_remove_pending(struct io_wq *wq, if (io_wq_is_hashed(work) && work == wq->hash_tail[hash]) { if (prev) prev_work = container_of(prev, struct io_wq_work, list); - if (prev_work && io_get_work_hash(prev_work) == hash) + if (prev_work && io_wq_is_hashed(prev_work) && + io_get_work_hash(prev_work) == hash) wq->hash_tail[hash] = prev_work; else wq->hash_tail[hash] = NULL; -- cgit 1.3-korg
5a20ebf0c81bio-wq: check that the predecessor is hashed in io_wq_remove_pending()
1 file changed · +2 −2
io_uring/io-wq.c+2 −2 modifieddiff --git a/io_uring/io-wq.c b/io_uring/io-wq.c index faa00f163e236..0aa32ec6de630 100644 --- a/io_uring/io-wq.c +++ b/io_uring/io-wq.c @@ -1044,7 +1044,8 @@ static inline void io_wq_remove_pending(struct io_wq *wq, if (io_wq_is_hashed(work) && work == wq->hash_tail[hash]) { if (prev) prev_work = container_of(prev, struct io_wq_work, list); - if (prev_work && io_get_work_hash(prev_work) == hash) + if (prev_work && io_wq_is_hashed(prev_work) && + io_get_work_hash(prev_work) == hash) wq->hash_tail[hash] = prev_work; else wq->hash_tail[hash] = NULL; -- cgit 1.3-korg
252c5051dba9io-wq: check that the predecessor is hashed in io_wq_remove_pending()
1 file changed · +2 −2
io_uring/io-wq.c+2 −2 modifieddiff --git a/io_uring/io-wq.c b/io_uring/io-wq.c index 49a9c914b4e97..bd47d32f71c25 100644 --- a/io_uring/io-wq.c +++ b/io_uring/io-wq.c @@ -1125,7 +1125,8 @@ static inline void io_wq_remove_pending(struct io_wq *wq, if (io_wq_is_hashed(work) && work == wq->hash_tail[hash]) { if (prev) prev_work = container_of(prev, struct io_wq_work, list); - if (prev_work && io_get_work_hash(prev_work) == hash) + if (prev_work && io_wq_is_hashed(prev_work) && + io_get_work_hash(prev_work) == hash) wq->hash_tail[hash] = prev_work; else wq->hash_tail[hash] = NULL; -- cgit 1.3-korg
d376c131af7cio-wq: check that the predecessor is hashed in io_wq_remove_pending()
1 file changed · +2 −2
io_uring/io-wq.c+2 −2 modifieddiff --git a/io_uring/io-wq.c b/io_uring/io-wq.c index 7a9f94a0ce6f2..8cc7b47d30894 100644 --- a/io_uring/io-wq.c +++ b/io_uring/io-wq.c @@ -1124,7 +1124,8 @@ static inline void io_wq_remove_pending(struct io_wq *wq, if (io_wq_is_hashed(work) && work == wq->hash_tail[hash]) { if (prev) prev_work = container_of(prev, struct io_wq_work, list); - if (prev_work && io_get_work_hash(prev_work) == hash) + if (prev_work && io_wq_is_hashed(prev_work) && + io_get_work_hash(prev_work) == hash) wq->hash_tail[hash] = prev_work; else wq->hash_tail[hash] = NULL; -- cgit 1.3-korg
d6bda9df0c0aio-wq: check that the predecessor is hashed in io_wq_remove_pending()
1 file changed · +2 −2
io_uring/io-wq.c+2 −2 modifieddiff --git a/io_uring/io-wq.c b/io_uring/io-wq.c index c848a5018d126..d8123dc5d742c 100644 --- a/io_uring/io-wq.c +++ b/io_uring/io-wq.c @@ -1046,7 +1046,8 @@ static inline void io_wq_remove_pending(struct io_wq *wq, if (io_wq_is_hashed(work) && work == wq->hash_tail[hash]) { if (prev) prev_work = container_of(prev, struct io_wq_work, list); - if (prev_work && io_get_work_hash(prev_work) == hash) + if (prev_work && io_wq_is_hashed(prev_work) && + io_get_work_hash(prev_work) == hash) wq->hash_tail[hash] = prev_work; else wq->hash_tail[hash] = NULL; -- cgit 1.3-korg
d6a2d7b04b5aio-wq: check that the predecessor is hashed in io_wq_remove_pending()
1 file changed · +2 −2
io_uring/io-wq.c+2 −2 modifieddiff --git a/io_uring/io-wq.c b/io_uring/io-wq.c index 7a9f94a0ce6f2..8cc7b47d30894 100644 --- a/io_uring/io-wq.c +++ b/io_uring/io-wq.c @@ -1124,7 +1124,8 @@ static inline void io_wq_remove_pending(struct io_wq *wq, if (io_wq_is_hashed(work) && work == wq->hash_tail[hash]) { if (prev) prev_work = container_of(prev, struct io_wq_work, list); - if (prev_work && io_get_work_hash(prev_work) == hash) + if (prev_work && io_wq_is_hashed(prev_work) && + io_get_work_hash(prev_work) == hash) wq->hash_tail[hash] = prev_work; else wq->hash_tail[hash] = NULL; -- cgit 1.3-korg
Vulnerability mechanics
No source-code context for this CVE — mechanics is only generated when we can read the actual fix diff. Without that, the four sections (root cause, attack vector, affected code, fix) would be speculation rather than analysis.
References
5- git.kernel.org/stable/c/252c5051dba9c709b6a72f2866f93e5e618b3f06nvd
- git.kernel.org/stable/c/5a20ebf0c81b61f5ea3b1b529c100cad69b9f603nvd
- git.kernel.org/stable/c/d376c131af7c7739a87ff037ed2fdb67c2542c8anvd
- git.kernel.org/stable/c/d6a2d7b04b5a093021a7a0e2e69e9d5237dfa8ccnvd
- git.kernel.org/stable/c/d6bda9df0c0a3080804181464d5c0f4d78a4e769nvd
News mentions
0No linked articles in our index yet.