diff options
author | Patrick McHardy <kaber@trash.net> | 2008-01-21 03:25:50 -0500 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2008-01-28 18:08:31 -0500 |
commit | af30151709bcace1ca844d4bb8b7e2e392ff81eb (patch) | |
tree | 5da346ea833fe8097359613463cd716613359215 | |
parent | acc5efbcd2a023c8801f2bba39971cf93812ce7c (diff) |
[VLAN]: Simplify vlan unregistration
Keep track of the number of VLAN devices in a vlan group. This allows
to have the caller sense when the group is going to be destroyed and
stop using it, which in turn allows to remove the wrapper around
unregister_vlan_dev for the NETDEV_UNREGISTER notifier and avoid
iterating over all possible VLAN ids whenever a device in unregistered.
Also fix what looks like a use-after-free (but is actually safe since
we're holding the RTNL), the real_dev reference should not be dropped
while we still use it.
Signed-off-by: Patrick McHardy <kaber@trash.net>
Signed-off-by: David S. Miller <davem@davemloft.net>
-rw-r--r-- | include/linux/if_vlan.h | 1 | ||||
-rw-r--r-- | net/8021q/vlan.c | 76 | ||||
-rw-r--r-- | net/8021q/vlan.h | 2 | ||||
-rw-r--r-- | net/8021q/vlan_netlink.c | 7 |
4 files changed, 23 insertions, 63 deletions
diff --git a/include/linux/if_vlan.h b/include/linux/if_vlan.h index 07db4169463e..129fa876dbe4 100644 --- a/include/linux/if_vlan.h +++ b/include/linux/if_vlan.h | |||
@@ -82,6 +82,7 @@ extern void vlan_ioctl_set(int (*hook)(struct net *, void __user *)); | |||
82 | 82 | ||
83 | struct vlan_group { | 83 | struct vlan_group { |
84 | int real_dev_ifindex; /* The ifindex of the ethernet(like) device the vlan is attached to. */ | 84 | int real_dev_ifindex; /* The ifindex of the ethernet(like) device the vlan is attached to. */ |
85 | unsigned int nr_vlans; | ||
85 | struct hlist_node hlist; /* linked list */ | 86 | struct hlist_node hlist; /* linked list */ |
86 | struct net_device **vlan_devices_arrays[VLAN_GROUP_ARRAY_SPLIT_PARTS]; | 87 | struct net_device **vlan_devices_arrays[VLAN_GROUP_ARRAY_SPLIT_PARTS]; |
87 | struct rcu_head rcu; | 88 | struct rcu_head rcu; |
diff --git a/net/8021q/vlan.c b/net/8021q/vlan.c index ad34e4a0326a..ac7963854103 100644 --- a/net/8021q/vlan.c +++ b/net/8021q/vlan.c | |||
@@ -132,33 +132,17 @@ static void vlan_rcu_free(struct rcu_head *rcu) | |||
132 | vlan_group_free(container_of(rcu, struct vlan_group, rcu)); | 132 | vlan_group_free(container_of(rcu, struct vlan_group, rcu)); |
133 | } | 133 | } |
134 | 134 | ||
135 | 135 | void unregister_vlan_dev(struct net_device *dev) | |
136 | /* This returns 0 if everything went fine. | ||
137 | * It will return 1 if the group was killed as a result. | ||
138 | * A negative return indicates failure. | ||
139 | * | ||
140 | * The RTNL lock must be held. | ||
141 | */ | ||
142 | static int unregister_vlan_dev(struct net_device *real_dev, | ||
143 | unsigned short vlan_id) | ||
144 | { | 136 | { |
145 | struct net_device *dev; | 137 | struct vlan_dev_info *vlan = VLAN_DEV_INFO(dev); |
146 | int real_dev_ifindex = real_dev->ifindex; | 138 | struct net_device *real_dev = vlan->real_dev; |
147 | struct vlan_group *grp; | 139 | struct vlan_group *grp; |
148 | unsigned int i; | 140 | unsigned short vlan_id = vlan->vlan_id; |
149 | int ret; | ||
150 | |||
151 | if (vlan_id >= VLAN_VID_MASK) | ||
152 | return -EINVAL; | ||
153 | 141 | ||
154 | ASSERT_RTNL(); | 142 | ASSERT_RTNL(); |
155 | grp = __vlan_find_group(real_dev_ifindex); | ||
156 | if (!grp) | ||
157 | return -ENOENT; | ||
158 | 143 | ||
159 | dev = vlan_group_get_device(grp, vlan_id); | 144 | grp = __vlan_find_group(real_dev->ifindex); |
160 | if (!dev) | 145 | BUG_ON(!grp); |
161 | return -ENOENT; | ||
162 | 146 | ||
163 | vlan_proc_rem_dev(dev); | 147 | vlan_proc_rem_dev(dev); |
164 | 148 | ||
@@ -169,20 +153,12 @@ static int unregister_vlan_dev(struct net_device *real_dev, | |||
169 | real_dev->vlan_rx_kill_vid(real_dev, vlan_id); | 153 | real_dev->vlan_rx_kill_vid(real_dev, vlan_id); |
170 | 154 | ||
171 | vlan_group_set_device(grp, vlan_id, NULL); | 155 | vlan_group_set_device(grp, vlan_id, NULL); |
172 | synchronize_net(); | 156 | grp->nr_vlans--; |
173 | 157 | ||
174 | /* Caller unregisters (and if necessary, puts) VLAN device, but we | 158 | synchronize_net(); |
175 | * get rid of the reference to real_dev here. | ||
176 | */ | ||
177 | dev_put(real_dev); | ||
178 | 159 | ||
179 | /* If the group is now empty, kill off the group. */ | 160 | /* If the group is now empty, kill off the group. */ |
180 | ret = 0; | 161 | if (grp->nr_vlans == 0) { |
181 | for (i = 0; i < VLAN_VID_MASK; i++) | ||
182 | if (vlan_group_get_device(grp, i)) | ||
183 | break; | ||
184 | |||
185 | if (i == VLAN_VID_MASK) { | ||
186 | if (real_dev->features & NETIF_F_HW_VLAN_RX) | 162 | if (real_dev->features & NETIF_F_HW_VLAN_RX) |
187 | real_dev->vlan_rx_register(real_dev, NULL); | 163 | real_dev->vlan_rx_register(real_dev, NULL); |
188 | 164 | ||
@@ -190,23 +166,12 @@ static int unregister_vlan_dev(struct net_device *real_dev, | |||
190 | 166 | ||
191 | /* Free the group, after all cpu's are done. */ | 167 | /* Free the group, after all cpu's are done. */ |
192 | call_rcu(&grp->rcu, vlan_rcu_free); | 168 | call_rcu(&grp->rcu, vlan_rcu_free); |
193 | ret = 1; | ||
194 | } | 169 | } |
195 | 170 | ||
196 | return ret; | 171 | /* Get rid of the vlan's reference to real_dev */ |
197 | } | 172 | dev_put(real_dev); |
198 | |||
199 | int unregister_vlan_device(struct net_device *dev) | ||
200 | { | ||
201 | int ret; | ||
202 | 173 | ||
203 | ret = unregister_vlan_dev(VLAN_DEV_INFO(dev)->real_dev, | ||
204 | VLAN_DEV_INFO(dev)->vlan_id); | ||
205 | unregister_netdevice(dev); | 174 | unregister_netdevice(dev); |
206 | |||
207 | if (ret == 1) | ||
208 | ret = 0; | ||
209 | return ret; | ||
210 | } | 175 | } |
211 | 176 | ||
212 | static void vlan_transfer_operstate(const struct net_device *dev, struct net_device *vlandev) | 177 | static void vlan_transfer_operstate(const struct net_device *dev, struct net_device *vlandev) |
@@ -291,6 +256,8 @@ int register_vlan_dev(struct net_device *dev) | |||
291 | * it into our local structure. | 256 | * it into our local structure. |
292 | */ | 257 | */ |
293 | vlan_group_set_device(grp, vlan_id, dev); | 258 | vlan_group_set_device(grp, vlan_id, dev); |
259 | grp->nr_vlans++; | ||
260 | |||
294 | if (ngrp && real_dev->features & NETIF_F_HW_VLAN_RX) | 261 | if (ngrp && real_dev->features & NETIF_F_HW_VLAN_RX) |
295 | real_dev->vlan_rx_register(real_dev, ngrp); | 262 | real_dev->vlan_rx_register(real_dev, ngrp); |
296 | if (real_dev->features & NETIF_F_HW_VLAN_FILTER) | 263 | if (real_dev->features & NETIF_F_HW_VLAN_FILTER) |
@@ -479,20 +446,16 @@ static int vlan_device_event(struct notifier_block *unused, unsigned long event, | |||
479 | case NETDEV_UNREGISTER: | 446 | case NETDEV_UNREGISTER: |
480 | /* Delete all VLANs for this dev. */ | 447 | /* Delete all VLANs for this dev. */ |
481 | for (i = 0; i < VLAN_GROUP_ARRAY_LEN; i++) { | 448 | for (i = 0; i < VLAN_GROUP_ARRAY_LEN; i++) { |
482 | int ret; | ||
483 | |||
484 | vlandev = vlan_group_get_device(grp, i); | 449 | vlandev = vlan_group_get_device(grp, i); |
485 | if (!vlandev) | 450 | if (!vlandev) |
486 | continue; | 451 | continue; |
487 | 452 | ||
488 | ret = unregister_vlan_dev(dev, | 453 | /* unregistration of last vlan destroys group, abort |
489 | VLAN_DEV_INFO(vlandev)->vlan_id); | 454 | * afterwards */ |
490 | 455 | if (grp->nr_vlans == 1) | |
491 | unregister_netdevice(vlandev); | 456 | i = VLAN_GROUP_ARRAY_LEN; |
492 | 457 | ||
493 | /* Group was destroyed? */ | 458 | unregister_vlan_dev(vlandev); |
494 | if (ret == 1) | ||
495 | break; | ||
496 | } | 459 | } |
497 | break; | 460 | break; |
498 | } | 461 | } |
@@ -598,7 +561,8 @@ static int vlan_ioctl_handler(struct net *net, void __user *arg) | |||
598 | err = -EPERM; | 561 | err = -EPERM; |
599 | if (!capable(CAP_NET_ADMIN)) | 562 | if (!capable(CAP_NET_ADMIN)) |
600 | break; | 563 | break; |
601 | err = unregister_vlan_device(dev); | 564 | unregister_vlan_dev(dev); |
565 | err = 0; | ||
602 | break; | 566 | break; |
603 | 567 | ||
604 | case GET_VLAN_REALDEV_NAME_CMD: | 568 | case GET_VLAN_REALDEV_NAME_CMD: |
diff --git a/net/8021q/vlan.h b/net/8021q/vlan.h index 56378651cc46..0cfdf77b497c 100644 --- a/net/8021q/vlan.h +++ b/net/8021q/vlan.h | |||
@@ -38,7 +38,7 @@ void vlan_dev_get_vid(const struct net_device *dev, unsigned short *result); | |||
38 | int vlan_check_real_dev(struct net_device *real_dev, unsigned short vlan_id); | 38 | int vlan_check_real_dev(struct net_device *real_dev, unsigned short vlan_id); |
39 | void vlan_setup(struct net_device *dev); | 39 | void vlan_setup(struct net_device *dev); |
40 | int register_vlan_dev(struct net_device *dev); | 40 | int register_vlan_dev(struct net_device *dev); |
41 | int unregister_vlan_device(struct net_device *dev); | 41 | void unregister_vlan_dev(struct net_device *dev); |
42 | 42 | ||
43 | int vlan_netlink_init(void); | 43 | int vlan_netlink_init(void); |
44 | void vlan_netlink_fini(void); | 44 | void vlan_netlink_fini(void); |
diff --git a/net/8021q/vlan_netlink.c b/net/8021q/vlan_netlink.c index 0996185e2ed5..9ee63583ed27 100644 --- a/net/8021q/vlan_netlink.c +++ b/net/8021q/vlan_netlink.c | |||
@@ -137,11 +137,6 @@ static int vlan_newlink(struct net_device *dev, | |||
137 | return register_vlan_dev(dev); | 137 | return register_vlan_dev(dev); |
138 | } | 138 | } |
139 | 139 | ||
140 | static void vlan_dellink(struct net_device *dev) | ||
141 | { | ||
142 | unregister_vlan_device(dev); | ||
143 | } | ||
144 | |||
145 | static inline size_t vlan_qos_map_size(unsigned int n) | 140 | static inline size_t vlan_qos_map_size(unsigned int n) |
146 | { | 141 | { |
147 | if (n == 0) | 142 | if (n == 0) |
@@ -226,7 +221,7 @@ struct rtnl_link_ops vlan_link_ops __read_mostly = { | |||
226 | .validate = vlan_validate, | 221 | .validate = vlan_validate, |
227 | .newlink = vlan_newlink, | 222 | .newlink = vlan_newlink, |
228 | .changelink = vlan_changelink, | 223 | .changelink = vlan_changelink, |
229 | .dellink = vlan_dellink, | 224 | .dellink = unregister_vlan_dev, |
230 | .get_size = vlan_get_size, | 225 | .get_size = vlan_get_size, |
231 | .fill_info = vlan_fill_info, | 226 | .fill_info = vlan_fill_info, |
232 | }; | 227 | }; |