net: fix dev_seq_next()
authorEric Dumazet <eric.dumazet@gmail.com>
Wed, 26 Jan 2011 18:08:02 +0000 (18:08 +0000)
committerDavid S. Miller <davem@davemloft.net>
Thu, 27 Jan 2011 23:02:56 +0000 (15:02 -0800)
Commit c6d14c84566d (net: Introduce for_each_netdev_rcu() iterator)
added a race in dev_seq_next().

The rcu_dereference() call should be done _before_ testing the end of
list, or we might return a wrong net_device if a concurrent thread
changes net_device list under us.

Note : discovered thanks to a sparse warning :

net/core/dev.c:3919:9: error: incompatible types in comparison expression
(different address spaces)

Signed-off-by: Eric Dumazet <eric.dumazet@gmail.com>
CC: Paul E. McKenney <paulmck@linux.vnet.ibm.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
include/linux/netdevice.h
net/core/dev.c

index 8858422..c7d7074 100644 (file)
@@ -1447,7 +1447,7 @@ static inline struct net_device *next_net_device_rcu(struct net_device *dev)
        struct net *net;
 
        net = dev_net(dev);
-       lh = rcu_dereference(dev->dev_list.next);
+       lh = rcu_dereference(list_next_rcu(&dev->dev_list));
        return lh == &net->dev_base_head ? NULL : net_device_entry(lh);
 }
 
@@ -1457,6 +1457,13 @@ static inline struct net_device *first_net_device(struct net *net)
                net_device_entry(net->dev_base_head.next);
 }
 
+static inline struct net_device *first_net_device_rcu(struct net *net)
+{
+       struct list_head *lh = rcu_dereference(list_next_rcu(&net->dev_base_head));
+
+       return lh == &net->dev_base_head ? NULL : net_device_entry(lh);
+}
+
 extern int                     netdev_boot_setup_check(struct net_device *dev);
 extern unsigned long           netdev_boot_base(const char *prefix, int unit);
 extern struct net_device *dev_getbyhwaddr_rcu(struct net *net, unsigned short type,
index 1b4c07f..ddd5df2 100644 (file)
@@ -4051,12 +4051,15 @@ void *dev_seq_start(struct seq_file *seq, loff_t *pos)
 
 void *dev_seq_next(struct seq_file *seq, void *v, loff_t *pos)
 {
-       struct net_device *dev = (v == SEQ_START_TOKEN) ?
-                                 first_net_device(seq_file_net(seq)) :
-                                 next_net_device((struct net_device *)v);
+       struct net_device *dev = v;
+
+       if (v == SEQ_START_TOKEN)
+               dev = first_net_device_rcu(seq_file_net(seq));
+       else
+               dev = next_net_device_rcu(dev);
 
        ++*pos;
-       return rcu_dereference(dev);
+       return dev;
 }
 
 void dev_seq_stop(struct seq_file *seq, void *v)