[PATCH] net: procfs: Fix RCU stall and soft lockup in ptype_seq_next()

fengwei_yin@linux.alibaba.com posted 1 patch 1 week, 2 days ago
net/core/net-procfs.c | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
[PATCH] net: procfs: Fix RCU stall and soft lockup in ptype_seq_next()
Posted by fengwei_yin@linux.alibaba.com 1 week, 2 days ago
From: Yin Fengwei <fengwei_yin@linux.alibaba.com>

On an arm64 server platform, fuzz testing can trigger an RCU stall
followed by a soft lockup in ptype_seq_show(). The backtrace shows
the stall occurs in traverse.part.0(), which is called from
seq_read_iter() during /proc/net/ptype access:

[  295.799369] rcu: INFO: rcu_preempt self-detected stall on CPU
[  295.799937] rcu:     1-....: (60005 ticks this GP) idle=0f94/1/0x4000000000000000 softirq=5198/5199 fqs=29992
[  295.800708] rcu:     (t=60008 jiffies g=11197 q=1553 ncpus=2)
[  295.801152] CPU: 1 UID: 0 PID: 2695 Comm: test2 Tainted: G		L      6.19.0-rc7+ #19 PREEMPT(voluntary)
[  295.801961] Tainted: [L]=SOFTLOCKUP
[  295.802245] Hardware name: linux,dummy-virt (DT)
[  295.802616] pstate: 83400005 (Nzcv daif +PAN -UAO +TCO +DIT -SSBS BTYPE=--)
[  295.803171] pc : traverse.part.0+0x80/0x190
[  295.803511] lr : traverse.part.0+0x4c/0x190
[  295.803850] sp : ffff800082dabb20
[  295.804117] x29: ffff800082dabb20 x28: ffff000004571280 x27: 0000000000000000
[  295.804680] x26: 0000000000000000 x25: ffff00000471fee0 x24: ffff00000471fed0
[  295.805245] x23: 00000000000080db x22: 0000000000000033 x21: 0000000000000000
[  295.805814] x20: ffff000003719160 x19: ffff00000471fea8 x18: 0000000000000000
[  295.806378] x17: 0000000000000000 x16: 0000000000000000 x15: 0000000000000000
[  295.806941] x14: 0000000000000000 x13: 0a6e6f6974636e75 x12: 4620202020202065
[  295.807507] x11: 0000000000000000 x10: 0000000000000001 x9 : ffffc27ab28f881c
[  295.808081] x8 : 000000000000000a x7 : ffffc27ab3a8db42 x6 : 000000000000000a
[  295.808652] x5 : 0000000000000000 x4 : 0000000000001000 x3 : ffffc27ab328a7f8
[  295.809219] x2 : ffff00000471fed0 x1 : ffff000003719160 x0 : ffff00000471fea8
[  295.809785] Call trace:
[  295.809983]  traverse.part.0+0x80/0x190 (P)
[  295.810322]  seq_read_iter+0x2f8/0x4f8
[  295.810625]  seq_read+0xe4/0x120
[  295.810890]  proc_reg_read+0x9c/0xf8
[  295.811185]  do_loop_readv_writev.part.0+0xbc/0x118
[  295.811574]  vfs_readv+0x174/0x1d8
[  295.811848]  do_preadv+0x90/0xf8
[  295.812110]  __arm64_sys_preadv+0x24/0x38
[  295.812435]  invoke_syscall+0x4c/0x110
[  295.812737]  el0_svc_common.constprop.0+0x44/0xe8
[  295.813113]  do_el0_svc+0x20/0x30
[  295.813382]  el0_svc+0x38/0x178
[  295.813636]  el0t_64_sync_handler+0x98/0xe0
[  295.813969]  el0t_64_sync+0x184/0x188

The root cause is in ptype_seq_next(): when iterating over packet
types, it's possible that a packet type entry (pt) has been removed,
its dev set to NULL, and pt->af_packet_net is not initialized.
In that case, the function may return the same 'nxt' pointer indefinitely.
This results in an infinite loop under RCU read-side critical section,
causing an RCU stall and eventually a soft lockup.

Fix the issue by properly handling the case where 'nxt' points to
an empty list, ensuring forward progress in the iterator.

