{"dataType":"CVE_RECORD","dataVersion":"5.2","cveMetadata":{"cveId":"CVE-2026-99231","assignerOrgId":"416baaa9-dc9f-4396-8d5f-8c081fb06d67","state":"PUBLISHED","assignerShortName":"Linux","dateReserved":"2026-09-25T10:25:14.402Z","datePublished":"2026-11-02T16:06:38.553Z","dateUpdated":"2026-11-06T17:15:12.226Z"},"containers":{"cna":{"providerMetadata":{"orgId":"416baaa9-dc9f-4396-8d5f-8c081fb06d67","shortName":"Linux","dateUpdated":"2026-11-06T17:15:12.226Z"},"descriptions":[{"lang":"en","value":"In the Linux kernel, the following vulnerability has been resolved:\n\nio_uring/waitid: own the request before clearing ->head\n\nio_waitid_remove_wq() only takes the wait queue lock if ->head is set.\nio_waitid_wait() clears ->head first thing, before it tries to become the\nowner of the request by bumping ->refs. If somebody else owns the request\nat that point, nothing keeps it from completing the request while the\nwake up handler is still going to touch it:\n\nCPU0 (io_waitid_wait)                 CPU1 (io_waitid, holds a ref)\nlist_del_init(&wait->entry)\nsmp_store_release(&iw->head, NULL)\n                                      __do_wait() finds another child\n                                      io_waitid_remove_wq()\n                                        // ->head == NULL, no locking\n                                      io_waitid_finish(), IOU_COMPLETE\n                                      // request freed and reused\natomic_fetch_inc(&iw->refs)\n\nThe same goes for __io_waitid_cancel() if it claims the request in that\nwindow. ->refs is part of the per opcode data of the request, so this\nincrements whatever the next user of the request keeps there. If that\nhappens to be zero, the handler concludes that it owns the request, and\nqueues io_waitid_cb() for it. With the window held open by a delay, and\nthe request reused for a receive that is waiting for data:\n\n  BUG: KASAN: slab-use-after-free in __wake_up_common+0x1ca/0x1f0\n   __wake_up_sync_key+0x36/0x50\n   sock_def_readable+0xf1/0x420\n   unix_stream_sendmsg+0x60b/0x1110\n  Allocated by task 87:\n   io_arm_apoll+0x1ed/0x940\n  Freed by task 87:\n   __io_submit_flush_completions+0x118d/0x1a10\n   tctx_task_work_run+0x327/0x950\n\nOnly clear ->head once the handler owns the request. If it doesn't, the\nowner finds ->head set and has to wait for the lock, which is held by the\ncaller of the handler. Deleting the already deleted entry again is\nharmless."}],"metrics":[{"cvssV3_1":{"version":"3.1","vectorString":"CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H","baseScore":7.8,"baseSeverity":"HIGH"},"scenarios":[{"lang":"en","value":"AV:L - io_uring_enter() submits an IORING_OP_WAITID SQE via io_submit_sqes()->io_init_req()->io_waitid_prep()/io_queue_sqe()->io_issue_sqe()->io_waitid(), which arms io_waitid_wait() on current->signal->wait_chldexit; the trigger is that local SQE plus the attacker’s own child exit through __wake_up_parent(), not a remote protocol message.\nAC:L - The attacker owns both sides of the race in io_waitid_wait(): they fork children so __wake_up_parent() runs the callback while io_waitid() still holds the issue ref in __do_wait(), or another thread’s IORING_OP_ASYNC_CANCEL claims the request in __io_waitid_cancel(), then recycle the freed io_kiocb from the request cache with a follow-up recv SQE.\nPR:L - io_uring_setup() calls io_uring_allowed(), and sysctl_io_uring_disabled defaults to 0 so no capability is required; IORING_OP_WAITID’s io_issue_def has no extra privilege flag, and kernel_waitid_prepare() only looks up the caller’s own children.\nUI:N - The same local process creates the ring, submits IORING_OP_WAITID, forks and exits its own children to wake wait_chldexit, and optionally submits IORING_OP_ASYNC_CANCEL; no other user must act.\nS:U - The use-after-free is of an io_kiocb in the host io_uring request cache; impact stays inside the same kernel authority and does not cross a VM, IOMMU, or guest/host boundary.\nC:H - After io_waitid_finish() frees the request, io_waitid_wait() still does atomic_fetch_inc(&iw->refs) on the cmd overlay of the recycled io_kiocb; occupying it with IORING_OP_RECV (as in the commit’s KASAN trace) and queueing io_waitid_cb() on that object yields a kernel read of the reused request.\nI:H - The same UAF lets io_waitid_wait() write req->io_task_work.func=io_waitid_cb on a request already reused as a receive, so io_waitid_complete() frees an in-flight poll wait; the commit’s KASAN hit in __wake_up_common() from unix_stream_sendmsg() is a type-confused write/control-flow primitive.\nA:H - Completing and freeing the io_kiocb while io_waitid_wait() still touches iw->refs, or running io_waitid_cb() on a recycled receive, produces the documented KASAN slab-use-after-free in __wake_up_common() and a kernel oops."}]}],"affected":[{"product":"Linux","vendor":"Linux","defaultStatus":"unaffected","repo":"https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git","programFiles":["io_uring/waitid.c"],"versions":[{"version":"09951a4d37e33b0197afc7fb77e39f09a3213ee6","lessThan":"bf01da1a9cca4a54e5c30b8344e52c26afb87be7","status":"affected","versionType":"git"},{"version":"934ffdd1cdef232eb125e63c2ebafb7f2deff6af","lessThan":"8381a912c65b8de16e8a848225c7c5a56b6ea834","status":"affected","versionType":"git"},{"version":"ab673c1bcaf20ac70352eeb6bf5b828462676693","lessThan":"c10ad2813e15186fb51b37a4d17fafddc99e89a5","status":"affected","versionType":"git"},{"version":"ab673c1bcaf20ac70352eeb6bf5b828462676693","lessThan":"6e11ce129b34fb9a43b2bbe838df49ea52878d5c","status":"affected","versionType":"git"},{"version":"ab673c1bcaf20ac70352eeb6bf5b828462676693","lessThan":"c254eec4364b9f4b4cbdce43be3fb4df5a651a70","status":"affected","versionType":"git"},{"version":"6.12.113","lessThan":"6.12.115","status":"affected","versionType":"semver"},{"version":"6.18.52","lessThan":"6.18.60","status":"affected","versionType":"semver"}]},{"product":"Linux","vendor":"Linux","defaultStatus":"affected","repo":"https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git","programFiles":["io_uring/waitid.c"],"versions":[{"version":"6.19","status":"affected"},{"version":"0","lessThan":"6.19","status":"unaffected","versionType":"semver"},{"version":"6.12.115","lessThanOrEqual":"6.12.*","status":"unaffected","versionType":"semver"},{"version":"6.18.60","lessThanOrEqual":"6.18.*","status":"unaffected","versionType":"semver"},{"version":"7.2.14","lessThanOrEqual":"7.2.*","status":"unaffected","versionType":"semver"},{"version":"7.3.2","lessThanOrEqual":"7.3.*","status":"unaffected","versionType":"semver"},{"version":"7.4-rc1","lessThanOrEqual":"*","status":"unaffected","versionType":"original_commit_for_fix"}]}],"cpeApplicability":[{"nodes":[{"operator":"OR","negate":false,"cpeMatch":[{"vulnerable":true,"criteria":"cpe:2.3:o:linux:linux_kernel:*:*:*:*:*:*:*:*","versionStartIncluding":"6.12.113","versionEndExcluding":"6.12.115"},{"vulnerable":true,"criteria":"cpe:2.3:o:linux:linux_kernel:*:*:*:*:*:*:*:*","versionStartIncluding":"6.18.52","versionEndExcluding":"6.18.60"},{"vulnerable":true,"criteria":"cpe:2.3:o:linux:linux_kernel:*:*:*:*:*:*:*:*","versionStartIncluding":"6.19","versionEndExcluding":"7.2.14"},{"vulnerable":true,"criteria":"cpe:2.3:o:linux:linux_kernel:*:*:*:*:*:*:*:*","versionStartIncluding":"6.19","versionEndExcluding":"7.3.2"},{"vulnerable":true,"criteria":"cpe:2.3:o:linux:linux_kernel:*:*:*:*:*:*:*:*","versionStartIncluding":"6.19","versionEndExcluding":"7.4-rc1"}]}]}],"references":[{"url":"https://git.kernel.org/stable/c/bf01da1a9cca4a54e5c30b8344e52c26afb87be7"},{"url":"https://git.kernel.org/stable/c/8381a912c65b8de16e8a848225c7c5a56b6ea834"},{"url":"https://git.kernel.org/stable/c/c10ad2813e15186fb51b37a4d17fafddc99e89a5"},{"url":"https://git.kernel.org/stable/c/6e11ce129b34fb9a43b2bbe838df49ea52878d5c"},{"url":"https://git.kernel.org/stable/c/c254eec4364b9f4b4cbdce43be3fb4df5a651a70"}],"title":"io_uring/waitid: own the request before clearing ->head","x_generator":{"engine":"bippy-1.2.0"}}}}