Commit 68365458 authored by Patrick McHardy's avatar Patrick McHardy Committed by David S. Miller

[NET]: rtnl_link: fix use-after-free

When unregistering the rtnl_link_ops, all existing devices using
the ops are destroyed. With nested devices this may lead to a
use-after-free despite the use of for_each_netdev_safe() in case
the upper device is next in the device list and is destroyed
by the NETDEV_UNREGISTER notifier.

The easy fix is to restart scanning the device list after removing
a device. Alternatively we could add new devices to the front of
the list to avoid having dependant devices follow the device they
depend on. A third option would be to only restart scanning if
dev->iflink of the next device matches dev->ifindex of the current
one. For now this seems like the safest solution.

With this patch, the veth rtnl_link_ops unregistration can use
rtnl_link_unregister() directly since it now also handles destruction
of multiple devices at once.
Signed-off-by: default avatarPatrick McHardy <kaber@trash.net>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent d4782c32
...@@ -459,19 +459,7 @@ static __init int veth_init(void) ...@@ -459,19 +459,7 @@ static __init int veth_init(void)
static __exit void veth_exit(void) static __exit void veth_exit(void)
{ {
struct veth_priv *priv, *next; rtnl_link_unregister(&veth_link_ops);
rtnl_lock();
/*
* cannot trust __rtnl_link_unregister() to unregister all
* devices, as each ->dellink call will remove two devices
* from the list at once.
*/
list_for_each_entry_safe(priv, next, &veth_list, list)
veth_dellink(priv->dev);
__rtnl_link_unregister(&veth_link_ops);
rtnl_unlock();
} }
module_init(veth_init); module_init(veth_init);
......
...@@ -308,9 +308,12 @@ void __rtnl_link_unregister(struct rtnl_link_ops *ops) ...@@ -308,9 +308,12 @@ void __rtnl_link_unregister(struct rtnl_link_ops *ops)
struct net *net; struct net *net;
for_each_net(net) { for_each_net(net) {
restart:
for_each_netdev_safe(net, dev, n) { for_each_netdev_safe(net, dev, n) {
if (dev->rtnl_link_ops == ops) if (dev->rtnl_link_ops == ops) {
ops->dellink(dev); ops->dellink(dev);
goto restart;
}
} }
} }
list_del(&ops->list); list_del(&ops->list);
......
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