]> Pileus Git - ~andy/linux/blobdiff - drivers/net/tun.c
tuntap: fix leaking reference count
[~andy/linux] / drivers / net / tun.c
index 504f7f1cad94655a52526b4fe136fb1468942f1d..af372d0957fe3c6c7bc47e12df7d2c1e827c7f20 100644 (file)
@@ -180,7 +180,6 @@ struct tun_struct {
        int debug;
 #endif
        spinlock_t lock;
-       struct kmem_cache *flow_cache;
        struct hlist_head flows[TUN_NUM_FLOW_ENTRIES];
        struct timer_list flow_gc_timer;
        unsigned long ageing_time;
@@ -209,8 +208,8 @@ static struct tun_flow_entry *tun_flow_create(struct tun_struct *tun,
                                              struct hlist_head *head,
                                              u32 rxhash, u16 queue_index)
 {
-       struct tun_flow_entry *e = kmem_cache_alloc(tun->flow_cache,
-                                                   GFP_ATOMIC);
+       struct tun_flow_entry *e = kmalloc(sizeof(*e), GFP_ATOMIC);
+
        if (e) {
                tun_debug(KERN_INFO, tun, "create flow: hash %u index %u\n",
                          rxhash, queue_index);
@@ -223,19 +222,12 @@ static struct tun_flow_entry *tun_flow_create(struct tun_struct *tun,
        return e;
 }
 
-static void tun_flow_free(struct rcu_head *head)
-{
-       struct tun_flow_entry *e
-               = container_of(head, struct tun_flow_entry, rcu);
-       kmem_cache_free(e->tun->flow_cache, e);
-}
-
 static void tun_flow_delete(struct tun_struct *tun, struct tun_flow_entry *e)
 {
        tun_debug(KERN_INFO, tun, "delete flow: hash %u index %u\n",
                  e->rxhash, e->queue_index);
        hlist_del_rcu(&e->hash_link);
-       call_rcu(&e->rcu, tun_flow_free);
+       kfree_rcu(e, rcu);
 }
 
 static void tun_flow_flush(struct tun_struct *tun)
@@ -412,8 +404,8 @@ static void __tun_detach(struct tun_file *tfile, bool clean)
        struct tun_struct *tun;
        struct net_device *dev;
 
-       tun = rcu_dereference_protected(tfile->tun,
-                                       lockdep_rtnl_is_held());
+       tun = rtnl_dereference(tfile->tun);
+
        if (tun) {
                u16 index = tfile->queue_index;
                BUG_ON(index >= tun->numqueues);
@@ -422,8 +414,7 @@ static void __tun_detach(struct tun_file *tfile, bool clean)
                rcu_assign_pointer(tun->tfiles[index],
                                   tun->tfiles[tun->numqueues - 1]);
                rcu_assign_pointer(tfile->tun, NULL);
-               ntfile = rcu_dereference_protected(tun->tfiles[index],
-                                                  lockdep_rtnl_is_held());
+               ntfile = rtnl_dereference(tun->tfiles[index]);
                ntfile->queue_index = index;
 
                --tun->numqueues;
@@ -437,8 +428,10 @@ static void __tun_detach(struct tun_file *tfile, bool clean)
                /* Drop read queue */
                skb_queue_purge(&tfile->sk.sk_receive_queue);
                tun_set_real_num_queues(tun);
-       } else if (tfile->detached && clean)
+       } else if (tfile->detached && clean) {
                tun = tun_enable_queue(tfile);
+               sock_put(&tfile->sk);
+       }
 
        if (clean) {
                if (tun && tun->numqueues == 0 && tun->numdisabled == 0 &&
@@ -466,8 +459,7 @@ static void tun_detach_all(struct net_device *dev)
        int i, n = tun->numqueues;
 
        for (i = 0; i < n; i++) {
-               tfile = rcu_dereference_protected(tun->tfiles[i],
-                                                 lockdep_rtnl_is_held());
+               tfile = rtnl_dereference(tun->tfiles[i]);
                BUG_ON(!tfile);
                wake_up_all(&tfile->wq.wait);
                rcu_assign_pointer(tfile->tun, NULL);
@@ -477,8 +469,7 @@ static void tun_detach_all(struct net_device *dev)
 
        synchronize_net();
        for (i = 0; i < n; i++) {
-               tfile = rcu_dereference_protected(tun->tfiles[i],
-                                                 lockdep_rtnl_is_held());
+               tfile = rtnl_dereference(tun->tfiles[i]);
                /* Drop read queue */
                skb_queue_purge(&tfile->sk.sk_receive_queue);
                sock_put(&tfile->sk);
@@ -489,6 +480,9 @@ static void tun_detach_all(struct net_device *dev)
                sock_put(&tfile->sk);
        }
        BUG_ON(tun->numdisabled != 0);
+
+       if (tun->flags & TUN_PERSIST)
+               module_put(THIS_MODULE);
 }
 
 static int tun_attach(struct tun_struct *tun, struct file *file)
@@ -497,7 +491,7 @@ static int tun_attach(struct tun_struct *tun, struct file *file)
        int err;
 
        err = -EINVAL;
-       if (rcu_dereference_protected(tfile->tun, lockdep_rtnl_is_held()))
+       if (rtnl_dereference(tfile->tun))
                goto out;
 
        err = -EBUSY;
@@ -833,12 +827,6 @@ static int tun_flow_init(struct tun_struct *tun)
 {
        int i;
 
-       tun->flow_cache = kmem_cache_create("tun_flow_cache",
-                                           sizeof(struct tun_flow_entry), 0, 0,
-                                           NULL);
-       if (!tun->flow_cache)
-               return -ENOMEM;
-
        for (i = 0; i < TUN_NUM_FLOW_ENTRIES; i++)
                INIT_HLIST_HEAD(&tun->flows[i]);
 
@@ -854,10 +842,6 @@ static void tun_flow_uninit(struct tun_struct *tun)
 {
        del_timer_sync(&tun->flow_gc_timer);
        tun_flow_flush(tun);
-
-       /* Wait for completion of call_rcu()'s */
-       rcu_barrier();
-       kmem_cache_destroy(tun->flow_cache);
 }
 
 /* Initialize net device. */
@@ -1562,6 +1546,9 @@ static int tun_set_iff(struct net *net, struct file *file, struct ifreq *ifr)
        struct net_device *dev;
        int err;
 
+       if (tfile->detached)
+               return -EINVAL;
+
        dev = __dev_get_by_name(net, ifr->ifr_name);
        if (dev) {
                if (ifr->ifr_flags & IFF_TUN_EXCL)
@@ -1756,8 +1743,7 @@ static void tun_detach_filter(struct tun_struct *tun, int n)
        struct tun_file *tfile;
 
        for (i = 0; i < n; i++) {
-               tfile = rcu_dereference_protected(tun->tfiles[i],
-                                                 lockdep_rtnl_is_held());
+               tfile = rtnl_dereference(tun->tfiles[i]);
                sk_detach_filter(tfile->socket.sk);
        }
 
@@ -1770,8 +1756,7 @@ static int tun_attach_filter(struct tun_struct *tun)
        struct tun_file *tfile;
 
        for (i = 0; i < tun->numqueues; i++) {
-               tfile = rcu_dereference_protected(tun->tfiles[i],
-                                                 lockdep_rtnl_is_held());
+               tfile = rtnl_dereference(tun->tfiles[i]);
                ret = sk_attach_filter(&tun->fprog, tfile->socket.sk);
                if (ret) {
                        tun_detach_filter(tun, i);
@@ -1789,8 +1774,7 @@ static void tun_set_sndbuf(struct tun_struct *tun)
        int i;
 
        for (i = 0; i < tun->numqueues; i++) {
-               tfile = rcu_dereference_protected(tun->tfiles[i],
-                                               lockdep_rtnl_is_held());
+               tfile = rtnl_dereference(tun->tfiles[i]);
                tfile->socket.sk->sk_sndbuf = tun->sndbuf;
        }
 }
@@ -1807,13 +1791,10 @@ static int tun_set_queue(struct file *file, struct ifreq *ifr)
                tun = tfile->detached;
                if (!tun)
                        ret = -EINVAL;
-               else if (tun_not_capable(tun))
-                       ret = -EPERM;
                else
                        ret = tun_attach(tun, file);
        } else if (ifr->ifr_flags & IFF_DETACH_QUEUE) {
-               tun = rcu_dereference_protected(tfile->tun,
-                                               lockdep_rtnl_is_held());
+               tun = rtnl_dereference(tfile->tun);
                if (!tun || !(tun->flags & TUN_TAP_MQ))
                        ret = -EINVAL;
                else
@@ -1898,10 +1879,11 @@ static long __tun_chr_ioctl(struct file *file, unsigned int cmd,
                /* Disable/Enable persist mode. Keep an extra reference to the
                 * module to prevent the module being unprobed.
                 */
-               if (arg) {
+               if (arg && !(tun->flags & TUN_PERSIST)) {
                        tun->flags |= TUN_PERSIST;
                        __module_get(THIS_MODULE);
-               } else {
+               }
+               if (!arg && (tun->flags & TUN_PERSIST)) {
                        tun->flags &= ~TUN_PERSIST;
                        module_put(THIS_MODULE);
                }