Commit fe18da60 authored by Girish Moodalbail's avatar Girish Moodalbail Committed by David S. Miller

ipvlan: NULL pointer dereference panic in ipvlan_port_destroy

When call to register_netdevice() (called from ipvlan_link_new()) fails,
we call ipvlan_uninit() (through ndo_uninit()) to destroy the ipvlan
port. After returning unsuccessfully from register_netdevice() we go
ahead and call ipvlan_port_destroy() again which causes NULL pointer
dereference panic. Fix the issue by making ipvlan_init() and
ipvlan_uninit() call symmetric.

The ipvlan port will now be created inside ipvlan_init() and will be
destroyed in ipvlan_uninit().

Fixes: 2ad7bf36 (ipvlan: Initial check-in of the IPVLAN driver)
Signed-off-by: default avatarGirish Moodalbail <girish.moodalbail@oracle.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent cebe84c6
...@@ -107,16 +107,6 @@ static int ipvlan_port_create(struct net_device *dev) ...@@ -107,16 +107,6 @@ static int ipvlan_port_create(struct net_device *dev)
struct ipvl_port *port; struct ipvl_port *port;
int err, idx; int err, idx;
if (dev->type != ARPHRD_ETHER || dev->flags & IFF_LOOPBACK) {
netdev_err(dev, "Master is either lo or non-ether device\n");
return -EINVAL;
}
if (netdev_is_rx_handler_busy(dev)) {
netdev_err(dev, "Device is already in use.\n");
return -EBUSY;
}
port = kzalloc(sizeof(struct ipvl_port), GFP_KERNEL); port = kzalloc(sizeof(struct ipvl_port), GFP_KERNEL);
if (!port) if (!port)
return -ENOMEM; return -ENOMEM;
...@@ -179,8 +169,9 @@ static void ipvlan_port_destroy(struct net_device *dev) ...@@ -179,8 +169,9 @@ static void ipvlan_port_destroy(struct net_device *dev)
static int ipvlan_init(struct net_device *dev) static int ipvlan_init(struct net_device *dev)
{ {
struct ipvl_dev *ipvlan = netdev_priv(dev); struct ipvl_dev *ipvlan = netdev_priv(dev);
const struct net_device *phy_dev = ipvlan->phy_dev; struct net_device *phy_dev = ipvlan->phy_dev;
struct ipvl_port *port = ipvlan->port; struct ipvl_port *port;
int err;
dev->state = (dev->state & ~IPVLAN_STATE_MASK) | dev->state = (dev->state & ~IPVLAN_STATE_MASK) |
(phy_dev->state & IPVLAN_STATE_MASK); (phy_dev->state & IPVLAN_STATE_MASK);
...@@ -196,18 +187,27 @@ static int ipvlan_init(struct net_device *dev) ...@@ -196,18 +187,27 @@ static int ipvlan_init(struct net_device *dev)
if (!ipvlan->pcpu_stats) if (!ipvlan->pcpu_stats)
return -ENOMEM; return -ENOMEM;
if (!netif_is_ipvlan_port(phy_dev)) {
err = ipvlan_port_create(phy_dev);
if (err < 0) {
free_percpu(ipvlan->pcpu_stats);
return err;
}
}
port = ipvlan_port_get_rtnl(phy_dev);
port->count += 1; port->count += 1;
return 0; return 0;
} }
static void ipvlan_uninit(struct net_device *dev) static void ipvlan_uninit(struct net_device *dev)
{ {
struct ipvl_dev *ipvlan = netdev_priv(dev); struct ipvl_dev *ipvlan = netdev_priv(dev);
struct ipvl_port *port = ipvlan->port; struct net_device *phy_dev = ipvlan->phy_dev;
struct ipvl_port *port;
free_percpu(ipvlan->pcpu_stats); free_percpu(ipvlan->pcpu_stats);
port = ipvlan_port_get_rtnl(phy_dev);
port->count -= 1; port->count -= 1;
if (!port->count) if (!port->count)
ipvlan_port_destroy(port->dev); ipvlan_port_destroy(port->dev);
...@@ -554,7 +554,6 @@ int ipvlan_link_new(struct net *src_net, struct net_device *dev, ...@@ -554,7 +554,6 @@ int ipvlan_link_new(struct net *src_net, struct net_device *dev,
struct net_device *phy_dev; struct net_device *phy_dev;
int err; int err;
u16 mode = IPVLAN_MODE_L3; u16 mode = IPVLAN_MODE_L3;
bool create = false;
if (!tb[IFLA_LINK]) if (!tb[IFLA_LINK])
return -EINVAL; return -EINVAL;
...@@ -568,28 +567,41 @@ int ipvlan_link_new(struct net *src_net, struct net_device *dev, ...@@ -568,28 +567,41 @@ int ipvlan_link_new(struct net *src_net, struct net_device *dev,
phy_dev = tmp->phy_dev; phy_dev = tmp->phy_dev;
} else if (!netif_is_ipvlan_port(phy_dev)) { } else if (!netif_is_ipvlan_port(phy_dev)) {
err = ipvlan_port_create(phy_dev); /* Exit early if the underlying link is invalid or busy */
if (err < 0) if (phy_dev->type != ARPHRD_ETHER ||
return err; phy_dev->flags & IFF_LOOPBACK) {
create = true; netdev_err(phy_dev,
"Master is either lo or non-ether device\n");
return -EINVAL;
} }
if (data && data[IFLA_IPVLAN_MODE]) if (netdev_is_rx_handler_busy(phy_dev)) {
mode = nla_get_u16(data[IFLA_IPVLAN_MODE]); netdev_err(phy_dev, "Device is already in use.\n");
return -EBUSY;
}
}
port = ipvlan_port_get_rtnl(phy_dev);
ipvlan->phy_dev = phy_dev; ipvlan->phy_dev = phy_dev;
ipvlan->dev = dev; ipvlan->dev = dev;
ipvlan->port = port;
ipvlan->sfeatures = IPVLAN_FEATURES; ipvlan->sfeatures = IPVLAN_FEATURES;
ipvlan_adjust_mtu(ipvlan, phy_dev); ipvlan_adjust_mtu(ipvlan, phy_dev);
INIT_LIST_HEAD(&ipvlan->addrs); INIT_LIST_HEAD(&ipvlan->addrs);
/* Flags are per port and latest update overrides. User has /* TODO Probably put random address here to be presented to the
* to be consistent in setting it just like the mode attribute. * world but keep using the physical-dev address for the outgoing
* packets.
*/ */
if (data && data[IFLA_IPVLAN_FLAGS]) memcpy(dev->dev_addr, phy_dev->dev_addr, ETH_ALEN);
ipvlan->port->flags = nla_get_u16(data[IFLA_IPVLAN_FLAGS]);
dev->priv_flags |= IFF_IPVLAN_SLAVE;
err = register_netdevice(dev);
if (err < 0)
return err;
/* ipvlan_init() would have created the port, if required */
port = ipvlan_port_get_rtnl(phy_dev);
ipvlan->port = port;
/* If the port-id base is at the MAX value, then wrap it around and /* If the port-id base is at the MAX value, then wrap it around and
* begin from 0x1 again. This may be due to a busy system where lots * begin from 0x1 again. This may be due to a busy system where lots
...@@ -609,31 +621,28 @@ int ipvlan_link_new(struct net *src_net, struct net_device *dev, ...@@ -609,31 +621,28 @@ int ipvlan_link_new(struct net *src_net, struct net_device *dev,
err = ida_simple_get(&port->ida, 0x1, port->dev_id_start, err = ida_simple_get(&port->ida, 0x1, port->dev_id_start,
GFP_KERNEL); GFP_KERNEL);
if (err < 0) if (err < 0)
goto destroy_ipvlan_port; goto unregister_netdev;
dev->dev_id = err; dev->dev_id = err;
/* Increment id-base to the next slot for the future assignment */ /* Increment id-base to the next slot for the future assignment */
port->dev_id_start = err + 1; port->dev_id_start = err + 1;
/* TODO Probably put random address here to be presented to the err = netdev_upper_dev_link(phy_dev, dev, extack);
* world but keep using the physical-dev address for the outgoing if (err)
* packets. goto remove_ida;
*/
memcpy(dev->dev_addr, phy_dev->dev_addr, ETH_ALEN);
dev->priv_flags |= IFF_IPVLAN_SLAVE; /* Flags are per port and latest update overrides. User has
* to be consistent in setting it just like the mode attribute.
*/
if (data && data[IFLA_IPVLAN_FLAGS])
port->flags = nla_get_u16(data[IFLA_IPVLAN_FLAGS]);
err = register_netdevice(dev); if (data && data[IFLA_IPVLAN_MODE])
if (err < 0) mode = nla_get_u16(data[IFLA_IPVLAN_MODE]);
goto remove_ida;
err = netdev_upper_dev_link(phy_dev, dev, extack);
if (err) {
goto unregister_netdev;
}
err = ipvlan_set_port_mode(port, mode); err = ipvlan_set_port_mode(port, mode);
if (err) { if (err)
goto unlink_netdev; goto unlink_netdev;
}
list_add_tail_rcu(&ipvlan->pnode, &port->ipvlans); list_add_tail_rcu(&ipvlan->pnode, &port->ipvlans);
netif_stacked_transfer_operstate(phy_dev, dev); netif_stacked_transfer_operstate(phy_dev, dev);
...@@ -641,13 +650,10 @@ int ipvlan_link_new(struct net *src_net, struct net_device *dev, ...@@ -641,13 +650,10 @@ int ipvlan_link_new(struct net *src_net, struct net_device *dev,
unlink_netdev: unlink_netdev:
netdev_upper_dev_unlink(phy_dev, dev); netdev_upper_dev_unlink(phy_dev, dev);
unregister_netdev:
unregister_netdevice(dev);
remove_ida: remove_ida:
ida_simple_remove(&port->ida, dev->dev_id); ida_simple_remove(&port->ida, dev->dev_id);
destroy_ipvlan_port: unregister_netdev:
if (create) unregister_netdevice(dev);
ipvlan_port_destroy(phy_dev);
return err; return err;
} }
EXPORT_SYMBOL_GPL(ipvlan_link_new); EXPORT_SYMBOL_GPL(ipvlan_link_new);
......
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