ipvlan_uninit() for the last ipvlan device resets the lower device's
rx_handler_data to NULL.
Once RTNL is removed, ipvlan_init() would race with ipvlan_uninit(),
which could leak a newly allocated ipvl_port.
ipvlan_init() ipvlan_uninit()
| |- if (refcount_dec_and_test(old_port))
... |- ipvlan_port_destroy(old_port)
| '
|- refcount_inc_not_zero(old_port) <-- fails
|- ipvlan_port_create(phy_dev) .
|- new_port = kzalloc() |
|- phy_dev->rx_handler_data = new_port
|- phy_dev->rx_handler_data = NULL
...
`- kfree(old_port);
Let's synchronise the two by holding the lower device's netdev_lock().
Signed-off-by: Kuniyuki Iwashima <kuniyu@google.com>
---
drivers/net/ipvlan/ipvlan_main.c | 15 +++++++++++++++
1 file changed, 15 insertions(+)
diff --git a/drivers/net/ipvlan/ipvlan_main.c b/drivers/net/ipvlan/ipvlan_main.c
index b4906a8d24ef..7adad781e9b5 100644
--- a/drivers/net/ipvlan/ipvlan_main.c
+++ b/drivers/net/ipvlan/ipvlan_main.c
@@ -177,9 +177,12 @@ static int ipvlan_init(struct net_device *dev)
if (!ipvlan->pcpu_stats)
return -ENOMEM;
+ netdev_lock(phy_dev);
+
if (!netif_is_ipvlan_port(phy_dev)) {
err = ipvlan_port_create(phy_dev);
if (err < 0) {
+ netdev_unlock(phy_dev);
free_percpu(ipvlan->pcpu_stats);
return err;
}@@ -190,6 +193,8 @@ static int ipvlan_init(struct net_device *dev)
refcount_inc(&port->count);
}
+ netdev_unlock(phy_dev);
+
ipvlan->port = port;
return 0;
@@ -198,9 +203,19 @@ static int ipvlan_init(struct net_device *dev)
static void ipvlan_uninit(struct net_device *dev)
{
struct ipvl_dev *ipvlan = netdev_priv(dev);
+ netdevice_tracker dev_tracker;
+ struct net_device *phy_dev;
free_percpu(ipvlan->pcpu_stats);
+
+ phy_dev = ipvlan->phy_dev;
+ netdev_hold(phy_dev, &dev_tracker, GFP_KERNEL);
+ netdev_lock(phy_dev);
+
ipvlan_port_put(ipvlan->port);
+
+ netdev_unlock(phy_dev);
+ netdev_put(phy_dev, &dev_tracker);
}
static int ipvlan_open(struct net_device *dev)--
2.55.0.rc0.799.gd6f94ed593-goog