]> git.proxmox.com Git - mirror_ubuntu-bionic-kernel.git/blobdiff - drivers/net/tun.c
tun: add mutex_unlock() call and napi.skb clearing in tun_get_user()
[mirror_ubuntu-bionic-kernel.git] / drivers / net / tun.c
index dcf6c615702f439ab5662690cd6384c2fd49dec5..83d267ded3465b9ce10e0eb2ba1ed9befd0670bd 100644 (file)
@@ -281,8 +281,8 @@ static void tun_napi_init(struct tun_struct *tun, struct tun_file *tfile,
        tfile->napi_enabled = napi_en;
        tfile->napi_frags_enabled = napi_en && napi_frags;
        if (napi_en) {
-               netif_napi_add(tun->dev, &tfile->napi, tun_napi_poll,
-                              NAPI_POLL_WEIGHT);
+               netif_tx_napi_add(tun->dev, &tfile->napi, tun_napi_poll,
+                                 NAPI_POLL_WEIGHT);
                napi_enable(&tfile->napi);
        }
 }
@@ -716,7 +716,8 @@ static void tun_detach_all(struct net_device *dev)
 }
 
 static int tun_attach(struct tun_struct *tun, struct file *file,
-                     bool skip_filter, bool napi, bool napi_frags)
+                     bool skip_filter, bool napi, bool napi_frags,
+                     bool publish_tun)
 {
        struct tun_file *tfile = file->private_data;
        struct net_device *dev = tun->dev;
@@ -758,10 +759,6 @@ static int tun_attach(struct tun_struct *tun, struct file *file,
 
        tfile->queue_index = tun->numqueues;
        tfile->socket.sk->sk_shutdown &= ~RCV_SHUTDOWN;
-       rcu_assign_pointer(tfile->tun, tun);
-       rcu_assign_pointer(tun->tfiles[tun->numqueues], tfile);
-       tun->numqueues++;
-
        if (tfile->detached) {
                tun_enable_queue(tfile);
        } else {
@@ -769,12 +766,19 @@ static int tun_attach(struct tun_struct *tun, struct file *file,
                tun_napi_init(tun, tfile, napi, napi_frags);
        }
 
-       tun_set_real_num_queues(tun);
-
        /* device is allowed to go away first, so no need to hold extra
         * refcnt.
         */
 
+       /* Publish tfile->tun and tun->tfiles only after we've fully
+        * initialized tfile; otherwise we risk using half-initialized
+        * object.
+        */
+       if (publish_tun)
+               rcu_assign_pointer(tfile->tun, tun);
+       rcu_assign_pointer(tun->tfiles[tun->numqueues], tfile);
+       tun->numqueues++;
+       tun_set_real_num_queues(tun);
 out:
        return err;
 }
@@ -914,18 +918,8 @@ static void tun_net_uninit(struct net_device *dev)
 /* Net device open. */
 static int tun_net_open(struct net_device *dev)
 {
-       struct tun_struct *tun = netdev_priv(dev);
-       int i;
-
        netif_tx_start_all_queues(dev);
 
-       for (i = 0; i < tun->numqueues; i++) {
-               struct tun_file *tfile;
-
-               tfile = rtnl_dereference(tun->tfiles[i]);
-               tfile->socket.sk->sk_write_space(tfile->socket.sk);
-       }
-
        return 0;
 }
 
@@ -1260,6 +1254,13 @@ static void tun_net_init(struct net_device *dev)
        dev->max_mtu = MAX_MTU - dev->hard_header_len;
 }
 
+static bool tun_sock_writeable(struct tun_struct *tun, struct tun_file *tfile)
+{
+       struct sock *sk = tfile->socket.sk;
+
+       return (tun->dev->flags & IFF_UP) && sock_writeable(sk);
+}
+
 /* Character device part */
 
 /* Poll */
@@ -1282,10 +1283,14 @@ static unsigned int tun_chr_poll(struct file *file, poll_table *wait)
        if (!skb_array_empty(&tfile->tx_array))
                mask |= POLLIN | POLLRDNORM;
 
-       if (tun->dev->flags & IFF_UP &&
-           (sock_writeable(sk) ||
-            (!test_and_set_bit(SOCKWQ_ASYNC_NOSPACE, &sk->sk_socket->flags) &&
-             sock_writeable(sk))))
+       /* Make sure SOCKWQ_ASYNC_NOSPACE is set if not writable to
+        * guarantee EPOLLOUT to be raised by either here or
+        * tun_sock_write_space(). Then process could get notification
+        * after it writes to a down device and meets -EIO.
+        */
+       if (tun_sock_writeable(tun, tfile) ||
+           (!test_and_set_bit(SOCKWQ_ASYNC_NOSPACE, &sk->sk_socket->flags) &&
+            tun_sock_writeable(tun, tfile)))
                mask |= POLLOUT | POLLWRNORM;
 
        if (tun->dev->reg_state != NETREG_REGISTERED)
@@ -1533,6 +1538,7 @@ static struct sk_buff *tun_build_skb(struct tun_struct *tun,
 
        skb_reserve(skb, pad - delta);
        skb_put(skb, len + delta);
+       skb_set_owner_w(skb, tfile->socket.sk);
        get_page(alloc_frag->page);
        alloc_frag->offset += buflen;
 
@@ -1577,9 +1583,6 @@ static ssize_t tun_get_user(struct tun_struct *tun, struct tun_file *tfile,
        int skb_xdp = 1;
        bool frags = tun_napi_frags_enabled(tfile);
 
-       if (!(tun->dev->flags & IFF_UP))
-               return -EIO;
-
        if (!(tun->flags & IFF_NO_PI)) {
                if (len < sizeof(pi))
                        return -EINVAL;
@@ -1681,6 +1684,8 @@ static ssize_t tun_get_user(struct tun_struct *tun, struct tun_file *tfile,
                        err = skb_copy_datagram_from_iter(skb, 0, from, len);
 
                if (err) {
+                       err = -EFAULT;
+drop:
                        this_cpu_inc(tun->pcpu_stats->rx_dropped);
                        kfree_skb(skb);
                        if (frags) {
@@ -1688,7 +1693,7 @@ static ssize_t tun_get_user(struct tun_struct *tun, struct tun_file *tfile,
                                mutex_unlock(&tfile->napi_mutex);
                        }
 
-                       return -EFAULT;
+                       return err;
                }
        }
 
@@ -1757,6 +1762,10 @@ static ssize_t tun_get_user(struct tun_struct *tun, struct tun_file *tfile,
                        if (ret != XDP_PASS) {
                                rcu_read_unlock();
                                local_bh_enable();
+                               if (frags) {
+                                       tfile->napi.skb = NULL;
+                                       mutex_unlock(&tfile->napi_mutex);
+                               }
                                return total_len;
                        }
                }
@@ -1766,6 +1775,13 @@ static ssize_t tun_get_user(struct tun_struct *tun, struct tun_file *tfile,
 
        rxhash = __skb_get_hash_symmetric(skb);
 
+       rcu_read_lock();
+       if (unlikely(!(tun->dev->flags & IFF_UP))) {
+               err = -EIO;
+               rcu_read_unlock();
+               goto drop;
+       }
+
        if (frags) {
                /* Exercise flow dissector code path. */
                u32 headlen = eth_get_headlen(skb->data, skb_headlen(skb));
@@ -1773,6 +1789,7 @@ static ssize_t tun_get_user(struct tun_struct *tun, struct tun_file *tfile,
                if (unlikely(headlen > skb_headlen(skb))) {
                        this_cpu_inc(tun->pcpu_stats->rx_dropped);
                        napi_free_frags(&tfile->napi);
+                       rcu_read_unlock();
                        mutex_unlock(&tfile->napi_mutex);
                        WARN_ON(1);
                        return -ENOMEM;
@@ -1800,6 +1817,7 @@ static ssize_t tun_get_user(struct tun_struct *tun, struct tun_file *tfile,
        } else {
                netif_rx_ni(skb);
        }
+       rcu_read_unlock();
 
        stats = get_cpu_ptr(tun->pcpu_stats);
        u64_stats_update_begin(&stats->syncp);
@@ -1943,9 +1961,9 @@ static struct sk_buff *tun_ring_recv(struct tun_file *tfile, int noblock,
        }
 
        add_wait_queue(&tfile->wq.wait, &wait);
-       current->state = TASK_INTERRUPTIBLE;
 
        while (1) {
+               set_current_state(TASK_INTERRUPTIBLE);
                skb = skb_array_consume(&tfile->tx_array);
                if (skb)
                        break;
@@ -1961,7 +1979,7 @@ static struct sk_buff *tun_ring_recv(struct tun_file *tfile, int noblock,
                schedule();
        }
 
-       current->state = TASK_RUNNING;
+       __set_current_state(TASK_RUNNING);
        remove_wait_queue(&tfile->wq.wait, &wait);
 
 out:
@@ -2252,7 +2270,7 @@ static int tun_set_iff(struct net *net, struct file *file, struct ifreq *ifr)
 
                err = tun_attach(tun, file, ifr->ifr_flags & IFF_NOFILTER,
                                 ifr->ifr_flags & IFF_NAPI,
-                                ifr->ifr_flags & IFF_NAPI_FRAGS);
+                                ifr->ifr_flags & IFF_NAPI_FRAGS, true);
                if (err < 0)
                        return err;
 
@@ -2342,13 +2360,17 @@ static int tun_set_iff(struct net *net, struct file *file, struct ifreq *ifr)
 
                INIT_LIST_HEAD(&tun->disabled);
                err = tun_attach(tun, file, false, ifr->ifr_flags & IFF_NAPI,
-                                ifr->ifr_flags & IFF_NAPI_FRAGS);
+                                ifr->ifr_flags & IFF_NAPI_FRAGS, false);
                if (err < 0)
                        goto err_free_flow;
 
                err = register_netdevice(tun->dev);
                if (err < 0)
                        goto err_detach;
+               /* free_netdev() won't check refcnt, to aovid race
+                * with dev_put() we need publish tun after registration.
+                */
+               rcu_assign_pointer(tfile->tun, tun);
        }
 
        netif_carrier_on(tun->dev);
@@ -2495,7 +2517,7 @@ static int tun_set_queue(struct file *file, struct ifreq *ifr)
                if (ret < 0)
                        goto unlock;
                ret = tun_attach(tun, file, false, tun->flags & IFF_NAPI,
-                                tun->flags & IFF_NAPI_FRAGS);
+                                tun->flags & IFF_NAPI_FRAGS, true);
        } else if (ifr->ifr_flags & IFF_DETACH_QUEUE) {
                tun = rtnl_dereference(tfile->tun);
                if (!tun || !(tun->flags & IFF_MULTI_QUEUE) || tfile->detached)
@@ -3070,6 +3092,7 @@ static int tun_device_event(struct notifier_block *unused,
 {
        struct net_device *dev = netdev_notifier_info_to_dev(ptr);
        struct tun_struct *tun = netdev_priv(dev);
+       int i;
 
        if (dev->rtnl_link_ops != &tun_link_ops)
                return NOTIFY_DONE;
@@ -3079,6 +3102,14 @@ static int tun_device_event(struct notifier_block *unused,
                if (tun_queue_resize(tun))
                        return NOTIFY_BAD;
                break;
+       case NETDEV_UP:
+               for (i = 0; i < tun->numqueues; i++) {
+                       struct tun_file *tfile;
+
+                       tfile = rtnl_dereference(tun->tfiles[i]);
+                       tfile->socket.sk->sk_write_space(tfile->socket.sk);
+               }
+               break;
        default:
                break;
        }