mirror of
git://git.kernel.org/pub/scm/linux/kernel/git/xiang/erofs.git
synced 2026-10-28 12:53:14 +01:00
net: avoid theoretical races with ref drain
Technically, it's illegal to take a ref on a netdev just because
we have a pointer on which we already hold a ref, with no other
protection. This is because our simple per-cpu refcount
implementation cannot atomically read the count.
Let's make sure we cancel outstanding work and never queue more
work for a device we know is dead. This way taking a ref on
a dev we know is on the netdev_work_list is always going to be safe.
Jiangshan Yi reports that the issues is caught by ref tracker infra
leading to a warning:
WARNING: lib/ref_tracker.c:322 at ref_tracker_free
WARNING: lib/ref_tracker.c:246 at ref_tracker_dir_exit
Reported-by: Jiangshan Yi <[email protected]>
Link: https://lore.kernel.org/[email protected]
Fixes: 12c765be84 ("net: turn the rx_mode work into a generic netdev_work facility")
Link: https://patch.msgid.link/[email protected]
Signed-off-by: Jakub Kicinski <[email protected]>
This commit is contained in:
1 parent
8e63c9e617
commit
d31c14e56a
3 files changed
+18
No files matched your search
@@ -12436,6 +12436,7 @@ void unregister_netdevice_many_notify(struct list_head *head,
|
||||
dev_tcx_uninstall(dev);
|
||||
dev_xdp_uninstall(dev);
|
||||
dev_memory_provider_uninstall(dev);
|
||||
netdev_work_cancel_all(dev);
|
||||
netdev_unlock_ops(dev);
|
||||
bpf_dev_bound_netdev_unregister(dev);
|
||||
|
||||
|
||||
@@ -179,6 +179,7 @@ enum netdev_work_core {
|
||||
void __netdev_work_core_sched(struct net_device *dev, unsigned long event);
|
||||
unsigned long
|
||||
__netdev_work_core_cancel(struct net_device *dev, unsigned long mask);
|
||||
void netdev_work_cancel_all(struct net_device *dev);
|
||||
|
||||
void __dev_notify_flags(struct net_device *dev, unsigned int old_flags,
|
||||
unsigned int gchanges, u32 portid,
|
||||
|
||||
@@ -31,6 +31,10 @@ static void netdev_work_enqueue(struct net_device *dev, unsigned long events,
|
||||
return;
|
||||
|
||||
spin_lock_bh(&netdev_work_lock);
|
||||
if (!dev_isalive(dev)) {
|
||||
spin_unlock_bh(&netdev_work_lock);
|
||||
return;
|
||||
}
|
||||
if (list_empty(&dev->work_node)) {
|
||||
list_add_tail(&dev->work_node, &netdev_work_list);
|
||||
netdev_hold(dev, &dev->work_tracker, GFP_ATOMIC);
|
||||
@@ -61,6 +65,18 @@ netdev_work_dequeue(struct net_device *dev, unsigned long *pending,
|
||||
return events;
|
||||
}
|
||||
|
||||
void netdev_work_cancel_all(struct net_device *dev)
|
||||
{
|
||||
spin_lock_bh(&netdev_work_lock);
|
||||
dev->work_pending = 0;
|
||||
dev->work_core_pending = 0;
|
||||
if (!list_empty(&dev->work_node)) {
|
||||
list_del_init(&dev->work_node);
|
||||
netdev_put(dev, &dev->work_tracker);
|
||||
}
|
||||
spin_unlock_bh(&netdev_work_lock);
|
||||
}
|
||||
|
||||
void netdev_work_sched(struct net_device *dev, unsigned long events)
|
||||
{
|
||||
netdev_work_enqueue(dev, events, 0);
|
||||
|
||||
Reference in new issue
Block a user