Skip to content

Commit

Permalink
net: Fix inconsistent teardown and release of private netdev state.
Browse files Browse the repository at this point in the history
Network devices can allocate reasources and private memory using
netdev_ops->ndo_init().  However, the release of these resources
can occur in one of two different places.

Either netdev_ops->ndo_uninit() or netdev->destructor().

The decision of which operation frees the resources depends upon
whether it is necessary for all netdev refs to be released before it
is safe to perform the freeing.

netdev_ops->ndo_uninit() presumably can occur right after the
NETDEV_UNREGISTER notifier completes and the unicast and multicast
address lists are flushed.

netdev->destructor(), on the other hand, does not run until the
netdev references all go away.

Further complicating the situation is that netdev->destructor()
almost universally does also a free_netdev().

This creates a problem for the logic in register_netdevice().
Because all callers of register_netdevice() manage the freeing
of the netdev, and invoke free_netdev(dev) if register_netdevice()
fails.

If netdev_ops->ndo_init() succeeds, but something else fails inside
of register_netdevice(), it does call ndo_ops->ndo_uninit().  But
it is not able to invoke netdev->destructor().

This is because netdev->destructor() will do a free_netdev() and
then the caller of register_netdevice() will do the same.

However, this means that the resources that would normally be released
by netdev->destructor() will not be.

Over the years drivers have added local hacks to deal with this, by
invoking their destructor parts by hand when register_netdevice()
fails.

Many drivers do not try to deal with this, and instead we have leaks.

Let's close this hole by formalizing the distinction between what
private things need to be freed up by netdev->destructor() and whether
the driver needs unregister_netdevice() to perform the free_netdev().

netdev->priv_destructor() performs all actions to free up the private
resources that used to be freed by netdev->destructor(), except for
free_netdev().

netdev->needs_free_netdev is a boolean that indicates whether
free_netdev() should be done at the end of unregister_netdevice().

Now, register_netdevice() can sanely release all resources after
ndo_ops->ndo_init() succeeds, by invoking both ndo_ops->ndo_uninit()
and netdev->priv_destructor().

And at the end of unregister_netdevice(), we invoke
netdev->priv_destructor() and optionally call free_netdev().

Signed-off-by: David S. Miller <davem@davemloft.net>
  • Loading branch information
davem330 committed Jun 7, 2017
1 parent 7005cad commit cf124db
Show file tree
Hide file tree
Showing 62 changed files with 105 additions and 103 deletions.
6 changes: 3 additions & 3 deletions drivers/net/bonding/bond_main.c
Original file line number Diff line number Diff line change
Expand Up @@ -4192,7 +4192,6 @@ static void bond_destructor(struct net_device *bond_dev)
struct bonding *bond = netdev_priv(bond_dev);
if (bond->wq)
destroy_workqueue(bond->wq);
free_netdev(bond_dev);
}

void bond_setup(struct net_device *bond_dev)
Expand All @@ -4212,7 +4211,8 @@ void bond_setup(struct net_device *bond_dev)
bond_dev->netdev_ops = &bond_netdev_ops;
bond_dev->ethtool_ops = &bond_ethtool_ops;

bond_dev->destructor = bond_destructor;
bond_dev->needs_free_netdev = true;
bond_dev->priv_destructor = bond_destructor;

SET_NETDEV_DEVTYPE(bond_dev, &bond_type);

Expand Down Expand Up @@ -4736,7 +4736,7 @@ int bond_create(struct net *net, const char *name)

rtnl_unlock();
if (res < 0)
bond_destructor(bond_dev);
free_netdev(bond_dev);
return res;
}

