* [PATCH] io_uring/napi: fix list use-after-free on PREEMPT_RT
@ 2026-10-29 10:22 Anvik Loitongbam
2026-10-29 13:35 ` Steven Rostedt
2026-10-29 13:42 ` Jens Axboe
0 siblings, 2 replies; 6+ messages in thread
From: Anvik Loitongbam @ 2026-10-29 10:22 UTC (permalink / raw)
To: Jens Axboe
Cc: Pavel Begunkov, Olivier Langlois, Sebastian Andrzej Siewior,
io-uring, linux-kernel, linux-rt-devel, stable, Anvik Loitongbam
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.
Fixes: 45b3941d09d1 ("io_uring/napi: fix io_napi_entry RCU accesses")
Cc: stable@vger.kernel.org
Signed-off-by: Anvik Loitongbam <permissiontogallop@gmail.com>
---
io_uring/napi.c | 14 ++++----------
1 file changed, 4 insertions(+), 10 deletions(-)
diff --git a/io_uring/napi.c b/io_uring/napi.c
index 7e698720cd51..d299c89cfc3d 100644
--- a/io_uring/napi.c
+++ b/io_uring/napi.c
@@ -110,16 +110,10 @@ static int __io_napi_del_id(struct io_ring_ctx *ctx, unsigned int napi_id)
static void __io_napi_remove_stale(struct io_ring_ctx *ctx)
{
- struct io_napi_entry *e;
+ struct io_napi_entry *e, *next;
guard(spinlock)(&ctx->napi_lock);
- /*
- * list_for_each_entry_safe() is not required as long as:
- * 1. list_del_rcu() does not reset the deleted node next pointer
- * 2. kfree_rcu() delays the memory freeing until the next quiescent
- * state
- */
- list_for_each_entry(e, &ctx->napi_list, list) {
+ list_for_each_entry_safe(e, next, &ctx->napi_list, list) {
if (time_after(jiffies, READ_ONCE(e->timeout))) {
list_del_rcu(&e->list);
hash_del_rcu(&e->node);
@@ -264,10 +258,10 @@ void io_napi_init(struct io_ring_ctx *ctx)
*/
void io_napi_free(struct io_ring_ctx *ctx)
{
- struct io_napi_entry *e;
+ struct io_napi_entry *e, *next;
guard(spinlock)(&ctx->napi_lock);
- list_for_each_entry(e, &ctx->napi_list, list) {
+ list_for_each_entry_safe(e, next, &ctx->napi_list, list) {
hash_del_rcu(&e->node);
kfree_rcu(e, rcu);
}
--
2.56.0
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH] io_uring/napi: fix list use-after-free on PREEMPT_RT
2026-10-29 10:22 [PATCH] io_uring/napi: fix list use-after-free on PREEMPT_RT Anvik Loitongbam
@ 2026-10-29 13:35 ` Steven Rostedt
2026-10-29 13:42 ` Jens Axboe
1 sibling, 0 replies; 6+ messages in thread
From: Steven Rostedt @ 2026-10-29 13:35 UTC (permalink / raw)
To: Anvik Loitongbam
Cc: Jens Axboe, Pavel Begunkov, Olivier Langlois,
Sebastian Andrzej Siewior, io-uring, linux-kernel,
linux-rt-devel, stable
On Thu, 29 Oct 2026 15:52:44 +0530
Anvik Loitongbam <permissiontogallop@gmail.com> 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.
Not to mention, depending on the internals of list_del_rcu() and
kfree_rcu() is just not good design, regardless that it only breaks under
PREEMPT_RT.
>
> 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.
>
> Fixes: 45b3941d09d1 ("io_uring/napi: fix io_napi_entry RCU accesses")
> Cc: stable@vger.kernel.org
> Signed-off-by: Anvik Loitongbam <permissiontogallop@gmail.com>
Reviewed-by: Steven Rostedt <rostedt@goodmis.org>
-- Steve
> ---
> io_uring/napi.c | 14 ++++----------
> 1 file changed, 4 insertions(+), 10 deletions(-)
>
> diff --git a/io_uring/napi.c b/io_uring/napi.c
> index 7e698720cd51..d299c89cfc3d 100644
> --- a/io_uring/napi.c
> +++ b/io_uring/napi.c
> @@ -110,16 +110,10 @@ static int __io_napi_del_id(struct io_ring_ctx *ctx, unsigned int napi_id)
>
> static void __io_napi_remove_stale(struct io_ring_ctx *ctx)
> {
> - struct io_napi_entry *e;
> + struct io_napi_entry *e, *next;
>
> guard(spinlock)(&ctx->napi_lock);
> - /*
> - * list_for_each_entry_safe() is not required as long as:
> - * 1. list_del_rcu() does not reset the deleted node next pointer
> - * 2. kfree_rcu() delays the memory freeing until the next quiescent
> - * state
> - */
> - list_for_each_entry(e, &ctx->napi_list, list) {
> + list_for_each_entry_safe(e, next, &ctx->napi_list, list) {
> if (time_after(jiffies, READ_ONCE(e->timeout))) {
> list_del_rcu(&e->list);
> hash_del_rcu(&e->node);
> @@ -264,10 +258,10 @@ void io_napi_init(struct io_ring_ctx *ctx)
> */
> void io_napi_free(struct io_ring_ctx *ctx)
> {
> - struct io_napi_entry *e;
> + struct io_napi_entry *e, *next;
>
> guard(spinlock)(&ctx->napi_lock);
> - list_for_each_entry(e, &ctx->napi_list, list) {
> + list_for_each_entry_safe(e, next, &ctx->napi_list, list) {
> hash_del_rcu(&e->node);
> kfree_rcu(e, rcu);
> }
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH] io_uring/napi: fix list use-after-free on PREEMPT_RT
2026-10-29 10:22 [PATCH] io_uring/napi: fix list use-after-free on PREEMPT_RT Anvik Loitongbam
2026-10-29 13:35 ` Steven Rostedt
@ 2026-10-29 13:42 ` Jens Axboe
2026-10-29 13:48 ` Steven Rostedt
1 sibling, 1 reply; 6+ messages in thread
From: Jens Axboe @ 2026-10-29 13:42 UTC (permalink / raw)
To: Anvik Loitongbam
Cc: Pavel Begunkov, Olivier Langlois, Sebastian Andrzej Siewior,
io-uring, linux-kernel, linux-rt-devel, stable
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
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH] io_uring/napi: fix list use-after-free on PREEMPT_RT
2026-10-29 13:42 ` Jens Axboe
@ 2026-10-29 13:48 ` Steven Rostedt
2026-10-29 14:28 ` Jens Axboe
0 siblings, 1 reply; 6+ messages in thread
From: Steven Rostedt @ 2026-10-29 13:48 UTC (permalink / raw)
To: Jens Axboe
Cc: Anvik Loitongbam, Pavel Begunkov, Olivier Langlois,
Sebastian Andrzej Siewior, io-uring, linux-kernel,
linux-rt-devel, stable
On Thu, 29 Oct 2026 07:42:09 -0600
Jens Axboe <axboe@kernel.dk> wrote:
> 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.
Good point.
>
> 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.
I agree, it should still be applied, but as a clean up and not a fix.
-- Steve
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH] io_uring/napi: fix list use-after-free on PREEMPT_RT
2026-10-29 13:48 ` Steven Rostedt
@ 2026-10-29 14:28 ` Jens Axboe
2026-10-29 14:31 ` Steven Rostedt
0 siblings, 1 reply; 6+ messages in thread
From: Jens Axboe @ 2026-10-29 14:28 UTC (permalink / raw)
To: Steven Rostedt
Cc: Anvik Loitongbam, Pavel Begunkov, Olivier Langlois,
Sebastian Andrzej Siewior, io-uring, linux-kernel,
linux-rt-devel, stable
On 10/29/26 7:48 AM, Steven Rostedt wrote:
> On Thu, 29 Oct 2026 07:42:09 -0600
> Jens Axboe <axboe@kernel.dk> wrote:
>
>> 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.
>
> Good point.
If even the preempt rt people can't get this right, that's a little
worrying :)
>> 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.
>
> I agree, it should still be applied, but as a clean up and not a fix.
Sure, like I said, as a cleanup. But would need a resend as the
analysis in the commit message is entirely wrong.
--
Jens Axboe
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH] io_uring/napi: fix list use-after-free on PREEMPT_RT
2026-10-29 14:28 ` Jens Axboe
@ 2026-10-29 14:31 ` Steven Rostedt
0 siblings, 0 replies; 6+ messages in thread
From: Steven Rostedt @ 2026-10-29 14:31 UTC (permalink / raw)
To: Jens Axboe
Cc: Anvik Loitongbam, Pavel Begunkov, Olivier Langlois,
Sebastian Andrzej Siewior, io-uring, linux-kernel,
linux-rt-devel, stable
On Thu, 29 Oct 2026 08:28:13 -0600
Jens Axboe <axboe@kernel.dk> wrote:
> On 10/29/26 7:48 AM, Steven Rostedt wrote:
> > On Thu, 29 Oct 2026 07:42:09 -0600
> > Jens Axboe <axboe@kernel.dk> wrote:
> >
> >> 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.
> >
> > Good point.
>
> If even the preempt rt people can't get this right, that's a little
> worrying :)
I really didn't even take a deep look at how preempt-rt was involved. I
just didn't like the assumption about the RCU internals in general ;-)
>
> >> 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.
> >
> > I agree, it should still be applied, but as a clean up and not a fix.
>
> Sure, like I said, as a cleanup. But would need a resend as the
> analysis in the commit message is entirely wrong.
>
Totally agree.
-- Steve
^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-10-29 14:31 UTC | newest]
Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-29 10:22 [PATCH] io_uring/napi: fix list use-after-free on PREEMPT_RT Anvik Loitongbam
2026-10-29 13:35 ` Steven Rostedt
2026-10-29 13:42 ` Jens Axboe
2026-10-29 13:48 ` Steven Rostedt
2026-10-29 14:28 ` Jens Axboe
2026-10-29 14:31 ` Steven Rostedt
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®