diff options
author | Eric Dumazet <eric.dumazet@gmail.com> | 2011-01-26 13:08:02 -0500 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2011-01-27 18:02:56 -0500 |
commit | ccf434380d1a67df2dcb9113206b77d0cb0a1cef (patch) | |
tree | 1e1a36691f42ed7448f27f71a9ca9445e88ab5f1 | |
parent | 065825402c058f4a123ddc53dbbe864cc5caaf64 (diff) |
net: fix dev_seq_next()
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>
-rw-r--r-- | include/linux/netdevice.h | 9 | ||||
-rw-r--r-- | net/core/dev.c | 11 |
2 files changed, 15 insertions, 5 deletions
diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h index 8858422c5c5d..c7d707452228 100644 --- a/include/linux/netdevice.h +++ b/include/linux/netdevice.h | |||
@@ -1447,7 +1447,7 @@ static inline struct net_device *next_net_device_rcu(struct net_device *dev) | |||
1447 | struct net *net; | 1447 | struct net *net; |
1448 | 1448 | ||
1449 | net = dev_net(dev); | 1449 | net = dev_net(dev); |
1450 | lh = rcu_dereference(dev->dev_list.next); | 1450 | lh = rcu_dereference(list_next_rcu(&dev->dev_list)); |
1451 | return lh == &net->dev_base_head ? NULL : net_device_entry(lh); | 1451 | return lh == &net->dev_base_head ? NULL : net_device_entry(lh); |
1452 | } | 1452 | } |
1453 | 1453 | ||
@@ -1457,6 +1457,13 @@ static inline struct net_device *first_net_device(struct net *net) | |||
1457 | net_device_entry(net->dev_base_head.next); | 1457 | net_device_entry(net->dev_base_head.next); |
1458 | } | 1458 | } |
1459 | 1459 | ||
1460 | static inline struct net_device *first_net_device_rcu(struct net *net) | ||
1461 | { | ||
1462 | struct list_head *lh = rcu_dereference(list_next_rcu(&net->dev_base_head)); | ||
1463 | |||
1464 | return lh == &net->dev_base_head ? NULL : net_device_entry(lh); | ||
1465 | } | ||
1466 | |||
1460 | extern int netdev_boot_setup_check(struct net_device *dev); | 1467 | extern int netdev_boot_setup_check(struct net_device *dev); |
1461 | extern unsigned long netdev_boot_base(const char *prefix, int unit); | 1468 | extern unsigned long netdev_boot_base(const char *prefix, int unit); |
1462 | extern struct net_device *dev_getbyhwaddr_rcu(struct net *net, unsigned short type, | 1469 | extern struct net_device *dev_getbyhwaddr_rcu(struct net *net, unsigned short type, |
diff --git a/net/core/dev.c b/net/core/dev.c index 1b4c07fe295f..ddd5df2b61d4 100644 --- a/net/core/dev.c +++ b/net/core/dev.c | |||
@@ -4051,12 +4051,15 @@ void *dev_seq_start(struct seq_file *seq, loff_t *pos) | |||
4051 | 4051 | ||
4052 | void *dev_seq_next(struct seq_file *seq, void *v, loff_t *pos) | 4052 | void *dev_seq_next(struct seq_file *seq, void *v, loff_t *pos) |
4053 | { | 4053 | { |
4054 | struct net_device *dev = (v == SEQ_START_TOKEN) ? | 4054 | struct net_device *dev = v; |
4055 | first_net_device(seq_file_net(seq)) : | 4055 | |
4056 | next_net_device((struct net_device *)v); | 4056 | if (v == SEQ_START_TOKEN) |
4057 | dev = first_net_device_rcu(seq_file_net(seq)); | ||
4058 | else | ||
4059 | dev = next_net_device_rcu(dev); | ||
4057 | 4060 | ||
4058 | ++*pos; | 4061 | ++*pos; |
4059 | return rcu_dereference(dev); | 4062 | return dev; |
4060 | } | 4063 | } |
4061 | 4064 | ||
4062 | void dev_seq_stop(struct seq_file *seq, void *v) | 4065 | void dev_seq_stop(struct seq_file *seq, void *v) |