Expand Down
2 changes: 1 addition & 1 deletion drivers/net/caif/caif_hsi.c
Original file line number Diff line number Diff line change
Expand Up @@ -1121,7 +1121,7 @@ static void cfhsi_setup(struct net_device *dev)
dev->flags = IFF_POINTOPOINT | IFF_NOARP;
dev->mtu = CFHSI_MAX_CAIF_FRAME_SZ;
dev->priv_flags |= IFF_NO_QUEUE;
dev->destructor = free_netdev;
dev->needs_free_netdev = true;
dev->netdev_ops = &cfhsi_netdevops;
for (i = 0; i < CFHSI_PRIO_LAST; ++i)
skb_queue_head_init(&cfhsi->qhead[i]);
Expand Down
2 changes: 1 addition & 1 deletion drivers/net/caif/caif_serial.c
Original file line number Diff line number Diff line change
Expand Up @@ -428,7 +428,7 @@ static void caifdev_setup(struct net_device *dev)
dev->flags = IFF_POINTOPOINT | IFF_NOARP;
dev->mtu = CAIF_MAX_MTU;
dev->priv_flags |= IFF_NO_QUEUE;
dev->destructor = free_netdev;
dev->needs_free_netdev = true;
skb_queue_head_init(&serdev->head);
serdev->common.link_select = CAIF_LINK_LOW_LATENCY;
serdev->common.use_frag = true;
Expand Down
2 changes: 1 addition & 1 deletion drivers/net/caif/caif_spi.c
Original file line number Diff line number Diff line change
Expand Up @@ -712,7 +712,7 @@ static void cfspi_setup(struct net_device *dev)
dev->flags = IFF_NOARP | IFF_POINTOPOINT;
dev->priv_flags |= IFF_NO_QUEUE;
dev->mtu = SPI_MAX_PAYLOAD_SIZE;
dev->destructor = free_netdev;
dev->needs_free_netdev = true;
skb_queue_head_init(&cfspi->qhead);
skb_queue_head_init(&cfspi->chead);
cfspi->cfdev.link_select = CAIF_LINK_HIGH_BANDW;
Expand Down
2 changes: 1 addition & 1 deletion drivers/net/caif/caif_virtio.c
Original file line number Diff line number Diff line change
Expand Up @@ -617,7 +617,7 @@ static void cfv_netdev_setup(struct net_device *netdev)
netdev->tx_queue_len = 100;
netdev->flags = IFF_POINTOPOINT | IFF_NOARP;
netdev->mtu = CFV_DEF_MTU_SIZE;
netdev->destructor = free_netdev;
netdev->needs_free_netdev = true;
}

/* Create debugfs counters for the device */
Expand Down
7 changes: 3 additions & 4 deletions drivers/net/can/slcan.c
Original file line number Diff line number Diff line change
Expand Up @@ -417,7 +417,7 @@ static int slc_open(struct net_device *dev)
static void slc_free_netdev(struct net_device *dev)
{
int i = dev->base_addr;
free_netdev(dev);

slcan_devs[i] = NULL;
}

Expand All @@ -436,7 +436,8 @@ static const struct net_device_ops slc_netdev_ops = {
static void slc_setup(struct net_device *dev)
{
dev->netdev_ops = &slc_netdev_ops;
dev->destructor = slc_free_netdev;
dev->needs_free_netdev = true;
dev->priv_destructor = slc_free_netdev;

dev->hard_header_len = 0;
dev->addr_len = 0;
Expand Down Expand Up @@ -761,8 +762,6 @@ static void __exit slcan_exit(void)
if (sl->tty) {
printk(KERN_ERR "%s: tty discipline still running\n",
dev->name);
/* Intentionally leak the control block. */
dev->destructor = NULL;
}

unregister_netdev(dev);
Expand Down
2 changes: 1 addition & 1 deletion drivers/net/can/vcan.c
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ static void vcan_setup(struct net_device *dev)
dev->flags |= IFF_ECHO;

dev->netdev_ops = &vcan_netdev_ops;
dev->destructor = free_netdev;
dev->needs_free_netdev = true;
}

static struct rtnl_link_ops vcan_link_ops __read_mostly = {
Expand Down
2 changes: 1 addition & 1 deletion drivers/net/can/vxcan.c
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ static void vxcan_setup(struct net_device *dev)
dev->tx_queue_len = 0;
dev->flags = (IFF_NOARP|IFF_ECHO);
dev->netdev_ops = &vxcan_netdev_ops;
dev->destructor = free_netdev;
dev->needs_free_netdev = true;
}

/* forward declaration for rtnl_create_link() */
Expand Down
4 changes: 2 additions & 2 deletions drivers/net/dummy.c
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,6 @@ static void dummy_free_netdev(struct net_device *dev)
struct dummy_priv *priv = netdev_priv(dev);

kfree(priv->vfinfo);
free_netdev(dev);
}

