]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
net: add proper RCU protection to /proc/net/ptype
authorEric Dumazet <edumazet@google.com>
Mon, 2 Feb 2026 20:52:17 +0000 (20:52 +0000)
committerJakub Kicinski <kuba@kernel.org>
Wed, 4 Feb 2026 03:20:30 +0000 (19:20 -0800)
Yin Fengwei reported an RCU stall in ptype_seq_show() and provided
a patch.

Real issue is that ptype_seq_next() and ptype_seq_show() violate
RCU rules.

ptype_seq_show() runs under rcu_read_lock(), and reads pt->dev
to get device name without any barrier.

At the same time, concurrent writers can remove a packet_type structure
(which is correctly freed after an RCU grace period) and clear pt->dev
without an RCU grace period.

Define ptype_iter_state to carry a dev pointer along seq_net_private:

struct ptype_iter_state {
struct seq_net_private p;
struct net_device *dev; // added in this patch
};

We need to record the device pointer in ptype_get_idx() and
ptype_seq_next() so that ptype_seq_show() is safe against
concurrent pt->dev changes.

We also need to add full RCU protection in ptype_seq_next().
(Missing READ_ONCE() when reading list.next values)

Many thanks to Dong Chenchen for providing a repro.

Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Fixes: 1d10f8a1f40b ("net-procfs: show net devices bound packet types")
Fixes: c353e8983e0d ("net: introduce per netns packet chains")
Reported-by: Yin Fengwei <fengwei_yin@linux.alibaba.com>
Reported-by: Dong Chenchen <dongchenchen2@huawei.com>
Closes: https://lore.kernel.org/netdev/CANn89iKRRKPnWjJmb-_3a=sq+9h6DvTQM4DBZHT5ZRGPMzQaiA@mail.gmail.com/T/#m7b80b9fc9b9267f90e0b7aad557595f686f9c50d
Signed-off-by: Eric Dumazet <edumazet@google.com>
Reviewed-by: Willem de Bruijn <willemb@google.com>
Tested-by: Yin Fengwei <fengwei_yin@linux.alibaba.com>
Link: https://patch.msgid.link/20260202205217.2881198-1-edumazet@google.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
net/core/net-procfs.c

index 70e0e9a3b650c0753f0b865642aa372a956a4bf5..7dbfa6109f0b871d4c98cb3bad81dddfe7f32f40 100644 (file)
@@ -170,8 +170,14 @@ static const struct seq_operations softnet_seq_ops = {
        .show  = softnet_seq_show,
 };
 
+struct ptype_iter_state {
+       struct seq_net_private  p;
+       struct net_device       *dev;
+};
+
 static void *ptype_get_idx(struct seq_file *seq, loff_t pos)
 {
+       struct ptype_iter_state *iter = seq->private;
        struct list_head *ptype_list = NULL;
        struct packet_type *pt = NULL;
        struct net_device *dev;
@@ -181,12 +187,16 @@ static void *ptype_get_idx(struct seq_file *seq, loff_t pos)
        for_each_netdev_rcu(seq_file_net(seq), dev) {
                ptype_list = &dev->ptype_all;
                list_for_each_entry_rcu(pt, ptype_list, list) {
-                       if (i == pos)
+                       if (i == pos) {
+                               iter->dev = dev;
                                return pt;
+                       }
                        ++i;
                }
        }
 
+       iter->dev = NULL;
+
        list_for_each_entry_rcu(pt, &seq_file_net(seq)->ptype_all, list) {
                if (i == pos)
                        return pt;
@@ -218,6 +228,7 @@ static void *ptype_seq_start(struct seq_file *seq, loff_t *pos)
 
 static void *ptype_seq_next(struct seq_file *seq, void *v, loff_t *pos)
 {
+       struct ptype_iter_state *iter = seq->private;
        struct net *net = seq_file_net(seq);
        struct net_device *dev;
        struct packet_type *pt;
@@ -229,19 +240,21 @@ static void *ptype_seq_next(struct seq_file *seq, void *v, loff_t *pos)
                return ptype_get_idx(seq, 0);
 
        pt = v;
-       nxt = pt->list.next;
-       if (pt->dev) {
-               if (nxt != &pt->dev->ptype_all)
+       nxt = READ_ONCE(pt->list.next);
+       dev = iter->dev;
+       if (dev) {
+               if (nxt != &dev->ptype_all)
                        goto found;
 
-               dev = pt->dev;
                for_each_netdev_continue_rcu(seq_file_net(seq), dev) {
-                       if (!list_empty(&dev->ptype_all)) {
-                               nxt = dev->ptype_all.next;
+                       nxt = READ_ONCE(dev->ptype_all.next);
+                       if (nxt != &dev->ptype_all) {
+                               iter->dev = dev;
                                goto found;
                        }
                }
-               nxt = net->ptype_all.next;
+               iter->dev = NULL;
+               nxt = READ_ONCE(net->ptype_all.next);
                goto net_ptype_all;
        }
 
@@ -252,20 +265,20 @@ net_ptype_all:
 
                if (nxt == &net->ptype_all) {
                        /* continue with ->ptype_specific if it's not empty */
-                       nxt = net->ptype_specific.next;
+                       nxt = READ_ONCE(net->ptype_specific.next);
                        if (nxt != &net->ptype_specific)
                                goto found;
                }
 
                hash = 0;
-               nxt = ptype_base[0].next;
+               nxt = READ_ONCE(ptype_base[0].next);
        } else
                hash = ntohs(pt->type) & PTYPE_HASH_MASK;
 
        while (nxt == &ptype_base[hash]) {
                if (++hash >= PTYPE_HASH_SIZE)
                        return NULL;
-               nxt = ptype_base[hash].next;
+               nxt = READ_ONCE(ptype_base[hash].next);
        }
 found:
        return list_entry(nxt, struct packet_type, list);
@@ -279,19 +292,24 @@ static void ptype_seq_stop(struct seq_file *seq, void *v)
 
 static int ptype_seq_show(struct seq_file *seq, void *v)
 {
+       struct ptype_iter_state *iter = seq->private;
        struct packet_type *pt = v;
+       struct net_device *dev;
 
-       if (v == SEQ_START_TOKEN)
+       if (v == SEQ_START_TOKEN) {
                seq_puts(seq, "Type Device      Function\n");
-       else if ((!pt->af_packet_net || net_eq(pt->af_packet_net, seq_file_net(seq))) &&
-                (!pt->dev || net_eq(dev_net(pt->dev), seq_file_net(seq)))) {
+               return 0;
+       }
+       dev = iter->dev;
+       if ((!pt->af_packet_net || net_eq(pt->af_packet_net, seq_file_net(seq))) &&
+                (!dev || net_eq(dev_net(dev), seq_file_net(seq)))) {
                if (pt->type == htons(ETH_P_ALL))
                        seq_puts(seq, "ALL ");
                else
                        seq_printf(seq, "%04x", ntohs(pt->type));
 
                seq_printf(seq, " %-8s %ps\n",
-                          pt->dev ? pt->dev->name : "", pt->func);
+                          dev ? dev->name : "", pt->func);
        }
 
        return 0;
@@ -315,7 +333,7 @@ static int __net_init dev_proc_net_init(struct net *net)
                         &softnet_seq_ops))
                goto out_dev;
        if (!proc_create_net("ptype", 0444, net->proc_net, &ptype_seq_ops,
-                       sizeof(struct seq_net_private)))
+                       sizeof(struct ptype_iter_state)))
                goto out_softnet;
 
        if (wext_proc_init(net))