Commit 7797b93b authored by Toshiaki Makita's avatar Toshiaki Makita Committed by David S. Miller

veth: Free queues on link delete

David Ahern reported memory leak in veth.

=======================================================================
$ cat /sys/kernel/debug/kmemleak
unreferenced object 0xffff8800354d5c00 (size 1024):
  comm "ip", pid 836, jiffies 4294722952 (age 25.904s)
  hex dump (first 32 bytes):
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
  backtrace:
    [<(____ptrval____)>] kmemleak_alloc+0x70/0x94
    [<(____ptrval____)>] slab_post_alloc_hook+0x42/0x52
    [<(____ptrval____)>] __kmalloc+0x101/0x142
    [<(____ptrval____)>] kmalloc_array.constprop.20+0x1e/0x26 [veth]
    [<(____ptrval____)>] veth_newlink+0x147/0x3ac [veth]
    ...
unreferenced object 0xffff88002e009c00 (size 1024):
  comm "ip", pid 836, jiffies 4294722958 (age 25.898s)
  hex dump (first 32 bytes):
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
  backtrace:
    [<(____ptrval____)>] kmemleak_alloc+0x70/0x94
    [<(____ptrval____)>] slab_post_alloc_hook+0x42/0x52
    [<(____ptrval____)>] __kmalloc+0x101/0x142
    [<(____ptrval____)>] kmalloc_array.constprop.20+0x1e/0x26 [veth]
    [<(____ptrval____)>] veth_newlink+0x219/0x3ac [veth]
=======================================================================

veth_rq allocated in veth_newlink() was not freed on dellink.

We need to free up them after veth_close() so that any packets will not
reference the queues afterwards. Thus free them in veth_dev_free() in
the same way as freeing stats structure (vstats).

Also move queues allocation to veth_dev_init() to be in line with stats
allocation.

Fixes: 638264dc ("veth: Support per queue XDP ring")
Reported-by: default avatarDavid Ahern <dsahern@gmail.com>
Signed-off-by: default avatarToshiaki Makita <makita.toshiaki@lab.ntt.co.jp>
Reviewed-by: default avatarDavid Ahern <dsahern@gmail.com>
Tested-by: default avatarDavid Ahern <dsahern@gmail.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent ff93bca7
...@@ -789,16 +789,48 @@ static int is_valid_veth_mtu(int mtu) ...@@ -789,16 +789,48 @@ static int is_valid_veth_mtu(int mtu)
return mtu >= ETH_MIN_MTU && mtu <= ETH_MAX_MTU; return mtu >= ETH_MIN_MTU && mtu <= ETH_MAX_MTU;
} }
static int veth_alloc_queues(struct net_device *dev)
{
struct veth_priv *priv = netdev_priv(dev);
int i;
priv->rq = kcalloc(dev->num_rx_queues, sizeof(*priv->rq), GFP_KERNEL);
if (!priv->rq)
return -ENOMEM;
for (i = 0; i < dev->num_rx_queues; i++)
priv->rq[i].dev = dev;
return 0;
}
static void veth_free_queues(struct net_device *dev)
{
struct veth_priv *priv = netdev_priv(dev);
kfree(priv->rq);
}
static int veth_dev_init(struct net_device *dev) static int veth_dev_init(struct net_device *dev)
{ {
int err;
dev->vstats = netdev_alloc_pcpu_stats(struct pcpu_vstats); dev->vstats = netdev_alloc_pcpu_stats(struct pcpu_vstats);
if (!dev->vstats) if (!dev->vstats)
return -ENOMEM; return -ENOMEM;
err = veth_alloc_queues(dev);
if (err) {
free_percpu(dev->vstats);
return err;
}
return 0; return 0;
} }
static void veth_dev_free(struct net_device *dev) static void veth_dev_free(struct net_device *dev)
{ {
veth_free_queues(dev);
free_percpu(dev->vstats); free_percpu(dev->vstats);
} }
...@@ -1040,31 +1072,13 @@ static int veth_validate(struct nlattr *tb[], struct nlattr *data[], ...@@ -1040,31 +1072,13 @@ static int veth_validate(struct nlattr *tb[], struct nlattr *data[],
return 0; return 0;
} }
static int veth_alloc_queues(struct net_device *dev)
{
struct veth_priv *priv = netdev_priv(dev);
priv->rq = kcalloc(dev->num_rx_queues, sizeof(*priv->rq), GFP_KERNEL);
if (!priv->rq)
return -ENOMEM;
return 0;
}
static void veth_free_queues(struct net_device *dev)
{
struct veth_priv *priv = netdev_priv(dev);
kfree(priv->rq);
}
static struct rtnl_link_ops veth_link_ops; static struct rtnl_link_ops veth_link_ops;
static int veth_newlink(struct net *src_net, struct net_device *dev, static int veth_newlink(struct net *src_net, struct net_device *dev,
struct nlattr *tb[], struct nlattr *data[], struct nlattr *tb[], struct nlattr *data[],
struct netlink_ext_ack *extack) struct netlink_ext_ack *extack)
{ {
int err, i; int err;
struct net_device *peer; struct net_device *peer;
struct veth_priv *priv; struct veth_priv *priv;
char ifname[IFNAMSIZ]; char ifname[IFNAMSIZ];
...@@ -1117,12 +1131,6 @@ static int veth_newlink(struct net *src_net, struct net_device *dev, ...@@ -1117,12 +1131,6 @@ static int veth_newlink(struct net *src_net, struct net_device *dev,
return PTR_ERR(peer); return PTR_ERR(peer);
} }
err = veth_alloc_queues(peer);
if (err) {
put_net(net);
goto err_peer_alloc_queues;
}
if (!ifmp || !tbp[IFLA_ADDRESS]) if (!ifmp || !tbp[IFLA_ADDRESS])
eth_hw_addr_random(peer); eth_hw_addr_random(peer);
...@@ -1151,10 +1159,6 @@ static int veth_newlink(struct net *src_net, struct net_device *dev, ...@@ -1151,10 +1159,6 @@ static int veth_newlink(struct net *src_net, struct net_device *dev,
* should be re-allocated * should be re-allocated
*/ */
err = veth_alloc_queues(dev);
if (err)
goto err_alloc_queues;
if (tb[IFLA_ADDRESS] == NULL) if (tb[IFLA_ADDRESS] == NULL)
eth_hw_addr_random(dev); eth_hw_addr_random(dev);
...@@ -1174,28 +1178,20 @@ static int veth_newlink(struct net *src_net, struct net_device *dev, ...@@ -1174,28 +1178,20 @@ static int veth_newlink(struct net *src_net, struct net_device *dev,
*/ */
priv = netdev_priv(dev); priv = netdev_priv(dev);
for (i = 0; i < dev->real_num_rx_queues; i++)
priv->rq[i].dev = dev;
rcu_assign_pointer(priv->peer, peer); rcu_assign_pointer(priv->peer, peer);
priv = netdev_priv(peer); priv = netdev_priv(peer);
for (i = 0; i < peer->real_num_rx_queues; i++)
priv->rq[i].dev = peer;
rcu_assign_pointer(priv->peer, dev); rcu_assign_pointer(priv->peer, dev);
return 0; return 0;
err_register_dev: err_register_dev:
veth_free_queues(dev);
err_alloc_queues:
/* nothing to do */ /* nothing to do */
err_configure_peer: err_configure_peer:
unregister_netdevice(peer); unregister_netdevice(peer);
return err; return err;
err_register_peer: err_register_peer:
veth_free_queues(peer);
err_peer_alloc_queues:
free_netdev(peer); free_netdev(peer);
return err; return err;
} }
......
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