static void dummy_setup(struct net_device *dev)
Expand All @@ -338,7 +337,8 @@ static void dummy_setup(struct net_device *dev)
/* Initialize the device structure. */
dev->netdev_ops = &dummy_netdev_ops;
dev->ethtool_ops = &dummy_ethtool_ops;
dev->destructor = dummy_free_netdev;
dev->needs_free_netdev = true;
dev->priv_destructor = dummy_free_netdev;

/* Fill in device structure with ethernet-generic values. */
dev->flags |= IFF_NOARP;
Expand Down
2 changes: 1 addition & 1 deletion drivers/net/ethernet/chelsio/cxgb4/cxgb4_main.c
Original file line number Diff line number Diff line change
Expand Up @@ -4525,7 +4525,7 @@ static void dummy_setup(struct net_device *dev)
/* Initialize the device structure. */
dev->netdev_ops = &cxgb4_mgmt_netdev_ops;
dev->ethtool_ops = &cxgb4_mgmt_ethtool_ops;
dev->destructor = free_netdev;
dev->needs_free_netdev = true;
}

static int config_mgmt_dev(struct pci_dev *pdev)
Expand Down
2 changes: 1 addition & 1 deletion drivers/net/geneve.c
Original file line number Diff line number Diff line change
Expand Up @@ -1007,7 +1007,7 @@ static void geneve_setup(struct net_device *dev)

dev->netdev_ops = &geneve_netdev_ops;
dev->ethtool_ops = &geneve_ethtool_ops;
dev->destructor = free_netdev;
dev->needs_free_netdev = true;

SET_NETDEV_DEVTYPE(dev, &geneve_type);

