Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

net: ensure net_todo_list is processed quickly

In [1], Will raised a potential issue that the cfg80211 code,
which does (from a locking perspective)

rtnl_lock()
wiphy_lock()
rtnl_unlock()

might be suspectible to ABBA deadlocks, because rtnl_unlock()
calls netdev_run_todo(), which might end up calling rtnl_lock()
again, which could then deadlock (see the comment in the code
added here for the scenario).

Some back and forth and thinking ensued, but clearly this can't
happen if the net_todo_list is empty at the rtnl_unlock() here.
Clearly, the code here cannot actually put an entry on it, and
all other users of rtnl_unlock() will empty it since that will
always go through netdev_run_todo(), emptying the list.

So the only other way to get there would be to add to the list
and then unlock the RTNL without going through rtnl_unlock(),
which is only possible through __rtnl_unlock(). However, this
isn't exported and not used in many places, and none of them
seem to be able to unregister before using it.

Therefore, add a WARN_ON() in the code to ensure this invariant
won't be broken, so that the cfg80211 (or any similar) code
stays safe.

[1] https://lore.kernel.org/r/Yjzpo3TfZxtKPMAG@google.com

Signed-off-by: Johannes Berg <johannes.berg@intel.com>
Link: https://lore.kernel.org/r/20220404113847.0ee02e4a70da.Ic73d206e217db20fd22dcec14fe5442ca732804b@changeid
Signed-off-by: Jakub Kicinski <kuba@kernel.org>

authored by

Johannes Berg and committed by
Jakub Kicinski
0b5c21bb 6f2f36e5

+36 -2
+2 -1
include/linux/netdevice.h
··· 3894 3894 extern int netdev_budget; 3895 3895 extern unsigned int netdev_budget_usecs; 3896 3896 3897 - /* Called by rtnetlink.c:rtnl_unlock() */ 3897 + /* Used by rtnetlink.c:__rtnl_unlock()/rtnl_unlock() */ 3898 + extern struct list_head net_todo_list; 3898 3899 void netdev_run_todo(void); 3899 3900 3900 3901 static inline void __dev_put(struct net_device *dev)
+1 -1
net/core/dev.c
··· 9431 9431 } 9432 9432 9433 9433 /* Delayed registration/unregisteration */ 9434 - static LIST_HEAD(net_todo_list); 9434 + LIST_HEAD(net_todo_list); 9435 9435 DECLARE_WAIT_QUEUE_HEAD(netdev_unregistering_wq); 9436 9436 9437 9437 static void net_set_todo(struct net_device *dev)
+33
net/core/rtnetlink.c
··· 95 95 96 96 defer_kfree_skb_list = NULL; 97 97 98 + /* Ensure that we didn't actually add any TODO item when __rtnl_unlock() 99 + * is used. In some places, e.g. in cfg80211, we have code that will do 100 + * something like 101 + * rtnl_lock() 102 + * wiphy_lock() 103 + * ... 104 + * rtnl_unlock() 105 + * 106 + * and because netdev_run_todo() acquires the RTNL for items on the list 107 + * we could cause a situation such as this: 108 + * Thread 1 Thread 2 109 + * rtnl_lock() 110 + * unregister_netdevice() 111 + * __rtnl_unlock() 112 + * rtnl_lock() 113 + * wiphy_lock() 114 + * rtnl_unlock() 115 + * netdev_run_todo() 116 + * __rtnl_unlock() 117 + * 118 + * // list not empty now 119 + * // because of thread 2 120 + * rtnl_lock() 121 + * while (!list_empty(...)) 122 + * rtnl_lock() 123 + * wiphy_lock() 124 + * **** DEADLOCK **** 125 + * 126 + * However, usage of __rtnl_unlock() is rare, and so we can ensure that 127 + * it's not used in cases where something is added to do the list. 128 + */ 129 + WARN_ON(!list_empty(&net_todo_list)); 130 + 98 131 mutex_unlock(&rtnl_mutex); 99 132 100 133 while (head) {