Commit 631ab46b authored by Eric W. Biederman's avatar Eric W. Biederman Committed by David S. Miller

tun: Introduce tun_file

Currently the tun code suffers from only having a single word of
data that exists for the entire life of the tun file descriptor.

This results in peculiar holding of references to the network namespace
as well as races between free_netdevice and tun_chr_close.

Fix this by introducing tun_file which will hold the per file state.
For the moment it still holds just a single word so the differences
are all logic changes with no changes in semantics.
Signed-off-by: default avatarEric W. Biederman <ebiederm@aristanetworks.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent eac9e902
...@@ -87,9 +87,13 @@ struct tap_filter { ...@@ -87,9 +87,13 @@ struct tap_filter {
unsigned char addr[FLT_EXACT_COUNT][ETH_ALEN]; unsigned char addr[FLT_EXACT_COUNT][ETH_ALEN];
}; };
struct tun_file {
struct tun_struct *tun;
};
struct tun_struct { struct tun_struct {
struct tun_file *tfile;
unsigned int flags; unsigned int flags;
int attached;
uid_t owner; uid_t owner;
gid_t group; gid_t group;
...@@ -108,14 +112,15 @@ struct tun_struct { ...@@ -108,14 +112,15 @@ struct tun_struct {
static int tun_attach(struct tun_struct *tun, struct file *file) static int tun_attach(struct tun_struct *tun, struct file *file)
{ {
struct tun_file *tfile = file->private_data;
const struct cred *cred = current_cred(); const struct cred *cred = current_cred();
ASSERT_RTNL(); ASSERT_RTNL();
if (file->private_data) if (tfile->tun)
return -EINVAL; return -EINVAL;
if (tun->attached) if (tun->tfile)
return -EBUSY; return -EBUSY;
/* Check permissions */ /* Check permissions */
...@@ -124,13 +129,41 @@ static int tun_attach(struct tun_struct *tun, struct file *file) ...@@ -124,13 +129,41 @@ static int tun_attach(struct tun_struct *tun, struct file *file)
!capable(CAP_NET_ADMIN)) !capable(CAP_NET_ADMIN))
return -EPERM; return -EPERM;
file->private_data = tun; tfile->tun = tun;
tun->attached = 1; tun->tfile = tfile;
get_net(dev_net(tun->dev)); get_net(dev_net(tun->dev));
return 0; return 0;
} }
static void __tun_detach(struct tun_struct *tun)
{
struct tun_file *tfile = tun->tfile;
/* Detach from net device */
tfile->tun = NULL;
tun->tfile = NULL;
put_net(dev_net(tun->dev));
/* Drop read queue */
skb_queue_purge(&tun->readq);
}
static struct tun_struct *__tun_get(struct tun_file *tfile)
{
return tfile->tun;
}
static struct tun_struct *tun_get(struct file *file)
{
return __tun_get(file->private_data);
}
static void tun_put(struct tun_struct *tun)
{
/* Noop for now */
}
/* TAP filterting */ /* TAP filterting */
static void addr_hash_set(u32 *mask, const u8 *addr) static void addr_hash_set(u32 *mask, const u8 *addr)
{ {
...@@ -261,7 +294,7 @@ static int tun_net_xmit(struct sk_buff *skb, struct net_device *dev) ...@@ -261,7 +294,7 @@ static int tun_net_xmit(struct sk_buff *skb, struct net_device *dev)
DBG(KERN_INFO "%s: tun_net_xmit %d\n", tun->dev->name, skb->len); DBG(KERN_INFO "%s: tun_net_xmit %d\n", tun->dev->name, skb->len);
/* Drop packet if interface is not attached */ /* Drop packet if interface is not attached */
if (!tun->attached) if (!tun->tfile)
goto drop; goto drop;
/* Drop if the filter does not like it. /* Drop if the filter does not like it.
...@@ -378,7 +411,7 @@ static void tun_net_init(struct net_device *dev) ...@@ -378,7 +411,7 @@ static void tun_net_init(struct net_device *dev)
/* Poll */ /* Poll */
static unsigned int tun_chr_poll(struct file *file, poll_table * wait) static unsigned int tun_chr_poll(struct file *file, poll_table * wait)
{ {
struct tun_struct *tun = file->private_data; struct tun_struct *tun = tun_get(file);
unsigned int mask = POLLOUT | POLLWRNORM; unsigned int mask = POLLOUT | POLLWRNORM;
if (!tun) if (!tun)
...@@ -391,6 +424,7 @@ static unsigned int tun_chr_poll(struct file *file, poll_table * wait) ...@@ -391,6 +424,7 @@ static unsigned int tun_chr_poll(struct file *file, poll_table * wait)
if (!skb_queue_empty(&tun->readq)) if (!skb_queue_empty(&tun->readq))
mask |= POLLIN | POLLRDNORM; mask |= POLLIN | POLLRDNORM;
tun_put(tun);
return mask; return mask;
} }
...@@ -575,14 +609,18 @@ static __inline__ ssize_t tun_get_user(struct tun_struct *tun, struct iovec *iv, ...@@ -575,14 +609,18 @@ static __inline__ ssize_t tun_get_user(struct tun_struct *tun, struct iovec *iv,
static ssize_t tun_chr_aio_write(struct kiocb *iocb, const struct iovec *iv, static ssize_t tun_chr_aio_write(struct kiocb *iocb, const struct iovec *iv,
unsigned long count, loff_t pos) unsigned long count, loff_t pos)
{ {
struct tun_struct *tun = iocb->ki_filp->private_data; struct tun_struct *tun = tun_get(iocb->ki_filp);
ssize_t result;
if (!tun) if (!tun)
return -EBADFD; return -EBADFD;
DBG(KERN_INFO "%s: tun_chr_write %ld\n", tun->dev->name, count); DBG(KERN_INFO "%s: tun_chr_write %ld\n", tun->dev->name, count);
return tun_get_user(tun, (struct iovec *) iv, iov_length(iv, count)); result = tun_get_user(tun, (struct iovec *) iv, iov_length(iv, count));
tun_put(tun);
return result;
} }
/* Put packet to the user space buffer */ /* Put packet to the user space buffer */
...@@ -655,7 +693,7 @@ static ssize_t tun_chr_aio_read(struct kiocb *iocb, const struct iovec *iv, ...@@ -655,7 +693,7 @@ static ssize_t tun_chr_aio_read(struct kiocb *iocb, const struct iovec *iv,
unsigned long count, loff_t pos) unsigned long count, loff_t pos)
{ {
struct file *file = iocb->ki_filp; struct file *file = iocb->ki_filp;
struct tun_struct *tun = file->private_data; struct tun_struct *tun = tun_get(file);
DECLARE_WAITQUEUE(wait, current); DECLARE_WAITQUEUE(wait, current);
struct sk_buff *skb; struct sk_buff *skb;
ssize_t len, ret = 0; ssize_t len, ret = 0;
...@@ -666,8 +704,10 @@ static ssize_t tun_chr_aio_read(struct kiocb *iocb, const struct iovec *iv, ...@@ -666,8 +704,10 @@ static ssize_t tun_chr_aio_read(struct kiocb *iocb, const struct iovec *iv,
DBG(KERN_INFO "%s: tun_chr_read\n", tun->dev->name); DBG(KERN_INFO "%s: tun_chr_read\n", tun->dev->name);
len = iov_length(iv, count); len = iov_length(iv, count);
if (len < 0) if (len < 0) {
return -EINVAL; ret = -EINVAL;
goto out;
}
add_wait_queue(&tun->read_wait, &wait); add_wait_queue(&tun->read_wait, &wait);
while (len) { while (len) {
...@@ -698,6 +738,8 @@ static ssize_t tun_chr_aio_read(struct kiocb *iocb, const struct iovec *iv, ...@@ -698,6 +738,8 @@ static ssize_t tun_chr_aio_read(struct kiocb *iocb, const struct iovec *iv,
current->state = TASK_RUNNING; current->state = TASK_RUNNING;
remove_wait_queue(&tun->read_wait, &wait); remove_wait_queue(&tun->read_wait, &wait);
out:
tun_put(tun);
return ret; return ret;
} }
...@@ -822,7 +864,7 @@ static int tun_set_iff(struct net *net, struct file *file, struct ifreq *ifr) ...@@ -822,7 +864,7 @@ static int tun_set_iff(struct net *net, struct file *file, struct ifreq *ifr)
static int tun_get_iff(struct net *net, struct file *file, struct ifreq *ifr) static int tun_get_iff(struct net *net, struct file *file, struct ifreq *ifr)
{ {
struct tun_struct *tun = file->private_data; struct tun_struct *tun = tun_get(file);
if (!tun) if (!tun)
return -EBADFD; return -EBADFD;
...@@ -847,6 +889,7 @@ static int tun_get_iff(struct net *net, struct file *file, struct ifreq *ifr) ...@@ -847,6 +889,7 @@ static int tun_get_iff(struct net *net, struct file *file, struct ifreq *ifr)
if (tun->flags & TUN_VNET_HDR) if (tun->flags & TUN_VNET_HDR)
ifr->ifr_flags |= IFF_VNET_HDR; ifr->ifr_flags |= IFF_VNET_HDR;
tun_put(tun);
return 0; return 0;
} }
...@@ -893,7 +936,7 @@ static int set_offload(struct net_device *dev, unsigned long arg) ...@@ -893,7 +936,7 @@ static int set_offload(struct net_device *dev, unsigned long arg)
static int tun_chr_ioctl(struct inode *inode, struct file *file, static int tun_chr_ioctl(struct inode *inode, struct file *file,
unsigned int cmd, unsigned long arg) unsigned int cmd, unsigned long arg)
{ {
struct tun_struct *tun = file->private_data; struct tun_struct *tun;
void __user* argp = (void __user*)arg; void __user* argp = (void __user*)arg;
struct ifreq ifr; struct ifreq ifr;
int ret; int ret;
...@@ -902,6 +945,16 @@ static int tun_chr_ioctl(struct inode *inode, struct file *file, ...@@ -902,6 +945,16 @@ static int tun_chr_ioctl(struct inode *inode, struct file *file,
if (copy_from_user(&ifr, argp, sizeof ifr)) if (copy_from_user(&ifr, argp, sizeof ifr))
return -EFAULT; return -EFAULT;
if (cmd == TUNGETFEATURES) {
/* Currently this just means: "what IFF flags are valid?".
* This is needed because we never checked for invalid flags on
* TUNSETIFF. */
return put_user(IFF_TUN | IFF_TAP | IFF_NO_PI | IFF_ONE_QUEUE |
IFF_VNET_HDR,
(unsigned int __user*)argp);
}
tun = tun_get(file);
if (cmd == TUNSETIFF && !tun) { if (cmd == TUNSETIFF && !tun) {
int err; int err;
...@@ -919,28 +972,21 @@ static int tun_chr_ioctl(struct inode *inode, struct file *file, ...@@ -919,28 +972,21 @@ static int tun_chr_ioctl(struct inode *inode, struct file *file,
return 0; return 0;
} }
if (cmd == TUNGETFEATURES) {
/* Currently this just means: "what IFF flags are valid?".
* This is needed because we never checked for invalid flags on
* TUNSETIFF. */
return put_user(IFF_TUN | IFF_TAP | IFF_NO_PI | IFF_ONE_QUEUE |
IFF_VNET_HDR,
(unsigned int __user*)argp);
}
if (!tun) if (!tun)
return -EBADFD; return -EBADFD;
DBG(KERN_INFO "%s: tun_chr_ioctl cmd %d\n", tun->dev->name, cmd); DBG(KERN_INFO "%s: tun_chr_ioctl cmd %d\n", tun->dev->name, cmd);
ret = 0;
switch (cmd) { switch (cmd) {
case TUNGETIFF: case TUNGETIFF:
ret = tun_get_iff(current->nsproxy->net_ns, file, &ifr); ret = tun_get_iff(current->nsproxy->net_ns, file, &ifr);
if (ret) if (ret)
return ret; break;
if (copy_to_user(argp, &ifr, sizeof(ifr))) if (copy_to_user(argp, &ifr, sizeof(ifr)))
return -EFAULT; ret = -EFAULT;
break; break;
case TUNSETNOCSUM: case TUNSETNOCSUM:
...@@ -992,7 +1038,7 @@ static int tun_chr_ioctl(struct inode *inode, struct file *file, ...@@ -992,7 +1038,7 @@ static int tun_chr_ioctl(struct inode *inode, struct file *file,
ret = 0; ret = 0;
} }
rtnl_unlock(); rtnl_unlock();
return ret; break;
#ifdef TUN_DEBUG #ifdef TUN_DEBUG
case TUNSETDEBUG: case TUNSETDEBUG:
...@@ -1003,24 +1049,25 @@ static int tun_chr_ioctl(struct inode *inode, struct file *file, ...@@ -1003,24 +1049,25 @@ static int tun_chr_ioctl(struct inode *inode, struct file *file,
rtnl_lock(); rtnl_lock();
ret = set_offload(tun->dev, arg); ret = set_offload(tun->dev, arg);
rtnl_unlock(); rtnl_unlock();
return ret; break;
case TUNSETTXFILTER: case TUNSETTXFILTER:
/* Can be set only for TAPs */ /* Can be set only for TAPs */
ret = -EINVAL;
if ((tun->flags & TUN_TYPE_MASK) != TUN_TAP_DEV) if ((tun->flags & TUN_TYPE_MASK) != TUN_TAP_DEV)
return -EINVAL; break;
rtnl_lock(); rtnl_lock();
ret = update_filter(&tun->txflt, (void __user *)arg); ret = update_filter(&tun->txflt, (void __user *)arg);
rtnl_unlock(); rtnl_unlock();
return ret; break;
case SIOCGIFHWADDR: case SIOCGIFHWADDR:
/* Get hw addres */ /* Get hw addres */
memcpy(ifr.ifr_hwaddr.sa_data, tun->dev->dev_addr, ETH_ALEN); memcpy(ifr.ifr_hwaddr.sa_data, tun->dev->dev_addr, ETH_ALEN);
ifr.ifr_hwaddr.sa_family = tun->dev->type; ifr.ifr_hwaddr.sa_family = tun->dev->type;
if (copy_to_user(argp, &ifr, sizeof ifr)) if (copy_to_user(argp, &ifr, sizeof ifr))
return -EFAULT; ret = -EFAULT;
return 0; break;
case SIOCSIFHWADDR: case SIOCSIFHWADDR:
/* Set hw address */ /* Set hw address */
...@@ -1030,18 +1077,19 @@ static int tun_chr_ioctl(struct inode *inode, struct file *file, ...@@ -1030,18 +1077,19 @@ static int tun_chr_ioctl(struct inode *inode, struct file *file,
rtnl_lock(); rtnl_lock();
ret = dev_set_mac_address(tun->dev, &ifr.ifr_hwaddr); ret = dev_set_mac_address(tun->dev, &ifr.ifr_hwaddr);
rtnl_unlock(); rtnl_unlock();
return ret; break;
default: default:
return -EINVAL; ret = -EINVAL;
break;
}; };
return 0; tun_put(tun);
return ret;
} }
static int tun_chr_fasync(int fd, struct file *file, int on) static int tun_chr_fasync(int fd, struct file *file, int on)
{ {
struct tun_struct *tun = file->private_data; struct tun_struct *tun = tun_get(file);
int ret; int ret;
if (!tun) if (!tun)
...@@ -1063,40 +1111,44 @@ static int tun_chr_fasync(int fd, struct file *file, int on) ...@@ -1063,40 +1111,44 @@ static int tun_chr_fasync(int fd, struct file *file, int on)
ret = 0; ret = 0;
out: out:
unlock_kernel(); unlock_kernel();
tun_put(tun);
return ret; return ret;
} }
static int tun_chr_open(struct inode *inode, struct file * file) static int tun_chr_open(struct inode *inode, struct file * file)
{ {
struct tun_file *tfile;
cycle_kernel_lock(); cycle_kernel_lock();
DBG1(KERN_INFO "tunX: tun_chr_open\n"); DBG1(KERN_INFO "tunX: tun_chr_open\n");
file->private_data = NULL;
tfile = kmalloc(sizeof(*tfile), GFP_KERNEL);
if (!tfile)
return -ENOMEM;
tfile->tun = NULL;
file->private_data = tfile;
return 0; return 0;
} }
static int tun_chr_close(struct inode *inode, struct file *file) static int tun_chr_close(struct inode *inode, struct file *file)
{ {
struct tun_struct *tun = file->private_data; struct tun_file *tfile = file->private_data;
struct tun_struct *tun = __tun_get(tfile);
if (!tun)
return 0;
DBG(KERN_INFO "%s: tun_chr_close\n", tun->dev->name);
rtnl_lock(); if (tun) {
DBG(KERN_INFO "%s: tun_chr_close\n", tun->dev->name);
/* Detach from net device */ rtnl_lock();
file->private_data = NULL; __tun_detach(tun);
tun->attached = 0;
put_net(dev_net(tun->dev));
/* Drop read queue */ /* If desireable, unregister the netdevice. */
skb_queue_purge(&tun->readq); if (!(tun->flags & TUN_PERSIST))
unregister_netdevice(tun->dev);
if (!(tun->flags & TUN_PERSIST)) rtnl_unlock();
unregister_netdevice(tun->dev); }
rtnl_unlock(); kfree(tfile);
return 0; return 0;
} }
...@@ -1177,7 +1229,7 @@ static void tun_set_msglevel(struct net_device *dev, u32 value) ...@@ -1177,7 +1229,7 @@ static void tun_set_msglevel(struct net_device *dev, u32 value)
static u32 tun_get_link(struct net_device *dev) static u32 tun_get_link(struct net_device *dev)
{ {
struct tun_struct *tun = netdev_priv(dev); struct tun_struct *tun = netdev_priv(dev);
return tun->attached; return !!tun->tfile;
} }
static u32 tun_get_rx_csum(struct net_device *dev) static u32 tun_get_rx_csum(struct net_device *dev)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment