]> 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 4e5d038a252d3b49c8aa980446a136fd895ef110..3ced0f259304be0450d8ff35d48d681478ec84e7 100644 (file)
@@ -611,14 +611,6 @@ static void tun_queue_purge(struct tun_file *tfile)
        skb_queue_purge(&tfile->sk.sk_error_queue);
 }
 
-static void tun_cleanup_tx_array(struct tun_file *tfile)
-{
-       if (tfile->tx_array.ring.queue) {
-               skb_array_cleanup(&tfile->tx_array);
-               memset(&tfile->tx_array, 0, sizeof(tfile->tx_array));
-       }
-}
-
 static void __tun_detach(struct tun_file *tfile, bool clean)
 {
        struct tun_file *ntfile;
@@ -665,7 +657,7 @@ static void __tun_detach(struct tun_file *tfile, bool clean)
                            tun->dev->reg_state == NETREG_REGISTERED)
                                unregister_netdevice(tun->dev);
                }
-               tun_cleanup_tx_array(tfile);
+               skb_array_cleanup(&tfile->tx_array);
                sock_put(&tfile->sk);
        }
 }
@@ -707,13 +699,11 @@ static void tun_detach_all(struct net_device *dev)
                /* Drop read queue */
                tun_queue_purge(tfile);
                sock_put(&tfile->sk);
-               tun_cleanup_tx_array(tfile);
        }
        list_for_each_entry_safe(tfile, tmp, &tun->disabled, next) {
                tun_enable_queue(tfile);
                tun_queue_purge(tfile);
                sock_put(&tfile->sk);
-               tun_cleanup_tx_array(tfile);
        }
        BUG_ON(tun->numdisabled != 0);
 
@@ -760,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;
        }
@@ -1164,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,
@@ -1176,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 = {
@@ -1195,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)
@@ -2501,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;
 
@@ -2771,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;
@@ -2847,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;
@@ -2867,8 +2887,6 @@ static int tun_chr_open(struct inode *inode, struct file * file)
 
        sock_set_flag(&tfile->sk, SOCK_ZEROCOPY);
 
-       memset(&tfile->tx_array, 0, sizeof(tfile->tx_array));
-
        return 0;
 }