net/core/net-procfs.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-)
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
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
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
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;
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
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
© 2016 - 2026 Red Hat, Inc.