---
id: "CVE-2026-99231"
kind: "vulnerability"
aliases: ["CVE-2026-99231"]
title: "Linux kernel: io_uring waitid race lets a freed request be reused and corrupted"
severity: "high"
cvss: 7.8
epss: null
exploited: false
status: "open"
category: "runtime"
products: []
ecosystems: []
tags: ["io-uring", "kernel", "linux", "local", "runtime"]
not_affected_if: ["io-uring-disabled"]
published_at: "2026-11-02T16:17:48+00:00"
first_seen_at: "2026-11-02T16:24:06+00:00"
updated_at: "2026-11-02T16:29:40+00:00"
sources: [{"source": "nvd", "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-99231", "first_seen_at": "2026-11-02T16:24:06+00:00"}, {"source": "analyst", "url": null, "first_seen_at": "2026-11-02T16:29:40+00:00"}]
---

# Linux kernel: io_uring waitid race lets a freed request be reused and corrupted

A race in the Linux kernel's io_uring waitid handling can complete a request while its wake-up handler still touches it, leaving a use-after-free. The sources describe the consequence as a KASAN-reported slab use-after-free reachable through socket wake-up, and the sources do not state what an attacker can gain beyond that.

_Summary, check and fix written by a model from the sources; labelled as such._

## How to check

- Run uname -r and compare against your distro's kernel security tracker for CVE-2026-99231; upstream fixed versions are not yet published in the sources.
- Run sysctl kernel.io_uring_disabled: 0 = enabled for all, 1 = restricted to privileged/group, 2 = disabled (on kernels that have the sysctl).
- Check whether the patched io_uring/waitid.c contains the 'pairs with the smp_load_acquire() in io_waitid_remove_wq()' comment.

## Fix

Upstream patch 'io_uring/waitid: own the request before clearing ->head' only clears ->head after the handler owns the request. Fixed kernel versions unknown; take your distro's kernel update once it lists CVE-2026-99231.

## Timeline

- 2026-11-02T16:24:06+00:00 nvd https://nvd.nist.gov/vuln/detail/CVE-2026-99231
- 2026-11-02T16:29:40+00:00 analyst — Read upstream patch posting (Josef Bacik, 2026-10-15, patch 01/43 to io-uring list; Fixes: ab673c1bcaf2). No merged commit hash, stable release or fixed kernel version found; NVD/cve.org pages carry only the kernel.org text. Nothing marked verified (patch posting, not vendor advisory). Unprivileged reachability not stated in sources; CVSS vector is local/low-priv.

## Documents

- [describes] NVD CVE-2026-99231 https://nvd.nist.gov/vuln/detail/CVE-2026-99231
- [fix] [PATCH 01/43] io_uring/waitid: own the request before clearing ->head (Josef Bacik, 2026-10-15) https://lore.gnuweeb.org/io-uring/20261015-b4-io-uring-fixes-v1-1-c1556f5d7bf5@toxicpanda.com/

## From the sources

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

io_uring/waitid: own the request before clearing ->head

io_waitid_remove_wq() only takes the wait queue lock if ->head is set.
io_waitid_wait() clears ->head first thing, before it tries to become the
owner of the request by bumping ->refs. If somebody else owns the request
at that point, nothing keeps it from completing the request while the
wake up handler is still going to touch it:

CPU0 (io_waitid_wait)                 CPU1 (io_waitid, holds a ref)
list_del_init(&wait->entry)
smp_store_release(&iw->head, NULL)
                                      __do_wait() finds another child
                                      io_waitid_remove_wq()
                                        // ->head == NULL, no locking
                                      io_waitid_finish(), IOU_COMPLETE
                                      // request freed and reused
atomic_fetch_inc(&iw->refs)

The same goes for __io_waitid_cancel() if it claims the request in that
window. ->refs is part of the per opcode data of the request, so this
increments whatever the next user of the request keeps there. If that
happens to be zero, the handler concludes that it owns the request, and
queues io_waitid_cb() for it. With the window held open by a delay, and
the request reused for a receive that is waiting for data:

  BUG: KASAN: slab-use-after-free in __wake_up_common+0x1ca/0x1f0
   __wake_up_sync_key+0x36/0x50
   sock_def_readable+0xf1/0x420
   unix_stream_sendmsg+0x60b/0x1110
  Allocated by task 87:
   io_arm_apoll+0x1ed/0x940
  Freed by task 87:
   __io_submit_flush_completions+0x118d/0x1a10
   tctx_task_work_run+0x327/0x950

Only clear ->head once the handler owns the request. If it doesn't, the
owner finds ->head set and has to wait for the lock, which is held by the
caller of the handler. Deleting the already deleted entry again is
harmless.

---

_Free to read at a human pace. An API with keys and an MCP server are in preparation; early access: hello@infrastack.ai_
