diff options
author | Ville Nuorvala <vnuorval@tcs.hut.fi> | 2006-11-24 20:05:41 -0500 |
---|---|---|
committer | David S. Miller <davem@sunset.davemloft.net> | 2006-12-03 00:30:24 -0500 |
commit | 567131a722ca064c917c0b06e4bcf07d47602103 (patch) | |
tree | d249b39f0c05aedeb3873a897151b47913632eee /net | |
parent | e94ef682053a6eeca91aefdaecf8efe7fd7e33a5 (diff) |
[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: Ville Nuorvala <vnuorval@tcs.hut.fi>
Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'net')
-rw-r--r-- | net/ipv6/ip6_tunnel.c | 111 |
1 files changed, 46 insertions, 65 deletions
diff --git a/net/ipv6/ip6_tunnel.c b/net/ipv6/ip6_tunnel.c index 25bc5ed49104..fdf1a2fa3a3d 100644 --- a/net/ipv6/ip6_tunnel.c +++ b/net/ipv6/ip6_tunnel.c | |||
@@ -215,11 +215,10 @@ ip6ip6_tnl_unlink(struct ip6_tnl *t) | |||
215 | * Create tunnel matching given parameters. | 215 | * Create tunnel matching given parameters. |
216 | * | 216 | * |
217 | * Return: | 217 | * Return: |
218 | * 0 on success | 218 | * created tunnel or NULL |
219 | **/ | 219 | **/ |
220 | 220 | ||
221 | static int | 221 | static struct ip6_tnl *ip6_tnl_create(struct ip6_tnl_parm *p) |
222 | ip6_tnl_create(struct ip6_tnl_parm *p, struct ip6_tnl **pt) | ||
223 | { | 222 | { |
224 | struct net_device *dev; | 223 | struct net_device *dev; |
225 | struct ip6_tnl *t; | 224 | struct ip6_tnl *t; |
@@ -236,11 +235,11 @@ ip6_tnl_create(struct ip6_tnl_parm *p, struct ip6_tnl **pt) | |||
236 | break; | 235 | break; |
237 | } | 236 | } |
238 | if (i == IP6_TNL_MAX) | 237 | if (i == IP6_TNL_MAX) |
239 | return -ENOBUFS; | 238 | goto failed; |
240 | } | 239 | } |
241 | dev = alloc_netdev(sizeof (*t), name, ip6ip6_tnl_dev_setup); | 240 | dev = alloc_netdev(sizeof (*t), name, ip6ip6_tnl_dev_setup); |
242 | if (dev == NULL) | 241 | if (dev == NULL) |
243 | return -ENOMEM; | 242 | goto failed; |
244 | 243 | ||
245 | t = netdev_priv(dev); | 244 | t = netdev_priv(dev); |
246 | dev->init = ip6ip6_tnl_dev_init; | 245 | dev->init = ip6ip6_tnl_dev_init; |
@@ -248,13 +247,13 @@ ip6_tnl_create(struct ip6_tnl_parm *p, struct ip6_tnl **pt) | |||
248 | 247 | ||
249 | if ((err = register_netdevice(dev)) < 0) { | 248 | if ((err = register_netdevice(dev)) < 0) { |
250 | free_netdev(dev); | 249 | free_netdev(dev); |
251 | return err; | 250 | goto failed; |
252 | } | 251 | } |
253 | dev_hold(dev); | 252 | dev_hold(dev); |
254 | |||
255 | ip6ip6_tnl_link(t); | 253 | ip6ip6_tnl_link(t); |
256 | *pt = t; | 254 | return t; |
257 | return 0; | 255 | failed: |
256 | return NULL; | ||
258 | } | 257 | } |
259 | 258 | ||
260 | /** | 259 | /** |
@@ -268,32 +267,23 @@ ip6_tnl_create(struct ip6_tnl_parm *p, struct ip6_tnl **pt) | |||
268 | * tunnel device is created and registered for use. | 267 | * tunnel device is created and registered for use. |
269 | * | 268 | * |
270 | * Return: | 269 | * Return: |
271 | * 0 if tunnel located or created, | 270 | * matching tunnel or NULL |
272 | * -EINVAL if parameters incorrect, | ||
273 | * -ENODEV if no matching tunnel available | ||
274 | **/ | 271 | **/ |
275 | 272 | ||
276 | static int | 273 | static struct ip6_tnl *ip6ip6_tnl_locate(struct ip6_tnl_parm *p, int create) |
277 | ip6ip6_tnl_locate(struct ip6_tnl_parm *p, struct ip6_tnl **pt, int create) | ||
278 | { | 274 | { |
279 | struct in6_addr *remote = &p->raddr; | 275 | struct in6_addr *remote = &p->raddr; |
280 | struct in6_addr *local = &p->laddr; | 276 | struct in6_addr *local = &p->laddr; |
281 | struct ip6_tnl *t; | 277 | struct ip6_tnl *t; |
282 | 278 | ||
283 | if (p->proto != IPPROTO_IPV6) | ||
284 | return -EINVAL; | ||
285 | |||
286 | for (t = *ip6ip6_bucket(p); t; t = t->next) { | 279 | for (t = *ip6ip6_bucket(p); t; t = t->next) { |
287 | if (ipv6_addr_equal(local, &t->parms.laddr) && | 280 | if (ipv6_addr_equal(local, &t->parms.laddr) && |
288 | ipv6_addr_equal(remote, &t->parms.raddr)) { | 281 | ipv6_addr_equal(remote, &t->parms.raddr)) |
289 | *pt = t; | 282 | return t; |
290 | return (create ? -EEXIST : 0); | ||
291 | } | ||
292 | } | 283 | } |
293 | if (!create) | 284 | if (!create) |
294 | return -ENODEV; | 285 | return NULL; |
295 | 286 | return ip6_tnl_create(p); | |
296 | return ip6_tnl_create(p, pt); | ||
297 | } | 287 | } |
298 | 288 | ||
299 | /** | 289 | /** |
@@ -920,26 +910,20 @@ static int | |||
920 | ip6ip6_tnl_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd) | 910 | ip6ip6_tnl_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd) |
921 | { | 911 | { |
922 | int err = 0; | 912 | int err = 0; |
923 | int create; | ||
924 | struct ip6_tnl_parm p; | 913 | struct ip6_tnl_parm p; |
925 | struct ip6_tnl *t = NULL; | 914 | struct ip6_tnl *t = NULL; |
926 | 915 | ||
927 | switch (cmd) { | 916 | switch (cmd) { |
928 | case SIOCGETTUNNEL: | 917 | case SIOCGETTUNNEL: |
929 | if (dev == ip6ip6_fb_tnl_dev) { | 918 | if (dev == ip6ip6_fb_tnl_dev) { |
930 | if (copy_from_user(&p, | 919 | if (copy_from_user(&p, ifr->ifr_ifru.ifru_data, sizeof (p))) { |
931 | ifr->ifr_ifru.ifru_data, | ||
932 | sizeof (p))) { | ||
933 | err = -EFAULT; | 920 | err = -EFAULT; |
934 | break; | 921 | break; |
935 | } | 922 | } |
936 | if ((err = ip6ip6_tnl_locate(&p, &t, 0)) == -ENODEV) | 923 | t = ip6ip6_tnl_locate(&p, 0); |
937 | t = netdev_priv(dev); | 924 | } |
938 | else if (err) | 925 | if (t == NULL) |
939 | break; | ||
940 | } else | ||
941 | t = netdev_priv(dev); | 926 | t = netdev_priv(dev); |
942 | |||
943 | memcpy(&p, &t->parms, sizeof (p)); | 927 | memcpy(&p, &t->parms, sizeof (p)); |
944 | if (copy_to_user(ifr->ifr_ifru.ifru_data, &p, sizeof (p))) { | 928 | if (copy_to_user(ifr->ifr_ifru.ifru_data, &p, sizeof (p))) { |
945 | err = -EFAULT; | 929 | err = -EFAULT; |
@@ -948,35 +932,36 @@ ip6ip6_tnl_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd) | |||
948 | case SIOCADDTUNNEL: | 932 | case SIOCADDTUNNEL: |
949 | case SIOCCHGTUNNEL: | 933 | case SIOCCHGTUNNEL: |
950 | err = -EPERM; | 934 | err = -EPERM; |
951 | create = (cmd == SIOCADDTUNNEL); | ||
952 | if (!capable(CAP_NET_ADMIN)) | 935 | if (!capable(CAP_NET_ADMIN)) |
953 | break; | 936 | break; |
954 | if (copy_from_user(&p, ifr->ifr_ifru.ifru_data, sizeof (p))) { | 937 | err = -EFAULT; |
955 | err = -EFAULT; | 938 | if (copy_from_user(&p, ifr->ifr_ifru.ifru_data, sizeof (p))) |
956 | break; | 939 | break; |
957 | } | 940 | err = -EINVAL; |
958 | if (!create && dev != ip6ip6_fb_tnl_dev) { | 941 | if (p.proto != IPPROTO_IPV6) |
959 | t = netdev_priv(dev); | ||
960 | } | ||
961 | if (!t && (err = ip6ip6_tnl_locate(&p, &t, create))) { | ||
962 | break; | 942 | break; |
963 | } | 943 | t = ip6ip6_tnl_locate(&p, cmd == SIOCADDTUNNEL); |
964 | if (cmd == SIOCCHGTUNNEL) { | 944 | if (dev != ip6ip6_fb_tnl_dev && cmd == SIOCCHGTUNNEL) { |
965 | if (t->dev != dev) { | 945 | if (t != NULL) { |
966 | err = -EEXIST; | 946 | if (t->dev != dev) { |
967 | break; | 947 | err = -EEXIST; |
968 | } | 948 | break; |
949 | } | ||
950 | } else | ||
951 | t = netdev_priv(dev); | ||
952 | |||
969 | ip6ip6_tnl_unlink(t); | 953 | ip6ip6_tnl_unlink(t); |
970 | err = ip6ip6_tnl_change(t, &p); | 954 | err = ip6ip6_tnl_change(t, &p); |
971 | ip6ip6_tnl_link(t); | 955 | ip6ip6_tnl_link(t); |
972 | netdev_state_change(dev); | 956 | netdev_state_change(dev); |
973 | } | 957 | } |
974 | if (copy_to_user(ifr->ifr_ifru.ifru_data, | 958 | if (t) { |
975 | &t->parms, sizeof (p))) { | ||
976 | err = -EFAULT; | ||
977 | } else { | ||
978 | err = 0; | 959 | err = 0; |
979 | } | 960 | if (copy_to_user(ifr->ifr_ifru.ifru_data, &t->parms, sizeof (p))) |
961 | err = -EFAULT; | ||
962 | |||
963 | } else | ||
964 | err = (cmd == SIOCADDTUNNEL ? -ENOBUFS : -ENOENT); | ||
980 | break; | 965 | break; |
981 | case SIOCDELTUNNEL: | 966 | case SIOCDELTUNNEL: |
982 | err = -EPERM; | 967 | err = -EPERM; |
@@ -984,22 +969,18 @@ ip6ip6_tnl_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd) | |||
984 | break; | 969 | break; |
985 | 970 | ||
986 | if (dev == ip6ip6_fb_tnl_dev) { | 971 | if (dev == ip6ip6_fb_tnl_dev) { |
987 | if (copy_from_user(&p, ifr->ifr_ifru.ifru_data, | 972 | err = -EFAULT; |
988 | sizeof (p))) { | 973 | if (copy_from_user(&p, ifr->ifr_ifru.ifru_data, sizeof (p))) |
989 | err = -EFAULT; | ||
990 | break; | 974 | break; |
991 | } | 975 | err = -ENOENT; |
992 | err = ip6ip6_tnl_locate(&p, &t, 0); | 976 | if ((t = ip6ip6_tnl_locate(&p, 0)) == NULL) |
993 | if (err) | ||
994 | break; | 977 | break; |
995 | if (t == netdev_priv(ip6ip6_fb_tnl_dev)) { | 978 | err = -EPERM; |
996 | err = -EPERM; | 979 | if (t->dev == ip6ip6_fb_tnl_dev) |
997 | break; | 980 | break; |
998 | } | 981 | dev = t->dev; |
999 | } else { | ||
1000 | t = netdev_priv(dev); | ||
1001 | } | 982 | } |
1002 | err = unregister_netdevice(t->dev); | 983 | err = unregister_netdevice(dev); |
1003 | break; | 984 | break; |
1004 | default: | 985 | default: |
1005 | err = -EINVAL; | 986 | err = -EINVAL; |