Signed-off-by: Yin Fengwei <fengwei_yin@linux.alibaba.com>
---
 net/core/net-procfs.c | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/net/core/net-procfs.c b/net/core/net-procfs.c
index 70e0e9a3b650..eb8c34bb1f0a 100644
--- a/net/core/net-procfs.c
+++ b/net/core/net-procfs.c
@@ -231,7 +231,7 @@ static void *ptype_seq_next(struct seq_file *seq, void *v, loff_t *pos)
 	pt = v;
 	nxt = pt->list.next;
 	if (pt->dev) {
-		if (nxt != &pt->dev->ptype_all)
+		if (!list_empty(nxt) && nxt != &pt->dev->ptype_all)
 			goto found;
 
 		dev = pt->dev;
@@ -247,7 +247,7 @@ static void *ptype_seq_next(struct seq_file *seq, void *v, loff_t *pos)
 
 	if (pt->af_packet_net) {
 net_ptype_all:
-		if (nxt != &net->ptype_all && nxt != &net->ptype_specific)
+		if (!list_empty(nxt) && nxt != &net->ptype_all && nxt != &net->ptype_specific)
 			goto found;
 
 		if (nxt == &net->ptype_all) {
@@ -267,6 +267,9 @@ static void *ptype_seq_next(struct seq_file *seq, void *v, loff_t *pos)
 			return NULL;
 		nxt = ptype_base[hash].next;
 	}
+
+	if (list_empty(nxt))
+		return NULL;
 found:
 	return list_entry(nxt, struct packet_type, list);
 }
-- 
2.43.5
Re: [PATCH] net: procfs: Fix RCU stall and soft lockup in ptype_seq_next()
Posted by Jakub Kicinski 1 week ago
On Wed, 28 Jan 2026 15:03:59 +0800 fengwei_yin@linux.alibaba.com wrote:
> The root cause is in ptype_seq_next(): when iterating over packet
> types, it's possible that a packet type entry (pt) has been removed,
> its dev set to NULL, and pt->af_packet_net is not initialized.
> In that case, the function may return the same 'nxt' pointer indefinitely.
> This results in an infinite loop under RCU read-side critical section,
> causing an RCU stall and eventually a soft lockup.
> 
> Fix the issue by properly handling the case where 'nxt' points to
> an empty list, ensuring forward progress in the iterator.

> @@ -247,7 +247,7 @@ static void *ptype_seq_next(struct seq_file *seq, void *v, loff_t *pos)
>  
>  	if (pt->af_packet_net) {
>  net_ptype_all:
> -		if (nxt != &net->ptype_all && nxt != &net->ptype_specific)
> +		if (!list_empty(nxt) && nxt != &net->ptype_all && nxt != &net->ptype_specific)
>  			goto found;
>  
>  		if (nxt == &net->ptype_all) {
> @@ -267,6 +267,9 @@ static void *ptype_seq_next(struct seq_file *seq, void *v, loff_t *pos)
>  			return NULL;
>  		nxt = ptype_base[hash].next;
>  	}
> +
> +	if (list_empty(nxt))
> +		return NULL;
>  found:
>  	return list_entry(nxt, struct packet_type, list);
>  }

I'm not sure this fix works, TBH, we're dealing with an RCU list here.
The elements are not deleted with list_del_init(), so they won't
look "empty".

If the pt entries are under RCU protection I think the issue is that
af_packet is clearing pt->dev before waiting for the grace period to
expire.

Willem, is there a reason for that or just convenience?
-- 
pw-bot: cr
Re: [PATCH] net: procfs: Fix RCU stall and soft lockup in ptype_seq_next()
Posted by Willem de Bruijn 6 days, 11 hours ago
Jakub Kicinski wrote:
> On Wed, 28 Jan 2026 15:03:59 +0800 fengwei_yin@linux.alibaba.com wrote:
> > The root cause is in ptype_seq_next(): when iterating over packet
> > types, it's possible that a packet type entry (pt) has been removed,
> > its dev set to NULL, and pt->af_packet_net is not initialized.
> > In that case, the function may return the same 'nxt' pointer indefinitely.
> > This results in an infinite loop under RCU read-side critical section,
> > causing an RCU stall and eventually a soft lockup.
> > 
> > Fix the issue by properly handling the case where 'nxt' points to
> > an empty list, ensuring forward progress in the iterator.
> 
> > @@ -247,7 +247,7 @@ static void *ptype_seq_next(struct seq_file *seq, void *v, loff_t *pos)
> >  
> >  	if (pt->af_packet_net) {
> >  net_ptype_all:
> > -		if (nxt != &net->ptype_all && nxt != &net->ptype_specific)
> > +		if (!list_empty(nxt) && nxt != &net->ptype_all && nxt != &net->ptype_specific)
> >  			goto found;
> >  
> >  		if (nxt == &net->ptype_all) {
> > @@ -267,6 +267,9 @@ static void *ptype_seq_next(struct seq_file *seq, void *v, loff_t *pos)
> >  			return NULL;
> >  		nxt = ptype_base[hash].next;
> >  	}
> > +
> > +	if (list_empty(nxt))
> > +		return NULL;
> >  found:
> >  	return list_entry(nxt, struct packet_type, list);
> >  }
> 
> I'm not sure this fix works, TBH, we're dealing with an RCU list here.
> The elements are not deleted with list_del_init(), so they won't
> look "empty".
> 
> If the pt entries are under RCU protection I think the issue is that
> af_packet is clearing pt->dev before waiting for the grace period to
> expire.
>
> Willem, is there a reason for that or just convenience?

That would be wrong. Do we see it doing that somewhere?

These handlers should get removed with dev_remove_pack. Or
__dev_remove_pack and observe the RCU grace period some other way.
I can review these, but was not aware of any abuses.

> -- 
> pw-bot: cr
Re: [PATCH] net: procfs: Fix RCU stall and soft lockup in ptype_seq_next()
Posted by Eric Dumazet 6 days, 11 hours ago
On Sat, Jan 31, 2026 at 6:41 PM Willem de Bruijn
<willemdebruijn.kernel@gmail.com> wrote:
>
> Jakub Kicinski wrote:
> > On Wed, 28 Jan 2026 15:03:59 +0800 fengwei_yin@linux.alibaba.com wrote:
> > > The root cause is in ptype_seq_next(): when iterating over packet
> > > types, it's possible that a packet type entry (pt) has been removed,
> > > its dev set to NULL, and pt->af_packet_net is not initialized.
> > > In that case, the function may return the same 'nxt' pointer indefinitely.
> > > This results in an infinite loop under RCU read-side critical section,
> > > causing an RCU stall and eventually a soft lockup.
> > >
> > > Fix the issue by properly handling the case where 'nxt' points to
> > > an empty list, ensuring forward progress in the iterator.
> >
> > > @@ -247,7 +247,7 @@ static void *ptype_seq_next(struct seq_file *seq, void *v, loff_t *pos)
> > >
> > >     if (pt->af_packet_net) {
> > >  net_ptype_all:
> > > -           if (nxt != &net->ptype_all && nxt != &net->ptype_specific)
> > > +           if (!list_empty(nxt) && nxt != &net->ptype_all && nxt != &net->ptype_specific)
> > >                     goto found;
> > >
> > >             if (nxt == &net->ptype_all) {
> > > @@ -267,6 +267,9 @@ static void *ptype_seq_next(struct seq_file *seq, void *v, loff_t *pos)
> > >                     return NULL;
> > >             nxt = ptype_base[hash].next;
> > >     }
> > > +
> > > +   if (list_empty(nxt))
> > > +           return NULL;
> > >  found:
> > >     return list_entry(nxt, struct packet_type, list);
> > >  }
> >
> > I'm not sure this fix works, TBH, we're dealing with an RCU list here.
> > The elements are not deleted with list_del_init(), so they won't
> > look "empty".
> >
> > If the pt entries are under RCU protection I think the issue is that
> > af_packet is clearing pt->dev before waiting for the grace period to
> > expire.
> >
> > Willem, is there a reason for that or just convenience?
>
> That would be wrong. Do we see it doing that somewhere?
>
> These handlers should get removed with dev_remove_pack. Or
> __dev_remove_pack and observe the RCU grace period some other way.
> I can review these, but was not aware of any abuses.
>

packet_notifier()

case NETDEV_DOWN:
if (dev->ifindex == po->ifindex) {
spin_lock(&po->bind_lock);
if (packet_sock_flag(po, PACKET_SOCK_RUNNING)) {
__unregister_prot_hook(sk, false);
/* removed without a synchronize_rcu() */
sk->sk_err = ENETDOWN;
if (!sock_flag(sk, SOCK_DEAD))
sk_error_report(sk);
}
if (msg == NETDEV_UNREGISTER) {
packet_cached_dev_reset(po);
WRITE_ONCE(po->ifindex, -1);
netdev_put(po->prot_hook.dev,
   &po->prot_hook.dev_tracker);
po->prot_hook.dev = NULL;                       // pointer set to NULL
}
spin_unlock(&po->bind_lock);
}
break;
Re: [PATCH] net: procfs: Fix RCU stall and soft lockup in ptype_seq_next()
Posted by Eric Dumazet 6 days, 11 hours ago
On Sat, Jan 31, 2026 at 6:50 PM Eric Dumazet <edumazet@google.com> wrote:
>
> On Sat, Jan 31, 2026 at 6:41 PM Willem de Bruijn
> <willemdebruijn.kernel@gmail.com> wrote:
> >
> > Jakub Kicinski wrote:
> > > On Wed, 28 Jan 2026 15:03:59 +0800 fengwei_yin@linux.alibaba.com wrote:
> > > > The root cause is in ptype_seq_next(): when iterating over packet
> > > > types, it's possible that a packet type entry (pt) has been removed,
> > > > its dev set to NULL, and pt->af_packet_net is not initialized.
> > > > In that case, the function may return the same 'nxt' pointer indefinitely.
> > > > This results in an infinite loop under RCU read-side critical section,
> > > > causing an RCU stall and eventually a soft lockup.
> > > >
> > > > Fix the issue by properly handling the case where 'nxt' points to
> > > > an empty list, ensuring forward progress in the iterator.
> > >
> > > > @@ -247,7 +247,7 @@ static void *ptype_seq_next(struct seq_file *seq, void *v, loff_t *pos)
> > > >
> > > >     if (pt->af_packet_net) {
> > > >  net_ptype_all:
> > > > -           if (nxt != &net->ptype_all && nxt != &net->ptype_specific)
> > > > +           if (!list_empty(nxt) && nxt != &net->ptype_all && nxt != &net->ptype_specific)
> > > >                     goto found;
> > > >
> > > >             if (nxt == &net->ptype_all) {
> > > > @@ -267,6 +267,9 @@ static void *ptype_seq_next(struct seq_file *seq, void *v, loff_t *pos)
> > > >                     return NULL;
> > > >             nxt = ptype_base[hash].next;
> > > >     }
> > > > +
> > > > +   if (list_empty(nxt))
> > > > +           return NULL;
> > > >  found:
> > > >     return list_entry(nxt, struct packet_type, list);
> > > >  }
> > >
> > > I'm not sure this fix works, TBH, we're dealing with an RCU list here.
> > > The elements are not deleted with list_del_init(), so they won't
> > > look "empty".
> > >
> > > If the pt entries are under RCU protection I think the issue is that
> > > af_packet is clearing pt->dev before waiting for the grace period to
> > > expire.
> > >
> > > Willem, is there a reason for that or just convenience?
> >
> > That would be wrong. Do we see it doing that somewhere?
> >
> > These handlers should get removed with dev_remove_pack. Or
> > __dev_remove_pack and observe the RCU grace period some other way.
> > I can review these, but was not aware of any abuses.
> >
>
> packet_notifier()
>
> case NETDEV_DOWN:
> if (dev->ifindex == po->ifindex) {
> spin_lock(&po->bind_lock);
> if (packet_sock_flag(po, PACKET_SOCK_RUNNING)) {
> __unregister_prot_hook(sk, false);
> /* removed without a synchronize_rcu() */
> sk->sk_err = ENETDOWN;
> if (!sock_flag(sk, SOCK_DEAD))
> sk_error_report(sk);
> }
> if (msg == NETDEV_UNREGISTER) {
> packet_cached_dev_reset(po);
> WRITE_ONCE(po->ifindex, -1);
> netdev_put(po->prot_hook.dev,
>    &po->prot_hook.dev_tracker);
> po->prot_hook.dev = NULL;                       // pointer set to NULL
> }
> spin_unlock(&po->bind_lock);
> }
> break;

And other places as well...

I would suggest adding proper RCU protection to prot_hook.dev
Re: [PATCH] net: procfs: Fix RCU stall and soft lockup in ptype_seq_next()
Posted by YinFengwei 5 days, 4 hours ago
Hi Eric,
> On Sat, Jan 31, 2026 at 6:50 PM Eric Dumazet <edumazet@google.com> wrote:
> >
> > On Sat, Jan 31, 2026 at 6:41 PM Willem de Bruijn
> > <willemdebruijn.kernel@gmail.com> wrote:
> > >
> > > Jakub Kicinski wrote:
> > > > On Wed, 28 Jan 2026 15:03:59 +0800 fengwei_yin@linux.alibaba.com wrote:
> > > > > The root cause is in ptype_seq_next(): when iterating over packet
> > > > > types, it's possible that a packet type entry (pt) has been removed,
> > > > > its dev set to NULL, and pt->af_packet_net is not initialized.
> > > > > In that case, the function may return the same 'nxt' pointer indefinitely.
> > > > > This results in an infinite loop under RCU read-side critical section,
> > > > > causing an RCU stall and eventually a soft lockup.
> > > > >
> > > > > Fix the issue by properly handling the case where 'nxt' points to
> > > > > an empty list, ensuring forward progress in the iterator.
> > > >
> > > > > @@ -247,7 +247,7 @@ static void *ptype_seq_next(struct seq_file *seq, void *v, loff_t *pos)
> > > > >
> > > > >     if (pt->af_packet_net) {
> > > > >  net_ptype_all:
> > > > > -           if (nxt != &net->ptype_all && nxt != &net->ptype_specific)
> > > > > +           if (!list_empty(nxt) && nxt != &net->ptype_all && nxt != &net->ptype_specific)
> > > > >                     goto found;
> > > > >
> > > > >             if (nxt == &net->ptype_all) {
> > > > > @@ -267,6 +267,9 @@ static void *ptype_seq_next(struct seq_file *seq, void *v, loff_t *pos)
> > > > >                     return NULL;
> > > > >             nxt = ptype_base[hash].next;
> > > > >     }
> > > > > +
> > > > > +   if (list_empty(nxt))
> > > > > +           return NULL;
> > > > >  found:
> > > > >     return list_entry(nxt, struct packet_type, list);
> > > > >  }
> > > >
> > > > I'm not sure this fix works, TBH, we're dealing with an RCU list here.
> > > > The elements are not deleted with list_del_init(), so they won't
> > > > look "empty".
> > > >
> > > > If the pt entries are under RCU protection I think the issue is that
> > > > af_packet is clearing pt->dev before waiting for the grace period to
> > > > expire.
> > > >
> > > > Willem, is there a reason for that or just convenience?
> > >
> > > That would be wrong. Do we see it doing that somewhere?
> > >
> > > These handlers should get removed with dev_remove_pack. Or
> > > __dev_remove_pack and observe the RCU grace period some other way.
> > > I can review these, but was not aware of any abuses.
> > >
> >
> > packet_notifier()
> >
> > case NETDEV_DOWN:
> > if (dev->ifindex == po->ifindex) {
> > spin_lock(&po->bind_lock);
> > if (packet_sock_flag(po, PACKET_SOCK_RUNNING)) {
> > __unregister_prot_hook(sk, false);
> > /* removed without a synchronize_rcu() */
> > sk->sk_err = ENETDOWN;
> > if (!sock_flag(sk, SOCK_DEAD))
> > sk_error_report(sk);
> > }
> > if (msg == NETDEV_UNREGISTER) {
> > packet_cached_dev_reset(po);
> > WRITE_ONCE(po->ifindex, -1);
> > netdev_put(po->prot_hook.dev,
> >    &po->prot_hook.dev_tracker);
> > po->prot_hook.dev = NULL;                       // pointer set to NULL
Yes. This line is the main problem which trigger the rcu stall.

> > }
> > spin_unlock(&po->bind_lock);
> > }
> > break;
> 
> And other places as well...
> 
> I would suggest adding proper RCU protection to prot_hook.dev

Agree. Using RCU to protect prot_hook.dev is the best fix. I saw
you sent the fixing patch already. Will give it a try and report
back. Thanks.


Regards
Yin, Fengwei