]> git.proxmox.com Git - mirror_ubuntu-bionic-kernel.git/blobdiff - drivers/net/tun.c
tuntap: fix use after free during release
[mirror_ubuntu-bionic-kernel.git] / drivers / net / tun.c
index 4f4a842a1c9cb8ac3397b329854a0fc7bd2f6aa3..3ced0f259304be0450d8ff35d48d681478ec84e7 100644 (file)
@@ -657,8 +657,7 @@ static void __tun_detach(struct tun_file *tfile, bool clean)
                            tun->dev->reg_state == NETREG_REGISTERED)
                                unregister_netdevice(tun->dev);
                }
-               if (tun)
-                       skb_array_cleanup(&tfile->tx_array);
+               skb_array_cleanup(&tfile->tx_array);
                sock_put(&tfile->sk);
        }
 }
@@ -751,7 +750,7 @@ static int tun_attach(struct tun_struct *tun, struct file *file,
        }
 
        if (!tfile->detached &&
-           skb_array_init(&tfile->tx_array, dev->tx_queue_len, GFP_KERNEL)) {
+           skb_array_resize(&tfile->tx_array, dev->tx_queue_len, GFP_KERNEL)) {
                err = -ENOMEM;
                goto out;
        }
@@ -1155,6 +1154,21 @@ static int tun_xdp(struct net_device *dev, struct netdev_bpf *xdp)
        }
 }
 
