aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/net/tun.c
diff options
context:
space:
mode:
authorHerbert Xu <herbert@gondor.apana.org.au>2009-04-18 10:15:52 -0400
committerDavid S. Miller <davem@davemloft.net>2009-04-20 06:01:47 -0400
commit9c3fea6ab04a7bd9298e635bf29b4a5379f6c476 (patch)
tree2b9977d85dc5f4de230a2d556e692cb30a6d945a /drivers/net/tun.c
parent5db8765a86a4cbaf45adaf8c231cf9a6ca2dcfaf (diff)
tun: Only free a netdev when all tun descriptors are closed
The commit c70f182940f988448f3c12a209d18b1edc276e33 ("tun: Fix races between tun_net_close and free_netdev") fixed a race where an asynchronous deletion of a tun device can hose a poll(2) on a tun fd attached to that device. However, this came at the cost of moving the tun wait queue into the tun file data structure. The problem with this is that it imposes restrictions on when and where the tun device can access the wait queue since the tun file may change at any time due to detaching and reattaching. In particular, now that we need to use the wait queue on the receive path it becomes difficult to properly synchronise this with the detachment of the tun device. This patch solves the original race in a different way. Since the race is only because the underlying memory gets freed, we can prevent it simply by ensuring that we don't do that until all tun descriptors ever attached to the device (even if they have since be detached because they may still be sitting in poll) have been closed. This is done by using reference counting the attached tun file descriptors. The refcount in tun->sk has been reappropriated for this purpose since it was already being used for that, albeit from the opposite angle. Note that we no longer zero tfile->tun since tun_get will return NULL anyway after the refcount on tfile hits zero. Instead it represents whether this device has ever been attached to a device. Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au> Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'drivers/net/tun.c')
-rw-r--r--drivers/net/tun.c32
1 files changed, 19 insertions, 13 deletions
diff --git a/drivers/net/tun.c b/drivers/net/tun.c
index 16716aef184c..95ae40ab8718 100644
--- a/drivers/net/tun.c
+++ b/drivers/net/tun.c
@@ -156,6 +156,7 @@ static int tun_attach(struct tun_struct *tun, struct file *file)
156 tfile->tun = tun; 156 tfile->tun = tun;
157 tun->tfile = tfile; 157 tun->tfile = tfile;
158 dev_hold(tun->dev); 158 dev_hold(tun->dev);
159 sock_hold(tun->sk);
159 atomic_inc(&tfile->count); 160 atomic_inc(&tfile->count);
160 161
161out: 162out:
@@ -165,11 +166,8 @@ out:
165 166
166static void __tun_detach(struct tun_struct *tun) 167static void __tun_detach(struct tun_struct *tun)
167{ 168{
168 struct tun_file *tfile = tun->tfile;
169
170 /* Detach from net device */ 169 /* Detach from net device */
171 netif_tx_lock_bh(tun->dev); 170 netif_tx_lock_bh(tun->dev);
172 tfile->tun = NULL;
173 tun->tfile = NULL; 171 tun->tfile = NULL;
174 netif_tx_unlock_bh(tun->dev); 172 netif_tx_unlock_bh(tun->dev);
175 173
@@ -339,6 +337,13 @@ static void tun_net_uninit(struct net_device *dev)
339 } 337 }
340} 338}
341 339
340static void tun_free_netdev(struct net_device *dev)
341{
342 struct tun_struct *tun = netdev_priv(dev);
343
344 sock_put(tun->sk);
345}
346
342/* Net device open. */ 347/* Net device open. */
343static int tun_net_open(struct net_device *dev) 348static int tun_net_open(struct net_device *dev)
344{ 349{
@@ -811,7 +816,7 @@ static void tun_setup(struct net_device *dev)
811 tun->group = -1; 816 tun->group = -1;
812 817
813 dev->ethtool_ops = &tun_ethtool_ops; 818 dev->ethtool_ops = &tun_ethtool_ops;
814 dev->destructor = free_netdev; 819 dev->destructor = tun_free_netdev;
815} 820}
816 821
817/* Trivial set of netlink ops to allow deleting tun or tap 822/* Trivial set of netlink ops to allow deleting tun or tap
@@ -848,7 +853,7 @@ static void tun_sock_write_space(struct sock *sk)
848 853
849static void tun_sock_destruct(struct sock *sk) 854static void tun_sock_destruct(struct sock *sk)
850{ 855{
851 dev_put(container_of(sk, struct tun_sock, sk)->tun->dev); 856 free_netdev(container_of(sk, struct tun_sock, sk)->tun->dev);
852} 857}
853 858
854static struct proto tun_proto = { 859static struct proto tun_proto = {
@@ -920,11 +925,8 @@ static int tun_set_iff(struct net *net, struct file *file, struct ifreq *ifr)
920 if (!sk) 925 if (!sk)
921 goto err_free_dev; 926 goto err_free_dev;
922 927
923 /* This ref count is for tun->sk. */
924 dev_hold(dev);
925 sock_init_data(&tun->socket, sk); 928 sock_init_data(&tun->socket, sk);
926 sk->sk_write_space = tun_sock_write_space; 929 sk->sk_write_space = tun_sock_write_space;
927 sk->sk_destruct = tun_sock_destruct;
928 sk->sk_sndbuf = INT_MAX; 930 sk->sk_sndbuf = INT_MAX;
929 sk->sk_sleep = &tfile->read_wait; 931 sk->sk_sleep = &tfile->read_wait;
930 932
@@ -942,11 +944,13 @@ static int tun_set_iff(struct net *net, struct file *file, struct ifreq *ifr)
942 err = -EINVAL; 944 err = -EINVAL;
943 err = register_netdevice(tun->dev); 945 err = register_netdevice(tun->dev);
944 if (err < 0) 946 if (err < 0)
945 goto err_free_dev; 947 goto err_free_sk;
948
949 sk->sk_destruct = tun_sock_destruct;
946 950
947 err = tun_attach(tun, file); 951 err = tun_attach(tun, file);
948 if (err < 0) 952 if (err < 0)
949 goto err_free_dev; 953 goto failed;
950 } 954 }
951 955
952 DBG(KERN_INFO "%s: tun_set_iff\n", tun->dev->name); 956 DBG(KERN_INFO "%s: tun_set_iff\n", tun->dev->name);
@@ -1284,14 +1288,16 @@ static int tun_chr_close(struct inode *inode, struct file *file)
1284 __tun_detach(tun); 1288 __tun_detach(tun);
1285 1289
1286 /* If desireable, unregister the netdevice. */ 1290 /* If desireable, unregister the netdevice. */
1287 if (!(tun->flags & TUN_PERSIST)) { 1291 if (!(tun->flags & TUN_PERSIST))
1288 sock_put(tun->sk);
1289 unregister_netdevice(tun->dev); 1292 unregister_netdevice(tun->dev);
1290 }
1291 1293
1292 rtnl_unlock(); 1294 rtnl_unlock();
1293 } 1295 }
1294 1296
1297 tun = tfile->tun;
1298 if (tun)
1299 sock_put(tun->sk);
1300
1295 put_net(tfile->net); 1301 put_net(tfile->net);
1296 kfree(tfile); 1302 kfree(tfile);
1297 1303