Re: [PATCH] io_uring/napi: fix list use-after-free on PREEMPT_RT

From: Jens Axboe

Date: Thu Oct 29 2026 - 09:50:27 EST


On 10/29/26 4:22 AM, Anvik Loitongbam wrote:
> Commit 45b3941d09d1 ("io_uring/napi: fix io_napi_entry RCU accesses")
> converted the NAPI list teardown to list_for_each_entry() and
> kfree_rcu(), with a comment claiming that list_for_each_entry_safe()
> is not necessary because kfree_rcu() defers the free to the next
> quiescent state.
>
> This assumption only holds while the surrounding spinlock section is
> non-preemptible. On PREEMPT_RT, spinlock_t is an rtmutex and the task
> can be preempted while holding ctx->napi_lock. The raw lock inside
> kfree_rcu() only defers the preemption until it is released, and the
> release still happens before the iterator advances. Once the task is
> switched out, the context switch is an RCU quiescent state, if the
> preemption lasts long enough for the batch to be flushed and a grace
> period to complete, the callback frees the entry, and the iterator
> then reads e->list.next from freed memory. The same pattern exists
> in io_napi_free(). I did try to produce a uaf on an rt kernel
> build, but it did not work.
>
> All list mutators are serialized by ctx->napi_lock, so the entry after
> the current one cannot be freed before its own iteration. Caching it
> first with list_for_each_entry_safe() in both loops might be a more
> safer and correct approach.

I don't think your analysis is fully correct. On PREEMPT_RT, spin_lock()
should still be an RCU read side critical section. __rt_spin_lock()
calls rcu_read_lock() after taking the rtmutex, and then
rt_spin_unlock() drops it at the end. There's even a comment on
kfree_rcu() callbacks in there too, it should not be able to run until
napi_lock is released, which is post the loop.

IOW, I think this patch is unnecessary - there's no bug there. The list
safe change could be done as a cleanup, but it's not a fix.

--
Jens Axboe