Expand Down
2 changes: 1 addition & 1 deletion drivers/net/gtp.c
Original file line number Diff line number Diff line change
Expand Up @@ -611,7 +611,7 @@ static const struct net_device_ops gtp_netdev_ops = {
static void gtp_link_setup(struct net_device *dev)
{
dev->netdev_ops = &gtp_netdev_ops;
dev->destructor = free_netdev;
dev->needs_free_netdev = true;

dev->hard_header_len = 0;
dev->addr_len = 0;
Expand Down
2 changes: 1 addition & 1 deletion drivers/net/hamradio/6pack.c
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,7 @@ static void sp_setup(struct net_device *dev)
{
/* Finish setting up the DEVICE info. */
dev->netdev_ops = &sp_netdev_ops;
dev->destructor = free_netdev;
dev->needs_free_netdev = true;
dev->mtu = SIXP_MTU;
dev->hard_header_len = AX25_MAX_HEADER_LEN;
dev->header_ops = &ax25_header_ops;
Expand Down
2 changes: 1 addition & 1 deletion drivers/net/hamradio/bpqether.c
Original file line number Diff line number Diff line change
Expand Up @@ -476,7 +476,7 @@ static const struct net_device_ops bpq_netdev_ops = {
static void bpq_setup(struct net_device *dev)
{
dev->netdev_ops = &bpq_netdev_ops;
dev->destructor = free_netdev;
dev->needs_free_netdev = true;

memcpy(dev->broadcast, &ax25_bcast, AX25_ADDR_LEN);
memcpy(dev->dev_addr, &ax25_defaddr, AX25_ADDR_LEN);
Expand Down
4 changes: 2 additions & 2 deletions drivers/net/ifb.c
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,6 @@ static void ifb_dev_free(struct net_device *dev)
__skb_queue_purge(&txp->tq);
}
kfree(dp->tx_private);
free_netdev(dev);
}

static void ifb_setup(struct net_device *dev)
Expand All @@ -230,7 +229,8 @@ static void ifb_setup(struct net_device *dev)
dev->priv_flags &= ~IFF_TX_SKB_SHARING;
netif_keep_dst(dev);
eth_hw_addr_random(dev);
dev->destructor = ifb_dev_free;
dev->needs_free_netdev = true;
dev->priv_destructor = ifb_dev_free;
}

static netdev_tx_t ifb_xmit(struct sk_buff *skb, struct net_device *dev)
Expand Down
2 changes: 1 addition & 1 deletion drivers/net/ipvlan/ipvlan_main.c
Original file line number Diff line number Diff line change
Expand Up @@ -632,7 +632,7 @@ void ipvlan_link_setup(struct net_device *dev)
dev->priv_flags &= ~(IFF_XMIT_DST_RELEASE | IFF_TX_SKB_SHARING);
dev->priv_flags |= IFF_UNICAST_FLT | IFF_NO_QUEUE;
dev->netdev_ops = &ipvlan_netdev_ops;
dev->destructor = free_netdev;
dev->needs_free_netdev = true;
dev->header_ops = &ipvlan_header_ops;
dev->ethtool_ops = &ipvlan_ethtool_ops;
}
Expand Down
4 changes: 2 additions & 2 deletions drivers/net/loopback.c
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,6 @@ static void loopback_dev_free(struct net_device *dev)
{
dev_net(dev)->loopback_dev = NULL;
free_percpu(dev->lstats);
free_netdev(dev);
}

static const struct net_device_ops loopback_ops = {
Expand Down Expand Up @@ -196,7 +195,8 @@ static void loopback_setup(struct net_device *dev)
dev->ethtool_ops = &loopback_ethtool_ops;
dev->header_ops = &eth_header_ops;
dev->netdev_ops = &loopback_ops;
dev->destructor = loopback_dev_free;
dev->needs_free_netdev = true;
dev->priv_destructor = loopback_dev_free;
}

/* Setup and register the loopback device. */
Expand Down
4 changes: 2 additions & 2 deletions drivers/net/macsec.c
Original file line number Diff line number Diff line change
Expand Up @@ -2996,7 +2996,6 @@ static void macsec_free_netdev(struct net_device *dev)
free_percpu(macsec->secy.tx_sc.stats);

dev_put(real_dev);
free_netdev(dev);
}

static void macsec_setup(struct net_device *dev)
Expand All @@ -3006,7 +3005,8 @@ static void macsec_setup(struct net_device *dev)
dev->max_mtu = ETH_MAX_MTU;
dev->priv_flags |= IFF_NO_QUEUE;
dev->netdev_ops = &macsec_netdev_ops;
dev->destructor = macsec_free_netdev;
dev->needs_free_netdev = true;
dev->priv_destructor = macsec_free_netdev;
SET_NETDEV_DEVTYPE(dev, &macsec_type);

eth_zero_addr(dev->broadcast);
Expand Down
2 changes: 1 addition & 1 deletion drivers/net/macvlan.c
Original file line number Diff line number Diff line change
Expand Up @@ -1092,7 +1092,7 @@ void macvlan_common_setup(struct net_device *dev)
netif_keep_dst(dev);
dev->priv_flags |= IFF_UNICAST_FLT;
dev->netdev_ops = &macvlan_netdev_ops;
dev->destructor = free_netdev;
dev->needs_free_netdev = true;
dev->header_ops = &macvlan_hard_header_ops;
dev->ethtool_ops = &macvlan_ethtool_ops;
}
Expand Down
2 changes: 1 addition & 1 deletion drivers/net/nlmon.c
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ static void nlmon_setup(struct net_device *dev)