+static int tun_net_change_carrier(struct net_device *dev, bool new_carrier)
+{
+       if (new_carrier) {
+               struct tun_struct *tun = netdev_priv(dev);
+
+               if (!tun->numqueues)
+                       return -EPERM;
+
+               netif_carrier_on(dev);
+       } else {
+               netif_carrier_off(dev);
+       }
+       return 0;
+}
+
 static const struct net_device_ops tun_netdev_ops = {
        .ndo_uninit             = tun_net_uninit,
        .ndo_open               = tun_net_open,
@@ -1167,6 +1181,7 @@ static const struct net_device_ops tun_netdev_ops = {
 #endif
        .ndo_set_rx_headroom    = tun_set_headroom,
        .ndo_get_stats64        = tun_net_get_stats64,
+       .ndo_change_carrier     = tun_net_change_carrier,
 };
 
 static const struct net_device_ops tap_netdev_ops = {
@@ -1186,6 +1201,7 @@ static const struct net_device_ops tap_netdev_ops = {
        .ndo_set_rx_headroom    = tun_set_headroom,
        .ndo_get_stats64        = tun_net_get_stats64,
        .ndo_bpf                = tun_xdp,
+       .ndo_change_carrier     = tun_net_change_carrier,
 };
 
 static void tun_flow_init(struct tun_struct *tun)
@@ -1308,27 +1324,23 @@ static struct sk_buff *tun_napi_alloc_frags(struct tun_file *tfile,
        skb->truesize += skb->data_len;
 
        for (i = 1; i < it->nr_segs; i++) {
+               struct page_frag *pfrag = &current->task_frag;
                size_t fragsz = it->iov[i].iov_len;
-               unsigned long offset;
-               struct page *page;
-               void *data;
 
                if (fragsz == 0 || fragsz > PAGE_SIZE) {
                        err = -EINVAL;
                        goto free;
                }
 
-               local_bh_disable();
-               data = napi_alloc_frag(fragsz);
-               local_bh_enable();
-               if (!data) {
+               if (!skb_page_frag_refill(fragsz, pfrag, GFP_KERNEL)) {
                        err = -ENOMEM;
                        goto free;
                }
 
-               page = virt_to_head_page(data);
-               offset = data - page_address(page);
-               skb_fill_page_desc(skb, i - 1, page, offset, fragsz);
+               skb_fill_page_desc(skb, i - 1, pfrag->page,
+                                  pfrag->offset, fragsz);
+               page_ref_inc(pfrag->page);
+               pfrag->offset += fragsz;
        }
 
        return skb;
@@ -1466,6 +1478,7 @@ static struct sk_buff *tun_build_skb(struct tun_struct *tun,
        else
                *skb_xdp = 0;
 
+       local_bh_disable();
        rcu_read_lock();
        xdp_prog = rcu_dereference(tun->xdp_prog);
        if (xdp_prog && !*skb_xdp) {
@@ -1485,9 +1498,11 @@ static struct sk_buff *tun_build_skb(struct tun_struct *tun,
                        get_page(alloc_frag->page);
                        alloc_frag->offset += buflen;
                        err = xdp_do_redirect(tun->dev, &xdp, xdp_prog);
+                       xdp_do_flush_map();
                        if (err)
                                goto err_redirect;
                        rcu_read_unlock();
+                       local_bh_enable();
                        return NULL;
                case XDP_TX:
                        xdp_xmit = true;
@@ -1509,6 +1524,7 @@ static struct sk_buff *tun_build_skb(struct tun_struct *tun,
        skb = build_skb(buf, buflen);
        if (!skb) {
                rcu_read_unlock();
+               local_bh_enable();
                return ERR_PTR(-ENOMEM);
        }
 
@@ -1521,10 +1537,12 @@ static struct sk_buff *tun_build_skb(struct tun_struct *tun,
                skb->dev = tun->dev;
                generic_xdp_tx(skb, xdp_prog);
                rcu_read_unlock();
+               local_bh_enable();
                return NULL;
        }
 
        rcu_read_unlock();
+       local_bh_enable();
 
        return skb;
 
@@ -1532,6 +1550,7 @@ err_redirect:
        put_page(alloc_frag->page);
 err_xdp:
        rcu_read_unlock();
+       local_bh_enable();
        this_cpu_inc(tun->pcpu_stats->rx_dropped);
        return NULL;
 }
@@ -1727,16 +1746,19 @@ static ssize_t tun_get_user(struct tun_struct *tun, struct tun_file *tfile,
                struct bpf_prog *xdp_prog;
                int ret;
 
+               local_bh_disable();
                rcu_read_lock();
                xdp_prog = rcu_dereference(tun->xdp_prog);
                if (xdp_prog) {
                        ret = do_xdp_generic(xdp_prog, skb);
                        if (ret != XDP_PASS) {
                                rcu_read_unlock();
+                               local_bh_enable();
                                return total_len;
                        }
                }
                rcu_read_unlock();
+               local_bh_enable();
        }
 
        rxhash = __skb_get_hash_symmetric(skb);
@@ -1846,7 +1868,8 @@ static ssize_t tun_put_user(struct tun_struct *tun,
                        return -EINVAL;
 
                if (virtio_net_hdr_from_skb(skb, &gso,
-                                           tun_is_little_endian(tun), true)) {
+                                           tun_is_little_endian(tun), true,
+                                           vlan_hlen)) {
                        struct skb_shared_info *sinfo = skb_shinfo(skb);
                        pr_err("unexpected GSO type: "
                               "0x%x, gso_size %d, hdr_len %d\n",
@@ -2485,12 +2508,12 @@ static long __tun_chr_ioctl(struct file *file, unsigned int cmd,
        struct tun_file *tfile = file->private_data;
        struct tun_struct *tun;
        void __user* argp = (void __user*)arg;
+       unsigned int ifindex, carrier;
        struct ifreq ifr;
        kuid_t owner;
        kgid_t group;
        int sndbuf;
        int vnet_hdr_sz;
-       unsigned int ifindex;
        int le;
        int ret;
 
@@ -2755,6 +2778,14 @@ static long __tun_chr_ioctl(struct file *file, unsigned int cmd,
                ret = 0;
                break;
 
+       case TUNSETCARRIER:
+               ret = -EFAULT;
+               if (copy_from_user(&carrier, argp, sizeof(carrier)))
+                       goto unlock;
+
+               ret = tun_net_change_carrier(tun->dev, (bool)carrier);
+               break;
+
        default:
                ret = -EINVAL;
                break;
@@ -2831,6 +2862,11 @@ static int tun_chr_open(struct inode *inode, struct file * file)
                                            &tun_proto, 0);
        if (!tfile)
                return -ENOMEM;
+       if (skb_array_init(&tfile->tx_array, 0, GFP_KERNEL)) {
+               sk_free(&tfile->sk);
+               return -ENOMEM;
+       }
+
        RCU_INIT_POINTER(tfile->tun, NULL);
        tfile->flags = 0;
        tfile->ifindex = 0;