Commit 567131a7 authored by Ville Nuorvala's avatar Ville Nuorvala Committed by David S. Miller

[IPV6]: Fix SIOCCHGTUNNEL bug in IPv6 tunnels

A logic bug in tunnel lookup could result in duplicate tunnels when
changing an existing device.
Signed-off-by: default avatarVille Nuorvala <vnuorval@tcs.hut.fi>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent e94ef682
...@@ -215,11 +215,10 @@ ip6ip6_tnl_unlink(struct ip6_tnl *t) ...@@ -215,11 +215,10 @@ ip6ip6_tnl_unlink(struct ip6_tnl *t)
* Create tunnel matching given parameters. * Create tunnel matching given parameters.
* *
* Return: * Return:
* 0 on success * created tunnel or NULL
**/ **/
static int static struct ip6_tnl *ip6_tnl_create(struct ip6_tnl_parm *p)
ip6_tnl_create(struct ip6_tnl_parm *p, struct ip6_tnl **pt)
{ {
struct net_device *dev; struct net_device *dev;
struct ip6_tnl *t; struct ip6_tnl *t;
...@@ -236,11 +235,11 @@ ip6_tnl_create(struct ip6_tnl_parm *p, struct ip6_tnl **pt) ...@@ -236,11 +235,11 @@ ip6_tnl_create(struct ip6_tnl_parm *p, struct ip6_tnl **pt)
break; break;
} }
if (i == IP6_TNL_MAX) if (i == IP6_TNL_MAX)
return -ENOBUFS; goto failed;
} }
dev = alloc_netdev(sizeof (*t), name, ip6ip6_tnl_dev_setup); dev = alloc_netdev(sizeof (*t), name, ip6ip6_tnl_dev_setup);
if (dev == NULL) if (dev == NULL)
return -ENOMEM; goto failed;
t = netdev_priv(dev); t = netdev_priv(dev);
dev->init = ip6ip6_tnl_dev_init; dev->init = ip6ip6_tnl_dev_init;
...@@ -248,13 +247,13 @@ ip6_tnl_create(struct ip6_tnl_parm *p, struct ip6_tnl **pt) ...@@ -248,13 +247,13 @@ ip6_tnl_create(struct ip6_tnl_parm *p, struct ip6_tnl **pt)
if ((err = register_netdevice(dev)) < 0) { if ((err = register_netdevice(dev)) < 0) {
free_netdev(dev); free_netdev(dev);
return err; goto failed;
} }
dev_hold(dev); dev_hold(dev);
ip6ip6_tnl_link(t); ip6ip6_tnl_link(t);
*pt = t; return t;
return 0; failed:
return NULL;
} }
/** /**
...@@ -268,32 +267,23 @@ ip6_tnl_create(struct ip6_tnl_parm *p, struct ip6_tnl **pt) ...@@ -268,32 +267,23 @@ ip6_tnl_create(struct ip6_tnl_parm *p, struct ip6_tnl **pt)
* tunnel device is created and registered for use. * tunnel device is created and registered for use.
* *
* Return: * Return:
* 0 if tunnel located or created, * matching tunnel or NULL
* -EINVAL if parameters incorrect,
* -ENODEV if no matching tunnel available
**/ **/
static int static struct ip6_tnl *ip6ip6_tnl_locate(struct ip6_tnl_parm *p, int create)
ip6ip6_tnl_locate(struct ip6_tnl_parm *p, struct ip6_tnl **pt, int create)
{ {
struct in6_addr *remote = &p->raddr; struct in6_addr *remote = &p->raddr;
struct in6_addr *local = &p->laddr; struct in6_addr *local = &p->laddr;
struct ip6_tnl *t; struct ip6_tnl *t;
if (p->proto != IPPROTO_IPV6)
return -EINVAL;
for (t = *ip6ip6_bucket(p); t; t = t->next) { for (t = *ip6ip6_bucket(p); t; t = t->next) {
if (ipv6_addr_equal(local, &t->parms.laddr) && if (ipv6_addr_equal(local, &t->parms.laddr) &&
ipv6_addr_equal(remote, &t->parms.raddr)) { ipv6_addr_equal(remote, &t->parms.raddr))
*pt = t; return t;
return (create ? -EEXIST : 0);
}
} }
if (!create) if (!create)
return -ENODEV; return NULL;
return ip6_tnl_create(p);
return ip6_tnl_create(p, pt);
} }
/** /**
...@@ -920,26 +910,20 @@ static int ...@@ -920,26 +910,20 @@ static int
ip6ip6_tnl_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd) ip6ip6_tnl_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
{ {
int err = 0; int err = 0;
int create;
struct ip6_tnl_parm p; struct ip6_tnl_parm p;
struct ip6_tnl *t = NULL; struct ip6_tnl *t = NULL;
switch (cmd) { switch (cmd) {
case SIOCGETTUNNEL: case SIOCGETTUNNEL:
if (dev == ip6ip6_fb_tnl_dev) { if (dev == ip6ip6_fb_tnl_dev) {
if (copy_from_user(&p, if (copy_from_user(&p, ifr->ifr_ifru.ifru_data, sizeof (p))) {
ifr->ifr_ifru.ifru_data,
sizeof (p))) {
err = -EFAULT; err = -EFAULT;
break; break;
} }
if ((err = ip6ip6_tnl_locate(&p, &t, 0)) == -ENODEV) t = ip6ip6_tnl_locate(&p, 0);
t = netdev_priv(dev); }
else if (err) if (t == NULL)
break;
} else
t = netdev_priv(dev); t = netdev_priv(dev);
memcpy(&p, &t->parms, sizeof (p)); memcpy(&p, &t->parms, sizeof (p));
if (copy_to_user(ifr->ifr_ifru.ifru_data, &p, sizeof (p))) { if (copy_to_user(ifr->ifr_ifru.ifru_data, &p, sizeof (p))) {
err = -EFAULT; err = -EFAULT;
...@@ -948,35 +932,36 @@ ip6ip6_tnl_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd) ...@@ -948,35 +932,36 @@ ip6ip6_tnl_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
case SIOCADDTUNNEL: case SIOCADDTUNNEL:
case SIOCCHGTUNNEL: case SIOCCHGTUNNEL:
err = -EPERM; err = -EPERM;
create = (cmd == SIOCADDTUNNEL);
if (!capable(CAP_NET_ADMIN)) if (!capable(CAP_NET_ADMIN))
break; break;
if (copy_from_user(&p, ifr->ifr_ifru.ifru_data, sizeof (p))) { err = -EFAULT;
err = -EFAULT; if (copy_from_user(&p, ifr->ifr_ifru.ifru_data, sizeof (p)))
break; break;
} err = -EINVAL;
if (!create && dev != ip6ip6_fb_tnl_dev) { if (p.proto != IPPROTO_IPV6)
t = netdev_priv(dev);
}
if (!t && (err = ip6ip6_tnl_locate(&p, &t, create))) {
break; break;
} t = ip6ip6_tnl_locate(&p, cmd == SIOCADDTUNNEL);
if (cmd == SIOCCHGTUNNEL) { if (dev != ip6ip6_fb_tnl_dev && cmd == SIOCCHGTUNNEL) {
if (t->dev != dev) { if (t != NULL) {
err = -EEXIST; if (t->dev != dev) {
break; err = -EEXIST;
} break;
}
} else
t = netdev_priv(dev);
ip6ip6_tnl_unlink(t); ip6ip6_tnl_unlink(t);
err = ip6ip6_tnl_change(t, &p); err = ip6ip6_tnl_change(t, &p);
ip6ip6_tnl_link(t); ip6ip6_tnl_link(t);
netdev_state_change(dev); netdev_state_change(dev);
} }
if (copy_to_user(ifr->ifr_ifru.ifru_data, if (t) {
&t->parms, sizeof (p))) {
err = -EFAULT;
} else {
err = 0; err = 0;
} if (copy_to_user(ifr->ifr_ifru.ifru_data, &t->parms, sizeof (p)))
err = -EFAULT;
} else
err = (cmd == SIOCADDTUNNEL ? -ENOBUFS : -ENOENT);
break; break;
case SIOCDELTUNNEL: case SIOCDELTUNNEL:
err = -EPERM; err = -EPERM;
...@@ -984,22 +969,18 @@ ip6ip6_tnl_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd) ...@@ -984,22 +969,18 @@ ip6ip6_tnl_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
break; break;
if (dev == ip6ip6_fb_tnl_dev) { if (dev == ip6ip6_fb_tnl_dev) {
if (copy_from_user(&p, ifr->ifr_ifru.ifru_data, err = -EFAULT;
sizeof (p))) { if (copy_from_user(&p, ifr->ifr_ifru.ifru_data, sizeof (p)))
err = -EFAULT;
break; break;
} err = -ENOENT;
err = ip6ip6_tnl_locate(&p, &t, 0); if ((t = ip6ip6_tnl_locate(&p, 0)) == NULL)
if (err)
break; break;
if (t == netdev_priv(ip6ip6_fb_tnl_dev)) { err = -EPERM;
err = -EPERM; if (t->dev == ip6ip6_fb_tnl_dev)
break; break;
} dev = t->dev;
} else {
t = netdev_priv(dev);
} }
err = unregister_netdevice(t->dev); err = unregister_netdevice(dev);
break; break;
default: default:
err = -EINVAL; err = -EINVAL;
......
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