dev->netdev_ops = &nlmon_ops;
dev->ethtool_ops = &nlmon_ethtool_ops;
dev->destructor = free_netdev;
dev->needs_free_netdev = true;

dev->features = NETIF_F_SG | NETIF_F_FRAGLIST |
NETIF_F_HIGHDMA | NETIF_F_LLTX;
Expand Down
7 changes: 3 additions & 4 deletions drivers/net/slip/slip.c
Original file line number Diff line number Diff line change
Expand Up @@ -629,7 +629,7 @@ static void sl_uninit(struct net_device *dev)
static void sl_free_netdev(struct net_device *dev)
{
int i = dev->base_addr;
free_netdev(dev);

slip_devs[i] = NULL;
}

Expand All @@ -651,7 +651,8 @@ static const struct net_device_ops sl_netdev_ops = {
static void sl_setup(struct net_device *dev)
{
dev->netdev_ops = &sl_netdev_ops;
dev->destructor = sl_free_netdev;
dev->needs_free_netdev = true;
dev->priv_destructor = sl_free_netdev;

dev->hard_header_len = 0;
dev->addr_len = 0;
Expand Down Expand Up @@ -1369,8 +1370,6 @@ static void __exit slip_exit(void)
if (sl->tty) {
printk(KERN_ERR "%s: tty discipline still running\n",
dev->name);
/* Intentionally leak the control block. */
dev->destructor = NULL;
}

unregister_netdev(dev);
Expand Down
4 changes: 2 additions & 2 deletions drivers/net/team/team.c
Original file line number Diff line number Diff line change
Expand Up @@ -1643,7 +1643,6 @@ static void team_destructor(struct net_device *dev)
struct team *team = netdev_priv(dev);

free_percpu(team->pcpu_stats);
free_netdev(dev);
}

static int team_open(struct net_device *dev)
Expand Down Expand Up @@ -2079,7 +2078,8 @@ static void team_setup(struct net_device *dev)

dev->netdev_ops = &team_netdev_ops;
dev->ethtool_ops = &team_ethtool_ops;
dev->destructor = team_destructor;
dev->needs_free_netdev = true;
dev->priv_destructor = team_destructor;
dev->priv_flags &= ~(IFF_XMIT_DST_RELEASE | IFF_TX_SKB_SHARING);
dev->priv_flags |= IFF_NO_QUEUE;
dev->priv_flags |= IFF_TEAM;
Expand Down
4 changes: 2 additions & 2 deletions drivers/net/tun.c
Original file line number Diff line number Diff line change
Expand Up @@ -1560,7 +1560,6 @@ static void tun_free_netdev(struct net_device *dev)
free_percpu(tun->pcpu_stats);
tun_flow_uninit(tun);
security_tun_dev_free_security(tun->security);
free_netdev(dev);
}

static void tun_setup(struct net_device *dev)
Expand All @@ -1571,7 +1570,8 @@ static void tun_setup(struct net_device *dev)
tun->group = INVALID_GID;

dev->ethtool_ops = &tun_ethtool_ops;
dev->destructor = tun_free_netdev;
dev->needs_free_netdev = true;
dev->priv_destructor = tun_free_netdev;
/* We prefer our own queue length */
dev->tx_queue_len = TUN_READQ_SIZE;
}
Expand Down
2 changes: 1 addition & 1 deletion drivers/net/usb/cdc-phonet.c
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,7 @@ static void usbpn_setup(struct net_device *dev)
dev->addr_len = 1;
dev->tx_queue_len = 3;

dev->destructor = free_netdev;
dev->needs_free_netdev = true;
}

