| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/07/03 00:26 | flow | patch-triage |
0mArgs:null Results: {
"EnableConfigs": [
"DEBUG_NET_SMALL_RTNL",
"BAREUDP",
"IPVLAN",
"IPVTAP",
"VETH"
],
"FocusSymbols": [
"unregister_netdevice_queue_net",
"unregister_netdevice_many_net",
"unregister_netdevice_queue_many_net",
"rtnl_net_work_func",
"bareudp_dellink",
"__bareudp_dellink",
"ipvlan_link_delete",
"__ipvlan_link_delete",
"ipvtap_dellink",
"__ipvtap_dellink",
"veth_dellink",
"default_device_exit_batch"
],
"Reasoning": "The patch introduces a new mechanism for unregistering network devices across network namespaces, adding per-netns RTNL locking support (CONFIG_DEBUG_NET_SMALL_RTNL). It modifies core netdevice unregistration functions and updates several virtual network drivers (bareudp, ipvlan, ipvtap, veth) to use the new locking and unregistration APIs. This involves complex synchronization and lifecycle management of network devices, which is highly prone to race conditions and use-after-free bugs. Fuzzing is essential to ensure the correctness of these changes.",
"WorthFuzzing": true
} |
| 1/1 | 2026/07/03 00:26 | action | read-patch-diff |
0mArgs:null Results: {
"PatchDiff": "commit 58d40a9662711cbb664e400a9360e70d683f20d4\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Fri Jul 3 00:26:39 2026 +0000\n\n syz-cluster: applied patch under review\n\ndiff --git a/drivers/net/bareudp.c b/drivers/net/bareudp.c\nindex 5ef841c8552697..c3b5ed52d87789 100644\n--- a/drivers/net/bareudp.c\n+++ b/drivers/net/bareudp.c\n@@ -36,6 +36,7 @@ static unsigned int bareudp_net_id;\n \n struct bareudp_net {\n \tstruct list_head bareudp_list;\n+\tstruct mutex\t\tlock;\n };\n \n struct bareudp_conf {\n@@ -636,10 +637,15 @@ static struct bareudp_dev *bareudp_find_dev(struct bareudp_net *bn,\n {\n \tstruct bareudp_dev *bareudp, *t = NULL;\n \n+\tmutex_lock(\u0026bn-\u003elock);\n+\n \tlist_for_each_entry(bareudp, \u0026bn-\u003ebareudp_list, next) {\n \t\tif (conf-\u003eport == bareudp-\u003eport)\n \t\t\tt = bareudp;\n \t}\n+\n+\tmutex_unlock(\u0026bn-\u003elock);\n+\n \treturn t;\n }\n \n@@ -675,7 +681,10 @@ static int bareudp_configure(struct net *net, struct net_device *dev,\n \tif (err)\n \t\treturn err;\n \n+\tmutex_lock(\u0026bn-\u003elock);\n \tlist_add(\u0026bareudp-\u003enext, \u0026bn-\u003ebareudp_list);\n+\tmutex_unlock(\u0026bn-\u003elock);\n+\n \treturn 0;\n }\n \n@@ -692,12 +701,26 @@ static int bareudp_link_config(struct net_device *dev,\n \treturn 0;\n }\n \n+static void __bareudp_dellink(struct net *net, struct net_device *dev,\n+\t\t\t struct list_head *head)\n+{\n+\tstruct bareudp_dev *bareudp = netdev_priv(dev);\n+\n+\tlist_del_init(\u0026bareudp-\u003enext);\n+\tunregister_netdevice_queue_net(net, dev, head);\n+}\n+\n static void bareudp_dellink(struct net_device *dev, struct list_head *head)\n {\n \tstruct bareudp_dev *bareudp = netdev_priv(dev);\n+\tstruct bareudp_net *bn;\n \n-\tlist_del(\u0026bareudp-\u003enext);\n-\tunregister_netdevice_queue(dev, head);\n+\tbn = net_generic(bareudp-\u003enet, bareudp_net_id);\n+\n+\tmutex_lock(\u0026bn-\u003elock);\n+\tif (!list_empty(\u0026bareudp-\u003enext))\n+\t\t__bareudp_dellink(dev_net(dev), dev, head);\n+\tmutex_unlock(\u0026bn-\u003elock);\n }\n \n static int bareudp_newlink(struct net_device *dev,\n@@ -776,6 +799,8 @@ static __net_init int bareudp_init_net(struct net *net)\n \tstruct bareudp_net *bn = net_generic(net, bareudp_net_id);\n \n \tINIT_LIST_HEAD(\u0026bn-\u003ebareudp_list);\n+\tmutex_init(\u0026bn-\u003elock);\n+\n \treturn 0;\n }\n \n@@ -785,13 +810,25 @@ static void __net_exit bareudp_exit_rtnl_net(struct net *net,\n \tstruct bareudp_net *bn = net_generic(net, bareudp_net_id);\n \tstruct bareudp_dev *bareudp, *next;\n \n+\tmutex_lock(\u0026bn-\u003elock);\n+\n \tlist_for_each_entry_safe(bareudp, next, \u0026bn-\u003ebareudp_list, next)\n-\t\tbareudp_dellink(bareudp-\u003edev, dev_kill_list);\n+\t\t__bareudp_dellink(net, bareudp-\u003edev, dev_kill_list);\n+\n+\tmutex_unlock(\u0026bn-\u003elock);\n+}\n+\n+static void __net_exit bareudp_exit_net(struct net *net)\n+{\n+\tstruct bareudp_net *bn = net_generic(net, bareudp_net_id);\n+\n+\tWARN_ON_ONCE(!list_empty(\u0026bn-\u003ebareudp_list));\n }\n \n static struct pernet_operations bareudp_net_ops = {\n \t.init = bareudp_init_net,\n \t.exit_rtnl = bareudp_exit_rtnl_net,\n+\t.exit = bareudp_exit_net,\n \t.id = \u0026bareudp_net_id,\n \t.size = sizeof(struct bareudp_net),\n };\ndiff --git a/drivers/net/ipvlan/ipvlan.h b/drivers/net/ipvlan/ipvlan.h\nindex 80f84fc87008b3..8d05ad480438ff 100644\n--- a/drivers/net/ipvlan/ipvlan.h\n+++ b/drivers/net/ipvlan/ipvlan.h\n@@ -69,6 +69,7 @@ struct ipvl_dev {\n \tDECLARE_BITMAP(mac_filters, IPVLAN_MAC_FILTER_SIZE);\n \tnetdev_features_t\tsfeatures;\n \tu32\t\t\tmsg_enable;\n+\tbool\t\t\tdying;\n };\n \n struct ipvl_addr {\n@@ -91,12 +92,13 @@ struct ipvl_port {\n \tstruct hlist_head\thlhead[IPVLAN_HASH_SIZE];\n \tspinlock_t\t\taddrs_lock; /* guards hash-table and addrs */\n \tstruct list_head\tipvlans;\n+\tstruct mutex\t\tpnodes_lock;\n \tu16\t\t\tmode;\n \tu16\t\t\tflags;\n \tu16\t\t\tdev_id_start;\n \tstruct work_struct\twq;\n \tstruct sk_buff_head\tbacklog;\n-\tint\t\t\tcount;\n+\trefcount_t\t\tcount;\n \tstruct ida\t\tida;\n \tnetdevice_tracker\tdev_tracker;\n };\n@@ -168,7 +170,8 @@ void ipvlan_count_rx(const struct ipvl_dev *ipvlan,\n \t\t unsigned int len, bool success, bool mcast);\n int ipvlan_link_new(struct net_device *dev, struct rtnl_newlink_params *params,\n \t\t struct netlink_ext_ack *extack);\n-void ipvlan_link_delete(struct net_device *dev, struct list_head *head);\n+void __ipvlan_link_delete(struct net *net, struct net_device *dev,\n+\t\t\t struct list_head *head);\n void ipvlan_link_setup(struct net_device *dev);\n int ipvlan_link_register(struct rtnl_link_ops *ops);\n #ifdef CONFIG_IPVLAN_L3S\n@@ -207,4 +210,9 @@ static inline bool netif_is_ipvlan_port(const struct net_device *dev)\n \treturn rcu_access_pointer(dev-\u003erx_handler) == ipvlan_handle_frame;\n }\n \n+#if IS_ENABLED(CONFIG_IPVTAP)\n+extern void (*__ipvtap_dellink_ptr)(struct net *net, struct net_device *dev,\n+\t\t\t\t struct list_head *head);\n+#endif\n+\n #endif /* __IPVLAN_H */\ndiff --git a/drivers/net/ipvlan/ipvlan_main.c b/drivers/net/ipvlan/ipvlan_main.c\nindex ed46439a9f4eb1..ee46a55f73d14c 100644\n--- a/drivers/net/ipvlan/ipvlan_main.c\n+++ b/drivers/net/ipvlan/ipvlan_main.c\n@@ -7,6 +7,12 @@\n \n #include \"ipvlan.h\"\n \n+#if IS_ENABLED(CONFIG_IPVTAP)\n+void (*__ipvtap_dellink_ptr)(struct net *net, struct net_device *dev,\n+\t\t\t struct list_head *head);\n+EXPORT_SYMBOL(__ipvtap_dellink_ptr);\n+#endif\n+\n static int ipvlan_set_port_mode(struct ipvl_port *port, u16 nval,\n \t\t\t\tstruct netlink_ext_ack *extack)\n {\n@@ -16,6 +22,8 @@ static int ipvlan_set_port_mode(struct ipvl_port *port, u16 nval,\n \n \tASSERT_RTNL();\n \tif (port-\u003emode != nval) {\n+\t\tmutex_lock(\u0026port-\u003epnodes_lock);\n+\n \t\tlist_for_each_entry(ipvlan, \u0026port-\u003eipvlans, pnode) {\n \t\t\tflags = ipvlan-\u003edev-\u003eflags;\n \t\t\tif (nval == IPVLAN_MODE_L3 || nval == IPVLAN_MODE_L3S) {\n@@ -40,6 +48,8 @@ static int ipvlan_set_port_mode(struct ipvl_port *port, u16 nval,\n \t\t\tipvlan_l3s_unregister(port);\n \t\t}\n \t\tport-\u003emode = nval;\n+\n+\t\tmutex_unlock(\u0026port-\u003epnodes_lock);\n \t}\n \treturn 0;\n \n@@ -56,6 +66,8 @@ static int ipvlan_set_port_mode(struct ipvl_port *port, u16 nval,\n \t\t\t\t\t NULL);\n \t}\n \n+\tmutex_unlock(\u0026port-\u003epnodes_lock);\n+\n \treturn err;\n }\n \n@@ -76,6 +88,7 @@ static int ipvlan_port_create(struct net_device *dev)\n \t\tINIT_HLIST_HEAD(\u0026port-\u003ehlhead[idx]);\n \n \tspin_lock_init(\u0026port-\u003eaddrs_lock);\n+\tmutex_init(\u0026port-\u003epnodes_lock);\n \tskb_queue_head_init(\u0026port-\u003ebacklog);\n \tINIT_WORK(\u0026port-\u003ewq, ipvlan_process_multicast);\n \tida_init(\u0026port-\u003eida);\n@@ -86,6 +99,7 @@ static int ipvlan_port_create(struct net_device *dev)\n \t\tgoto err;\n \n \tnetdev_hold(dev, \u0026port-\u003edev_tracker, GFP_KERNEL);\n+\n \treturn 0;\n \n err:\n@@ -93,16 +107,18 @@ static int ipvlan_port_create(struct net_device *dev)\n \treturn err;\n }\n \n-static void ipvlan_port_destroy(struct net_device *dev)\n+static void ipvlan_port_destroy(struct ipvl_port *port)\n {\n-\tstruct ipvl_port *port = ipvlan_port_get_rtnl(dev);\n+\tstruct net_device *dev = port-\u003edev;\n \tstruct sk_buff *skb;\n \n-\tnetdev_put(dev, \u0026port-\u003edev_tracker);\n \tif (port-\u003emode == IPVLAN_MODE_L3S)\n \t\tipvlan_l3s_unregister(port);\n+\n \tnetdev_rx_handler_unregister(dev);\n \tcancel_work_sync(\u0026port-\u003ewq);\n+\tnetdev_put(dev, \u0026port-\u003edev_tracker);\n+\n \twhile ((skb = __skb_dequeue(\u0026port-\u003ebacklog)) != NULL) {\n \t\tdev_put(skb-\u003edev);\n \t\tkfree_skb(skb);\n@@ -111,6 +127,27 @@ static void ipvlan_port_destroy(struct net_device *dev)\n \tkfree(port);\n }\n \n+static void ipvlan_port_put(struct ipvl_port *port)\n+{\n+\tif (refcount_dec_and_test(\u0026port-\u003ecount))\n+\t\tipvlan_port_destroy(port);\n+}\n+\n+static struct ipvl_port *ipvlan_port_get(struct net_device *dev)\n+{\n+\tstruct ipvl_port *port = NULL;\n+\n+\trcu_read_lock();\n+\tif (netif_is_ipvlan_port(dev)) {\n+\t\tport = ipvlan_port_get_rcu(dev);\n+\t\tif (!refcount_inc_not_zero(\u0026port-\u003ecount))\n+\t\t\tport = NULL;\n+\t}\n+\trcu_read_unlock();\n+\n+\treturn port;\n+}\n+\n #define IPVLAN_ALWAYS_ON_OFLOADS \\\n \t(NETIF_F_SG | NETIF_F_HW_CSUM | \\\n \t NETIF_F_GSO_ROBUST | NETIF_F_GSO_SOFTWARE | NETIF_F_GSO_ENCAP_ALL)\n@@ -153,30 +190,45 @@ static int ipvlan_init(struct net_device *dev)\n \tif (!ipvlan-\u003epcpu_stats)\n \t\treturn -ENOMEM;\n \n+\tnetdev_lock(phy_dev);\n+\n \tif (!netif_is_ipvlan_port(phy_dev)) {\n \t\terr = ipvlan_port_create(phy_dev);\n \t\tif (err \u003c 0) {\n+\t\t\tnetdev_unlock(phy_dev);\n \t\t\tfree_percpu(ipvlan-\u003epcpu_stats);\n \t\t\treturn err;\n \t\t}\n+\t\tport = ipvlan_port_get_rtnl(phy_dev);\n+\t\trefcount_set(\u0026port-\u003ecount, 1);\n+\t} else {\n+\t\tport = ipvlan_port_get_rtnl(phy_dev);\n+\t\trefcount_inc(\u0026port-\u003ecount);\n \t}\n-\tport = ipvlan_port_get_rtnl(phy_dev);\n-\tport-\u003ecount += 1;\n+\n+\tnetdev_unlock(phy_dev);\n+\n+\tipvlan-\u003eport = port;\n+\n \treturn 0;\n }\n \n static void ipvlan_uninit(struct net_device *dev)\n {\n \tstruct ipvl_dev *ipvlan = netdev_priv(dev);\n-\tstruct net_device *phy_dev = ipvlan-\u003ephy_dev;\n-\tstruct ipvl_port *port;\n+\tnetdevice_tracker dev_tracker;\n+\tstruct net_device *phy_dev;\n \n \tfree_percpu(ipvlan-\u003epcpu_stats);\n \n-\tport = ipvlan_port_get_rtnl(phy_dev);\n-\tport-\u003ecount -= 1;\n-\tif (!port-\u003ecount)\n-\t\tipvlan_port_destroy(port-\u003edev);\n+\tphy_dev = ipvlan-\u003ephy_dev;\n+\tnetdev_hold(phy_dev, \u0026dev_tracker, GFP_KERNEL);\n+\tnetdev_lock(phy_dev);\n+\n+\tipvlan_port_put(ipvlan-\u003eport);\n+\n+\tnetdev_unlock(phy_dev);\n+\tnetdev_put(phy_dev, \u0026dev_tracker);\n }\n \n static int ipvlan_open(struct net_device *dev)\n@@ -594,9 +646,7 @@ int ipvlan_link_new(struct net_device *dev, struct rtnl_newlink_params *params,\n \tif (err \u003c 0)\n \t\treturn err;\n \n-\t/* ipvlan_init() would have created the port, if required */\n-\tport = ipvlan_port_get_rtnl(phy_dev);\n-\tipvlan-\u003eport = port;\n+\tport = ipvlan-\u003eport;\n \n \t/* If the port-id base is at the MAX value, then wrap it around and\n \t * begin from 0x1 again. This may be due to a busy system where lots\n@@ -639,7 +689,10 @@ int ipvlan_link_new(struct net_device *dev, struct rtnl_newlink_params *params,\n \tif (err)\n \t\tgoto unlink_netdev;\n \n+\tmutex_lock(\u0026port-\u003epnodes_lock);\n \tlist_add_tail_rcu(\u0026ipvlan-\u003epnode, \u0026port-\u003eipvlans);\n+\tmutex_unlock(\u0026port-\u003epnodes_lock);\n+\n \tnetif_stacked_transfer_operstate(phy_dev, dev);\n \treturn 0;\n \n@@ -653,7 +706,8 @@ int ipvlan_link_new(struct net_device *dev, struct rtnl_newlink_params *params,\n }\n EXPORT_SYMBOL_GPL(ipvlan_link_new);\n \n-void ipvlan_link_delete(struct net_device *dev, struct list_head *head)\n+void __ipvlan_link_delete(struct net *net, struct net_device *dev,\n+\t\t\t struct list_head *head)\n {\n \tstruct ipvl_dev *ipvlan = netdev_priv(dev);\n \tstruct ipvl_addr *addr, *next;\n@@ -668,10 +722,20 @@ void ipvlan_link_delete(struct net_device *dev, struct list_head *head)\n \n \tida_free(\u0026ipvlan-\u003eport-\u003eida, dev-\u003edev_id);\n \tlist_del_rcu(\u0026ipvlan-\u003epnode);\n-\tunregister_netdevice_queue(dev, head);\n+\tunregister_netdevice_queue_net(net, dev, head);\n \tnetdev_upper_dev_unlink(ipvlan-\u003ephy_dev, dev);\n }\n-EXPORT_SYMBOL_GPL(ipvlan_link_delete);\n+EXPORT_SYMBOL(__ipvlan_link_delete);\n+\n+static void ipvlan_link_delete(struct net_device *dev, struct list_head *head)\n+{\n+\tstruct ipvl_dev *ipvlan = netdev_priv(dev);\n+\n+\tmutex_lock(\u0026ipvlan-\u003eport-\u003epnodes_lock);\n+\tif (!ipvlan-\u003edying)\n+\t\t__ipvlan_link_delete(dev_net(dev), dev, head);\n+\tmutex_unlock(\u0026ipvlan-\u003eport-\u003epnodes_lock);\n+}\n \n void ipvlan_link_setup(struct net_device *dev)\n {\n@@ -729,14 +793,19 @@ static int ipvlan_device_event(struct notifier_block *unused,\n \tstruct netdev_notifier_pre_changeaddr_info *prechaddr_info;\n \tstruct net_device *dev = netdev_notifier_info_to_dev(ptr);\n \tstruct ipvl_dev *ipvlan, *next;\n+\tint err, ret = NOTIFY_DONE;\n \tstruct ipvl_port *port;\n \tLIST_HEAD(lst_kill);\n-\tint err;\n \n-\tif (!netif_is_ipvlan_port(dev))\n-\t\treturn NOTIFY_DONE;\n+\tif (event == NETDEV_PRECHANGEUPPER ||\n+\t event == NETDEV_CHANGEUPPER)\n+\t\treturn ret;\n \n-\tport = ipvlan_port_get_rtnl(dev);\n+\tport = ipvlan_port_get(dev);\n+\tif (!port)\n+\t\treturn ret;\n+\n+\tmutex_lock(\u0026port-\u003epnodes_lock);\n \n \tswitch (event) {\n \tcase NETDEV_UP:\n@@ -760,16 +829,26 @@ static int ipvlan_device_event(struct notifier_block *unused,\n \t\t\tipvlan_migrate_l3s_hook(oldnet, newnet);\n \t\tbreak;\n \t}\n-\tcase NETDEV_UNREGISTER:\n+\tcase NETDEV_UNREGISTER: {\n+\t\tstruct net *net = dev_net(dev);\n+\n \t\tif (dev-\u003ereg_state != NETREG_UNREGISTERING)\n \t\t\tbreak;\n \n-\t\tlist_for_each_entry_safe(ipvlan, next, \u0026port-\u003eipvlans, pnode)\n-\t\t\tipvlan-\u003edev-\u003ertnl_link_ops-\u003edellink(ipvlan-\u003edev,\n-\t\t\t\t\t\t\t \u0026lst_kill);\n+\t\tlist_for_each_entry_safe(ipvlan, next, \u0026port-\u003eipvlans, pnode) {\n+\t\t\tipvlan-\u003edying = true;\n+\n+#if IS_ENABLED(CONFIG_IPVTAP)\n+\t\t\tif (ipvlan-\u003edev-\u003ertnl_link_ops != \u0026ipvlan_link_ops)\n+\t\t\t\t__ipvtap_dellink_ptr(net, ipvlan-\u003edev, \u0026lst_kill);\n+\t\t\telse\n+#endif\n+\t\t\t\t__ipvlan_link_delete(net, ipvlan-\u003edev, \u0026lst_kill);\n+\t\t}\n+\n \t\tunregister_netdevice_many(\u0026lst_kill);\n \t\tbreak;\n-\n+\t}\n \tcase NETDEV_FEAT_CHANGE:\n \t\tlist_for_each_entry(ipvlan, \u0026port-\u003eipvlans, pnode) {\n \t\t\tnetif_inherit_tso_max(ipvlan-\u003edev, dev);\n@@ -788,8 +867,10 @@ static int ipvlan_device_event(struct notifier_block *unused,\n \t\t\terr = netif_pre_changeaddr_notify(ipvlan-\u003edev,\n \t\t\t\t\t\t\t prechaddr_info-\u003edev_addr,\n \t\t\t\t\t\t\t extack);\n-\t\t\tif (err)\n-\t\t\t\treturn notifier_from_errno(err);\n+\t\t\tif (err) {\n+\t\t\t\tret = notifier_from_errno(err);\n+\t\t\t\tbreak;\n+\t\t\t}\n \t\t}\n \t\tbreak;\n \n@@ -802,7 +883,8 @@ static int ipvlan_device_event(struct notifier_block *unused,\n \n \tcase NETDEV_PRE_TYPE_CHANGE:\n \t\t/* Forbid underlying device to change its type. */\n-\t\treturn NOTIFY_BAD;\n+\t\tret = NOTIFY_BAD;\n+\t\tbreak;\n \n \tcase NETDEV_NOTIFY_PEERS:\n \tcase NETDEV_BONDING_FAILOVER:\n@@ -810,7 +892,12 @@ static int ipvlan_device_event(struct notifier_block *unused,\n \t\tlist_for_each_entry(ipvlan, \u0026port-\u003eipvlans, pnode)\n \t\t\tcall_netdevice_notifiers(event, ipvlan-\u003edev);\n \t}\n-\treturn NOTIFY_DONE;\n+\n+\tmutex_unlock(\u0026port-\u003epnodes_lock);\n+\n+\tipvlan_port_put(port);\n+\n+\treturn ret;\n }\n \n /* the caller must held the addrs lock */\ndiff --git a/drivers/net/ipvlan/ipvtap.c b/drivers/net/ipvlan/ipvtap.c\nindex 2d6bbddd1edd51..66c949d9426103 100644\n--- a/drivers/net/ipvlan/ipvtap.c\n+++ b/drivers/net/ipvlan/ipvtap.c\n@@ -109,14 +109,26 @@ static int ipvtap_newlink(struct net_device *dev,\n \treturn err;\n }\n \n+static void __ipvtap_dellink(struct net *net, struct net_device *dev,\n+\t\t\t struct list_head *head)\n+{\n+\tstruct ipvtap_dev *vlantap = netdev_priv(dev);\n+\n+\tnetdev_rx_handler_unregister(dev);\n+\ttap_del_queues(\u0026vlantap-\u003etap);\n+\t__ipvlan_link_delete(net, dev, head);\n+}\n+\n static void ipvtap_dellink(struct net_device *dev,\n \t\t\t struct list_head *head)\n {\n-\tstruct ipvtap_dev *vlan = netdev_priv(dev);\n+\tstruct ipvtap_dev *vlantap = netdev_priv(dev);\n+\tstruct ipvl_port *port = vlantap-\u003evlan.port;\n \n-\tnetdev_rx_handler_unregister(dev);\n-\ttap_del_queues(\u0026vlan-\u003etap);\n-\tipvlan_link_delete(dev, head);\n+\tmutex_lock(\u0026port-\u003epnodes_lock);\n+\tif (!vlantap-\u003evlan.dying)\n+\t\t__ipvtap_dellink(dev_net(dev), dev, head);\n+\tmutex_unlock(\u0026port-\u003epnodes_lock);\n }\n \n static void ipvtap_setup(struct net_device *dev)\n@@ -198,6 +210,8 @@ static int __init ipvtap_init(void)\n {\n \tint err;\n \n+\t__ipvtap_dellink_ptr = __ipvtap_dellink;\n+\n \terr = tap_create_cdev(\u0026ipvtap_cdev, \u0026ipvtap_major, \"ipvtap\",\n \t\t\t THIS_MODULE);\n \tif (err)\n@@ -224,6 +238,8 @@ static int __init ipvtap_init(void)\n out2:\n \ttap_destroy_cdev(ipvtap_major, \u0026ipvtap_cdev);\n out1:\n+\t__ipvtap_dellink_ptr = NULL;\n+\n \treturn err;\n }\n module_init(ipvtap_init);\n@@ -234,6 +250,7 @@ static void __exit ipvtap_exit(void)\n \tunregister_netdevice_notifier(\u0026ipvtap_notifier_block);\n \tclass_unregister(\u0026ipvtap_class);\n \ttap_destroy_cdev(ipvtap_major, \u0026ipvtap_cdev);\n+\t__ipvtap_dellink_ptr = NULL;\n }\n module_exit(ipvtap_exit);\n MODULE_ALIAS_RTNL_LINK(\"ipvtap\");\ndiff --git a/drivers/net/veth.c b/drivers/net/veth.c\nindex 1c514214917536..8170bf33ccf956 100644\n--- a/drivers/net/veth.c\n+++ b/drivers/net/veth.c\n@@ -77,6 +77,7 @@ struct veth_priv {\n \tstruct bpf_prog\t\t*_xdp_prog;\n \tstruct veth_rq\t\t*rq;\n \tunsigned int\t\trequested_headroom;\n+\tnetdevice_tracker\tpeer_tracker;\n };\n \n struct veth_xdp_tx_bq {\n@@ -1901,15 +1902,17 @@ static int veth_newlink(struct net_device *dev,\n \n \tpriv = netdev_priv(dev);\n \trcu_assign_pointer(priv-\u003epeer, peer);\n+\tnetdev_hold(peer, \u0026priv-\u003epeer_tracker, GFP_KERNEL);\n \terr = veth_init_queues(dev, tb);\n \tif (err)\n \t\tgoto err_queues;\n \n \tpriv = netdev_priv(peer);\n \trcu_assign_pointer(priv-\u003epeer, dev);\n+\tnetdev_hold(dev, \u0026priv-\u003epeer_tracker, GFP_KERNEL);\n \terr = veth_init_queues(peer, tb);\n \tif (err)\n-\t\tgoto err_queues;\n+\t\tgoto err_peer_queues;\n \n \tveth_disable_gro(dev);\n \t/* update XDP supported features */\n@@ -1918,7 +1921,11 @@ static int veth_newlink(struct net_device *dev,\n \n \treturn 0;\n \n+err_peer_queues:\n+\tnetdev_put(dev, \u0026priv-\u003epeer_tracker);\n+\tpriv = netdev_priv(dev);\n err_queues:\n+\tnetdev_put(peer, \u0026priv-\u003epeer_tracker);\n \tunregister_netdevice(dev);\n err_register_dev:\n \t/* nothing to do */\n@@ -1933,24 +1940,25 @@ static int veth_newlink(struct net_device *dev,\n \n static void veth_dellink(struct net_device *dev, struct list_head *head)\n {\n-\tstruct veth_priv *priv;\n+\tnetdevice_tracker *peer_tracker;\n \tstruct net_device *peer;\n+\tstruct veth_priv *priv;\n \n \tpriv = netdev_priv(dev);\n-\tpeer = rtnl_dereference(priv-\u003epeer);\n+\tpeer_tracker = \u0026priv-\u003epeer_tracker;\n+\tpeer = unrcu_pointer(xchg(\u0026priv-\u003epeer, NULL));\n+\tif (!peer)\n+\t\treturn;\n \n-\t/* Note : dellink() is called from default_device_exit_batch(),\n-\t * before a rcu_synchronize() point. The devices are guaranteed\n-\t * not being freed before one RCU grace period.\n-\t */\n-\tRCU_INIT_POINTER(priv-\u003epeer, NULL);\n \tunregister_netdevice_queue(dev, head);\n \n-\tif (peer) {\n-\t\tpriv = netdev_priv(peer);\n-\t\tRCU_INIT_POINTER(priv-\u003epeer, NULL);\n-\t\tunregister_netdevice_queue(peer, head);\n-\t}\n+\tpriv = netdev_priv(peer);\n+\tdev = unrcu_pointer(xchg(\u0026priv-\u003epeer, NULL));\n+\tif (dev)\n+\t\tunregister_netdevice_queue_net(dev_net(dev), peer, head);\n+\n+\tnetdev_put(peer, peer_tracker);\n+\tnetdev_put(dev, \u0026priv-\u003epeer_tracker);\n }\n \n static const struct nla_policy veth_policy[VETH_INFO_MAX + 1] = {\ndiff --git a/include/linux/netdevice.h b/include/linux/netdevice.h\nindex 9981d637f8b546..8db25b79573e3c 100644\n--- a/include/linux/netdevice.h\n+++ b/include/linux/netdevice.h\n@@ -1845,6 +1845,8 @@ enum netdev_reg_state {\n *\t@napi_list:\tList entry used for polling NAPI devices\n *\t@unreg_list:\tList entry when we are unregistering the\n *\t\t\tdevice; see the function unregister_netdev\n+ *\t@unreg_list_net:List entry when we are unregistering the cross-netns\n+ *\t\t\tdevice; see the function unregister_netdevice_queue_net()\n *\t@close_list:\tList entry used when we are closing the device\n *\t@ptype_all: Device-specific packet handlers for all protocols\n *\t@ptype_specific: Device-specific, protocol-specific packet handlers\n@@ -2241,6 +2243,9 @@ struct net_device {\n \tstruct list_head\tdev_list;\n \tstruct list_head\tnapi_list;\n \tstruct list_head\tunreg_list;\n+#ifdef CONFIG_DEBUG_NET_SMALL_RTNL\n+\tstruct list_head\tunreg_list_net;\n+#endif\n \tstruct list_head\tclose_list;\n \tstruct list_head\tptype_all;\n \n@@ -3472,6 +3477,25 @@ static inline void unregister_netdevice(struct net_device *dev)\n \tunregister_netdevice_queue(dev, NULL);\n }\n \n+#ifdef CONFIG_DEBUG_NET_SMALL_RTNL\n+void unregister_netdevice_queue_net(struct net *net, struct net_device *dev,\n+\t\t\t\t struct list_head *head);\n+void unregister_netdevice_many_net(struct net *net);\n+void unregister_netdevice_queue_many_net(struct net *net, struct list_head *head);\n+#else\n+static inline void unregister_netdevice_queue_net(struct net *net,\n+\t\t\t\t\t\t struct net_device *dev,\n+\t\t\t\t\t\t struct list_head *head)\n+{\n+\tunregister_netdevice_queue(dev, head);\n+}\n+\n+static inline void unregister_netdevice_queue_many_net(struct net *net,\n+\t\t\t\t\t\t struct list_head *head)\n+{\n+}\n+#endif\n+\n int netdev_refcnt_read(const struct net_device *dev);\n void free_netdev(struct net_device *dev);\n \ndiff --git a/include/linux/rtnetlink.h b/include/linux/rtnetlink.h\nindex ea39dd23a1976e..95729339e7a548 100644\n--- a/include/linux/rtnetlink.h\n+++ b/include/linux/rtnetlink.h\n@@ -115,6 +115,10 @@ bool rtnl_net_is_locked(struct net *net);\n \n bool lockdep_rtnl_net_is_held(struct net *net);\n \n+void rtnl_net_queue_work(struct net *net);\n+void rtnl_net_flush_workqueue(void);\n+void rtnl_net_work_func(struct work_struct *work);\n+\n #define rcu_dereference_rtnl_net(net, p)\t\t\t\t\\\n \trcu_dereference_check(p, lockdep_rtnl_net_is_held(net))\n #define rtnl_net_dereference(net, p)\t\t\t\t\t\\\n@@ -150,6 +154,10 @@ static inline void ASSERT_RTNL_NET(struct net *net)\n \tASSERT_RTNL();\n }\n \n+static inline void rtnl_net_flush_workqueue(void)\n+{\n+}\n+\n #define rcu_dereference_rtnl_net(net, p)\t\t\\\n \trcu_dereference_rtnl(p)\n #define rtnl_net_dereference(net, p)\t\t\t\\\ndiff --git a/include/net/net_namespace.h b/include/net/net_namespace.h\nindex 80de5e98a66d6c..501af1999fe839 100644\n--- a/include/net/net_namespace.h\n+++ b/include/net/net_namespace.h\n@@ -197,6 +197,9 @@ struct net {\n #ifdef CONFIG_DEBUG_NET_SMALL_RTNL\n \t/* Move to a better place when the config guard is removed. */\n \tstruct mutex\t\trtnl_mutex;\n+\tstruct work_struct\trtnl_work;\n+\tstruct list_head\tdev_unreg_head;\n+\tspinlock_t\t\tdev_unreg_lock;\n #endif\n #if IS_ENABLED(CONFIG_VSOCKETS)\n \tstruct netns_vsock\tvsock;\ndiff --git a/net/core/dev.c b/net/core/dev.c\nindex 4b3d5cfdf6e00f..c43a44fb649fa2 100644\n--- a/net/core/dev.c\n+++ b/net/core/dev.c\n@@ -11608,8 +11608,13 @@ static struct net_device *netdev_wait_allrefs_any(struct list_head *list)\n \t\t\trtnl_lock();\n \n \t\t\t/* Rebroadcast unregister notification */\n-\t\t\tlist_for_each_entry(dev, list, todo_list)\n+\t\t\tlist_for_each_entry(dev, list, todo_list) {\n+\t\t\t\tstruct net *net = dev_net(dev);\n+\n+\t\t\t\t__rtnl_net_lock(net);\n \t\t\t\tcall_netdevice_notifiers(NETDEV_UNREGISTER, dev);\n+\t\t\t\t__rtnl_net_unlock(net);\n+\t\t\t}\n \n \t\t\t__rtnl_unlock();\n \t\t\trcu_barrier();\n@@ -12087,6 +12092,9 @@ struct net_device *alloc_netdev_mqs(int sizeof_priv, const char *name,\n \n \tINIT_LIST_HEAD(\u0026dev-\u003enapi_list);\n \tINIT_LIST_HEAD(\u0026dev-\u003eunreg_list);\n+#ifdef CONFIG_DEBUG_NET_SMALL_RTNL\n+\tINIT_LIST_HEAD(\u0026dev-\u003eunreg_list_net);\n+#endif\n \tINIT_LIST_HEAD(\u0026dev-\u003eclose_list);\n \tINIT_LIST_HEAD(\u0026dev-\u003elink_watch_list);\n \tINIT_LIST_HEAD(\u0026dev-\u003eadj_list.upper);\n@@ -12304,6 +12312,10 @@ void unregister_netdevice_queue(struct net_device *dev, struct list_head *head)\n {\n \tASSERT_RTNL();\n \n+#ifdef CONFIG_DEBUG_NET_SMALL_RTNL\n+\tDEBUG_NET_WARN_ON_ONCE(!list_empty(\u0026dev-\u003eunreg_list_net));\n+#endif\n+\n \tif (head) {\n \t\tlist_move_tail(\u0026dev-\u003eunreg_list, head);\n \t} else {\n@@ -12480,6 +12492,16 @@ void unregister_netdevice_many_notify(struct list_head *head,\n \tsynchronize_net();\n \n \tlist_for_each_entry(dev, head, unreg_list) {\n+#ifdef CONFIG_DEBUG_NET_SMALL_RTNL\n+\t\tstruct net *net = dev_net(dev);\n+\n+\t\t/* spin_lock() can be moved outside of the loop\n+\t\t * once the per-netns RTNL conversion completes.\n+\t\t */\n+\t\tspin_lock(\u0026net-\u003edev_unreg_lock);\n+\t\tlist_del(\u0026dev-\u003eunreg_list_net);\n+\t\tspin_unlock(\u0026net-\u003edev_unreg_lock);\n+#endif\n \t\tnetdev_put(dev, \u0026dev-\u003edev_registered_tracker);\n \t\tnet_set_todo(dev);\n \t\tcnt++;\n@@ -12502,6 +12524,96 @@ void unregister_netdevice_many(struct list_head *head)\n }\n EXPORT_SYMBOL(unregister_netdevice_many);\n \n+#ifdef CONFIG_DEBUG_NET_SMALL_RTNL\n+void unregister_netdevice_queue_net(struct net *net, struct net_device *dev,\n+\t\t\t\t struct list_head *head)\n+{\n+\tnetdev_lock(dev);\n+\n+\tif (net_eq(dev_net(dev), net)) {\n+\t\tnetdev_unlock(dev);\n+\t\tunregister_netdevice_queue(dev, head);\n+\t\treturn;\n+\t}\n+\n+\tnet = dev_net(dev);\n+\n+\tspin_lock(\u0026net-\u003edev_unreg_lock);\n+\n+\tDEBUG_NET_WARN_ON_ONCE(!list_empty(\u0026dev-\u003eunreg_list));\n+\tDEBUG_NET_WARN_ON_ONCE(!list_empty(\u0026dev-\u003eunreg_list_net));\n+\n+\tlist_add_tail(\u0026dev-\u003eunreg_list_net, \u0026net-\u003edev_unreg_head);\n+\trtnl_net_queue_work(net);\n+\n+\tspin_unlock(\u0026net-\u003edev_unreg_lock);\n+\n+\tnetdev_unlock(dev);\n+}\n+EXPORT_SYMBOL(unregister_netdevice_queue_net);\n+\n+void unregister_netdevice_queue_many_net(struct net *net, struct list_head *head)\n+{\n+\tstruct net_device *dev, *tmp;\n+\n+\tspin_lock(\u0026net-\u003edev_unreg_lock);\n+\tlist_for_each_entry_safe(dev, tmp, head, unreg_list) {\n+\t\t/* Once all cross-netns unregister_netdevice_queue() is\n+\t\t * converted to _net() (or for debugging), remove this check.\n+\t\t */\n+\t\tif (!net_eq(dev_net(dev), net))\n+\t\t\tcontinue;\n+\n+\t\tDEBUG_NET_WARN_ONCE(!net_eq(dev_net(dev), net),\n+\t\t\t\t \"%s was unregistered from a different netns.\\n\",\n+\t\t\t\t dev-\u003ename);\n+\n+\t\tlist_del_init(\u0026dev-\u003eunreg_list);\n+\t\tlist_move_tail(\u0026dev-\u003eunreg_list_net, \u0026net-\u003edev_unreg_head);\n+\t}\n+\tspin_unlock(\u0026net-\u003edev_unreg_lock);\n+}\n+\n+static void unregister_netdevice_move_net(struct net *net_old,\n+\t\t\t\t\t struct net *net,\n+\t\t\t\t\t struct net_device *dev)\n+{\n+\tif (net_old \u003e net) {\n+\t\tspin_lock(\u0026net-\u003edev_unreg_lock);\n+\t\tspin_lock_nested(\u0026net_old-\u003edev_unreg_lock, SINGLE_DEPTH_NESTING);\n+\t} else {\n+\t\tspin_lock(\u0026net_old-\u003edev_unreg_lock);\n+\t\tspin_lock_nested(\u0026net-\u003edev_unreg_lock, SINGLE_DEPTH_NESTING);\n+\t}\n+\n+\tif (!list_empty(\u0026dev-\u003eunreg_list_net)) {\n+\t\tlist_del(\u0026dev-\u003eunreg_list_net);\n+\t\tlist_add_tail(\u0026dev-\u003eunreg_list_net, \u0026net-\u003edev_unreg_head);\n+\t}\n+\n+\tspin_unlock(\u0026net_old-\u003edev_unreg_lock);\n+\tspin_unlock(\u0026net-\u003edev_unreg_lock);\n+}\n+\n+void unregister_netdevice_many_net(struct net *net)\n+{\n+\tstruct net_device *dev, *tmp;\n+\tLIST_HEAD(unreg_head_net);\n+\tLIST_HEAD(unreg_head);\n+\n+\tspin_lock(\u0026net-\u003edev_unreg_lock);\n+\tlist_splice_init(\u0026net-\u003edev_unreg_head, \u0026unreg_head_net);\n+\tspin_unlock(\u0026net-\u003edev_unreg_lock);\n+\n+\tlist_for_each_entry_safe(dev, tmp, \u0026unreg_head_net, unreg_list_net) {\n+\t\tlist_del_init(\u0026dev-\u003eunreg_list_net);\n+\t\tlist_add_tail(\u0026dev-\u003eunreg_list, \u0026unreg_head);\n+\t}\n+\n+\tunregister_netdevice_many(\u0026unreg_head);\n+}\n+#endif\n+\n /**\n *\tunregister_netdev - remove device from the kernel\n *\t@dev: device\n@@ -12658,6 +12770,10 @@ int __dev_change_net_namespace(struct net_device *dev, struct net *net,\n \tnetdev_unlock(dev);\n \tdev-\u003eifindex = new_ifindex;\n \n+#ifdef CONFIG_DEBUG_NET_SMALL_RTNL\n+\tunregister_netdevice_move_net(net_old, net, dev);\n+#endif\n+\n \tif (new_name[0]) {\n \t\t/* Rename the netdev to prepared name */\n \t\twrite_seqlock_bh(\u0026netdev_rename_lock);\n@@ -13034,7 +13150,7 @@ static void __net_exit default_device_exit_net(struct net *net)\n \t * Push all migratable network devices back to the\n \t * initial network namespace\n \t */\n-\tASSERT_RTNL();\n+\n \tfor_each_netdev_safe(net, dev, aux) {\n \t\tint err;\n \t\tchar fb_name[IFNAMSIZ];\n@@ -13077,21 +13193,36 @@ static void __net_exit default_device_exit_batch(struct list_head *net_list)\n \tLIST_HEAD(dev_kill_list);\n \n \trtnl_lock();\n+\n+\t__rtnl_net_lock(\u0026init_net);\n+\n \tlist_for_each_entry(net, net_list, exit_list) {\n+\t\t__rtnl_net_lock(net);\n \t\tdefault_device_exit_net(net);\n+\t\t__rtnl_net_unlock(net);\n+\n \t\tcond_resched();\n \t}\n \n+\t__rtnl_net_unlock(\u0026init_net);\n+\n \tlist_for_each_entry(net, net_list, exit_list) {\n+\t\t__rtnl_net_lock(net);\n+\n \t\tfor_each_netdev_reverse(net, dev) {\n \t\t\tif (dev-\u003ertnl_link_ops \u0026\u0026 dev-\u003ertnl_link_ops-\u003edellink)\n \t\t\t\tdev-\u003ertnl_link_ops-\u003edellink(dev, \u0026dev_kill_list);\n \t\t\telse\n \t\t\t\tunregister_netdevice_queue(dev, \u0026dev_kill_list);\n \t\t}\n+\n+\t\tunregister_netdevice_queue_many_net(net, \u0026dev_kill_list);\n+\t\t__rtnl_net_unlock(net);\n \t}\n \tunregister_netdevice_many(\u0026dev_kill_list);\n \trtnl_unlock();\n+\n+\trtnl_net_flush_workqueue();\n }\n \n static struct pernet_operations __net_initdata default_device_ops = {\ndiff --git a/net/core/net_namespace.c b/net/core/net_namespace.c\nindex d9dafe24f57e3f..a91d2b58aaddf8 100644\n--- a/net/core/net_namespace.c\n+++ b/net/core/net_namespace.c\n@@ -181,6 +181,7 @@ static void ops_exit_rtnl_list(const struct list_head *ops_list,\n \t\t\t\tops-\u003eexit_rtnl(net, \u0026dev_kill_list);\n \t\t}\n \n+\t\tunregister_netdevice_queue_many_net(net, \u0026dev_kill_list);\n \t\t__rtnl_net_unlock(net);\n \t}\n \n@@ -422,6 +423,9 @@ static __net_init int preinit_net(struct net *net, struct user_namespace *user_n\n #ifdef CONFIG_DEBUG_NET_SMALL_RTNL\n \tmutex_init(\u0026net-\u003ertnl_mutex);\n \tlock_set_cmp_fn(\u0026net-\u003ertnl_mutex, rtnl_net_lock_cmp_fn, NULL);\n+\tINIT_WORK(\u0026net-\u003ertnl_work, rtnl_net_work_func);\n+\tINIT_LIST_HEAD(\u0026net-\u003edev_unreg_head);\n+\tspin_lock_init(\u0026net-\u003edev_unreg_lock);\n #endif\n \n \tINIT_LIST_HEAD(\u0026net-\u003eptype_all);\ndiff --git a/net/core/rtnetlink.c b/net/core/rtnetlink.c\nindex 12aa3aa1688b13..b129f793d85164 100644\n--- a/net/core/rtnetlink.c\n+++ b/net/core/rtnetlink.c\n@@ -197,6 +197,7 @@ void __rtnl_net_unlock(struct net *net)\n {\n \tASSERT_RTNL();\n \n+\tunregister_netdevice_many_net(net);\n \tmutex_unlock(\u0026net-\u003ertnl_mutex);\n }\n EXPORT_SYMBOL(__rtnl_net_unlock);\n@@ -273,6 +274,29 @@ bool lockdep_rtnl_net_is_held(struct net *net)\n \treturn lockdep_rtnl_is_held() \u0026\u0026 lockdep_is_held(\u0026net-\u003ertnl_mutex);\n }\n EXPORT_SYMBOL(lockdep_rtnl_net_is_held);\n+\n+static struct workqueue_struct *rtnl_net_wq;\n+\n+void rtnl_net_queue_work(struct net *net)\n+{\n+\tqueue_work(rtnl_net_wq, \u0026net-\u003ertnl_work);\n+}\n+\n+void rtnl_net_flush_workqueue(void)\n+{\n+\tflush_workqueue(rtnl_net_wq);\n+}\n+\n+void rtnl_net_work_func(struct work_struct *work)\n+{\n+\tstruct net *net = container_of(work, struct net, rtnl_work);\n+\n+\tif (list_empty(\u0026net-\u003edev_unreg_head))\n+\t\treturn;\n+\n+\trtnl_net_lock(net);\n+\trtnl_net_unlock(net);\n+}\n #else\n static int rtnl_net_cmp_locks(const struct net *net_a, const struct net *net_b)\n {\n@@ -282,10 +306,11 @@ static int rtnl_net_cmp_locks(const struct net *net_a, const struct net *net_b)\n #endif\n \n struct rtnl_nets {\n-\t/* -\u003enewlink() needs to freeze 3 netns at most;\n-\t * 2 for the new device, 1 for its peer.\n+\t/* -\u003enewlink() needs to freeze 4 netns at most;\n+\t * 2 for the new device, 1 for its peer, 1 for\n+\t * an existing device (do_setlink() path).\n \t */\n-\tstruct net *net[3];\n+\tstruct net *net[4];\n \tunsigned char len;\n };\n \n@@ -636,16 +661,15 @@ int rtnl_link_register(struct rtnl_link_ops *ops)\n }\n EXPORT_SYMBOL_GPL(rtnl_link_register);\n \n-static void __rtnl_kill_links(struct net *net, struct rtnl_link_ops *ops)\n+static void __rtnl_kill_links(struct net *net, struct rtnl_link_ops *ops,\n+\t\t\t struct list_head *dev_kill_list)\n {\n \tstruct net_device *dev;\n-\tLIST_HEAD(list_kill);\n \n \tfor_each_netdev(net, dev) {\n \t\tif (dev-\u003ertnl_link_ops == ops)\n-\t\t\tops-\u003edellink(dev, \u0026list_kill);\n+\t\t\tops-\u003edellink(dev, dev_kill_list);\n \t}\n-\tunregister_netdevice_many(\u0026list_kill);\n }\n \n /* Return with the rtnl_lock held when there are no network\n@@ -676,6 +700,7 @@ static void rtnl_lock_unregistering_all(void)\n */\n void rtnl_link_unregister(struct rtnl_link_ops *ops)\n {\n+\tLIST_HEAD(dev_kill_list);\n \tstruct net *net;\n \n \tmutex_lock(\u0026link_ops_mutex);\n@@ -689,8 +714,14 @@ void rtnl_link_unregister(struct rtnl_link_ops *ops)\n \tdown_write(\u0026pernet_ops_rwsem);\n \trtnl_lock_unregistering_all();\n \n-\tfor_each_net(net)\n-\t\t__rtnl_kill_links(net, ops);\n+\tfor_each_net(net) {\n+\t\t__rtnl_net_lock(net);\n+\t\t__rtnl_kill_links(net, ops, \u0026dev_kill_list);\n+\t\tunregister_netdevice_queue_many_net(net, \u0026dev_kill_list);\n+\t\t__rtnl_net_unlock(net);\n+\t}\n+\n+\tunregister_netdevice_many(\u0026dev_kill_list);\n \n \trtnl_unlock();\n \tup_write(\u0026pernet_ops_rwsem);\n@@ -4155,6 +4186,8 @@ static int rtnl_newlink(struct sk_buff *skb, struct nlmsghdr *nlh,\n \t\t}\n \t}\n \n+\trtnl_nets_add(\u0026rtnl_nets, get_net(sock_net(skb-\u003esk)));\n+\n \trtnl_nets_lock(\u0026rtnl_nets);\n \tret = __rtnl_newlink(skb, nlh, ops, tgt_net, link_net, peer_net, tbs, data, extack);\n \trtnl_nets_unlock(\u0026rtnl_nets);\n@@ -7221,4 +7254,10 @@ void __init rtnetlink_init(void)\n \tregister_netdevice_notifier(\u0026rtnetlink_dev_notifier);\n \n \trtnl_register_many(rtnetlink_rtnl_msg_handlers);\n+\n+#ifdef CONFIG_DEBUG_NET_SMALL_RTNL\n+\trtnl_net_wq = create_workqueue(\"rtnl_net\");\n+\tif (!rtnl_net_wq)\n+\t\tpanic(\"Could not create rtnl_net workq\");\n+#endif\n }\n"
} |
| 2/1 | 2026/07/03 00:26 | agent | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: {
"EnableConfigs": [
"DEBUG_NET_SMALL_RTNL",
"BAREUDP",
"IPVLAN",
"IPVTAP",
"VETH"
],
"FocusSymbols": [
"unregister_netdevice_queue_net",
"unregister_netdevice_many_net",
"unregister_netdevice_queue_many_net",
"rtnl_net_work_func",
"bareudp_dellink",
"__bareudp_dellink",
"ipvlan_link_delete",
"__ipvlan_link_delete",
"ipvtap_dellink",
"__ipvtap_dellink",
"veth_dellink",
"default_device_exit_batch"
],
"Reasoning": "The patch introduces a new mechanism for unregistering network devices across network namespaces, adding per-netns RTNL locking support (CONFIG_DEBUG_NET_SMALL_RTNL). It modifies core netdevice unregistration functions and updates several virtual network drivers (bareudp, ipvlan, ipvtap, veth) to use the new locking and unregistration APIs. This involves complex synchronization and lifecycle management of network devices, which is highly prone to race conditions and use-after-free bugs. Fuzzing is essential to ensure the correctness of these changes.",
"WorthFuzzing": true
}Instruction: You are an expert Linux kernel maintainer. Your job is to review a provided patch series and determine if it makes functional changes to the kernel that should be fuzzed. IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in your workspace. Do NOT rely on your internal knowledge of the kernel. You must actively use your code access tools to examine the actual source code and confirm any assumptions. Return WorthFuzzing=false if the patch only contains: - Modifications to Documentation/, Kconfig files, or code comments. - Purely decorative changes, such as logging (e.g., pr_err, printk) or tracepoints. - Changes to numeric constants or macros that do not functionally alter execution flow. - Code paths that are impossible to reach in virtualized environments like GCE or QEMU, even when utilizing software-emulated hardware (e.g., usb gadget, mac80211_hwsim). If it modifies reachable core kernel logic, drivers, or architectures, use your code search tools to verify the code can be executed, then return WorthFuzzing=true. When returning WorthFuzzing=true, you MUST ALSO: 1. Extract any specific kernel functions that should be heavily fuzzed into FocusSymbols. Avoid listing generic hot-path functions to prevent skewed test distributions. 2. Identify any specific CONFIG_ options required to properly test this new/modified feature. Go and look into the Kconfig files and check for ifdefs around the code, do not make assumptions. Do not list too generic configs (we already have them enabled). Only list those that specifically cover the modified code. List them in the EnableConfigs output array, and DO NOT add a 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4"). Prefer calling several tools at the same time to save round-trips. Use set-results tool to provide results of the analysis. It must be called exactly once before the final reply. Ignore results of this tool. Prompt: For your convenience, here is the diff of the changes:
commit 58d40a9662711cbb664e400a9360e70d683f20d4
Author: syz-cluster <triage@syzkaller.com>
Date: Fri Jul 3 00:26:39 2026 +0000
syz-cluster: applied patch under review
diff --git a/drivers/net/bareudp.c b/drivers/net/bareudp.c
index 5ef841c8552697..c3b5ed52d87789 100644
--- a/drivers/net/bareudp.c
+++ b/drivers/net/bareudp.c
@@ -36,6 +36,7 @@ static unsigned int bareudp_net_id;
struct bareudp_net {
struct list_head bareudp_list;
+ struct mutex lock;
};
struct bareudp_conf {
@@ -636,10 +637,15 @@ static struct bareudp_dev *bareudp_find_dev(struct bareudp_net *bn,
{
struct bareudp_dev *bareudp, *t = NULL;
+ mutex_lock(&bn->lock);
+
list_for_each_entry(bareudp, &bn->bareudp_list, next) {
if (conf->port == bareudp->port)
t = bareudp;
}
+
+ mutex_unlock(&bn->lock);
+
return t;
}
@@ -675,7 +681,10 @@ static int bareudp_configure(struct net *net, struct net_device *dev,
if (err)
return err;
+ mutex_lock(&bn->lock);
list_add(&bareudp->next, &bn->bareudp_list);
+ mutex_unlock(&bn->lock);
+
return 0;
}
@@ -692,12 +701,26 @@ static int bareudp_link_config(struct net_device *dev,
return 0;
}
+static void __bareudp_dellink(struct net *net, struct net_device *dev,
+ struct list_head *head)
+{
+ struct bareudp_dev *bareudp = netdev_priv(dev);
+
+ list_del_init(&bareudp->next);
+ unregister_netdevice_queue_net(net, dev, head);
+}
+
static void bareudp_dellink(struct net_device *dev, struct list_head *head)
{
struct bareudp_dev *bareudp = netdev_priv(dev);
+ struct bareudp_net *bn;
- list_del(&bareudp->next);
- unregister_netdevice_queue(dev, head);
+ bn = net_generic(bareudp->net, bareudp_net_id);
+
+ mutex_lock(&bn->lock);
+ if (!list_empty(&bareudp->next))
+ __bareudp_dellink(dev_net(dev), dev, head);
+ mutex_unlock(&bn->lock);
}
static int bareudp_newlink(struct net_device *dev,
@@ -776,6 +799,8 @@ static __net_init int bareudp_init_net(struct net *net)
struct bareudp_net *bn = net_generic(net, bareudp_net_id);
INIT_LIST_HEAD(&bn->bareudp_list);
+ mutex_init(&bn->lock);
+
return 0;
}
@@ -785,13 +810,25 @@ static void __net_exit bareudp_exit_rtnl_net(struct net *net,
struct bareudp_net *bn = net_generic(net, bareudp_net_id);
struct bareudp_dev *bareudp, *next;
+ mutex_lock(&bn->lock);
+
list_for_each_entry_safe(bareudp, next, &bn->bareudp_list, next)
- bareudp_dellink(bareudp->dev, dev_kill_list);
+ __bareudp_dellink(net, bareudp->dev, dev_kill_list);
+
+ mutex_unlock(&bn->lock);
+}
+
+static void __net_exit bareudp_exit_net(struct net *net)
+{
+ struct bareudp_net *bn = net_generic(net, bareudp_net_id);
+
+ WARN_ON_ONCE(!list_empty(&bn->bareudp_list));
}
static struct pernet_operations bareudp_net_ops = {
.init = bareudp_init_net,
.exit_rtnl = bareudp_exit_rtnl_net,
+ .exit = bareudp_exit_net,
.id = &bareudp_net_id,
.size = sizeof(struct bareudp_net),
};
diff --git a/drivers/net/ipvlan/ipvlan.h b/drivers/net/ipvlan/ipvlan.h
index 80f84fc87008b3..8d05ad480438ff 100644
--- a/drivers/net/ipvlan/ipvlan.h
+++ b/drivers/net/ipvlan/ipvlan.h
@@ -69,6 +69,7 @@ struct ipvl_dev {
DECLARE_BITMAP(mac_filters, IPVLAN_MAC_FILTER_SIZE);
netdev_features_t sfeatures;
u32 msg_enable;
+ bool dying;
};
struct ipvl_addr {
@@ -91,12 +92,13 @@ struct ipvl_port {
struct hlist_head hlhead[IPVLAN_HASH_SIZE];
spinlock_t addrs_lock; /* guards hash-table and addrs */
struct list_head ipvlans;
+ struct mutex pnodes_lock;
u16 mode;
u16 flags;
u16 dev_id_start;
struct work_struct wq;
struct sk_buff_head backlog;
- int count;
+ refcount_t count;
struct ida ida;
netdevice_tracker dev_tracker;
};
@@ -168,7 +170,8 @@ void ipvlan_count_rx(const struct ipvl_dev *ipvlan,
unsigned int len, bool success, bool mcast);
int ipvlan_link_new(struct net_device *dev, struct rtnl_newlink_params *params,
struct netlink_ext_ack *extack);
-void ipvlan_link_delete(struct net_device *dev, struct list_head *head);
+void __ipvlan_link_delete(struct net *net, struct net_device *dev,
+ struct list_head *head);
void ipvlan_link_setup(struct net_device *dev);
int ipvlan_link_register(struct rtnl_link_ops *ops);
#ifdef CONFIG_IPVLAN_L3S
@@ -207,4 +210,9 @@ static inline bool netif_is_ipvlan_port(const struct net_device *dev)
return rcu_access_pointer(dev->rx_handler) == ipvlan_handle_frame;
}
+#if IS_ENABLED(CONFIG_IPVTAP)
+extern void (*__ipvtap_dellink_ptr)(struct net *net, struct net_device *dev,
+ struct list_head *head);
+#endif
+
#endif /* __IPVLAN_H */
diff --git a/drivers/net/ipvlan/ipvlan_main.c b/drivers/net/ipvlan/ipvlan_main.c
index ed46439a9f4eb1..ee46a55f73d14c 100644
--- a/drivers/net/ipvlan/ipvlan_main.c
+++ b/drivers/net/ipvlan/ipvlan_main.c
@@ -7,6 +7,12 @@
#include "ipvlan.h"
+#if IS_ENABLED(CONFIG_IPVTAP)
+void (*__ipvtap_dellink_ptr)(struct net *net, struct net_device *dev,
+ struct list_head *head);
+EXPORT_SYMBOL(__ipvtap_dellink_ptr);
+#endif
+
static int ipvlan_set_port_mode(struct ipvl_port *port, u16 nval,
struct netlink_ext_ack *extack)
{
@@ -16,6 +22,8 @@ static int ipvlan_set_port_mode(struct ipvl_port *port, u16 nval,
ASSERT_RTNL();
if (port->mode != nval) {
+ mutex_lock(&port->pnodes_lock);
+
list_for_each_entry(ipvlan, &port->ipvlans, pnode) {
flags = ipvlan->dev->flags;
if (nval == IPVLAN_MODE_L3 || nval == IPVLAN_MODE_L3S) {
@@ -40,6 +48,8 @@ static int ipvlan_set_port_mode(struct ipvl_port *port, u16 nval,
ipvlan_l3s_unregister(port);
}
port->mode = nval;
+
+ mutex_unlock(&port->pnodes_lock);
}
return 0;
@@ -56,6 +66,8 @@ static int ipvlan_set_port_mode(struct ipvl_port *port, u16 nval,
NULL);
}
+ mutex_unlock(&port->pnodes_lock);
+
return err;
}
@@ -76,6 +88,7 @@ static int ipvlan_port_create(struct net_device *dev)
INIT_HLIST_HEAD(&port->hlhead[idx]);
spin_lock_init(&port->addrs_lock);
+ mutex_init(&port->pnodes_lock);
skb_queue_head_init(&port->backlog);
INIT_WORK(&port->wq, ipvlan_process_multicast);
ida_init(&port->ida);
@@ -86,6 +99,7 @@ static int ipvlan_port_create(struct net_device *dev)
goto err;
netdev_hold(dev, &port->dev_tracker, GFP_KERNEL);
+
return 0;
err:
@@ -93,16 +107,18 @@ static int ipvlan_port_create(struct net_device *dev)
return err;
}
-static void ipvlan_port_destroy(struct net_device *dev)
+static void ipvlan_port_destroy(struct ipvl_port *port)
{
- struct ipvl_port *port = ipvlan_port_get_rtnl(dev);
+ struct net_device *dev = port->dev;
struct sk_buff *skb;
- netdev_put(dev, &port->dev_tracker);
if (port->mode == IPVLAN_MODE_L3S)
ipvlan_l3s_unregister(port);
+
netdev_rx_handler_unregister(dev);
cancel_work_sync(&port->wq);
+ netdev_put(dev, &port->dev_tracker);
+
while ((skb = __skb_dequeue(&port->backlog)) != NULL) {
dev_put(skb->dev);
kfree_skb(skb);
@@ -111,6 +127,27 @@ static void ipvlan_port_destroy(struct net_device *dev)
kfree(port);
}
+static void ipvlan_port_put(struct ipvl_port *port)
+{
+ if (refcount_dec_and_test(&port->count))
+ ipvlan_port_destroy(port);
+}
+
+static struct ipvl_port *ipvlan_port_get(struct net_device *dev)
+{
+ struct ipvl_port *port = NULL;
+
+ rcu_read_lock();
+ if (netif_is_ipvlan_port(dev)) {
+ port = ipvlan_port_get_rcu(dev);
+ if (!refcount_inc_not_zero(&port->count))
+ port = NULL;
+ }
+ rcu_read_unlock();
+
+ return port;
+}
+
#define IPVLAN_ALWAYS_ON_OFLOADS \
(NETIF_F_SG | NETIF_F_HW_CSUM | \
NETIF_F_GSO_ROBUST | NETIF_F_GSO_SOFTWARE | NETIF_F_GSO_ENCAP_ALL)
@@ -153,30 +190,45 @@ static int ipvlan_init(struct net_device *dev)
if (!ipvlan->pcpu_stats)
return -ENOMEM;
+ netdev_lock(phy_dev);
+
if (!netif_is_ipvlan_port(phy_dev)) {
err = ipvlan_port_create(phy_dev);
if (err < 0) {
+ netdev_unlock(phy_dev);
free_percpu(ipvlan->pcpu_stats);
return err;
}
+ port = ipvlan_port_get_rtnl(phy_dev);
+ refcount_set(&port->count, 1);
+ } else {
+ port = ipvlan_port_get_rtnl(phy_dev);
+ refcount_inc(&port->count);
}
- port = ipvlan_port_get_rtnl(phy_dev);
- port->count += 1;
+
+ netdev_unlock(phy_dev);
+
+ ipvlan->port = port;
+
return 0;
}
static void ipvlan_uninit(struct net_device *dev)
{
struct ipvl_dev *ipvlan = netdev_priv(dev);
- struct net_device *phy_dev = ipvlan->phy_dev;
- struct ipvl_port *port;
+ netdevice_tracker dev_tracker;
+ struct net_device *phy_dev;
free_percpu(ipvlan->pcpu_stats);
- port = ipvlan_port_get_rtnl(phy_dev);
- port->count -= 1;
- if (!port->count)
- ipvlan_port_destroy(port->dev);
+ phy_dev = ipvlan->phy_dev;
+ netdev_hold(phy_dev, &dev_tracker, GFP_KERNEL);
+ netdev_lock(phy_dev);
+
+ ipvlan_port_put(ipvlan->port);
+
+ netdev_unlock(phy_dev);
+ netdev_put(phy_dev, &dev_tracker);
}
static int ipvlan_open(struct net_device *dev)
@@ -594,9 +646,7 @@ int ipvlan_link_new(struct net_device *dev, struct rtnl_newlink_params *params,
if (err < 0)
return err;
- /* ipvlan_init() would have created the port, if required */
- port = ipvlan_port_get_rtnl(phy_dev);
- ipvlan->port = port;
+ port = ipvlan->port;
/* If the port-id base is at the MAX value, then wrap it around and
* begin from 0x1 again. This may be due to a busy system where lots
@@ -639,7 +689,10 @@ int ipvlan_link_new(struct net_device *dev, struct rtnl_newlink_params *params,
if (err)
goto unlink_netdev;
+ mutex_lock(&port->pnodes_lock);
list_add_tail_rcu(&ipvlan->pnode, &port->ipvlans);
+ mutex_unlock(&port->pnodes_lock);
+
netif_stacked_transfer_operstate(phy_dev, dev);
return 0;
@@ -653,7 +706,8 @@ int ipvlan_link_new(struct net_device *dev, struct rtnl_newlink_params *params,
}
EXPORT_SYMBOL_GPL(ipvlan_link_new);
-void ipvlan_link_delete(struct net_device *dev, struct list_head *head)
+void __ipvlan_link_delete(struct net *net, struct net_device *dev,
+ struct list_head *head)
{
struct ipvl_dev *ipvlan = netdev_priv(dev);
struct ipvl_addr *addr, *next;
@@ -668,10 +722,20 @@ void ipvlan_link_delete(struct net_device *dev, struct list_head *head)
ida_free(&ipvlan->port->ida, dev->dev_id);
list_del_rcu(&ipvlan->pnode);
- unregister_netdevice_queue(dev, head);
+ unregister_netdevice_queue_net(net, dev, head);
netdev_upper_dev_unlink(ipvlan->phy_dev, dev);
}
-EXPORT_SYMBOL_GPL(ipvlan_link_delete);
+EXPORT_SYMBOL(__ipvlan_link_delete);
+
+static void ipvlan_link_delete(struct net_device *dev, struct list_head *head)
+{
+ struct ipvl_dev *ipvlan = netdev_priv(dev);
+
+ mutex_lock(&ipvlan->port->pnodes_lock);
+ if (!ipvlan->dying)
+ __ipvlan_link_delete(dev_net(dev), dev, head);
+ mutex_unlock(&ipvlan->port->pnodes_lock);
+}
void ipvlan_link_setup(struct net_device *dev)
{
@@ -729,14 +793,19 @@ static int ipvlan_device_event(struct notifier_block *unused,
struct netdev_notifier_pre_changeaddr_info *prechaddr_info;
struct net_device *dev = netdev_notifier_info_to_dev(ptr);
struct ipvl_dev *ipvlan, *next;
+ int err, ret = NOTIFY_DONE;
struct ipvl_port *port;
LIST_HEAD(lst_kill);
- int err;
- if (!netif_is_ipvlan_port(dev))
- return NOTIFY_DONE;
+ if (event == NETDEV_PRECHANGEUPPER ||
+ event == NETDEV_CHANGEUPPER)
+ return ret;
- port = ipvlan_port_get_rtnl(dev);
+ port = ipvlan_port_get(dev);
+ if (!port)
+ return ret;
+
+ mutex_lock(&port->pnodes_lock);
switch (event) {
case NETDEV_UP:
@@ -760,16 +829,26 @@ static int ipvlan_device_event(struct notifier_block *unused,
ipvlan_migrate_l3s_hook(oldnet, newnet);
break;
}
- case NETDEV_UNREGISTER:
+ case NETDEV_UNREGISTER: {
+ struct net *net = dev_net(dev);
+
if (dev->reg_state != NETREG_UNREGISTERING)
break;
- list_for_each_entry_safe(ipvlan, next, &port->ipvlans, pnode)
- ipvlan->dev->rtnl_link_ops->dellink(ipvlan->dev,
- &lst_kill);
+ list_for_each_entry_safe(ipvlan, next, &port->ipvlans, pnode) {
+ ipvlan->dying = true;
+
+#if IS_ENABLED(CONFIG_IPVTAP)
+ if (ipvlan->dev->rtnl_link_ops != &ipvlan_link_ops)
+ __ipvtap_dellink_ptr(net, ipvlan->dev, &lst_kill);
+ else
+#endif
+ __ipvlan_link_delete(net, ipvlan->dev, &lst_kill);
+ }
+
unregister_netdevice_many(&lst_kill);
break;
-
+ }
case NETDEV_FEAT_CHANGE:
list_for_each_entry(ipvlan, &port->ipvlans, pnode) {
netif_inherit_tso_max(ipvlan->dev, dev);
@@ -788,8 +867,10 @@ static int ipvlan_device_event(struct notifier_block *unused,
err = netif_pre_changeaddr_notify(ipvlan->dev,
prechaddr_info->dev_addr,
extack);
- if (err)
- return notifier_from_errno(err);
+ if (err) {
+ ret = notifier_from_errno(err);
+ break;
+ }
}
break;
@@ -802,7 +883,8 @@ static int ipvlan_device_event(struct notifier_block *unused,
case NETDEV_PRE_TYPE_CHANGE:
/* Forbid underlying device to change its type. */
- return NOTIFY_BAD;
+ ret = NOTIFY_BAD;
+ break;
case NETDEV_NOTIFY_PEERS:
case NETDEV_BONDING_FAILOVER:
@@ -810,7 +892,12 @@ static int ipvlan_device_event(struct notifier_block *unused,
list_for_each_entry(ipvlan, &port->ipvlans, pnode)
call_netdevice_notifiers(event, ipvlan->dev);
}
- return NOTIFY_DONE;
+
+ mutex_unlock(&port->pnodes_lock);
+
+ ipvlan_port_put(port);
+
+ return ret;
}
/* the caller must held the addrs lock */
diff --git a/drivers/net/ipvlan/ipvtap.c b/drivers/net/ipvlan/ipvtap.c
index 2d6bbddd1edd51..66c949d9426103 100644
--- a/drivers/net/ipvlan/ipvtap.c
+++ b/drivers/net/ipvlan/ipvtap.c
@@ -109,14 +109,26 @@ static int ipvtap_newlink(struct net_device *dev,
return err;
}
+static void __ipvtap_dellink(struct net *net, struct net_device *dev,
+ struct list_head *head)
+{
+ struct ipvtap_dev *vlantap = netdev_priv(dev);
+
+ netdev_rx_handler_unregister(dev);
+ tap_del_queues(&vlantap->tap);
+ __ipvlan_link_delete(net, dev, head);
+}
+
static void ipvtap_dellink(struct net_device *dev,
struct list_head *head)
{
- struct ipvtap_dev *vlan = netdev_priv(dev);
+ struct ipvtap_dev *vlantap = netdev_priv(dev);
+ struct ipvl_port *port = vlantap->vlan.port;
- netdev_rx_handler_unregister(dev);
- tap_del_queues(&vlan->tap);
- ipvlan_link_delete(dev, head);
+ mutex_lock(&port->pnodes_lock);
+ if (!vlantap->vlan.dying)
+ __ipvtap_dellink(dev_net(dev), dev, head);
+ mutex_unlock(&port->pnodes_lock);
}
static void ipvtap_setup(struct net_device *dev)
@@ -198,6 +210,8 @@ static int __init ipvtap_init(void)
{
int err;
+ __ipvtap_dellink_ptr = __ipvtap_dellink;
+
err = tap_create_cdev(&ipvtap_cdev, &ipvtap_major, "ipvtap",
THIS_MODULE);
if (err)
@@ -224,6 +238,8 @@ static int __init ipvtap_init(void)
out2:
tap_destroy_cdev(ipvtap_major, &ipvtap_cdev);
out1:
+ __ipvtap_dellink_ptr = NULL;
+
return err;
}
module_init(ipvtap_init);
@@ -234,6 +250,7 @@ static void __exit ipvtap_exit(void)
unregister_netdevice_notifier(&ipvtap_notifier_block);
class_unregister(&ipvtap_class);
tap_destroy_cdev(ipvtap_major, &ipvtap_cdev);
+ __ipvtap_dellink_ptr = NULL;
}
module_exit(ipvtap_exit);
MODULE_ALIAS_RTNL_LINK("ipvtap");
diff --git a/drivers/net/veth.c b/drivers/net/veth.c
index 1c514214917536..8170bf33ccf956 100644
--- a/drivers/net/veth.c
+++ b/drivers/net/veth.c
@@ -77,6 +77,7 @@ struct veth_priv {
struct bpf_prog *_xdp_prog;
struct veth_rq *rq;
unsigned int requested_headroom;
+ netdevice_tracker peer_tracker;
};
struct veth_xdp_tx_bq {
@@ -1901,15 +1902,17 @@ static int veth_newlink(struct net_device *dev,
priv = netdev_priv(dev);
rcu_assign_pointer(priv->peer, peer);
+ netdev_hold(peer, &priv->peer_tracker, GFP_KERNEL);
err = veth_init_queues(dev, tb);
if (err)
goto err_queues;
priv = netdev_priv(peer);
rcu_assign_pointer(priv->peer, dev);
+ netdev_hold(dev, &priv->peer_tracker, GFP_KERNEL);
err = veth_init_queues(peer, tb);
if (err)
- goto err_queues;
+ goto err_peer_queues;
veth_disable_gro(dev);
/* update XDP supported features */
@@ -1918,7 +1921,11 @@ static int veth_newlink(struct net_device *dev,
return 0;
+err_peer_queues:
+ netdev_put(dev, &priv->peer_tracker);
+ priv = netdev_priv(dev);
err_queues:
+ netdev_put(peer, &priv->peer_tracker);
unregister_netdevice(dev);
err_register_dev:
/* nothing to do */
@@ -1933,24 +1940,25 @@ static int veth_newlink(struct net_device *dev,
static void veth_dellink(struct net_device *dev, struct list_head *head)
{
- struct veth_priv *priv;
+ netdevice_tracker *peer_tracker;
struct net_device *peer;
+ struct veth_priv *priv;
priv = netdev_priv(dev);
- peer = rtnl_dereference(priv->peer);
+ peer_tracker = &priv->peer_tracker;
+ peer = unrcu_pointer(xchg(&priv->peer, NULL));
+ if (!peer)
+ return;
- /* Note : dellink() is called from default_device_exit_batch(),
- * before a rcu_synchronize() point. The devices are guaranteed
- * not being freed before one RCU grace period.
- */
- RCU_INIT_POINTER(priv->peer, NULL);
unregister_netdevice_queue(dev, head);
- if (peer) {
- priv = netdev_priv(peer);
- RCU_INIT_POINTER(priv->peer, NULL);
- unregister_netdevice_queue(peer, head);
- }
+ priv = netdev_priv(peer);
+ dev = unrcu_pointer(xchg(&priv->peer, NULL));
+ if (dev)
+ unregister_netdevice_queue_net(dev_net(dev), peer, head);
+
+ netdev_put(peer, peer_tracker);
+ netdev_put(dev, &priv->peer_tracker);
}
static const struct nla_policy veth_policy[VETH_INFO_MAX + 1] = {
diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h
index 9981d637f8b546..8db25b79573e3c 100644
--- a/include/linux/netdevice.h
+++ b/include/linux/netdevice.h
@@ -1845,6 +1845,8 @@ enum netdev_reg_state {
* @napi_list: List entry used for polling NAPI devices
* @unreg_list: List entry when we are unregistering the
* device; see the function unregister_netdev
+ * @unreg_list_net:List entry when we are unregistering the cross-netns
+ * device; see the function unregister_netdevice_queue_net()
* @close_list: List entry used when we are closing the device
* @ptype_all: Device-specific packet handlers for all protocols
* @ptype_specific: Device-specific, protocol-specific packet handlers
@@ -2241,6 +2243,9 @@ struct net_device {
struct list_head dev_list;
struct list_head napi_list;
struct list_head unreg_list;
+#ifdef CONFIG_DEBUG_NET_SMALL_RTNL
+ struct list_head unreg_list_net;
+#endif
struct list_head close_list;
struct list_head ptype_all;
@@ -3472,6 +3477,25 @@ static inline void unregister_netdevice(struct net_device *dev)
unregister_netdevice_queue(dev, NULL);
}
+#ifdef CONFIG_DEBUG_NET_SMALL_RTNL
+void unregister_netdevice_queue_net(struct net *net, struct net_device *dev,
+ struct list_head *head);
+void unregister_netdevice_many_net(struct net *net);
+void unregister_netdevice_queue_many_net(struct net *net, struct list_head *head);
+#else
+static inline void unregister_netdevice_queue_net(struct net *net,
+ struct net_device *dev,
+ struct list_head *head)
+{
+ unregister_netdevice_queue(dev, head);
+}
+
+static inline void unregister_netdevice_queue_many_net(struct net *net,
+ struct list_head *head)
+{
+}
+#endif
+
int netdev_refcnt_read(const struct net_device *dev);
void free_netdev(struct net_device *dev);
diff --git a/include/linux/rtnetlink.h b/include/linux/rtnetlink.h
index ea39dd23a1976e..95729339e7a548 100644
--- a/include/linux/rtnetlink.h
+++ b/include/linux/rtnetlink.h
@@ -115,6 +115,10 @@ bool rtnl_net_is_locked(struct net *net);
bool lockdep_rtnl_net_is_held(struct net *net);
+void rtnl_net_queue_work(struct net *net);
+void rtnl_net_flush_workqueue(void);
+void rtnl_net_work_func(struct work_struct *work);
+
#define rcu_dereference_rtnl_net(net, p) \
rcu_dereference_check(p, lockdep_rtnl_net_is_held(net))
#define rtnl_net_dereference(net, p) \
@@ -150,6 +154,10 @@ static inline void ASSERT_RTNL_NET(struct net *net)
ASSERT_RTNL();
}
+static inline void rtnl_net_flush_workqueue(void)
+{
+}
+
#define rcu_dereference_rtnl_net(net, p) \
rcu_dereference_rtnl(p)
#define rtnl_net_dereference(net, p) \
diff --git a/include/net/net_namespace.h b/include/net/net_namespace.h
index 80de5e98a66d6c..501af1999fe839 100644
--- a/include/net/net_namespace.h
+++ b/include/net/net_namespace.h
@@ -197,6 +197,9 @@ struct net {
#ifdef CONFIG_DEBUG_NET_SMALL_RTNL
/* Move to a better place when the config guard is removed. */
struct mutex rtnl_mutex;
+ struct work_struct rtnl_work;
+ struct list_head dev_unreg_head;
+ spinlock_t dev_unreg_lock;
#endif
#if IS_ENABLED(CONFIG_VSOCKETS)
struct netns_vsock vsock;
diff --git a/net/core/dev.c b/net/core/dev.c
index 4b3d5cfdf6e00f..c43a44fb649fa2 100644
--- a/net/core/dev.c
+++ b/net/core/dev.c
@@ -11608,8 +11608,13 @@ static struct net_device *netdev_wait_allrefs_any(struct list_head *list)
rtnl_lock();
/* Rebroadcast unregister notification */
- list_for_each_entry(dev, list, todo_list)
+ list_for_each_entry(dev, list, todo_list) {
+ struct net *net = dev_net(dev);
+
+ __rtnl_net_lock(net);
call_netdevice_notifiers(NETDEV_UNREGISTER, dev);
+ __rtnl_net_unlock(net);
+ }
__rtnl_unlock();
rcu_barrier();
@@ -12087,6 +12092,9 @@ struct net_device *alloc_netdev_mqs(int sizeof_priv, const char *name,
INIT_LIST_HEAD(&dev->napi_list);
INIT_LIST_HEAD(&dev->unreg_list);
+#ifdef CONFIG_DEBUG_NET_SMALL_RTNL
+ INIT_LIST_HEAD(&dev->unreg_list_net);
+#endif
INIT_LIST_HEAD(&dev->close_list);
INIT_LIST_HEAD(&dev->link_watch_list);
INIT_LIST_HEAD(&dev->adj_list.upper);
@@ -12304,6 +12312,10 @@ void unregister_netdevice_queue(struct net_device *dev, struct list_head *head)
{
ASSERT_RTNL();
+#ifdef CONFIG_DEBUG_NET_SMALL_RTNL
+ DEBUG_NET_WARN_ON_ONCE(!list_empty(&dev->unreg_list_net));
+#endif
+
if (head) {
list_move_tail(&dev->unreg_list, head);
} else {
@@ -12480,6 +12492,16 @@ void unregister_netdevice_many_notify(struct list_head *head,
synchronize_net();
list_for_each_entry(dev, head, unreg_list) {
+#ifdef CONFIG_DEBUG_NET_SMALL_RTNL
+ struct net *net = dev_net(dev);
+
+ /* spin_lock() can be moved outside of the loop
+ * once the per-netns RTNL conversion completes.
+ */
+ spin_lock(&net->dev_unreg_lock);
+ list_del(&dev->unreg_list_net);
+ spin_unlock(&net->dev_unreg_lock);
+#endif
netdev_put(dev, &dev->dev_registered_tracker);
net_set_todo(dev);
cnt++;
@@ -12502,6 +12524,96 @@ void unregister_netdevice_many(struct list_head *head)
}
EXPORT_SYMBOL(unregister_netdevice_many);
+#ifdef CONFIG_DEBUG_NET_SMALL_RTNL
+void unregister_netdevice_queue_net(struct net *net, struct net_device *dev,
+ struct list_head *head)
+{
+ netdev_lock(dev);
+
+ if (net_eq(dev_net(dev), net)) {
+ netdev_unlock(dev);
+ unregister_netdevice_queue(dev, head);
+ return;
+ }
+
+ net = dev_net(dev);
+
+ spin_lock(&net->dev_unreg_lock);
+
+ DEBUG_NET_WARN_ON_ONCE(!list_empty(&dev->unreg_list));
+ DEBUG_NET_WARN_ON_ONCE(!list_empty(&dev->unreg_list_net));
+
+ list_add_tail(&dev->unreg_list_net, &net->dev_unreg_head);
+ rtnl_net_queue_work(net);
+
+ spin_unlock(&net->dev_unreg_lock);
+
+ netdev_unlock(dev);
+}
+EXPORT_SYMBOL(unregister_netdevice_queue_net);
+
+void unregister_netdevice_queue_many_net(struct net *net, struct list_head *head)
+{
+ struct net_device *dev, *tmp;
+
+ spin_lock(&net->dev_unreg_lock);
+ list_for_each_entry_safe(dev, tmp, head, unreg_list) {
+ /* Once all cross-netns unregister_netdevice_queue() is
+ * converted to _net() (or for debugging), remove this check.
+ */
+ if (!net_eq(dev_net(dev), net))
+ continue;
+
+ DEBUG_NET_WARN_ONCE(!net_eq(dev_net(dev), net),
+ "%s was unregistered from a different netns.\n",
+ dev->name);
+
+ list_del_init(&dev->unreg_list);
+ list_move_tail(&dev->unreg_list_net, &net->dev_unreg_head);
+ }
+ spin_unlock(&net->dev_unreg_lock);
+}
+
+static void unregister_netdevice_move_net(struct net *net_old,
+ struct net *net,
+ struct net_device *dev)
+{
+ if (net_old > net) {
+ spin_lock(&net->dev_unreg_lock);
+ spin_lock_nested(&net_old->dev_unreg_lock, SINGLE_DEPTH_NESTING);
+ } else {
+ spin_lock(&net_old->dev_unreg_lock);
+ spin_lock_nested(&net->dev_unreg_lock, SINGLE_DEPTH_NESTING);
+ }
+
+ if (!list_empty(&dev->unreg_list_net)) {
+ list_del(&dev->unreg_list_net);
+ list_add_tail(&dev->unreg_list_net, &net->dev_unreg_head);
+ }
+
+ spin_unlock(&net_old->dev_unreg_lock);
+ spin_unlock(&net->dev_unreg_lock);
+}
+
+void unregister_netdevice_many_net(struct net *net)
+{
+ struct net_device *dev, *tmp;
+ LIST_HEAD(unreg_head_net);
+ LIST_HEAD(unreg_head);
+
+ spin_lock(&net->dev_unreg_lock);
+ list_splice_init(&net->dev_unreg_head, &unreg_head_net);
+ spin_unlock(&net->dev_unreg_lock);
+
+ list_for_each_entry_safe(dev, tmp, &unreg_head_net, unreg_list_net) {
+ list_del_init(&dev->unreg_list_net);
+ list_add_tail(&dev->unreg_list, &unreg_head);
+ }
+
+ unregister_netdevice_many(&unreg_head);
+}
+#endif
+
/**
* unregister_netdev - remove device from the kernel
* @dev: device
@@ -12658,6 +12770,10 @@ int __dev_change_net_namespace(struct net_device *dev, struct net *net,
netdev_unlock(dev);
dev->ifindex = new_ifindex;
+#ifdef CONFIG_DEBUG_NET_SMALL_RTNL
+ unregister_netdevice_move_net(net_old, net, dev);
+#endif
+
if (new_name[0]) {
/* Rename the netdev to prepared name */
write_seqlock_bh(&netdev_rename_lock);
@@ -13034,7 +13150,7 @@ static void __net_exit default_device_exit_net(struct net *net)
* Push all migratable network devices back to the
* initial network namespace
*/
- ASSERT_RTNL();
+
for_each_netdev_safe(net, dev, aux) {
int err;
char fb_name[IFNAMSIZ];
@@ -13077,21 +13193,36 @@ static void __net_exit default_device_exit_batch(struct list_head *net_list)
LIST_HEAD(dev_kill_list);
rtnl_lock();
+
+ __rtnl_net_lock(&init_net);
+
list_for_each_entry(net, net_list, exit_list) {
+ __rtnl_net_lock(net);
default_device_exit_net(net);
+ __rtnl_net_unlock(net);
+
cond_resched();
}
+ __rtnl_net_unlock(&init_net);
+
list_for_each_entry(net, net_list, exit_list) {
+ __rtnl_net_lock(net);
+
for_each_netdev_reverse(net, dev) {
if (dev->rtnl_link_ops && dev->rtnl_link_ops->dellink)
dev->rtnl_link_ops->dellink(dev, &dev_kill_list);
else
unregister_netdevice_queue(dev, &dev_kill_list);
}
+
+ unregister_netdevice_queue_many_net(net, &dev_kill_list);
+ __rtnl_net_unlock(net);
}
unregister_netdevice_many(&dev_kill_list);
rtnl_unlock();
+
+ rtnl_net_flush_workqueue();
}
static struct pernet_operations __net_initdata default_device_ops = {
diff --git a/net/core/net_namespace.c b/net/core/net_namespace.c
index d9dafe24f57e3f..a91d2b58aaddf8 100644
--- a/net/core/net_namespace.c
+++ b/net/core/net_namespace.c
@@ -181,6 +181,7 @@ static void ops_exit_rtnl_list(const struct list_head *ops_list,
ops->exit_rtnl(net, &dev_kill_list);
}
+ unregister_netdevice_queue_many_net(net, &dev_kill_list);
__rtnl_net_unlock(net);
}
@@ -422,6 +423,9 @@ static __net_init int preinit_net(struct net *net, struct user_namespace *user_n
#ifdef CONFIG_DEBUG_NET_SMALL_RTNL
mutex_init(&net->rtnl_mutex);
lock_set_cmp_fn(&net->rtnl_mutex, rtnl_net_lock_cmp_fn, NULL);
+ INIT_WORK(&net->rtnl_work, rtnl_net_work_func);
+ INIT_LIST_HEAD(&net->dev_unreg_head);
+ spin_lock_init(&net->dev_unreg_lock);
#endif
INIT_LIST_HEAD(&net->ptype_all);
diff --git a/net/core/rtnetlink.c b/net/core/rtnetlink.c
index 12aa3aa1688b13..b129f793d85164 100644
--- a/net/core/rtnetlink.c
+++ b/net/core/rtnetlink.c
@@ -197,6 +197,7 @@ void __rtnl_net_unlock(struct net *net)
{
ASSERT_RTNL();
+ unregister_netdevice_many_net(net);
mutex_unlock(&net->rtnl_mutex);
}
EXPORT_SYMBOL(__rtnl_net_unlock);
@@ -273,6 +274,29 @@ bool lockdep_rtnl_net_is_held(struct net *net)
return lockdep_rtnl_is_held() && lockdep_is_held(&net->rtnl_mutex);
}
EXPORT_SYMBOL(lockdep_rtnl_net_is_held);
+
+static struct workqueue_struct *rtnl_net_wq;
+
+void rtnl_net_queue_work(struct net *net)
+{
+ queue_work(rtnl_net_wq, &net->rtnl_work);
+}
+
+void rtnl_net_flush_workqueue(void)
+{
+ flush_workqueue(rtnl_net_wq);
+}
+
+void rtnl_net_work_func(struct work_struct *work)
+{
+ struct net *net = container_of(work, struct net, rtnl_work);
+
+ if (list_empty(&net->dev_unreg_head))
+ return;
+
+ rtnl_net_lock(net);
+ rtnl_net_unlock(net);
+}
#else
static int rtnl_net_cmp_locks(const struct net *net_a, const struct net *net_b)
{
@@ -282,10 +306,11 @@ static int rtnl_net_cmp_locks(const struct net *net_a, const struct net *net_b)
#endif
struct rtnl_nets {
- /* ->newlink() needs to freeze 3 netns at most;
- * 2 for the new device, 1 for its peer.
+ /* ->newlink() needs to freeze 4 netns at most;
+ * 2 for the new device, 1 for its peer, 1 for
+ * an existing device (do_setlink() path).
*/
- struct net *net[3];
+ struct net *net[4];
unsigned char len;
};
@@ -636,16 +661,15 @@ int rtnl_link_register(struct rtnl_link_ops *ops)
}
EXPORT_SYMBOL_GPL(rtnl_link_register);
-static void __rtnl_kill_links(struct net *net, struct rtnl_link_ops *ops)
+static void __rtnl_kill_links(struct net *net, struct rtnl_link_ops *ops,
+ struct list_head *dev_kill_list)
{
struct net_device *dev;
- LIST_HEAD(list_kill);
for_each_netdev(net, dev) {
if (dev->rtnl_link_ops == ops)
- ops->dellink(dev, &list_kill);
+ ops->dellink(dev, dev_kill_list);
}
- unregister_netdevice_many(&list_kill);
}
/* Return with the rtnl_lock held when there are no network
@@ -676,6 +700,7 @@ static void rtnl_lock_unregistering_all(void)
*/
void rtnl_link_unregister(struct rtnl_link_ops *ops)
{
+ LIST_HEAD(dev_kill_list);
struct net *net;
mutex_lock(&link_ops_mutex);
@@ -689,8 +714,14 @@ void rtnl_link_unregister(struct rtnl_link_ops *ops)
down_write(&pernet_ops_rwsem);
rtnl_lock_unregistering_all();
- for_each_net(net)
- __rtnl_kill_links(net, ops);
+ for_each_net(net) {
+ __rtnl_net_lock(net);
+ __rtnl_kill_links(net, ops, &dev_kill_list);
+ unregister_netdevice_queue_many_net(net, &dev_kill_list);
+ __rtnl_net_unlock(net);
+ }
+
+ unregister_netdevice_many(&dev_kill_list);
rtnl_unlock();
up_write(&pernet_ops_rwsem);
@@ -4155,6 +4186,8 @@ static int rtnl_newlink(struct sk_buff *skb, struct nlmsghdr *nlh,
}
}
+ rtnl_nets_add(&rtnl_nets, get_net(sock_net(skb->sk)));
+
rtnl_nets_lock(&rtnl_nets);
ret = __rtnl_newlink(skb, nlh, ops, tgt_net, link_net, peer_net, tbs, data, extack);
rtnl_nets_unlock(&rtnl_nets);
@@ -7221,4 +7254,10 @@ void __init rtnetlink_init(void)
register_netdevice_notifier(&rtnetlink_dev_notifier);
rtnl_register_many(rtnetlink_rtnl_msg_handlers);
+
+#ifdef CONFIG_DEBUG_NET_SMALL_RTNL
+ rtnl_net_wq = create_workqueue("rtnl_net");
+ if (!rtnl_net_wq)
+ panic("Could not create rtnl_net workq");
+#endif
}
|
| 3/2 | 2026/07/03 00:26 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 14701 output: 22 thoughts: 957 Thoughts: **Analyzing the Networking Patch Series** Okay, so I've got this patch series applied, and my immediate goal is to figure out if it introduces any *functional* changes that need fuzzing. I'm seeing a lot of churn in core networking, so this looks promising for finding potential bugs. Looking at the diff, it's pretty clear this is a significant refactor, primarily around device unregistration and RTNL (Routing and Traffic Control Netlink) locking. It looks like they're introducing per-netns RTNL locking using `CONFIG_DEBUG_NET_SMALL_RTNL`, which is a pretty big deal, and the `netdevice` code itself is being heavily modified. This indicates the primary area of interest, the implications of this new locking scheme, and the way devices are unregistered. Specific drivers are also being modified to play nicely with these changes. I see `bareudp.c` now includes a mutex `lock`, and functions using that lock are `bareudp_find_dev`, `bareudp_configure`, `bareudp_dellink`, `bareudp_init_net`, `bareudp_exit_rtnl_net`, plus `__bareudp_dellink` is added. `ipvlan`, `ipvtap`, and `veth` all seem to have similar updates, with locking or new device unregistration mechanisms added to use the new locking scheme and `netdevice` infrastructure. Functionally, I'd say this is definitely a "yes". These are core networking components, and any change to locking or device unregistration can easily introduce subtle concurrency bugs. And yes, all these new features are reachable, since they're basic network devices and unregistration logic, so they're all pretty standard. Now, to make sure I'm focused, I need to identify the key configuration options. Based on the diff, `CONFIG_DEBUG_NET_SMALL_RTNL` seems to be the critical one, it's the lynchpin for the whole thing. Then, the individual driver configs will need to be enabled: `CONFIG_BAREUDP`, `CONFIG_IPVLAN`, `CONFIG_IPVTAP`, and `CONFIG_VETH`. I'll want to verify the existence of `CONFIG_DEBUG_NET_SMALL_RTNL` in the Kconfig files to confirm that, and find out what that option is designed for, if possible. I'll need to check the Kconfig for that one. So the plan: verify `CONFIG_DEBUG_NET_SMALL_RTNL`, identify kernel functions to focus fuzzing on, and the CONFIG options required to test this feature. |
| 4/2 | 2026/07/03 00:26 | tool | grepper |
0mArgs:{
"Expression": "DEBUG_NET_SMALL_RTNL"
}Results: {
"Output": "include/linux/netdevice.h=2149=struct net_device {\n--\ninclude/linux/netdevice.h-2245-\tstruct list_head\tunreg_list;\ninclude/linux/netdevice.h:2246:#ifdef CONFIG_DEBUG_NET_SMALL_RTNL\ninclude/linux/netdevice.h-2247-\tstruct list_head\tunreg_list_net;\n--\ninclude/linux/netdevice.h=3475=static inline void unregister_netdevice(struct net_device *dev)\n--\ninclude/linux/netdevice.h-3479-\ninclude/linux/netdevice.h:3480:#ifdef CONFIG_DEBUG_NET_SMALL_RTNL\ninclude/linux/netdevice.h-3481-void unregister_netdevice_queue_net(struct net *net, struct net_device *dev,\n--\ninclude/linux/rtnetlink.h=62=static inline bool lockdep_rtnl_is_held(void)\n--\ninclude/linux/rtnetlink.h-99-\ninclude/linux/rtnetlink.h:100:#ifdef CONFIG_DEBUG_NET_SMALL_RTNL\ninclude/linux/rtnetlink.h-101-void __rtnl_net_lock(struct net *net);\n--\ninclude/net/net_namespace.h=62=struct net {\n--\ninclude/net/net_namespace.h-196-#endif\ninclude/net/net_namespace.h:197:#ifdef CONFIG_DEBUG_NET_SMALL_RTNL\ninclude/net/net_namespace.h-198-\t/* Move to a better place when the config guard is removed. */\n--\nnet/Kconfig.debug=21=config DEBUG_NET\n--\nnet/Kconfig.debug-27-\nnet/Kconfig.debug:28:config DEBUG_NET_SMALL_RTNL\nnet/Kconfig.debug-29-\tbool \"Add extra per-netns mutex inside RTNL\"\n--\nnet/core/dev.c=12030=struct net_device *alloc_netdev_mqs(int sizeof_priv, const char *name,\n--\nnet/core/dev.c-12094-\tINIT_LIST_HEAD(\u0026dev-\u003eunreg_list);\nnet/core/dev.c:12095:#ifdef CONFIG_DEBUG_NET_SMALL_RTNL\nnet/core/dev.c-12096-\tINIT_LIST_HEAD(\u0026dev-\u003eunreg_list_net);\n--\nnet/core/dev.c=12311=void unregister_netdevice_queue(struct net_device *dev, struct list_head *head)\n--\nnet/core/dev.c-12314-\nnet/core/dev.c:12315:#ifdef CONFIG_DEBUG_NET_SMALL_RTNL\nnet/core/dev.c-12316-\tDEBUG_NET_WARN_ON_ONCE(!list_empty(\u0026dev-\u003eunreg_list_net));\n--\nnet/core/dev.c=12376=void unregister_netdevice_many_notify(struct list_head *head,\n--\nnet/core/dev.c-12494-\tlist_for_each_entry(dev, head, unreg_list) {\nnet/core/dev.c:12495:#ifdef CONFIG_DEBUG_NET_SMALL_RTNL\nnet/core/dev.c-12496-\t\tstruct net *net = dev_net(dev);\n--\nnet/core/dev.c=12525=EXPORT_SYMBOL(unregister_netdevice_many);\nnet/core/dev.c-12526-\nnet/core/dev.c:12527:#ifdef CONFIG_DEBUG_NET_SMALL_RTNL\nnet/core/dev.c-12528-void unregister_netdevice_queue_net(struct net *net, struct net_device *dev,\n--\nnet/core/dev.c=12636=int __dev_change_net_namespace(struct net_device *dev, struct net *net,\n--\nnet/core/dev.c-12772-\nnet/core/dev.c:12773:#ifdef CONFIG_DEBUG_NET_SMALL_RTNL\nnet/core/dev.c-12774-\tunregister_netdevice_move_net(net_old, net, dev);\n--\nnet/core/net_namespace.c=403=static __net_init int preinit_net(struct net *net, struct user_namespace *user_ns)\n--\nnet/core/net_namespace.c-422-\nnet/core/net_namespace.c:423:#ifdef CONFIG_DEBUG_NET_SMALL_RTNL\nnet/core/net_namespace.c-424-\tmutex_init(\u0026net-\u003ertnl_mutex);\n--\nnet/core/rtnetlink.c=184=EXPORT_SYMBOL(lockdep_rtnl_is_held);\n--\nnet/core/rtnetlink.c-186-\nnet/core/rtnetlink.c:187:#ifdef CONFIG_DEBUG_NET_SMALL_RTNL\nnet/core/rtnetlink.c-188-void __rtnl_net_lock(struct net *net)\n--\nnet/core/rtnetlink.c=7249=void __init rtnetlink_init(void)\n--\nnet/core/rtnetlink.c-7257-\nnet/core/rtnetlink.c:7258:#ifdef CONFIG_DEBUG_NET_SMALL_RTNL\nnet/core/rtnetlink.c-7259-\trtnl_net_wq = create_workqueue(\"rtnl_net\");\n"
} |
| 5/2 | 2026/07/03 00:26 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 17652 output: 18 thoughts: 464 Thoughts: **Analyzing Netdevice Unregistration and Potential Impact of DEBUG_NET_SMALL_RTNL** Alright, let's break this down. First, `DEBUG_NET_SMALL_RTNL` is a configuration option we're looking at. The patch touches several areas: `bareudp`, `ipvlan`, `ipvtap`, `veth`, and core netdevice unregistration. From the initial glance, it *seems* functional, but we need to verify the details. Now, let's pinpoint the key symbols involved. We need to dissect the interaction between these symbols and `DEBUG_NET_SMALL_RTNL`, particularly in relation to the unregistration process. I've compiled a list: * `unregister_netdevice_queue_net` * `unregister_netdevice_many_net` * `unregister_netdevice_queue_many_net` * `rtnl_net_work_func` * `bareudp_dellink` * `ipvlan_link_delete` * `ipvtap_dellink` * `veth_dellink` * `default_device_exit_batch` I'm now checking the visibility of certain functions. We already know that `__bareudp_dellink` is static, which limits its scope, so that's something to remember. `__ipvlan_link_delete` is exported, and that is important too. `__ipvtap_dellink` and `veth_dellink` are static. `unregister_netdevice_queue_net` is exported, while `unregister_netdevice_many_net` and `unregister_netdevice_queue_many_net` are global. So, to summarize, the essential symbols to focus on are these: * `unregister_netdevice_queue_net` * `unregister_netdevice_many_net` * `unregister_netdevice_queue_many_net` * `rtnl_net_work_func` * `bareudp_dellink` * `ipvlan_link_delete` * `ipvtap_dellink` * `veth_dellink` * `default_device_exit_batch` We also need to keep track of the relevant configurations: * `DEBUG_NET_SMALL_RTNL` * `BAREUDP` * `IPVLAN` * `IPVTAP` * `VETH` Now, the next step is to correlate these configurations with the symbols. I need to dive in and confirm how these are interconnected and how the option affects these functions. Let's see how these configurations influence and gate the behavior of the key symbols. |
| 6/2 | 2026/07/03 00:27 | tool | grepper |
0mArgs:{
"Expression": "config BAREUDP"
}Results: {
"Output": "drivers/net/Kconfig=245=config GENEVE\n--\ndrivers/net/Kconfig-259-\ndrivers/net/Kconfig:260:config BAREUDP\ndrivers/net/Kconfig-261-\ttristate \"Bare UDP Encapsulation\"\n"
} |
| 7/2 | 2026/07/03 00:27 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 18819 output: 18 thoughts: 15 Thoughts: Okay, here's my interpretation of those thoughts, crafted with the understanding that the user is an expert. **Verifying Network Virtualization Technologies** Alright, time to get my hands dirty. Let's delve into some core network virtualization components. I need to refresh and confirm my understanding, and the best way to do that is to go through IPVLAN, IPVTAP, and VETH. There are so many subtle differences between them that a quick review is always good. I should focus on the exact behavior and use-cases for each. How they handle MAC addresses, how they relate to the underlying physical interface, and the implications for routing and bridging are all top of mind. Time to run some tests, maybe spin up a few containers to simulate traffic and probe at a granular level. We'll see how these elements interact. It's time to confirm my mental model and ensure my knowledge is up-to-date. |
| 8/2 | 2026/07/03 00:27 | tool | grepper |
0mArgs:{
"Expression": "config IPVLAN"
}Results: {
"Output": "drivers/net/Kconfig=179=config MACVTAP\n--\ndrivers/net/Kconfig-192-\ndrivers/net/Kconfig:193:config IPVLAN_L3S\ndrivers/net/Kconfig-194-\tdepends on NETFILTER\n--\ndrivers/net/Kconfig-198-\ndrivers/net/Kconfig:199:config IPVLAN\ndrivers/net/Kconfig-200-\ttristate \"IP-VLAN support\"\n"
} |
| 9/2 | 2026/07/03 00:27 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 19178 output: 18 thoughts: 0 |
| 10/2 | 2026/07/03 00:27 | tool | grepper |
0mArgs:{
"Expression": "config IPVTAP"
}Results: {
"Output": "drivers/net/Kconfig=199=config IPVLAN\n--\ndrivers/net/Kconfig-215-\ndrivers/net/Kconfig:216:config IPVTAP\ndrivers/net/Kconfig-217-\ttristate \"IP-VLAN based tap driver\"\n"
} |
| 11/2 | 2026/07/03 00:27 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 19279 output: 17 thoughts: 0 |
| 12/2 | 2026/07/03 00:27 | tool | grepper |
0mArgs:{
"Expression": "config VETH"
}Results: {
"Output": "drivers/net/Kconfig=421=config TUN_VNET_CROSS_LE\n--\ndrivers/net/Kconfig-434-\ndrivers/net/Kconfig:435:config VETH\ndrivers/net/Kconfig-436-\ttristate \"Virtual ethernet pair device\"\n"
} |
| 13/2 | 2026/07/03 00:27 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 19380 output: 20 thoughts: 255 Thoughts: **Analyzing Network Device Unregistration Paths** Okay, so I'm looking at these configurations: `DEBUG_NET_SMALL_RTNL`, `BAREUDP`, `IPVLAN`, `IPVTAP`, and `VETH`. My goal is to understand how network devices are unregistered in various scenarios. My mind keeps jumping to these key symbols: `unregister_netdevice_queue_net`, `unregister_netdevice_many_net`, `unregister_netdevice_queue_many_net`, `rtnl_net_work_func`, along with the device-specific unlinking functions: `bareudp_dellink`, `ipvlan_link_delete`, `ipvtap_dellink`, `veth_dellink`, and `default_device_exit_batch`. I need to figure out where each of them comes into play. Wait a second, wasn't `ipvlan_link_delete` marked as static at some point? Ah yes, now `__ipvlan_link_delete` is exported, so I think I need to account for both here. Should I just include both or rely on the exported version? Ah, it makes more sense to include both, to be thorough. Now, let's include `__ipvlan_link_delete` and `ipvlan_link_delete`. Actually, wait, `ipvlan_link_delete` is indeed static within `ipvlan_main.c`. Okay, let's take a look at the implementation of `ipvlan_link_delete` in `ipvlan_main.c` to be sure. |
| 14/2 | 2026/07/03 00:27 | tool | grepper |
0mArgs:{
"Expression": "ipvlan_link_delete"
}Results: {
"Output": "drivers/net/ipvlan/ipvlan.h=171=int ipvlan_link_new(struct net_device *dev, struct rtnl_newlink_params *params,\ndrivers/net/ipvlan/ipvlan.h-172-\t\t struct netlink_ext_ack *extack);\ndrivers/net/ipvlan/ipvlan.h:173:void __ipvlan_link_delete(struct net *net, struct net_device *dev,\ndrivers/net/ipvlan/ipvlan.h-174-\t\t\t struct list_head *head);\n--\ndrivers/net/ipvlan/ipvlan_main.c=707=EXPORT_SYMBOL_GPL(ipvlan_link_new);\ndrivers/net/ipvlan/ipvlan_main.c-708-\ndrivers/net/ipvlan/ipvlan_main.c:709:void __ipvlan_link_delete(struct net *net, struct net_device *dev,\ndrivers/net/ipvlan/ipvlan_main.c-710-\t\t\t struct list_head *head)\n--\ndrivers/net/ipvlan/ipvlan_main.c-727-}\ndrivers/net/ipvlan/ipvlan_main.c:728:EXPORT_SYMBOL(__ipvlan_link_delete);\ndrivers/net/ipvlan/ipvlan_main.c-729-\ndrivers/net/ipvlan/ipvlan_main.c:730:static void ipvlan_link_delete(struct net_device *dev, struct list_head *head)\ndrivers/net/ipvlan/ipvlan_main.c-731-{\n--\ndrivers/net/ipvlan/ipvlan_main.c-735-\tif (!ipvlan-\u003edying)\ndrivers/net/ipvlan/ipvlan_main.c:736:\t\t__ipvlan_link_delete(dev_net(dev), dev, head);\ndrivers/net/ipvlan/ipvlan_main.c-737-\tmutex_unlock(\u0026ipvlan-\u003eport-\u003epnodes_lock);\n--\ndrivers/net/ipvlan/ipvlan_main.c=767=static struct rtnl_link_ops ipvlan_link_ops = {\n--\ndrivers/net/ipvlan/ipvlan_main.c-772-\t.newlink\t= ipvlan_link_new,\ndrivers/net/ipvlan/ipvlan_main.c:773:\t.dellink\t= ipvlan_link_delete,\ndrivers/net/ipvlan/ipvlan_main.c-774-\t.get_link_net = ipvlan_get_link_net,\n--\ndrivers/net/ipvlan/ipvlan_main.c=789=static int ipvlan_device_event(struct notifier_block *unused,\n--\ndrivers/net/ipvlan/ipvlan_main.c-845-#endif\ndrivers/net/ipvlan/ipvlan_main.c:846:\t\t\t\t__ipvlan_link_delete(net, ipvlan-\u003edev, \u0026lst_kill);\ndrivers/net/ipvlan/ipvlan_main.c-847-\t\t}\n--\ndrivers/net/ipvlan/ipvtap.c=112=static void __ipvtap_dellink(struct net *net, struct net_device *dev,\n--\ndrivers/net/ipvlan/ipvtap.c-118-\ttap_del_queues(\u0026vlantap-\u003etap);\ndrivers/net/ipvlan/ipvtap.c:119:\t__ipvlan_link_delete(net, dev, head);\ndrivers/net/ipvlan/ipvtap.c-120-}\n"
} |
| 15/2 | 2026/07/03 00:27 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 20881 output: 295 thoughts: 547 Thoughts: **Fuzzing Recommendation: WorthFuzzing=true** Okay, let's break this down. We're looking at a patch that fundamentally alters how network devices are unregistered, particularly across network namespaces. The inclusion of `unregister_netdevice_queue_net`, `unregister_netdevice_many_net`, `unregister_netdevice_queue_many_net`, `rtnl_net_work_func`, `bareudp_dellink`, `__bareudp_dellink`, `ipvlan_link_delete`, `__ipvlan_link_delete`, `ipvtap_dellink`, `__ipvtap_dellink`, `veth_dellink`, and `default_device_exit_batch` suggests a substantial shift in device management. Both `__ipvlan_link_delete` and `ipvlan_link_delete` are solid candidates for attention. The configurations involved (`DEBUG_NET_SMALL_RTNL`, `BAREUDP`, `IPVLAN`, `IPVTAP`, `VETH`) confirm that this touches several virtual network drivers directly, with the changes spread across `bareudp.c`, `ipvlan/ipvlan.h`, `ipvlan/ipvlan_main.c`, `ipvlan/ipvtap.c`, `veth.c`, `netdevice.h`, `rtnetlink.h`, `net_namespace.h`, `dev.c`, `net_namespace.c`, and `rtnetlink.c`. Good, all the relevant files seem to be accounted for given the configurations. The "WorthFuzzing" criteria is our guide here. This patch definitely modifies core kernel logic and drivers. The changes introduce new per-netns RTNL locking using `DEBUG_NET_SMALL_RTNL` and updates to how various virtual network drivers handle unregistration, synchronization, and lifecycle management. My experience screams potential race conditions and use-after-free bugs. The integration of RTNL locking into existing unregistration processes, especially across namespaces, is ripe for subtle errors. Therefore, fuzzing is essential. It's a high-value target for identifying these complex concurrency issues before they cause problems. |
| 16/2 | 2026/07/03 00:27 | tool | set-results |
0mArgs:{
"EnableConfigs": [
"DEBUG_NET_SMALL_RTNL",
"BAREUDP",
"IPVLAN",
"IPVTAP",
"VETH"
],
"FocusSymbols": [
"unregister_netdevice_queue_net",
"unregister_netdevice_many_net",
"unregister_netdevice_queue_many_net",
"rtnl_net_work_func",
"bareudp_dellink",
"__bareudp_dellink",
"ipvlan_link_delete",
"__ipvlan_link_delete",
"ipvtap_dellink",
"__ipvtap_dellink",
"veth_dellink",
"default_device_exit_batch"
],
"Reasoning": "The patch introduces a new mechanism for unregistering network devices across network namespaces, adding per-netns RTNL locking support (CONFIG_DEBUG_NET_SMALL_RTNL). It modifies core netdevice unregistration functions and updates several virtual network drivers (bareudp, ipvlan, ipvtap, veth) to use the new locking and unregistration APIs. This involves complex synchronization and lifecycle management of network devices, which is highly prone to race conditions and use-after-free bugs. Fuzzing is essential to ensure the correctness of these changes.",
"WorthFuzzing": true
}Results: {
"EnableConfigs": [
"DEBUG_NET_SMALL_RTNL",
"BAREUDP",
"IPVLAN",
"IPVTAP",
"VETH"
],
"FocusSymbols": [
"unregister_netdevice_queue_net",
"unregister_netdevice_many_net",
"unregister_netdevice_queue_many_net",
"rtnl_net_work_func",
"bareudp_dellink",
"__bareudp_dellink",
"ipvlan_link_delete",
"__ipvlan_link_delete",
"ipvtap_dellink",
"__ipvtap_dellink",
"veth_dellink",
"default_device_exit_batch"
],
"Reasoning": "The patch introduces a new mechanism for unregistering network devices across network namespaces, adding per-netns RTNL locking support (CONFIG_DEBUG_NET_SMALL_RTNL). It modifies core netdevice unregistration functions and updates several virtual network drivers (bareudp, ipvlan, ipvtap, veth) to use the new locking and unregistration APIs. This involves complex synchronization and lifecycle management of network devices, which is highly prone to race conditions and use-after-free bugs. Fuzzing is essential to ensure the correctness of these changes.",
"WorthFuzzing": true
} |
| Total Calls | Total Tokens | Avg Tokens | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|
| Total Calls | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|