diff options
author | Nicolas Schichan <nschichan@freebox.fr> | 2013-06-26 11:23:42 -0400 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2013-06-26 16:42:54 -0400 |
commit | 5dbe7c178d3f0a4634f088d9e729f1909b9ddcd1 (patch) | |
tree | 8945b6c5125b57cee5f36e903fc995e58664a639 /net | |
parent | 6d446ec32f169c6a5d9bc90684a8082a6cbe90f6 (diff) |
net: fix kernel deadlock with interface rename and netdev name retrieval.
When the kernel (compiled with CONFIG_PREEMPT=n) is performing the
rename of a network interface, it can end up waiting for a workqueue
to complete. If userland is able to invoke a SIOCGIFNAME ioctl or a
SO_BINDTODEVICE getsockopt in between, the kernel will deadlock due to
the fact that read_secklock_begin() will spin forever waiting for the
writer process (the one doing the interface rename) to update the
devnet_rename_seq sequence.
This patch fixes the problem by adding a helper (netdev_get_name())
and using it in the code handling the SIOCGIFNAME ioctl and
SO_BINDTODEVICE setsockopt.
The netdev_get_name() helper uses raw_seqcount_begin() to avoid
spinning forever, waiting for devnet_rename_seq->sequence to become
even. cond_resched() is used in the contended case, before retrying
the access to give the writer process a chance to finish.
The use of raw_seqcount_begin() will incur some unneeded work in the
reader process in the contended case, but this is better than
deadlocking the system.
Signed-off-by: Nicolas Schichan <nschichan@freebox.fr>
Acked-by: Eric Dumazet <edumazet@google.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'net')
-rw-r--r-- | net/core/dev.c | 34 | ||||
-rw-r--r-- | net/core/dev_ioctl.c | 19 | ||||
-rw-r--r-- | net/core/sock.c | 17 |
3 files changed, 40 insertions, 30 deletions
diff --git a/net/core/dev.c b/net/core/dev.c index fc1e289397f5..faebb398fb46 100644 --- a/net/core/dev.c +++ b/net/core/dev.c | |||
@@ -792,6 +792,40 @@ struct net_device *dev_get_by_index(struct net *net, int ifindex) | |||
792 | EXPORT_SYMBOL(dev_get_by_index); | 792 | EXPORT_SYMBOL(dev_get_by_index); |
793 | 793 | ||
794 | /** | 794 | /** |
795 | * netdev_get_name - get a netdevice name, knowing its ifindex. | ||
796 | * @net: network namespace | ||
797 | * @name: a pointer to the buffer where the name will be stored. | ||
798 | * @ifindex: the ifindex of the interface to get the name from. | ||
799 | * | ||
800 | * The use of raw_seqcount_begin() and cond_resched() before | ||
801 | * retrying is required as we want to give the writers a chance | ||
802 | * to complete when CONFIG_PREEMPT is not set. | ||
803 | */ | ||
804 | int netdev_get_name(struct net *net, char *name, int ifindex) | ||
805 | { | ||
806 | struct net_device *dev; | ||
807 | unsigned int seq; | ||
808 | |||
809 | retry: | ||
810 | seq = raw_seqcount_begin(&devnet_rename_seq); | ||
811 | rcu_read_lock(); | ||
812 | dev = dev_get_by_index_rcu(net, ifindex); | ||
813 | if (!dev) { | ||
814 | rcu_read_unlock(); | ||
815 | return -ENODEV; | ||
816 | } | ||
817 | |||
818 | strcpy(name, dev->name); | ||
819 | rcu_read_unlock(); | ||
820 | if (read_seqcount_retry(&devnet_rename_seq, seq)) { | ||
821 | cond_resched(); | ||
822 | goto retry; | ||
823 | } | ||
824 | |||
825 | return 0; | ||
826 | } | ||
827 | |||
828 | /** | ||
795 | * dev_getbyhwaddr_rcu - find a device by its hardware address | 829 | * dev_getbyhwaddr_rcu - find a device by its hardware address |
796 | * @net: the applicable net namespace | 830 | * @net: the applicable net namespace |
797 | * @type: media type of device | 831 | * @type: media type of device |
diff --git a/net/core/dev_ioctl.c b/net/core/dev_ioctl.c index 6cc0481faade..5b7d0e1d0664 100644 --- a/net/core/dev_ioctl.c +++ b/net/core/dev_ioctl.c | |||
@@ -19,9 +19,8 @@ | |||
19 | 19 | ||
20 | static int dev_ifname(struct net *net, struct ifreq __user *arg) | 20 | static int dev_ifname(struct net *net, struct ifreq __user *arg) |
21 | { | 21 | { |
22 | struct net_device *dev; | ||
23 | struct ifreq ifr; | 22 | struct ifreq ifr; |
24 | unsigned seq; | 23 | int error; |
25 | 24 | ||
26 | /* | 25 | /* |
27 | * Fetch the caller's info block. | 26 | * Fetch the caller's info block. |
@@ -30,19 +29,9 @@ static int dev_ifname(struct net *net, struct ifreq __user *arg) | |||
30 | if (copy_from_user(&ifr, arg, sizeof(struct ifreq))) | 29 | if (copy_from_user(&ifr, arg, sizeof(struct ifreq))) |
31 | return -EFAULT; | 30 | return -EFAULT; |
32 | 31 | ||
33 | retry: | 32 | error = netdev_get_name(net, ifr.ifr_name, ifr.ifr_ifindex); |
34 | seq = read_seqcount_begin(&devnet_rename_seq); | 33 | if (error) |
35 | rcu_read_lock(); | 34 | return error; |
36 | dev = dev_get_by_index_rcu(net, ifr.ifr_ifindex); | ||
37 | if (!dev) { | ||
38 | rcu_read_unlock(); | ||
39 | return -ENODEV; | ||
40 | } | ||
41 | |||
42 | strcpy(ifr.ifr_name, dev->name); | ||
43 | rcu_read_unlock(); | ||
44 | if (read_seqcount_retry(&devnet_rename_seq, seq)) | ||
45 | goto retry; | ||
46 | 35 | ||
47 | if (copy_to_user(arg, &ifr, sizeof(struct ifreq))) | 36 | if (copy_to_user(arg, &ifr, sizeof(struct ifreq))) |
48 | return -EFAULT; | 37 | return -EFAULT; |
diff --git a/net/core/sock.c b/net/core/sock.c index 88868a9d21da..d6d024cfaaaf 100644 --- a/net/core/sock.c +++ b/net/core/sock.c | |||
@@ -571,9 +571,7 @@ static int sock_getbindtodevice(struct sock *sk, char __user *optval, | |||
571 | int ret = -ENOPROTOOPT; | 571 | int ret = -ENOPROTOOPT; |
572 | #ifdef CONFIG_NETDEVICES | 572 | #ifdef CONFIG_NETDEVICES |
573 | struct net *net = sock_net(sk); | 573 | struct net *net = sock_net(sk); |
574 | struct net_device *dev; | ||
575 | char devname[IFNAMSIZ]; | 574 | char devname[IFNAMSIZ]; |
576 | unsigned seq; | ||
577 | 575 | ||
578 | if (sk->sk_bound_dev_if == 0) { | 576 | if (sk->sk_bound_dev_if == 0) { |
579 | len = 0; | 577 | len = 0; |
@@ -584,20 +582,9 @@ static int sock_getbindtodevice(struct sock *sk, char __user *optval, | |||
584 | if (len < IFNAMSIZ) | 582 | if (len < IFNAMSIZ) |
585 | goto out; | 583 | goto out; |
586 | 584 | ||
587 | retry: | 585 | ret = netdev_get_name(net, devname, sk->sk_bound_dev_if); |
588 | seq = read_seqcount_begin(&devnet_rename_seq); | 586 | if (ret) |
589 | rcu_read_lock(); | ||
590 | dev = dev_get_by_index_rcu(net, sk->sk_bound_dev_if); | ||
591 | ret = -ENODEV; | ||
592 | if (!dev) { | ||
593 | rcu_read_unlock(); | ||
594 | goto out; | 587 | goto out; |
595 | } | ||
596 | |||
597 | strcpy(devname, dev->name); | ||
598 | rcu_read_unlock(); | ||
599 | if (read_seqcount_retry(&devnet_rename_seq, seq)) | ||
600 | goto retry; | ||
601 | 588 | ||
602 | len = strlen(devname) + 1; | 589 | len = strlen(devname) + 1; |
603 | 590 | ||