[PATCH v4] net/ncsi: Fix Use-After-Free in NCSI channel and package removal
From: Wong Boon Jhee <hidden>
Date: 2026-09-05 14:59:17
Subsystem:
ncsi library, networking [general], the rest · Maintainers:
Samuel Mendoza-Jonas, "David S. Miller", Eric Dumazet, Jakub Kicinski, Paolo Abeni, Linus Torvalds
In net/ncsi/ncsi-manage.c, ncsi_remove_channel() and
ncsi_remove_package() remove objects from an RCU-protected linked list
using list_del_rcu() and immediately free them using kfree().
Because there is no call to synchronize_rcu() or kfree_rcu(), concurrent
readers traversing these lists under rcu_read_lock() (such as Netlink
dump handlers) can still hold a valid pointer to the object. When kfree()
executes, the reader is left holding a dangling pointer to freed memory,
resulting in a slab-use-after-free.
This patch fixes the issue by implementing a proper kref and RCU lifetime
model for NCSI objects. It replaces kfree() with kfree_rcu() / call_rcu()
to defer memory freeing until all pre-existing RCU readers have finished
their critical sections. It also introduces a kref for ncsi_dev_priv to
prevent the device structure from being freed while netlink handlers are
still operating on it. Concurrent lockless readers are now explicitly
protected with rcu_read_lock().
RCU read-side critical sections are added around lockless package and
channel traversals that may race with object removal. The KASAN
reproducer no longer reports the slab-use-after-free.
Fixes: 2d283bdd079c ("net/ncsi: Resource management")
Signed-off-by: Wong Boon Jhee <redacted>
---
v3 -> v4:
- Reworked NCSI object lifetime to pin the underlying net_device using
dev_hold()/dev_put() inside ncsi_dev_get()/ncsi_dev_put(), preventing
UAF if the driver unbinds while netlink handlers are active.
- Moved package/channel destruction and request sweeping into the
ncsi_dev_release() kref callback to guarantee all asynchronous
producers are fully stopped before teardown begins.
- Fixed pre-existing get_net() namespace leaks in all netlink handlers.
- Fixed RCU lock leaks on early returns in ncsi_check_hwa() and
ncsi_set_channel_mask_nl().
- Unified channel_queue semantics by removing incorrect RCU usage and
protecting traversals with ndp->lock.
- Extracted channel IDs inside the RCU read section in
ncsi_update_tx_channel() to prevent pointer escapes.
- Fixed memory corruption in ncsi_vlan_rx_kill_vid() by using a
temporary pointer for the freed VLAN object.
net/ncsi/internal.h | 8 +-
net/ncsi/ncsi-aen.c | 2 +
net/ncsi/ncsi-manage.c | 260 +++++++++++++++++++++++++++++++---------
net/ncsi/ncsi-netlink.c | 69 ++++++++---
net/ncsi/ncsi-rsp.c | 17 ++-
5 files changed, 275 insertions(+), 81 deletions(-)
diff --git a/net/ncsi/internal.h b/net/ncsi/internal.h
index adee6dcabdc3..fbd90f270f5e 100644
--- a/net/ncsi/internal.h
+++ b/net/ncsi/internal.h@@ -239,6 +239,7 @@ struct ncsi_channel { } monitor; struct list_head node; struct list_head link; + struct rcu_head rcu; /* RCU cleanup */ }; struct ncsi_package {
@@ -253,6 +254,7 @@ struct ncsi_package { bool multi_channel; /* Enable multiple channels */ u32 channel_whitelist; /* Channels to configure */ struct ncsi_channel *preferred_channel; /* Primary channel */ + struct rcu_head rcu; /* RCU cleanup */ }; struct ncsi_request {
@@ -315,6 +317,8 @@ struct vlan_vid { }; struct ncsi_dev_priv { + struct rcu_head rcu; /* RCU cleanup */ + struct kref ref; struct ncsi_dev ndev; /* Associated NCSI device */ unsigned int flags; /* NCSI device flags */ #define NCSI_DEV_PROBED 1 /* Finalized NCSI topology */
@@ -337,6 +341,7 @@ struct ncsi_dev_priv { struct ncsi_channel *active_channel; /* Currently handled channel */ struct list_head channel_queue; /* Config queue of channels */ struct work_struct work; /* For channel management */ + bool work_cancelled; /* Work has been cancelled */ struct packet_type ptype; /* NCSI packet Rx handler */ struct list_head node; /* Form NCSI device list */ #define NCSI_MAX_VLAN_VIDS 15
@@ -406,7 +411,8 @@ int ncsi_update_tx_channel(struct ncsi_dev_priv *ndp, struct ncsi_package *np, struct ncsi_channel *disable, struct ncsi_channel *enable); - +struct ncsi_dev_priv *ncsi_dev_get(struct net_device *dev); +void ncsi_dev_put(struct ncsi_dev_priv *ndp); /* Packet handlers */ u32 ncsi_calculate_checksum(unsigned char *data, int len); int ncsi_xmit_cmd(struct ncsi_cmd_arg *nca);
diff --git a/net/ncsi/ncsi-aen.c b/net/ncsi/ncsi-aen.c
index 040a31557201..b88bbfd349cb 100644
--- a/net/ncsi/ncsi-aen.c
+++ b/net/ncsi/ncsi-aen.c@@ -115,6 +115,7 @@ static int ncsi_aen_handler_lsc(struct ncsi_dev_priv *ndp, /* Return Tx to preferred channel */ ncsi_update_tx_channel(ndp, nc->package, NULL, nc); } else if (has_link) { + rcu_read_lock(); NCSI_FOR_EACH_PACKAGE(ndp, np) { NCSI_FOR_EACH_CHANNEL(np, tmp) { /* Enable Tx on this channel if the current Tx
@@ -129,6 +130,7 @@ static int ncsi_aen_handler_lsc(struct ncsi_dev_priv *ndp, } } } + rcu_read_unlock(); } /* Leave configured channels active in a multi-channel scenario so
diff --git a/net/ncsi/ncsi-manage.c b/net/ncsi/ncsi-manage.c
index 54d0df0a9efe..ce99eff33c3c 100644
--- a/net/ncsi/ncsi-manage.c
+++ b/net/ncsi/ncsi-manage.c@@ -10,6 +10,7 @@ #include <linux/skbuff.h> #include <linux/of.h> #include <linux/platform_device.h> +#include <linux/rcupdate.h> #include <net/ncsi.h> #include <net/net_namespace.h>
@@ -25,6 +26,12 @@ LIST_HEAD(ncsi_dev_list); DEFINE_SPINLOCK(ncsi_dev_lock); +static void ncsi_schedule_work(struct ncsi_dev_priv *ndp) +{ + if (!READ_ONCE(ndp->work_cancelled)) + schedule_work(&ndp->work); +} + bool ncsi_channel_has_link(struct ncsi_channel *channel) { return !!(channel->modes[NCSI_MODE_LINK].data[2] & 0x1);
@@ -35,17 +42,24 @@ bool ncsi_channel_is_last(struct ncsi_dev_priv *ndp, { struct ncsi_package *np; struct ncsi_channel *nc; + unsigned long flags; + bool is_last = true; - NCSI_FOR_EACH_PACKAGE(ndp, np) + rcu_read_lock(); + NCSI_FOR_EACH_PACKAGE(ndp, np) { NCSI_FOR_EACH_CHANNEL(np, nc) { if (nc == channel) continue; if (nc->state == NCSI_CHANNEL_ACTIVE && - ncsi_channel_has_link(nc)) - return false; + ncsi_channel_has_link(nc)) { + is_last = false; + goto out; + } } - - return true; + } + out: + rcu_read_unlock(); + return is_last; } static void ncsi_report_link(struct ncsi_dev_priv *ndp, bool force_down)
@@ -62,6 +76,7 @@ static void ncsi_report_link(struct ncsi_dev_priv *ndp, bool force_down) } nd->link_up = 0; + rcu_read_lock(); NCSI_FOR_EACH_PACKAGE(ndp, np) { NCSI_FOR_EACH_CHANNEL(np, nc) { spin_lock_irqsave(&nc->lock, flags);
@@ -75,12 +90,14 @@ static void ncsi_report_link(struct ncsi_dev_priv *ndp, bool force_down) if (ncsi_channel_has_link(nc)) { spin_unlock_irqrestore(&nc->lock, flags); nd->link_up = 1; + rcu_read_unlock(); goto report; } spin_unlock_irqrestore(&nc->lock, flags); } } + rcu_read_unlock(); report: nd->handler(nd);
@@ -242,28 +259,37 @@ struct ncsi_channel *ncsi_add_channel(struct ncsi_package *np, unsigned char id) return nc; } +static void ncsi_channel_rcu_free(struct rcu_head *head) +{ + struct ncsi_channel *nc = container_of(head, struct ncsi_channel, rcu); + + kfree(nc->mac_filter.addrs); + kfree(nc->vlan_filter.vids); + kfree(nc); +} + static void ncsi_remove_channel(struct ncsi_channel *nc) { struct ncsi_package *np = nc->package; + struct ncsi_dev_priv *ndp = np->ndp; unsigned long flags; - spin_lock_irqsave(&nc->lock, flags); - - /* Release filters */ - kfree(nc->mac_filter.addrs); - kfree(nc->vlan_filter.vids); + ncsi_stop_channel_monitor(nc); + spin_lock_irqsave(&nc->lock, flags); nc->state = NCSI_CHANNEL_INACTIVE; spin_unlock_irqrestore(&nc->lock, flags); - ncsi_stop_channel_monitor(nc); - /* Remove and free channel */ + spin_lock_irqsave(&ndp->lock, flags); + list_del_rcu(&nc->link); + spin_unlock_irqrestore(&ndp->lock, flags); + spin_lock_irqsave(&np->lock, flags); list_del_rcu(&nc->node); np->channel_num--; spin_unlock_irqrestore(&np->lock, flags); - kfree(nc); + call_rcu(&nc->rcu, ncsi_channel_rcu_free); } struct ncsi_package *ncsi_find_package(struct ncsi_dev_priv *ndp,
@@ -326,7 +352,7 @@ void ncsi_remove_package(struct ncsi_package *np) ndp->package_num--; spin_unlock_irqrestore(&ndp->lock, flags); - kfree(np); + kfree_rcu(np, rcu); } void ncsi_find_package_and_channel(struct ncsi_dev_priv *ndp,
@@ -408,8 +434,12 @@ void ncsi_free_request(struct ncsi_request *nr) driven = !!(nr->flags & NCSI_REQ_FLAG_EVENT_DRIVEN); spin_unlock_irqrestore(&ndp->lock, flags); - if (driven && cmd && --ndp->pending_req_num == 0) - schedule_work(&ndp->work); + if (driven && cmd) { + spin_lock_irqsave(&ndp->lock, flags); + if (--ndp->pending_req_num == 0 && !ndp->work_cancelled) + ncsi_schedule_work(ndp); + spin_unlock_irqrestore(&ndp->lock, flags); + } /* Release command and response */ consume_skb(cmd);
@@ -552,6 +582,7 @@ static void ncsi_suspend_channel(struct ncsi_dev_priv *ndp) if (ret) goto error; + rcu_read_lock(); NCSI_FOR_EACH_CHANNEL(np, tmp) { /* If there is another channel active on this package * do not deselect the package.
@@ -561,6 +592,7 @@ static void ncsi_suspend_channel(struct ncsi_dev_priv *ndp) break; } } + rcu_read_unlock(); break; case ncsi_dev_state_suspend_deselect: ndp->pending_req_num = 1;
@@ -859,7 +891,10 @@ static bool ncsi_channel_is_tx(struct ncsi_dev_priv *ndp, struct ncsi_channel_mode *ncm; struct ncsi_channel *channel; struct ncsi_package *np; + unsigned long flags; + bool ret; + rcu_read_lock(); /* Check if any other channel has Tx enabled; a channel may have already * been configured and removed from the channel queue. */
@@ -869,29 +904,43 @@ static bool ncsi_channel_is_tx(struct ncsi_dev_priv *ndp, NCSI_FOR_EACH_CHANNEL(np, channel) { ncm = &channel->modes[NCSI_MODE_TX_ENABLE]; if (ncm->enable) - return false; + goto out_false; } } /* This channel is the preferred channel and has link */ - list_for_each_entry_rcu(channel, &ndp->channel_queue, link) { + spin_lock_irqsave(&ndp->lock, flags); + list_for_each_entry(channel, &ndp->channel_queue, link) { np = channel->package; if (np->preferred_channel && ncsi_channel_has_link(np->preferred_channel)) { - return np->preferred_channel == nc; + ret = np->preferred_channel == nc; + spin_unlock_irqrestore(&ndp->lock, flags); + rcu_read_unlock(); + return ret; } } /* This channel has link */ - if (ncsi_channel_has_link(nc)) + if (ncsi_channel_has_link(nc)) { + spin_unlock_irqrestore(&ndp->lock, flags); + rcu_read_unlock(); return true; + } - list_for_each_entry_rcu(channel, &ndp->channel_queue, link) + list_for_each_entry(channel, &ndp->channel_queue, link) if (ncsi_channel_has_link(channel)) - return false; + goto out_false; /* No other channel has link; default to this one */ + spin_unlock_irqrestore(&ndp->lock, flags); + rcu_read_unlock(); return true; + +out_false: + spin_unlock_irqrestore(&ndp->lock, flags); + rcu_read_unlock(); + return false; } /* Change the active Tx channel in a multi-channel setup */
@@ -904,6 +953,8 @@ int ncsi_update_tx_channel(struct ncsi_dev_priv *ndp, struct ncsi_channel *nc; struct ncsi_package *np; int ret = 0; + u8 disable_id = 0, disable_package = 0; + u8 enable_id = 0, enable_package = 0; if (!package->multi_channel && !ndp->multi_package) netdev_warn(ndp->ndev.dev,
@@ -912,6 +963,7 @@ int ncsi_update_tx_channel(struct ncsi_dev_priv *ndp, nca.req_flags = 0; /* Find current channel with Tx enabled */ + rcu_read_lock(); NCSI_FOR_EACH_PACKAGE(ndp, np) { if (disable) break;
@@ -924,8 +976,14 @@ int ncsi_update_tx_channel(struct ncsi_dev_priv *ndp, break; } } + if (disable) { + disable_id = disable->id; + disable_package = disable->package->id; + } + rcu_read_unlock(); /* Find a suitable channel for Tx */ + rcu_read_lock(); NCSI_FOR_EACH_PACKAGE(ndp, np) { if (enable) break;
@@ -949,8 +1007,13 @@ int ncsi_update_tx_channel(struct ncsi_dev_priv *ndp, enable = nc; break; } - } + } } + if (enable) { + enable_id = enable->id; + enable_package = enable->package->id; + } + rcu_read_unlock(); if (disable == enable) return -1;
@@ -959,8 +1022,8 @@ int ncsi_update_tx_channel(struct ncsi_dev_priv *ndp, return -1; if (disable) { - nca.channel = disable->id; - nca.package = disable->package->id; + nca.channel = disable_id; + nca.package = disable_package; nca.type = NCSI_PKT_CMD_DCNT; ret = ncsi_xmit_cmd(&nca); if (ret)
@@ -971,8 +1034,8 @@ int ncsi_update_tx_channel(struct ncsi_dev_priv *ndp, netdev_info(ndp->ndev.dev, "NCSI: channel %u enables Tx\n", enable->id); - nca.channel = enable->id; - nca.package = enable->package->id; + nca.channel = enable_id; + nca.package = enable_package; nca.type = NCSI_PKT_CMD_ECNT; ret = ncsi_xmit_cmd(&nca); if (ret)
@@ -1052,7 +1115,7 @@ static void ncsi_configure_channel(struct ncsi_dev_priv *ndp) } if (ret < 0) { nd->state = ncsi_dev_state_config_clear_vids; - schedule_work(&ndp->work); + ncsi_schedule_work(ndp); } break;
@@ -1086,7 +1149,7 @@ static void ncsi_configure_channel(struct ncsi_dev_priv *ndp) ret = clear_one_vid(ndp, nc, &nca); if (ret) { nd->state = ncsi_dev_state_config_svf; - schedule_work(&ndp->work); + ncsi_schedule_work(ndp); break; } /* Repeat */
@@ -1096,7 +1159,7 @@ static void ncsi_configure_channel(struct ncsi_dev_priv *ndp) ret = set_one_vid(ndp, nc, &nca); if (ret) { nd->state = ncsi_dev_state_config_ev; - schedule_work(&ndp->work); + ncsi_schedule_work(ndp); break; } /* Repeat */
@@ -1253,6 +1316,7 @@ static int ncsi_choose_active_channel(struct ncsi_dev_priv *ndp) */ found = NULL; with_link = false; + rcu_read_lock(); NCSI_FOR_EACH_PACKAGE(ndp, np) { if (!(ndp->package_whitelist & (0x1 << np->id))) continue;
@@ -1304,20 +1368,22 @@ static int ncsi_choose_active_channel(struct ncsi_dev_priv *ndp) if (with_link && !ndp->multi_package) break; } + rcu_read_unlock(); + spin_lock_irqsave(&ndp->lock, flags); if (list_empty(&ndp->channel_queue) && found) { netdev_info(ndp->ndev.dev, "NCSI: No channel with link found, configuring channel %u\n", found->id); - spin_lock_irqsave(&ndp->lock, flags); list_add_tail_rcu(&found->link, &ndp->channel_queue); - spin_unlock_irqrestore(&ndp->lock, flags); } else if (!found) { + spin_unlock_irqrestore(&ndp->lock, flags); netdev_warn(ndp->ndev.dev, "NCSI: No channel found to configure!\n"); ncsi_report_link(ndp, true); return -ENODEV; } + spin_unlock_irqrestore(&ndp->lock, flags); return ncsi_process_next_channel(ndp); }
@@ -1328,10 +1394,12 @@ static bool ncsi_check_hwa(struct ncsi_dev_priv *ndp) struct ncsi_channel *nc; unsigned int cap; bool has_channel = false; + bool supported = true; /* The hardware arbitration is disabled if any one channel * doesn't support explicitly. */ + rcu_read_lock(); NCSI_FOR_EACH_PACKAGE(ndp, np) { NCSI_FOR_EACH_CHANNEL(np, nc) { has_channel = true;
@@ -1341,10 +1409,15 @@ static bool ncsi_check_hwa(struct ncsi_dev_priv *ndp) (cap & NCSI_CAP_GENERIC_HWA_MASK) != NCSI_CAP_GENERIC_HWA_SUPPORT) { ndp->flags &= ~NCSI_DEV_HWA; - return false; + supported = false; + goto out; } } } +out: + rcu_read_unlock(); + if (!supported) + return false; if (has_channel) { ndp->flags |= NCSI_DEV_HWA;
@@ -1408,7 +1481,7 @@ static void ncsi_probe_channel(struct ncsi_dev_priv *ndp) if (!ndp->active_package) { /* No response */ nd->state = ncsi_dev_state_probe_dp; - schedule_work(&ndp->work); + ncsi_schedule_work(ndp); break; } nd->state = ncsi_dev_state_probe_cis;
@@ -1416,7 +1489,7 @@ static void ncsi_probe_channel(struct ncsi_dev_priv *ndp) ndp->mlx_multi_host) nd->state = ncsi_dev_state_probe_mlx_gma; - schedule_work(&ndp->work); + ncsi_schedule_work(ndp); break; case ncsi_dev_state_probe_mlx_gma: ndp->pending_req_num = 1;
@@ -1620,6 +1693,7 @@ static int ncsi_kick_channels(struct ncsi_dev_priv *ndp) unsigned long flags; unsigned int n = 0; + rcu_read_lock(); NCSI_FOR_EACH_PACKAGE(ndp, np) { NCSI_FOR_EACH_CHANNEL(np, nc) { spin_lock_irqsave(&nc->lock, flags);
@@ -1659,6 +1733,7 @@ static int ncsi_kick_channels(struct ncsi_dev_priv *ndp) } } + rcu_read_unlock(); return n; }
@@ -1667,26 +1742,27 @@ int ncsi_vlan_rx_add_vid(struct net_device *dev, __be16 proto, u16 vid) struct ncsi_dev_priv *ndp; unsigned int n_vids = 0; struct vlan_vid *vlan; - struct ncsi_dev *nd; bool found = false; + int ret; if (vid == 0) return 0; - nd = ncsi_find_dev(dev); - if (!nd) { + ndp = ncsi_dev_get(dev); + if (!ndp) { netdev_warn(dev, "NCSI: No net_device?\n"); return 0; } - ndp = TO_NCSI_DEV_PRIV(nd); - /* Add the VLAN id to our internal list */ + rcu_read_lock(); list_for_each_entry_rcu(vlan, &ndp->vlan_vids, list) { n_vids++; if (vlan->vid == vid) { netdev_dbg(dev, "NCSI: vid %u already registered\n", vid); + rcu_read_unlock(); + ncsi_dev_put(ndp); return 0; } }
@@ -1694,12 +1770,17 @@ int ncsi_vlan_rx_add_vid(struct net_device *dev, __be16 proto, u16 vid) netdev_warn(dev, "tried to add vlan id %u but NCSI max already registered (%u)\n", vid, NCSI_MAX_VLAN_VIDS); + rcu_read_unlock(); + ncsi_dev_put(ndp); return -ENOSPC; } + rcu_read_unlock(); vlan = kzalloc_obj(*vlan); - if (!vlan) + if (!vlan) { + ncsi_dev_put(ndp); return -ENOMEM; + } vlan->proto = proto; vlan->vid = vid;
@@ -1708,46 +1789,54 @@ int ncsi_vlan_rx_add_vid(struct net_device *dev, __be16 proto, u16 vid) netdev_dbg(dev, "NCSI: Added new vid %u\n", vid); found = ncsi_kick_channels(ndp) != 0; - - return found ? ncsi_process_next_channel(ndp) : 0; + ret = found ? ncsi_process_next_channel(ndp) : 0; + ncsi_dev_put(ndp); + return ret; } EXPORT_SYMBOL_GPL(ncsi_vlan_rx_add_vid); int ncsi_vlan_rx_kill_vid(struct net_device *dev, __be16 proto, u16 vid) { struct vlan_vid *vlan, *tmp; + struct vlan_vid *found_vlan = NULL; struct ncsi_dev_priv *ndp; - struct ncsi_dev *nd; bool found = false; + int ret; if (vid == 0) return 0; - nd = ncsi_find_dev(dev); - if (!nd) { + ndp = ncsi_dev_get(dev); + if (!ndp) { netdev_warn(dev, "NCSI: no net_device?\n"); return 0; } - ndp = TO_NCSI_DEV_PRIV(nd); - /* Remove the VLAN id from our internal list */ + rcu_read_lock(); list_for_each_entry_safe(vlan, tmp, &ndp->vlan_vids, list) if (vlan->vid == vid) { netdev_dbg(dev, "NCSI: vid %u found, removing\n", vid); list_del_rcu(&vlan->list); + found_vlan = vlan; found = true; - kfree(vlan); + break; } + rcu_read_unlock(); if (!found) { netdev_err(dev, "NCSI: vid %u wasn't registered!\n", vid); + ncsi_dev_put(ndp); return -EINVAL; } + synchronize_rcu(); + kfree(found_vlan); found = ncsi_kick_channels(ndp) != 0; - return found ? ncsi_process_next_channel(ndp) : 0; + ret = found ? ncsi_process_next_channel(ndp) : 0; + ncsi_dev_put(ndp); + return ret; } EXPORT_SYMBOL_GPL(ncsi_vlan_rx_kill_vid);
@@ -1762,7 +1851,9 @@ struct ncsi_dev *ncsi_register_dev(struct net_device *dev, int i; /* Check if the device has been registered or not */ + rcu_read_lock(); nd = ncsi_find_dev(dev); + rcu_read_unlock(); if (nd) return nd;
@@ -1782,6 +1873,7 @@ struct ncsi_dev *ncsi_register_dev(struct net_device *dev, ndp->package_whitelist = UINT_MAX; /* Initialize private NCSI device */ + kref_init(&ndp->ref); spin_lock_init(&ndp->lock); INIT_LIST_HEAD(&ndp->packages); ndp->request_id = NCSI_REQ_START_IDX;
@@ -1826,7 +1918,7 @@ int ncsi_start_dev(struct ncsi_dev *nd) ndp->package_probe_id = 0; ndp->channel_probe_id = 0; nd->state = ncsi_dev_state_probe; - schedule_work(&ndp->work); + ncsi_schedule_work(ndp); return 0; }
@@ -1842,6 +1934,7 @@ void ncsi_stop_dev(struct ncsi_dev *nd) bool chained; int old_state; unsigned long flags; + rcu_read_lock(); /* Stop the channel monitor on any active channels. Don't reset the * channel state so we know which were active when ncsi_start_dev()
@@ -1860,6 +1953,7 @@ void ncsi_stop_dev(struct ncsi_dev *nd) old_state == NCSI_CHANNEL_INVISIBLE); } } + rcu_read_unlock(); netdev_dbg(ndp->ndev.dev, "NCSI: Stopping device\n"); ncsi_report_link(ndp, true);
@@ -1915,6 +2009,7 @@ int ncsi_reset_dev(struct ncsi_dev *nd) spin_unlock_irqrestore(&ndp->lock, flags); active = NULL; + rcu_read_lock(); NCSI_FOR_EACH_PACKAGE(ndp, np) { NCSI_FOR_EACH_CHANNEL(np, nc) { spin_lock_irqsave(&nc->lock, flags);
@@ -1932,6 +2027,7 @@ int ncsi_reset_dev(struct ncsi_dev *nd) if (active) break; } + rcu_read_unlock(); if (!active) { /* Done */
@@ -1948,27 +2044,77 @@ int ncsi_reset_dev(struct ncsi_dev *nd) spin_unlock_irqrestore(&ndp->lock, flags); nd->state = ncsi_dev_state_suspend; - schedule_work(&ndp->work); + ncsi_schedule_work(ndp); return 0; } -void ncsi_unregister_dev(struct ncsi_dev *nd) +static void ncsi_dev_release(struct kref *ref) { - struct ncsi_dev_priv *ndp = TO_NCSI_DEV_PRIV(nd); + struct ncsi_dev_priv *ndp; struct ncsi_package *np, *tmp; - unsigned long flags; + int i; - dev_remove_pack(&ndp->ptype); + ndp = container_of(ref, struct ncsi_dev_priv, ref); + /* All producers have been stopped before the last reference is dropped. */ list_for_each_entry_safe(np, tmp, &ndp->packages, node) ncsi_remove_package(np); + for (i = 0; i < ARRAY_SIZE(ndp->requests); i++) { + struct ncsi_request *nr = &ndp->requests[i]; + + timer_delete_sync(&nr->timer); + if (nr->used || nr->cmd || nr->rsp) + ncsi_free_request(nr); + } + + kfree_rcu(ndp, rcu); +} + +struct ncsi_dev_priv *ncsi_dev_get(struct net_device *dev) +{ + struct ncsi_dev_priv *ndp = NULL; + struct ncsi_dev *nd; + + rcu_read_lock(); + nd = ncsi_find_dev(dev); + if (nd) { + ndp = TO_NCSI_DEV_PRIV(nd); + /* Safely grab a reference while under RCU lock */ + if (!kref_get_unless_zero(&ndp->ref)) + ndp = NULL; + else + dev_hold(dev); + } + rcu_read_unlock(); + + return ndp; +} + +void ncsi_dev_put(struct ncsi_dev_priv *ndp) +{ + if (ndp) { + dev_put(ndp->ndev.dev); + kref_put(&ndp->ref, ncsi_dev_release); + } +} + +void ncsi_unregister_dev(struct ncsi_dev *nd) +{ + struct ncsi_dev_priv *ndp = TO_NCSI_DEV_PRIV(nd); + unsigned long flags; + spin_lock_irqsave(&ncsi_dev_lock, flags); list_del_rcu(&ndp->node); spin_unlock_irqrestore(&ncsi_dev_lock, flags); + spin_lock_irqsave(&ndp->lock, flags); + ndp->work_cancelled = true; + spin_unlock_irqrestore(&ndp->lock, flags); + + dev_remove_pack(&ndp->ptype); disable_work_sync(&ndp->work); - kfree(ndp); + kref_put(&ndp->ref, ncsi_dev_release); } EXPORT_SYMBOL_GPL(ncsi_unregister_dev);
diff --git a/net/ncsi/ncsi-netlink.c b/net/ncsi/ncsi-netlink.c
index 8cc538358f6a..a7094b124753 100644
--- a/net/ncsi/ncsi-netlink.c
+++ b/net/ncsi/ncsi-netlink.c@@ -35,8 +35,6 @@ static struct ncsi_dev_priv *ndp_from_ifindex(struct net *net, u32 ifindex) { struct ncsi_dev_priv *ndp; struct net_device *dev; - struct ncsi_dev *nd; - struct ncsi_dev; if (!net) return NULL;
@@ -47,9 +45,7 @@ static struct ncsi_dev_priv *ndp_from_ifindex(struct net *net, u32 ifindex) return NULL; } - nd = ncsi_find_dev(dev); - ndp = nd ? TO_NCSI_DEV_PRIV(nd) : NULL; - + ndp = ncsi_dev_get(dev); dev_put(dev); return ndp; }
@@ -175,13 +171,16 @@ static int ncsi_pkg_info_nl(struct sk_buff *msg, struct genl_info *info) return -ENODEV; skb = genlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); - if (!skb) + if (!skb) { + ncsi_dev_put(ndp); return -ENOMEM; + } hdr = genlmsg_put(skb, info->snd_portid, info->snd_seq, &ncsi_genl_family, 0, NCSI_CMD_PKG_INFO); if (!hdr) { kfree_skb(skb); + ncsi_dev_put(ndp); return -EMSGSIZE; }
@@ -190,9 +189,13 @@ static int ncsi_pkg_info_nl(struct sk_buff *msg, struct genl_info *info) attr = nla_nest_start_noflag(skb, NCSI_ATTR_PACKAGE_LIST); if (!attr) { kfree_skb(skb); + ncsi_dev_put(ndp); return -EMSGSIZE; } + + rcu_read_lock(); rc = ncsi_write_package_info(skb, ndp, package_id); + rcu_read_unlock(); if (rc) { nla_nest_cancel(skb, attr);
@@ -202,10 +205,12 @@ static int ncsi_pkg_info_nl(struct sk_buff *msg, struct genl_info *info) nla_nest_end(skb, attr); genlmsg_end(skb, hdr); + ncsi_dev_put(ndp); return genlmsg_reply(skb, info); err: kfree_skb(skb); + ncsi_dev_put(ndp); return rc; }
@@ -228,21 +233,24 @@ static int ncsi_pkg_info_all_nl(struct sk_buff *skb, if (!attrs[NCSI_ATTR_IFINDEX]) return -EINVAL; - ndp = ndp_from_ifindex(get_net(sock_net(skb->sk)), + ndp = ndp_from_ifindex(sock_net(skb->sk), nla_get_u32(attrs[NCSI_ATTR_IFINDEX])); if (!ndp) return -ENODEV; package_id = cb->args[0]; + rcu_read_lock(); package = NULL; NCSI_FOR_EACH_PACKAGE(ndp, np) if (np->id == package_id) package = np; - if (!package) + if (!package) { + rcu_read_unlock(); + ncsi_dev_put(ndp); return 0; /* done */ - + } hdr = genlmsg_put(skb, NETLINK_CB(cb->skb).portid, cb->nlh->nlmsg_seq, &ncsi_genl_family, NLM_F_MULTI, NCSI_CMD_PKG_INFO); if (!hdr) {
@@ -255,7 +263,9 @@ static int ncsi_pkg_info_all_nl(struct sk_buff *skb, rc = -EMSGSIZE; goto err; } + rc = ncsi_write_package_info(skb, ndp, package->id); + if (rc) { nla_nest_cancel(skb, attr); goto err;
@@ -266,9 +276,13 @@ static int ncsi_pkg_info_all_nl(struct sk_buff *skb, cb->args[0] = package_id + 1; + rcu_read_unlock(); + ncsi_dev_put(ndp); return skb->len; err: + rcu_read_unlock(); genlmsg_cancel(skb, hdr); + ncsi_dev_put(ndp); return rc; }
@@ -289,7 +303,7 @@ static int ncsi_set_interface_nl(struct sk_buff *msg, struct genl_info *info) if (!info->attrs[NCSI_ATTR_PACKAGE_ID]) return -EINVAL; - ndp = ndp_from_ifindex(get_net(sock_net(msg->sk)), + ndp = ndp_from_ifindex(sock_net(msg->sk), nla_get_u32(info->attrs[NCSI_ATTR_IFINDEX])); if (!ndp) return -ENODEV;
@@ -297,11 +311,15 @@ static int ncsi_set_interface_nl(struct sk_buff *msg, struct genl_info *info) package_id = nla_get_u32(info->attrs[NCSI_ATTR_PACKAGE_ID]); package = NULL; + rcu_read_lock(); NCSI_FOR_EACH_PACKAGE(ndp, np) if (np->id == package_id) package = np; + if (!package) { /* The user has set a package that does not exist */ + rcu_read_unlock(); + ncsi_dev_put(ndp); return -ERANGE; }
@@ -317,6 +335,8 @@ static int ncsi_set_interface_nl(struct sk_buff *msg, struct genl_info *info) netdev_info(ndp->ndev.dev, "NCSI: Channel %u does not exist!\n", channel_id); + rcu_read_unlock(); + ncsi_dev_put(ndp); return -ERANGE; } }
@@ -337,6 +357,7 @@ static int ncsi_set_interface_nl(struct sk_buff *msg, struct genl_info *info) package->preferred_channel = NULL; } spin_unlock_irqrestore(&package->lock, flags); + rcu_read_unlock(); if (channel) netdev_info(ndp->ndev.dev,
@@ -350,6 +371,7 @@ static int ncsi_set_interface_nl(struct sk_buff *msg, struct genl_info *info) if (!(ndp->flags & NCSI_DEV_RESET)) ncsi_reset_dev(&ndp->ndev); + ncsi_dev_put(ndp); return 0; }
@@ -365,7 +387,7 @@ static int ncsi_clear_interface_nl(struct sk_buff *msg, struct genl_info *info) if (!info->attrs[NCSI_ATTR_IFINDEX]) return -EINVAL; - ndp = ndp_from_ifindex(get_net(sock_net(msg->sk)), + ndp = ndp_from_ifindex(sock_net(msg->sk), nla_get_u32(info->attrs[NCSI_ATTR_IFINDEX])); if (!ndp) return -ENODEV;
@@ -376,6 +398,7 @@ static int ncsi_clear_interface_nl(struct sk_buff *msg, struct genl_info *info) ndp->multi_package = false; spin_unlock_irqrestore(&ndp->lock, flags); + rcu_read_lock(); NCSI_FOR_EACH_PACKAGE(ndp, np) { spin_lock_irqsave(&np->lock, flags); np->multi_channel = false;
@@ -383,18 +406,21 @@ static int ncsi_clear_interface_nl(struct sk_buff *msg, struct genl_info *info) np->preferred_channel = NULL; spin_unlock_irqrestore(&np->lock, flags); } + rcu_read_unlock(); + netdev_info(ndp->ndev.dev, "NCSI: Cleared preferred package/channel\n"); /* Update channel configuration */ if (!(ndp->flags & NCSI_DEV_RESET)) ncsi_reset_dev(&ndp->ndev); + ncsi_dev_put(ndp); return 0; } static int ncsi_send_cmd_nl(struct sk_buff *msg, struct genl_info *info) { - struct ncsi_dev_priv *ndp; + struct ncsi_dev_priv *ndp = NULL; struct ncsi_pkt_hdr *hdr; struct ncsi_cmd_arg nca; unsigned char *data;
@@ -427,7 +453,7 @@ static int ncsi_send_cmd_nl(struct sk_buff *msg, struct genl_info *info) goto out; } - ndp = ndp_from_ifindex(get_net(sock_net(msg->sk)), + ndp = ndp_from_ifindex(sock_net(msg->sk), nla_get_u32(info->attrs[NCSI_ATTR_IFINDEX])); if (!ndp) { ret = -ENODEV;
@@ -480,6 +506,7 @@ static int ncsi_send_cmd_nl(struct sk_buff *msg, struct genl_info *info) ret); } out: + ncsi_dev_put(ndp); return ret; }
@@ -608,7 +635,7 @@ static int ncsi_set_package_mask_nl(struct sk_buff *msg, if (!info->attrs[NCSI_ATTR_PACKAGE_MASK]) return -EINVAL; - ndp = ndp_from_ifindex(get_net(sock_net(msg->sk)), + ndp = ndp_from_ifindex(sock_net(msg->sk), nla_get_u32(info->attrs[NCSI_ATTR_IFINDEX])); if (!ndp) return -ENODEV;
@@ -639,6 +666,7 @@ static int ncsi_set_package_mask_nl(struct sk_buff *msg, ncsi_reset_dev(&ndp->ndev); } + ncsi_dev_put(ndp); return rc; }
@@ -663,21 +691,24 @@ static int ncsi_set_channel_mask_nl(struct sk_buff *msg, if (!info->attrs[NCSI_ATTR_CHANNEL_MASK]) return -EINVAL; - ndp = ndp_from_ifindex(get_net(sock_net(msg->sk)), + ndp = ndp_from_ifindex(sock_net(msg->sk), nla_get_u32(info->attrs[NCSI_ATTR_IFINDEX])); if (!ndp) return -ENODEV; package_id = nla_get_u32(info->attrs[NCSI_ATTR_PACKAGE_ID]); package = NULL; + rcu_read_lock(); NCSI_FOR_EACH_PACKAGE(ndp, np) if (np->id == package_id) { package = np; break; } - if (!package) + if (!package) { + rcu_read_unlock(); + ncsi_dev_put(ndp); return -ERANGE; - + } spin_lock_irqsave(&package->lock, flags); channel = NULL;
@@ -690,6 +721,8 @@ static int ncsi_set_channel_mask_nl(struct sk_buff *msg, } if (!channel) { spin_unlock_irqrestore(&package->lock, flags); + rcu_read_unlock(); + ncsi_dev_put(ndp); return -ERANGE; } netdev_dbg(ndp->ndev.dev,
@@ -716,11 +749,13 @@ static int ncsi_set_channel_mask_nl(struct sk_buff *msg, } spin_unlock_irqrestore(&package->lock, flags); + rcu_read_unlock(); /* Update channel configuration */ if (!(ndp->flags & NCSI_DEV_RESET)) ncsi_reset_dev(&ndp->ndev); + ncsi_dev_put(ndp); return 0; }
diff --git a/net/ncsi/ncsi-rsp.c b/net/ncsi/ncsi-rsp.c
index fbd84bc8026a..1fb73bc2e846 100644
--- a/net/ncsi/ncsi-rsp.c
+++ b/net/ncsi/ncsi-rsp.c@@ -149,11 +149,13 @@ static int ncsi_rsp_handler_dp(struct ncsi_request *nr) return -ENODEV; /* Change state of all channels attached to the package */ + rcu_read_lock(); NCSI_FOR_EACH_CHANNEL(np, nc) { spin_lock_irqsave(&nc->lock, flags); nc->state = NCSI_CHANNEL_INACTIVE; spin_unlock_irqrestore(&nc->lock, flags); } + rcu_read_unlock(); return 0; }
@@ -1166,7 +1168,6 @@ int ncsi_rcv_rsp(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt, struct net_device *orig_dev) { struct ncsi_rsp_handler *nrh = NULL; - struct ncsi_dev *nd; struct ncsi_dev_priv *ndp; struct ncsi_request *nr; struct ncsi_pkt_hdr *hdr;
@@ -1174,8 +1175,7 @@ int ncsi_rcv_rsp(struct sk_buff *skb, struct net_device *dev, int payload, i, ret; /* Find the NCSI device */ - nd = ncsi_find_dev(orig_dev); - ndp = nd ? TO_NCSI_DEV_PRIV(nd) : NULL; + ndp = ncsi_dev_get(orig_dev); if (!ndp) { ret = -ENODEV; goto err_free_skb;
@@ -1183,8 +1183,11 @@ int ncsi_rcv_rsp(struct sk_buff *skb, struct net_device *dev, /* Check if it is AEN packet */ hdr = (struct ncsi_pkt_hdr *)skb_network_header(skb); - if (hdr->type == NCSI_PKT_AEN) - return ncsi_aen_handler(ndp, skb); + if (hdr->type == NCSI_PKT_AEN) { + ret = ncsi_aen_handler(ndp, skb); + ncsi_dev_put(ndp); + return ret; + } /* Find the handler */ for (i = 0; i < ARRAY_SIZE(ncsi_rsp_handlers); i++) {
@@ -1199,7 +1202,7 @@ int ncsi_rcv_rsp(struct sk_buff *skb, struct net_device *dev, } if (!nrh) { - netdev_err(nd->dev, "Received unrecognized packet (0x%x)\n", + netdev_err(ndp->ndev.dev, "Received unrecognized packet (0x%x)\n", hdr->type); ret = -ENOENT; goto err_free_skb;
@@ -1264,9 +1267,11 @@ int ncsi_rcv_rsp(struct sk_buff *skb, struct net_device *dev, out: ncsi_free_request(nr); + ncsi_dev_put(ndp); return ret; err_free_skb: kfree_skb(skb); + ncsi_dev_put(ndp); return ret; }
--
2.55.0