/*
Expand Down
2 changes: 1 addition & 1 deletion drivers/net/usb/qmi_wwan.c
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ static void qmimux_setup(struct net_device *dev)
dev->addr_len = 0;
dev->flags = IFF_POINTOPOINT | IFF_NOARP | IFF_MULTICAST;
dev->netdev_ops = &qmimux_netdev_ops;
dev->destructor = free_netdev;
dev->needs_free_netdev = true;
}

static struct net_device *qmimux_find_dev(struct usbnet *dev, u8 mux_id)
Expand Down
4 changes: 2 additions & 2 deletions drivers/net/veth.c
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,6 @@ static int veth_dev_init(struct net_device *dev)
static void veth_dev_free(struct net_device *dev)
{
free_percpu(dev->vstats);
free_netdev(dev);
}

#ifdef CONFIG_NET_POLL_CONTROLLER
Expand Down Expand Up @@ -317,7 +316,8 @@ static void veth_setup(struct net_device *dev)
NETIF_F_HW_VLAN_STAG_TX |
NETIF_F_HW_VLAN_CTAG_RX |
NETIF_F_HW_VLAN_STAG_RX);
dev->destructor = veth_dev_free;
dev->needs_free_netdev = true;
dev->priv_destructor = veth_dev_free;
dev->max_mtu = ETH_MAX_MTU;

dev->hw_features = VETH_FEATURES;
Expand Down
2 changes: 1 addition & 1 deletion drivers/net/vrf.c
Original file line number Diff line number Diff line change
Expand Up @@ -1348,7 +1348,7 @@ static void vrf_setup(struct net_device *dev)
dev->netdev_ops = &vrf_netdev_ops;
dev->l3mdev_ops = &vrf_l3mdev_ops;
dev->ethtool_ops = &vrf_ethtool_ops;
dev->destructor = free_netdev;
dev->needs_free_netdev = true;

/* Fill in device structure with ethernet-generic values. */
eth_hw_addr_random(dev);
Expand Down
2 changes: 1 addition & 1 deletion drivers/net/vsockmon.c
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ static void vsockmon_setup(struct net_device *dev)

dev->netdev_ops = &vsockmon_ops;
dev->ethtool_ops = &vsockmon_ethtool_ops;
dev->destructor = free_netdev;
dev->needs_free_netdev = true;

dev->features = NETIF_F_SG | NETIF_F_FRAGLIST |
NETIF_F_HIGHDMA | NETIF_F_LLTX;
Expand Down
2 changes: 1 addition & 1 deletion drivers/net/vxlan.c
Original file line number Diff line number Diff line change
Expand Up @@ -2611,7 +2611,7 @@ static void vxlan_setup(struct net_device *dev)
eth_hw_addr_random(dev);
ether_setup(dev);

dev->destructor = free_netdev;
dev->needs_free_netdev = true;
SET_NETDEV_DEVTYPE(dev, &vxlan_type);

dev->features |= NETIF_F_LLTX;
Expand Down
2 changes: 1 addition & 1 deletion drivers/net/wan/dlci.c
Original file line number Diff line number Diff line change
Expand Up @@ -475,7 +475,7 @@ static void dlci_setup(struct net_device *dev)
dev->flags = 0;
dev->header_ops = &dlci_header_ops;
dev->netdev_ops = &dlci_netdev_ops;
dev->destructor = free_netdev;
dev->needs_free_netdev = true;

dlp->receive = dlci_receive;

Expand Down
2 changes: 1 addition & 1 deletion drivers/net/wan/hdlc_fr.c
Original file line number Diff line number Diff line change
Expand Up @@ -1106,7 +1106,7 @@ static int fr_add_pvc(struct net_device *frad, unsigned int dlci, int type)
return -EIO;
}

dev->destructor = free_netdev;
dev->needs_free_netdev = true;
*get_dev_p(pvc, type) = dev;
if (!used) {
state(hdlc)->dce_changed = 1;
Expand Down
Loading

0 comments on commit cf124db

Please sign in to comment.