| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/06/24 18:45 | flow | patch-triage |
0mArgs:null Results: {
"EnableConfigs": [
"VLAN_8021Q",
"BONDING"
],
"FocusSymbols": [
"netdev_work_sched",
"netdev_work_cancel",
"netdev_work_proc",
"netdev_work_run",
"vlan_dev_work",
"vlan_device_event"
],
"Reasoning": "This patch introduces a new asynchronous work mechanism (`netdev_work`) for network devices and modifies the VLAN driver to use it for state propagation (MTU, features, link state) to avoid deadlocks during nested notifications. This is a significant functional change to core networking and VLAN logic, reachable from userspace by modifying device states (e.g., bringing devices up/down, changing MTU). It should be fuzzed to ensure the new asynchronous mechanism doesn't introduce race conditions, use-after-free, or other concurrency bugs.",
"WorthFuzzing": true
} |
| 1/1 | 2026/06/24 18:45 | action | read-patch-diff |
0mArgs:null Results: {
"PatchDiff": "commit 37e08e8a12e3b5571fb77e3e99d0cf06174a9c1e\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Wed Jun 24 18:45:17 2026 +0000\n\n syz-cluster: applied patch under review\n\ndiff --git a/Documentation/networking/netdevices.rst b/Documentation/networking/netdevices.rst\nindex fde601acd1d25a..d2a238f8cc8b91 100644\n--- a/Documentation/networking/netdevices.rst\n+++ b/Documentation/networking/netdevices.rst\n@@ -433,6 +433,8 @@ exceptions) notifiers run under the instance lock. Please extend this\n documentation whenever you make explicit assumption about lock being held\n from a notifier.\n \n+Drivers **must not** generate nested notifications of the ops-locked types.\n+\n NETDEV_INTERNAL symbol namespace\n ================================\n \ndiff --git a/include/linux/netdevice.h b/include/linux/netdevice.h\nindex b67a12541eac28..9981d637f8b546 100644\n--- a/include/linux/netdevice.h\n+++ b/include/linux/netdevice.h\n@@ -1131,6 +1131,9 @@ struct netdev_net_notifier {\n *\tnetdev_hw_addr_list_for_each(ha, uc). Return 0 on success or a\n *\tnegative errno to request a retry via the core backoff.\n *\n+ * void (*ndo_work)(struct net_device *dev, unsigned long events);\n+ *\tRun deferred work scheduled with netdev_work_sched(@events).\n+ *\n * int (*ndo_set_mac_address)(struct net_device *dev, void *addr);\n *\tThis function is called when the Media Access Control address\n *\tneeds to be changed. If this interface is not defined, the\n@@ -1460,6 +1463,8 @@ struct net_device_ops {\n \t\t\t\t\tstruct net_device *dev,\n \t\t\t\t\tstruct netdev_hw_addr_list *uc,\n \t\t\t\t\tstruct netdev_hw_addr_list *mc);\n+\tvoid\t\t\t(*ndo_work)(struct net_device *dev,\n+\t\t\t\t\t unsigned long events);\n \tint\t\t\t(*ndo_set_mac_address)(struct net_device *dev,\n \t\t\t\t\t\t void *addr);\n \tint\t\t\t(*ndo_validate_addr)(struct net_device *dev);\n@@ -1930,8 +1935,11 @@ enum netdev_reg_state {\n *\t\t\t\thas been enabled due to the need to listen to\n *\t\t\t\tadditional unicast addresses in a device that\n *\t\t\t\tdoes not implement ndo_set_rx_mode()\n- *\t@rx_mode_node:\t\tList entry for rx_mode work processing\n- *\t@rx_mode_tracker:\tRefcount tracker for rx_mode work\n+ *\t@work_node:\t\tList entry for async netdev_work processing\n+ *\t@work_tracker:\t\tRefcount tracker for async netdev_work\n+ *\t@work_pending:\t\tDriver-defined pending netdev_work, passed to\n+ *\t\t\t\tndo_work() (see netdev_work_sched())\n+ *\t@work_core_pending:\tCore-defined pending netdev_work (NETDEV_WORK_*)\n *\t@rx_mode_addr_cache:\tRecycled snapshot entries for rx_mode work\n *\t@rx_mode_retry_timer:\tTimer that re-queues rx_mode work after failure\n *\t@rx_mode_retry_count:\tNumber of consecutive retries already scheduled\n@@ -2326,8 +2334,10 @@ struct net_device {\n \tunsigned int\t\tpromiscuity;\n \tunsigned int\t\tallmulti;\n \tbool\t\t\tuc_promisc;\n-\tstruct list_head\trx_mode_node;\n-\tnetdevice_tracker\trx_mode_tracker;\n+\tstruct list_head\twork_node;\n+\tnetdevice_tracker\twork_tracker;\n+\tunsigned long\t\twork_pending;\n+\tunsigned long\t\twork_core_pending;\n \tstruct netdev_hw_addr_list\trx_mode_addr_cache;\n \tstruct timer_list\trx_mode_retry_timer;\n \tunsigned int\t\trx_mode_retry_count;\n@@ -5176,6 +5186,9 @@ void dev_fetch_sw_netstats(struct rtnl_link_stats64 *s,\n \t\t\t const struct pcpu_sw_netstats __percpu *netstats);\n void dev_get_tstats64(struct net_device *dev, struct rtnl_link_stats64 *s);\n \n+void netdev_work_sched(struct net_device *dev, unsigned long events);\n+unsigned long netdev_work_cancel(struct net_device *dev, unsigned long mask);\n+\n enum {\n \tNESTED_SYNC_IMM_BIT,\n \tNESTED_SYNC_TODO_BIT,\ndiff --git a/net/8021q/vlan.c b/net/8021q/vlan.c\nindex 2b74ed56eb166d..2d2efb877975d5 100644\n--- a/net/8021q/vlan.c\n+++ b/net/8021q/vlan.c\n@@ -77,9 +77,9 @@ static int vlan_group_prealloc_vid(struct vlan_group *vg,\n \treturn 0;\n }\n \n-static void vlan_stacked_transfer_operstate(const struct net_device *rootdev,\n-\t\t\t\t\t struct net_device *dev,\n-\t\t\t\t\t struct vlan_dev_priv *vlan)\n+void vlan_stacked_transfer_operstate(const struct net_device *rootdev,\n+\t\t\t\t struct net_device *dev,\n+\t\t\t\t struct vlan_dev_priv *vlan)\n {\n \tif (!(vlan-\u003eflags \u0026 VLAN_FLAG_BRIDGE_BINDING))\n \t\tnetif_stacked_transfer_operstate(rootdev, dev);\n@@ -316,29 +316,6 @@ static void vlan_sync_address(struct net_device *dev,\n \tether_addr_copy(vlan-\u003ereal_dev_addr, dev-\u003edev_addr);\n }\n \n-static void vlan_transfer_features(struct net_device *dev,\n-\t\t\t\t struct net_device *vlandev)\n-{\n-\tstruct vlan_dev_priv *vlan = vlan_dev_priv(vlandev);\n-\n-\tnetif_inherit_tso_max(vlandev, dev);\n-\n-\tif (vlan_hw_offload_capable(dev-\u003efeatures, vlan-\u003evlan_proto))\n-\t\tvlandev-\u003ehard_header_len = dev-\u003ehard_header_len;\n-\telse\n-\t\tvlandev-\u003ehard_header_len = dev-\u003ehard_header_len + VLAN_HLEN;\n-\n-#if IS_ENABLED(CONFIG_FCOE)\n-\tvlandev-\u003efcoe_ddp_xid = dev-\u003efcoe_ddp_xid;\n-#endif\n-\n-\tvlandev-\u003epriv_flags \u0026= ~IFF_XMIT_DST_RELEASE;\n-\tvlandev-\u003epriv_flags |= (vlan-\u003ereal_dev-\u003epriv_flags \u0026 IFF_XMIT_DST_RELEASE);\n-\tvlandev-\u003ehw_enc_features = vlan_tnl_features(vlan-\u003ereal_dev);\n-\n-\tnetdev_update_features(vlandev);\n-}\n-\n static int __vlan_device_event(struct net_device *dev, unsigned long event)\n {\n \tint err = 0;\n@@ -391,13 +368,11 @@ static void vlan_vid0_del(struct net_device *dev)\n static int vlan_device_event(struct notifier_block *unused, unsigned long event,\n \t\t\t void *ptr)\n {\n-\tstruct netlink_ext_ack *extack = netdev_notifier_info_to_extack(ptr);\n \tstruct net_device *dev = netdev_notifier_info_to_dev(ptr);\n \tstruct vlan_group *grp;\n \tstruct vlan_info *vlan_info;\n \tint i, flgs;\n \tstruct net_device *vlandev;\n-\tstruct vlan_dev_priv *vlan;\n \tbool last = false;\n \tLIST_HEAD(list);\n \tint err;\n@@ -447,54 +422,19 @@ static int vlan_device_event(struct notifier_block *unused, unsigned long event,\n \t\t\tif (vlandev-\u003emtu \u003c= dev-\u003emtu)\n \t\t\t\tcontinue;\n \n-\t\t\tdev_set_mtu(vlandev, dev-\u003emtu);\n+\t\t\tnetdev_work_sched(vlandev, VLAN_WORK_MTU);\n \t\t}\n \t\tbreak;\n \n \tcase NETDEV_FEAT_CHANGE:\n-\t\t/* Propagate device features to underlying device */\n \t\tvlan_group_for_each_dev(grp, i, vlandev)\n-\t\t\tvlan_transfer_features(dev, vlandev);\n+\t\t\tnetdev_work_sched(vlandev, VLAN_WORK_FEATURES);\n \t\tbreak;\n \n-\tcase NETDEV_DOWN: {\n-\t\tstruct net_device *tmp;\n-\t\tLIST_HEAD(close_list);\n-\n-\t\t/* Put all VLANs for this dev in the down state too. */\n-\t\tvlan_group_for_each_dev(grp, i, vlandev) {\n-\t\t\tflgs = vlandev-\u003eflags;\n-\t\t\tif (!(flgs \u0026 IFF_UP))\n-\t\t\t\tcontinue;\n-\n-\t\t\tvlan = vlan_dev_priv(vlandev);\n-\t\t\tif (!(vlan-\u003eflags \u0026 VLAN_FLAG_LOOSE_BINDING))\n-\t\t\t\tlist_add(\u0026vlandev-\u003eclose_list, \u0026close_list);\n-\t\t}\n-\n-\t\tnetif_close_many(\u0026close_list, false);\n-\n-\t\tlist_for_each_entry_safe(vlandev, tmp, \u0026close_list, close_list) {\n-\t\t\tvlan_stacked_transfer_operstate(dev, vlandev,\n-\t\t\t\t\t\t\tvlan_dev_priv(vlandev));\n-\t\t\tlist_del_init(\u0026vlandev-\u003eclose_list);\n-\t\t}\n-\t\tlist_del(\u0026close_list);\n-\t\tbreak;\n-\t}\n+\tcase NETDEV_DOWN:\n \tcase NETDEV_UP:\n-\t\t/* Put all VLANs for this dev in the up state too. */\n-\t\tvlan_group_for_each_dev(grp, i, vlandev) {\n-\t\t\tflgs = netif_get_flags(vlandev);\n-\t\t\tif (flgs \u0026 IFF_UP)\n-\t\t\t\tcontinue;\n-\n-\t\t\tvlan = vlan_dev_priv(vlandev);\n-\t\t\tif (!(vlan-\u003eflags \u0026 VLAN_FLAG_LOOSE_BINDING))\n-\t\t\t\tdev_change_flags(vlandev, flgs | IFF_UP,\n-\t\t\t\t\t\t extack);\n-\t\t\tvlan_stacked_transfer_operstate(dev, vlandev, vlan);\n-\t\t}\n+\t\tvlan_group_for_each_dev(grp, i, vlandev)\n+\t\t\tnetdev_work_sched(vlandev, VLAN_WORK_LINK_STATE);\n \t\tbreak;\n \n \tcase NETDEV_UNREGISTER:\ndiff --git a/net/8021q/vlan.h b/net/8021q/vlan.h\nindex c7ffe591d59366..c41caaf94095f3 100644\n--- a/net/8021q/vlan.h\n+++ b/net/8021q/vlan.h\n@@ -125,6 +125,17 @@ static inline netdev_features_t vlan_tnl_features(struct net_device *real_dev)\n int vlan_filter_push_vids(struct vlan_info *vlan_info, __be16 proto);\n void vlan_filter_drop_vids(struct vlan_info *vlan_info, __be16 proto);\n \n+/* netdev_work events propagated from the real device, see vlan_dev_work(). */\n+enum {\n+\tVLAN_WORK_LINK_STATE\t= BIT(0), /* sync up/down with real_dev */\n+\tVLAN_WORK_MTU\t\t= BIT(1), /* clamp mtu to real_dev's */\n+\tVLAN_WORK_FEATURES\t= BIT(2), /* re-inherit real_dev features */\n+};\n+\n+void vlan_stacked_transfer_operstate(const struct net_device *rootdev,\n+\t\t\t\t struct net_device *dev,\n+\t\t\t\t struct vlan_dev_priv *vlan);\n+\n /* found in vlan_dev.c */\n void vlan_dev_set_ingress_priority(const struct net_device *dev,\n \t\t\t\t u32 skb_prio, u16 vlan_prio);\ndiff --git a/net/8021q/vlan_dev.c b/net/8021q/vlan_dev.c\nindex 7aa3af8b10ead0..ec2569b3f8dac6 100644\n--- a/net/8021q/vlan_dev.c\n+++ b/net/8021q/vlan_dev.c\n@@ -270,6 +270,9 @@ static int vlan_dev_open(struct net_device *dev)\n \t !(vlan-\u003eflags \u0026 VLAN_FLAG_LOOSE_BINDING))\n \t\treturn -ENETDOWN;\n \n+\t/* The explicit open supersedes any deferred link-state sync */\n+\tnetdev_work_cancel(dev, VLAN_WORK_LINK_STATE);\n+\n \tif (!ether_addr_equal(dev-\u003edev_addr, real_dev-\u003edev_addr) \u0026\u0026\n \t !vlan_dev_inherit_address(dev, real_dev)) {\n \t\terr = dev_uc_add(real_dev, dev-\u003edev_addr);\n@@ -300,6 +303,9 @@ static int vlan_dev_stop(struct net_device *dev)\n \tstruct vlan_dev_priv *vlan = vlan_dev_priv(dev);\n \tstruct net_device *real_dev = vlan-\u003ereal_dev;\n \n+\t/* The explicit close supersedes any deferred link-state sync */\n+\tnetdev_work_cancel(dev, VLAN_WORK_LINK_STATE);\n+\n \tdev_mc_unsync(real_dev, dev);\n \tdev_uc_unsync(real_dev, dev);\n \n@@ -1016,6 +1022,59 @@ static const struct ethtool_ops vlan_ethtool_ops = {\n \t.get_ts_info\t\t= vlan_ethtool_get_ts_info,\n };\n \n+static void vlan_transfer_features(struct net_device *dev,\n+\t\t\t\t struct net_device *vlandev)\n+{\n+\tstruct vlan_dev_priv *vlan = vlan_dev_priv(vlandev);\n+\n+\tnetif_inherit_tso_max(vlandev, dev);\n+\n+\tif (vlan_hw_offload_capable(dev-\u003efeatures, vlan-\u003evlan_proto))\n+\t\tvlandev-\u003ehard_header_len = dev-\u003ehard_header_len;\n+\telse\n+\t\tvlandev-\u003ehard_header_len = dev-\u003ehard_header_len + VLAN_HLEN;\n+\n+#if IS_ENABLED(CONFIG_FCOE)\n+\tvlandev-\u003efcoe_ddp_xid = dev-\u003efcoe_ddp_xid;\n+#endif\n+\n+\tvlandev-\u003epriv_flags \u0026= ~IFF_XMIT_DST_RELEASE;\n+\tvlandev-\u003epriv_flags |= (vlan-\u003ereal_dev-\u003epriv_flags \u0026 IFF_XMIT_DST_RELEASE);\n+\tvlandev-\u003ehw_enc_features = vlan_tnl_features(vlan-\u003ereal_dev);\n+\n+\tnetdev_update_features(vlandev);\n+}\n+\n+static void vlan_dev_work(struct net_device *vlandev, unsigned long events)\n+{\n+\tstruct vlan_dev_priv *vlan = vlan_dev_priv(vlandev);\n+\tstruct net_device *real_dev = vlan-\u003ereal_dev;\n+\tbool loose = vlan-\u003eflags \u0026 VLAN_FLAG_LOOSE_BINDING;\n+\tunsigned int flgs;\n+\n+\tif (events \u0026 VLAN_WORK_LINK_STATE) {\n+\t\tflgs = netif_get_flags(vlandev);\n+\t\tif (real_dev-\u003eflags \u0026 IFF_UP) {\n+\t\t\tif (!(flgs \u0026 IFF_UP)) {\n+\t\t\t\tif (!loose)\n+\t\t\t\t\tnetif_change_flags(vlandev,\n+\t\t\t\t\t\t\t flgs | IFF_UP, NULL);\n+\t\t\t\tvlan_stacked_transfer_operstate(real_dev,\n+\t\t\t\t\t\t\t\tvlandev, vlan);\n+\t\t\t}\n+\t\t} else if ((flgs \u0026 IFF_UP) \u0026\u0026 !loose) {\n+\t\t\tnetif_change_flags(vlandev, flgs \u0026 ~IFF_UP, NULL);\n+\t\t\tvlan_stacked_transfer_operstate(real_dev, vlandev, vlan);\n+\t\t}\n+\t}\n+\n+\tif ((events \u0026 VLAN_WORK_MTU) \u0026\u0026 vlandev-\u003emtu \u003e real_dev-\u003emtu)\n+\t\tnetif_set_mtu(vlandev, real_dev-\u003emtu);\n+\n+\tif (events \u0026 VLAN_WORK_FEATURES)\n+\t\tvlan_transfer_features(real_dev, vlandev);\n+}\n+\n static const struct net_device_ops vlan_netdev_ops = {\n \t.ndo_change_mtu\t\t= vlan_dev_change_mtu,\n \t.ndo_init\t\t= vlan_dev_init,\n@@ -1027,6 +1086,7 @@ static const struct net_device_ops vlan_netdev_ops = {\n \t.ndo_set_mac_address\t= vlan_dev_set_mac_address,\n \t.ndo_set_rx_mode\t= vlan_dev_set_rx_mode,\n \t.ndo_change_rx_flags\t= vlan_dev_change_rx_flags,\n+\t.ndo_work\t\t= vlan_dev_work,\n \t.ndo_eth_ioctl\t\t= vlan_dev_ioctl,\n \t.ndo_neigh_setup\t= vlan_dev_neigh_setup,\n \t.ndo_get_stats64\t= vlan_dev_get_stats64,\ndiff --git a/net/core/Makefile b/net/core/Makefile\nindex dc17c5a61e9a4a..b3fdcb4e355fa6 100644\n--- a/net/core/Makefile\n+++ b/net/core/Makefile\n@@ -13,7 +13,7 @@ obj-y\t\t += dev.o dev_api.o dev_addr_lists.o dst.o netevent.o \\\n \t\t\tneighbour.o rtnetlink.o utils.o link_watch.o filter.o \\\n \t\t\tsock_diag.o dev_ioctl.o tso.o sock_reuseport.o \\\n \t\t\tfib_notifier.o xdp.o flow_offload.o gro.o \\\n-\t\t\tnetdev-genl.o netdev-genl-gen.o gso.o\n+\t\t\tnetdev-genl.o netdev-genl-gen.o netdev_work.o gso.o\n \n obj-$(CONFIG_NETDEV_ADDR_LIST_TEST) += dev_addr_lists_test.o\n \ndiff --git a/net/core/dev.c b/net/core/dev.c\nindex 5c01dfaa6c4450..4b3d5cfdf6e00f 100644\n--- a/net/core/dev.c\n+++ b/net/core/dev.c\n@@ -9822,6 +9822,7 @@ int netif_change_flags(struct net_device *dev, unsigned int flags,\n \t__dev_notify_flags(dev, old_flags, changes, 0, NULL);\n \treturn ret;\n }\n+EXPORT_SYMBOL(netif_change_flags);\n \n int __netif_set_mtu(struct net_device *dev, int new_mtu)\n {\n@@ -12093,6 +12094,7 @@ struct net_device *alloc_netdev_mqs(int sizeof_priv, const char *name,\n \tINIT_LIST_HEAD(\u0026dev-\u003eptype_all);\n \tINIT_LIST_HEAD(\u0026dev-\u003eptype_specific);\n \tINIT_LIST_HEAD(\u0026dev-\u003enet_notifier_list);\n+\tINIT_LIST_HEAD(\u0026dev-\u003ework_node);\n #ifdef CONFIG_NET_SCHED\n \thash_init(dev-\u003eqdisc_hash);\n #endif\ndiff --git a/net/core/dev.h b/net/core/dev.h\nindex 4121c50e7c88be..5d0b0305d3baaf 100644\n--- a/net/core/dev.h\n+++ b/net/core/dev.h\n@@ -167,10 +167,19 @@ int dev_change_carrier(struct net_device *dev, bool new_carrier);\n void __dev_set_rx_mode(struct net_device *dev);\n int __dev_set_promiscuity(struct net_device *dev, int inc, bool notify);\n void netif_rx_mode_init(struct net_device *dev);\n-bool netif_rx_mode_clean(struct net_device *dev);\n+void netif_rx_mode_run(struct net_device *dev);\n void netif_rx_mode_sync(struct net_device *dev);\n void netif_rx_mode_cancel_retry(struct net_device *dev);\n \n+/* Events for the async netdev work, tracked in netdev-\u003ework_core_pending. */\n+enum netdev_work_core {\n+\tNETDEV_WORK_RX_MODE\t= BIT(0),\t/* run the rx_mode update */\n+};\n+\n+void __netdev_work_core_sched(struct net_device *dev, unsigned long event);\n+unsigned long\n+__netdev_work_core_cancel(struct net_device *dev, unsigned long mask);\n+\n void __dev_notify_flags(struct net_device *dev, unsigned int old_flags,\n \t\t\tunsigned int gchanges, u32 portid,\n \t\t\tconst struct nlmsghdr *nlh);\ndiff --git a/net/core/dev_addr_lists.c b/net/core/dev_addr_lists.c\nindex e17f64a65e17be..08528ca0a8b314 100644\n--- a/net/core/dev_addr_lists.c\n+++ b/net/core/dev_addr_lists.c\n@@ -12,17 +12,10 @@\n #include \u003clinux/export.h\u003e\n #include \u003clinux/list.h\u003e\n #include \u003clinux/spinlock.h\u003e\n-#include \u003clinux/workqueue.h\u003e\n #include \u003ckunit/visibility.h\u003e\n \n #include \"dev.h\"\n \n-static void netdev_rx_mode_work(struct work_struct *work);\n-\n-static LIST_HEAD(rx_mode_list);\n-static DEFINE_SPINLOCK(rx_mode_lock);\n-static DECLARE_WORK(rx_mode_work, netdev_rx_mode_work);\n-\n /*\n * General list handling functions\n */\n@@ -1281,7 +1274,7 @@ void netif_rx_mode_cancel_retry(struct net_device *dev)\n \tdev-\u003erx_mode_retry_count = 0;\n }\n \n-static void netif_rx_mode_run(struct net_device *dev)\n+void netif_rx_mode_run(struct net_device *dev)\n {\n \tstruct netdev_hw_addr_list uc_snap, mc_snap, uc_ref, mc_ref;\n \tconst struct net_device_ops *ops = dev-\u003enetdev_ops;\n@@ -1339,49 +1332,9 @@ static void netif_rx_mode_run(struct net_device *dev)\n \t}\n }\n \n-static void netdev_rx_mode_work(struct work_struct *work)\n-{\n-\tstruct net_device *dev;\n-\n-\trtnl_lock();\n-\n-\twhile (true) {\n-\t\tspin_lock_bh(\u0026rx_mode_lock);\n-\t\tif (list_empty(\u0026rx_mode_list)) {\n-\t\t\tspin_unlock_bh(\u0026rx_mode_lock);\n-\t\t\tbreak;\n-\t\t}\n-\t\tdev = list_first_entry(\u0026rx_mode_list, struct net_device,\n-\t\t\t\t rx_mode_node);\n-\t\tlist_del_init(\u0026dev-\u003erx_mode_node);\n-\t\t/* We must free netdev tracker under\n-\t\t * the spinlock protection.\n-\t\t */\n-\t\tnetdev_tracker_free(dev, \u0026dev-\u003erx_mode_tracker);\n-\t\tspin_unlock_bh(\u0026rx_mode_lock);\n-\n-\t\tnetdev_lock_ops(dev);\n-\t\tnetif_rx_mode_run(dev);\n-\t\tnetdev_unlock_ops(dev);\n-\t\t/* Use __dev_put() because netdev_tracker_free() was already\n-\t\t * called above. Must be after netdev_unlock_ops() to prevent\n-\t\t * netdev_run_todo() from freeing the device while still in use.\n-\t\t */\n-\t\t__dev_put(dev);\n-\t}\n-\n-\trtnl_unlock();\n-}\n-\n static void netif_rx_mode_queue(struct net_device *dev)\n {\n-\tspin_lock_bh(\u0026rx_mode_lock);\n-\tif (list_empty(\u0026dev-\u003erx_mode_node)) {\n-\t\tlist_add_tail(\u0026dev-\u003erx_mode_node, \u0026rx_mode_list);\n-\t\tnetdev_hold(dev, \u0026dev-\u003erx_mode_tracker, GFP_ATOMIC);\n-\t}\n-\tspin_unlock_bh(\u0026rx_mode_lock);\n-\tschedule_work(\u0026rx_mode_work);\n+\t__netdev_work_core_sched(dev, NETDEV_WORK_RX_MODE);\n }\n \n static void netif_rx_mode_retry(struct timer_list *t)\n@@ -1394,7 +1347,6 @@ static void netif_rx_mode_retry(struct timer_list *t)\n \n void netif_rx_mode_init(struct net_device *dev)\n {\n-\tINIT_LIST_HEAD(\u0026dev-\u003erx_mode_node);\n \t__hw_addr_init(\u0026dev-\u003erx_mode_addr_cache);\n \ttimer_setup(\u0026dev-\u003erx_mode_retry_timer, netif_rx_mode_retry, 0);\n }\n@@ -1442,24 +1394,6 @@ void dev_set_rx_mode(struct net_device *dev)\n \tnetif_addr_unlock_bh(dev);\n }\n \n-bool netif_rx_mode_clean(struct net_device *dev)\n-{\n-\tbool clean = false;\n-\n-\tspin_lock_bh(\u0026rx_mode_lock);\n-\tif (!list_empty(\u0026dev-\u003erx_mode_node)) {\n-\t\tlist_del_init(\u0026dev-\u003erx_mode_node);\n-\t\tclean = true;\n-\t\t/* We must release netdev tracker under\n-\t\t * the spinlock protection.\n-\t\t */\n-\t\tnetdev_tracker_free(dev, \u0026dev-\u003erx_mode_tracker);\n-\t}\n-\tspin_unlock_bh(\u0026rx_mode_lock);\n-\n-\treturn clean;\n-}\n-\n /**\n * netif_rx_mode_sync() - sync rx mode inline\n * @dev: network device\n@@ -1473,11 +1407,6 @@ bool netif_rx_mode_clean(struct net_device *dev)\n */\n void netif_rx_mode_sync(struct net_device *dev)\n {\n-\tif (netif_rx_mode_clean(dev)) {\n+\tif (__netdev_work_core_cancel(dev, NETDEV_WORK_RX_MODE))\n \t\tnetif_rx_mode_run(dev);\n-\t\t/* Use __dev_put() because netdev_tracker_free() was already\n-\t\t * called inside netif_rx_mode_clean().\n-\t\t */\n-\t\t__dev_put(dev);\n-\t}\n }\ndiff --git a/net/core/netdev_work.c b/net/core/netdev_work.c\nnew file mode 100644\nindex 00000000000000..3109fae132ad18\n--- /dev/null\n+++ b/net/core/netdev_work.c\n@@ -0,0 +1,162 @@\n+// SPDX-License-Identifier: GPL-2.0-or-later\n+\n+#include \u003clinux/export.h\u003e\n+#include \u003clinux/list.h\u003e\n+#include \u003clinux/netdevice.h\u003e\n+#include \u003clinux/rtnetlink.h\u003e\n+#include \u003clinux/spinlock.h\u003e\n+#include \u003clinux/workqueue.h\u003e\n+#include \u003cnet/netdev_lock.h\u003e\n+\n+#include \"dev.h\"\n+\n+static void netdev_work_proc(struct work_struct *work);\n+\n+/* @netdev_work_lock protects:\n+ * - @netdev_work_list\n+ * - within the list entries (struct net_device fields):\n+ *\t- work_node\n+ *\t- work_tracker\n+ *\t- work_pending\n+ *\t- work_core_pending\n+ */\n+static LIST_HEAD(netdev_work_list);\n+static DEFINE_SPINLOCK(netdev_work_lock);\n+static DECLARE_WORK(netdev_work, netdev_work_proc);\n+\n+static void netdev_work_enqueue(struct net_device *dev, unsigned long events,\n+\t\t\t\tunsigned long core)\n+{\n+\tif (!events \u0026\u0026 !core)\n+\t\treturn;\n+\n+\tspin_lock_bh(\u0026netdev_work_lock);\n+\tif (list_empty(\u0026dev-\u003ework_node)) {\n+\t\tlist_add_tail(\u0026dev-\u003ework_node, \u0026netdev_work_list);\n+\t\tnetdev_hold(dev, \u0026dev-\u003ework_tracker, GFP_ATOMIC);\n+\t}\n+\tdev-\u003ework_pending |= events;\n+\tdev-\u003ework_core_pending |= core;\n+\tspin_unlock_bh(\u0026netdev_work_lock);\n+\n+\tschedule_work(\u0026netdev_work);\n+}\n+\n+static unsigned long\n+netdev_work_dequeue(struct net_device *dev, unsigned long *pending,\n+\t\t unsigned long mask)\n+{\n+\tunsigned long events;\n+\n+\tspin_lock_bh(\u0026netdev_work_lock);\n+\tevents = *pending \u0026 mask;\n+\t*pending \u0026= ~events;\n+\tif (!list_empty(\u0026dev-\u003ework_node) \u0026\u0026\n+\t !dev-\u003ework_pending \u0026\u0026 !dev-\u003ework_core_pending) {\n+\t\tlist_del_init(\u0026dev-\u003ework_node);\n+\t\tnetdev_put(dev, \u0026dev-\u003ework_tracker);\n+\t}\n+\tspin_unlock_bh(\u0026netdev_work_lock);\n+\n+\treturn events;\n+}\n+\n+void netdev_work_sched(struct net_device *dev, unsigned long events)\n+{\n+\tnetdev_work_enqueue(dev, events, 0);\n+}\n+EXPORT_SYMBOL(netdev_work_sched);\n+\n+/**\n+ * netdev_work_cancel() - cancel selected work for a netdev\n+ * @dev: net_device\n+ * @mask: events to cancel\n+ *\n+ * Clear @mask from the device's work pending mask. If no work is left pending\n+ * the device is dequeued and its ndo_work won't be called.\n+ *\n+ * No expectations on locking, but also no guarantees provided. If the caller\n+ * wants to touch @dev afterwards (e.g. call the work that got canceled)\n+ * they have to ensure @dev does not get freed.\n+ *\n+ * Returns: the subset of @mask that was actually pending, so the caller can run\n+ * those events inline.\n+ */\n+unsigned long netdev_work_cancel(struct net_device *dev, unsigned long mask)\n+{\n+\treturn netdev_work_dequeue(dev, \u0026dev-\u003ework_pending, mask);\n+}\n+EXPORT_SYMBOL(netdev_work_cancel);\n+\n+void __netdev_work_core_sched(struct net_device *dev, unsigned long events)\n+{\n+\tnetdev_work_enqueue(dev, 0, events);\n+}\n+\n+unsigned long\n+__netdev_work_core_cancel(struct net_device *dev, unsigned long mask)\n+{\n+\treturn netdev_work_dequeue(dev, \u0026dev-\u003ework_core_pending, mask);\n+}\n+\n+static void netdev_work_run(struct net_device *dev, unsigned long events,\n+\t\t\t unsigned long core)\n+{\n+\tif (!netif_device_present(dev))\n+\t\treturn;\n+\n+\tif (core \u0026 NETDEV_WORK_RX_MODE)\n+\t\tnetif_rx_mode_run(dev);\n+\tif (events \u0026\u0026 dev-\u003enetdev_ops-\u003endo_work)\n+\t\tdev-\u003enetdev_ops-\u003endo_work(dev, events);\n+}\n+\n+static void netdev_work_proc(struct work_struct *work)\n+{\n+\trtnl_lock();\n+\n+\twhile (true) {\n+\t\tunsigned long events = 0, core = 0;\n+\t\tnetdevice_tracker tracker;\n+\t\tstruct net_device *dev;\n+\n+\t\tspin_lock_bh(\u0026netdev_work_lock);\n+\t\tif (list_empty(\u0026netdev_work_list)) {\n+\t\t\tspin_unlock_bh(\u0026netdev_work_lock);\n+\t\t\tbreak;\n+\t\t}\n+\t\tdev = list_first_entry(\u0026netdev_work_list, struct net_device,\n+\t\t\t\t work_node);\n+\t\t/* Take a temporary reference so @dev can't be freed while we\n+\t\t * drop the lock to grab its ops lock; the work reference is\n+\t\t * only released once we claim the work below.\n+\t\t * The re-locking dance is to ensure that ops lock is enough\n+\t\t * to ensure canceling work is not racy with dequeue.\n+\t\t */\n+\t\tnetdev_hold(dev, \u0026tracker, GFP_ATOMIC);\n+\t\tspin_unlock_bh(\u0026netdev_work_lock);\n+\n+\t\tnetdev_lock_ops(dev);\n+\t\tspin_lock_bh(\u0026netdev_work_lock);\n+\t\tif (!list_empty(\u0026dev-\u003ework_node)) {\n+\t\t\tlist_del_init(\u0026dev-\u003ework_node);\n+\t\t\tcore = dev-\u003ework_core_pending;\n+\t\t\tdev-\u003ework_core_pending = 0;\n+\t\t\tevents = dev-\u003ework_pending;\n+\t\t\tdev-\u003ework_pending = 0;\n+\t\t\t/* We took another ref above */\n+\t\t\tnetdev_put(dev, \u0026dev-\u003ework_tracker);\n+\n+\t\t\tif (!dev_isalive(dev))\n+\t\t\t\tcore = events = 0;\n+\t\t}\n+\t\tspin_unlock_bh(\u0026netdev_work_lock);\n+\n+\t\tnetdev_work_run(dev, events, core);\n+\t\tnetdev_unlock_ops(dev);\n+\n+\t\tnetdev_put(dev, \u0026tracker);\n+\t}\n+\n+\trtnl_unlock();\n+}\ndiff --git a/tools/testing/selftests/drivers/net/bonding/Makefile b/tools/testing/selftests/drivers/net/bonding/Makefile\nindex be130bf585a419..6364ca02642d68 100644\n--- a/tools/testing/selftests/drivers/net/bonding/Makefile\n+++ b/tools/testing/selftests/drivers/net/bonding/Makefile\n@@ -13,6 +13,7 @@ TEST_PROGS := \\\n \tbond_options.sh \\\n \tbond_passive_lacp.sh \\\n \tbond_stacked_header_parse.sh \\\n+\tbond_vlan_real_dev.sh \\\n \tdev_addr_lists.sh \\\n \tmode-1-recovery-updelay.sh \\\n \tmode-2-recovery-updelay.sh \\\ndiff --git a/tools/testing/selftests/drivers/net/bonding/bond_vlan_real_dev.sh b/tools/testing/selftests/drivers/net/bonding/bond_vlan_real_dev.sh\nnew file mode 100755\nindex 00000000000000..542d9ffc4819f9\n--- /dev/null\n+++ b/tools/testing/selftests/drivers/net/bonding/bond_vlan_real_dev.sh\n@@ -0,0 +1,180 @@\n+#!/bin/bash\n+# SPDX-License-Identifier: GPL-2.0\n+#\n+# Test propagation of a real device's state to the VLANs stacked on top of it\n+# when the real device is (or becomes) a bond member.\n+#\n+# The kernel mirrors a real device's UP/DOWN, MTU and feature changes onto its\n+# VLANs. This is done asynchronously (netdev_work): doing it synchronously from\n+# the real device's notifier could deadlock. If the real device is brought up\n+# while enslaved to a bond - so its instance lock is held across NETDEV_UP - and\n+# a VLAN on top of it is itself a bond member, the synchronous propagation\n+# re-entered the stack and tried to take the same instance lock again.\n+#\n+# Cover both halves:\n+# - the deferred UP/DOWN, MTU and feature propagation actually lands on the\n+# VLAN (link state and MTU use an ops-locked dummy, i.e. the deferral path),\n+# - the deadlock-prone topology - a VLAN on a dummy, with the VLAN and the\n+# dummy each enslaved to a different bond - can be built without hanging.\n+\n+ALL_TESTS=\"\n+\tvlan_link_state\n+\tvlan_mtu\n+\tvlan_features\n+\tvlan_real_dev_enslave\n+\"\n+\n+REQUIRE_MZ=no\n+NUM_NETIFS=0\n+lib_dir=$(dirname \"$0\")\n+source \"$lib_dir\"/../../../net/forwarding/lib.sh\n+\n+# Return 0 if $dev in netns $ns has flag $flag set (e.g. UP) in its \u003c...\u003e flags.\n+link_has_flag()\n+{\n+\tlocal ns=$1 dev=$2 flag=$3\n+\n+\tip -n \"$ns\" link show dev \"$dev\" 2\u003e/dev/null | grep -q \"[\u003c,]${flag}[,\u003e]\"\n+}\n+\n+link_lacks_flag()\n+{\n+\t! link_has_flag \"$@\"\n+}\n+\n+link_mtu_is()\n+{\n+\tlocal ns=$1 dev=$2 want=$3 cur\n+\n+\tcur=$(ip -n \"$ns\" link show dev \"$dev\" 2\u003e/dev/null | \\\n+\t\tsed -n 's/.* mtu \\([0-9]\\+\\).*/\\1/p')\n+\t[ \"$cur\" = \"$want\" ]\n+}\n+\n+vlan_feature_is()\n+{\n+\tlocal ns=$1 dev=$2 feature=$3 value=$4\n+\n+\tip netns exec \"$ns\" ethtool -k \"$dev\" 2\u003e/dev/null | \\\n+\t\tgrep -q \"^$feature: $value\"\n+}\n+\n+link_has_master()\n+{\n+\tlocal ns=$1 dev=$2 master=$3\n+\n+\tip -n \"$ns\" -o link show dev \"$dev\" 2\u003e/dev/null | grep -q \"master $master\"\n+}\n+\n+vlan_link_state()\n+{\n+\tRET=0\n+\n+\tip -n \"$NS\" link add ls_dummy type dummy\n+\tip -n \"$NS\" link add link ls_dummy name ls_vlan type vlan id 100\n+\n+\t# Bringing the real device up must propagate UP to the VLAN.\n+\tip -n \"$NS\" link set ls_dummy up\n+\tbusywait \"$BUSYWAIT_TIMEOUT\" link_has_flag \"$NS\" ls_vlan UP\n+\tcheck_err $? \"VLAN did not go UP after the real device went UP\"\n+\n+\t# ... and likewise for DOWN.\n+\tip -n \"$NS\" link set ls_dummy down\n+\tbusywait \"$BUSYWAIT_TIMEOUT\" link_lacks_flag \"$NS\" ls_vlan UP\n+\tcheck_err $? \"VLAN did not go DOWN after the real device went DOWN\"\n+\n+\tip -n \"$NS\" link del ls_vlan\n+\tip -n \"$NS\" link del ls_dummy\n+\n+\tlog_test \"VLAN link state follows the real device\"\n+}\n+\n+vlan_mtu()\n+{\n+\tRET=0\n+\n+\t# The VLAN inherits the real device's MTU (2000) at creation time.\n+\tip -n \"$NS\" link add mtu_dummy mtu 2000 type dummy\n+\tip -n \"$NS\" link add link mtu_dummy name mtu_vlan type vlan id 100\n+\n+\t# Shrinking the real device's MTU must clamp the VLAN's MTU.\n+\tip -n \"$NS\" link set mtu_dummy mtu 1500\n+\tbusywait \"$BUSYWAIT_TIMEOUT\" link_mtu_is \"$NS\" mtu_vlan 1500\n+\tcheck_err $? \"VLAN MTU not clamped after the real device's MTU shrank\"\n+\n+\tip -n \"$NS\" link del mtu_vlan\n+\tip -n \"$NS\" link del mtu_dummy\n+\n+\tlog_test \"VLAN MTU clamped to the real device\"\n+}\n+\n+vlan_features()\n+{\n+\tRET=0\n+\n+\t# Use veth as the real device: unlike dummy it exports vlan_features, so\n+\t# the VLAN actually inherits a toggleable offload to assert on.\n+\tip -n \"$NS\" link add ft_veth type veth peer name ft_veth_pr\n+\tip -n \"$NS\" link add link ft_veth name ft_vlan type vlan id 100\n+\n+\tvlan_feature_is \"$NS\" ft_vlan scatter-gather on\n+\tcheck_err $? \"VLAN did not inherit scatter-gather from the real device\"\n+\n+\t# Toggling the offload on the real device must propagate to the VLAN.\n+\tip netns exec \"$NS\" ethtool -K ft_veth sg off\n+\tbusywait \"$BUSYWAIT_TIMEOUT\" \\\n+\t\tvlan_feature_is \"$NS\" ft_vlan scatter-gather off\n+\tcheck_err $? \"VLAN scatter-gather still on after disabling it on real dev\"\n+\n+\tip netns exec \"$NS\" ethtool -K ft_veth sg on\n+\tbusywait \"$BUSYWAIT_TIMEOUT\" \\\n+\t\tvlan_feature_is \"$NS\" ft_vlan scatter-gather on\n+\tcheck_err $? \"VLAN scatter-gather still off after enabling it on real dev\"\n+\n+\tip -n \"$NS\" link del ft_vlan\n+\tip -n \"$NS\" link del ft_veth\n+\n+\tlog_test \"VLAN features follow the real device\"\n+}\n+\n+vlan_real_dev_enslave()\n+{\n+\tRET=0\n+\n+\t# dummy \u003c- VLAN -\u003e bond0, then enslave the dummy itself to bond1. The\n+\t# last step brings the dummy up under bond1's instance lock, which used\n+\t# to deadlock while synchronously propagating UP to the (bond-enslaved)\n+\t# VLAN on top.\n+\tip -n \"$NS\" link add dl_dummy type dummy\n+\tip -n \"$NS\" link set dl_dummy up\n+\tip -n \"$NS\" link add link dl_dummy name dl_vlan type vlan id 100\n+\n+\tip -n \"$NS\" link add dl_bond0 type bond mode active-backup\n+\tip -n \"$NS\" link set dl_vlan down\n+\tip -n \"$NS\" link set dl_vlan master dl_bond0\n+\tcheck_err $? \"could not enslave the VLAN to bond0\"\n+\n+\tip -n \"$NS\" link add dl_bond1 type bond mode active-backup\n+\tip -n \"$NS\" link set dl_dummy down\n+\tip -n \"$NS\" link set dl_dummy master dl_bond1\n+\tcheck_err $? \"could not enslave the real device to bond1\"\n+\n+\t# If we got here the kernel did not deadlock; make sure it is still\n+\t# responsive and the enslave really took effect.\n+\tlink_has_master \"$NS\" dl_dummy dl_bond1\n+\tcheck_err $? \"real device not enslaved to bond1\"\n+\n+\tip -n \"$NS\" link del dl_bond1\n+\tip -n \"$NS\" link del dl_bond0\n+\tip -n \"$NS\" link del dl_vlan\n+\tip -n \"$NS\" link del dl_dummy\n+\n+\tlog_test \"VLAN real device enslaved to a second bond\"\n+}\n+\n+setup_ns NS\n+trap 'cleanup_ns $NS' EXIT\n+\n+tests_run\n+\n+exit \"$EXIT_STATUS\"\n"
} |
| 2/1 | 2026/06/24 18:45 | agent | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: {
"EnableConfigs": [
"VLAN_8021Q",
"BONDING"
],
"FocusSymbols": [
"netdev_work_sched",
"netdev_work_cancel",
"netdev_work_proc",
"netdev_work_run",
"vlan_dev_work",
"vlan_device_event"
],
"Reasoning": "This patch introduces a new asynchronous work mechanism (`netdev_work`) for network devices and modifies the VLAN driver to use it for state propagation (MTU, features, link state) to avoid deadlocks during nested notifications. This is a significant functional change to core networking and VLAN logic, reachable from userspace by modifying device states (e.g., bringing devices up/down, changing MTU). It should be fuzzed to ensure the new asynchronous mechanism doesn't introduce race conditions, use-after-free, or other concurrency bugs.",
"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 37e08e8a12e3b5571fb77e3e99d0cf06174a9c1e
Author: syz-cluster <triage@syzkaller.com>
Date: Wed Jun 24 18:45:17 2026 +0000
syz-cluster: applied patch under review
diff --git a/Documentation/networking/netdevices.rst b/Documentation/networking/netdevices.rst
index fde601acd1d25a..d2a238f8cc8b91 100644
--- a/Documentation/networking/netdevices.rst
+++ b/Documentation/networking/netdevices.rst
@@ -433,6 +433,8 @@ exceptions) notifiers run under the instance lock. Please extend this
documentation whenever you make explicit assumption about lock being held
from a notifier.
+Drivers **must not** generate nested notifications of the ops-locked types.
+
NETDEV_INTERNAL symbol namespace
================================
diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h
index b67a12541eac28..9981d637f8b546 100644
--- a/include/linux/netdevice.h
+++ b/include/linux/netdevice.h
@@ -1131,6 +1131,9 @@ struct netdev_net_notifier {
* netdev_hw_addr_list_for_each(ha, uc). Return 0 on success or a
* negative errno to request a retry via the core backoff.
*
+ * void (*ndo_work)(struct net_device *dev, unsigned long events);
+ * Run deferred work scheduled with netdev_work_sched(@events).
+ *
* int (*ndo_set_mac_address)(struct net_device *dev, void *addr);
* This function is called when the Media Access Control address
* needs to be changed. If this interface is not defined, the
@@ -1460,6 +1463,8 @@ struct net_device_ops {
struct net_device *dev,
struct netdev_hw_addr_list *uc,
struct netdev_hw_addr_list *mc);
+ void (*ndo_work)(struct net_device *dev,
+ unsigned long events);
int (*ndo_set_mac_address)(struct net_device *dev,
void *addr);
int (*ndo_validate_addr)(struct net_device *dev);
@@ -1930,8 +1935,11 @@ enum netdev_reg_state {
* has been enabled due to the need to listen to
* additional unicast addresses in a device that
* does not implement ndo_set_rx_mode()
- * @rx_mode_node: List entry for rx_mode work processing
- * @rx_mode_tracker: Refcount tracker for rx_mode work
+ * @work_node: List entry for async netdev_work processing
+ * @work_tracker: Refcount tracker for async netdev_work
+ * @work_pending: Driver-defined pending netdev_work, passed to
+ * ndo_work() (see netdev_work_sched())
+ * @work_core_pending: Core-defined pending netdev_work (NETDEV_WORK_*)
* @rx_mode_addr_cache: Recycled snapshot entries for rx_mode work
* @rx_mode_retry_timer: Timer that re-queues rx_mode work after failure
* @rx_mode_retry_count: Number of consecutive retries already scheduled
@@ -2326,8 +2334,10 @@ struct net_device {
unsigned int promiscuity;
unsigned int allmulti;
bool uc_promisc;
- struct list_head rx_mode_node;
- netdevice_tracker rx_mode_tracker;
+ struct list_head work_node;
+ netdevice_tracker work_tracker;
+ unsigned long work_pending;
+ unsigned long work_core_pending;
struct netdev_hw_addr_list rx_mode_addr_cache;
struct timer_list rx_mode_retry_timer;
unsigned int rx_mode_retry_count;
@@ -5176,6 +5186,9 @@ void dev_fetch_sw_netstats(struct rtnl_link_stats64 *s,
const struct pcpu_sw_netstats __percpu *netstats);
void dev_get_tstats64(struct net_device *dev, struct rtnl_link_stats64 *s);
+void netdev_work_sched(struct net_device *dev, unsigned long events);
+unsigned long netdev_work_cancel(struct net_device *dev, unsigned long mask);
+
enum {
NESTED_SYNC_IMM_BIT,
NESTED_SYNC_TODO_BIT,
diff --git a/net/8021q/vlan.c b/net/8021q/vlan.c
index 2b74ed56eb166d..2d2efb877975d5 100644
--- a/net/8021q/vlan.c
+++ b/net/8021q/vlan.c
@@ -77,9 +77,9 @@ static int vlan_group_prealloc_vid(struct vlan_group *vg,
return 0;
}
-static void vlan_stacked_transfer_operstate(const struct net_device *rootdev,
- struct net_device *dev,
- struct vlan_dev_priv *vlan)
+void vlan_stacked_transfer_operstate(const struct net_device *rootdev,
+ struct net_device *dev,
+ struct vlan_dev_priv *vlan)
{
if (!(vlan->flags & VLAN_FLAG_BRIDGE_BINDING))
netif_stacked_transfer_operstate(rootdev, dev);
@@ -316,29 +316,6 @@ static void vlan_sync_address(struct net_device *dev,
ether_addr_copy(vlan->real_dev_addr, dev->dev_addr);
}
-static void vlan_transfer_features(struct net_device *dev,
- struct net_device *vlandev)
-{
- struct vlan_dev_priv *vlan = vlan_dev_priv(vlandev);
-
- netif_inherit_tso_max(vlandev, dev);
-
- if (vlan_hw_offload_capable(dev->features, vlan->vlan_proto))
- vlandev->hard_header_len = dev->hard_header_len;
- else
- vlandev->hard_header_len = dev->hard_header_len + VLAN_HLEN;
-
-#if IS_ENABLED(CONFIG_FCOE)
- vlandev->fcoe_ddp_xid = dev->fcoe_ddp_xid;
-#endif
-
- vlandev->priv_flags &= ~IFF_XMIT_DST_RELEASE;
- vlandev->priv_flags |= (vlan->real_dev->priv_flags & IFF_XMIT_DST_RELEASE);
- vlandev->hw_enc_features = vlan_tnl_features(vlan->real_dev);
-
- netdev_update_features(vlandev);
-}
-
static int __vlan_device_event(struct net_device *dev, unsigned long event)
{
int err = 0;
@@ -391,13 +368,11 @@ static void vlan_vid0_del(struct net_device *dev)
static int vlan_device_event(struct notifier_block *unused, unsigned long event,
void *ptr)
{
- struct netlink_ext_ack *extack = netdev_notifier_info_to_extack(ptr);
struct net_device *dev = netdev_notifier_info_to_dev(ptr);
struct vlan_group *grp;
struct vlan_info *vlan_info;
int i, flgs;
struct net_device *vlandev;
- struct vlan_dev_priv *vlan;
bool last = false;
LIST_HEAD(list);
int err;
@@ -447,54 +422,19 @@ static int vlan_device_event(struct notifier_block *unused, unsigned long event,
if (vlandev->mtu <= dev->mtu)
continue;
- dev_set_mtu(vlandev, dev->mtu);
+ netdev_work_sched(vlandev, VLAN_WORK_MTU);
}
break;
case NETDEV_FEAT_CHANGE:
- /* Propagate device features to underlying device */
vlan_group_for_each_dev(grp, i, vlandev)
- vlan_transfer_features(dev, vlandev);
+ netdev_work_sched(vlandev, VLAN_WORK_FEATURES);
break;
- case NETDEV_DOWN: {
- struct net_device *tmp;
- LIST_HEAD(close_list);
-
- /* Put all VLANs for this dev in the down state too. */
- vlan_group_for_each_dev(grp, i, vlandev) {
- flgs = vlandev->flags;
- if (!(flgs & IFF_UP))
- continue;
-
- vlan = vlan_dev_priv(vlandev);
- if (!(vlan->flags & VLAN_FLAG_LOOSE_BINDING))
- list_add(&vlandev->close_list, &close_list);
- }
-
- netif_close_many(&close_list, false);
-
- list_for_each_entry_safe(vlandev, tmp, &close_list, close_list) {
- vlan_stacked_transfer_operstate(dev, vlandev,
- vlan_dev_priv(vlandev));
- list_del_init(&vlandev->close_list);
- }
- list_del(&close_list);
- break;
- }
+ case NETDEV_DOWN:
case NETDEV_UP:
- /* Put all VLANs for this dev in the up state too. */
- vlan_group_for_each_dev(grp, i, vlandev) {
- flgs = netif_get_flags(vlandev);
- if (flgs & IFF_UP)
- continue;
-
- vlan = vlan_dev_priv(vlandev);
- if (!(vlan->flags & VLAN_FLAG_LOOSE_BINDING))
- dev_change_flags(vlandev, flgs | IFF_UP,
- extack);
- vlan_stacked_transfer_operstate(dev, vlandev, vlan);
- }
+ vlan_group_for_each_dev(grp, i, vlandev)
+ netdev_work_sched(vlandev, VLAN_WORK_LINK_STATE);
break;
case NETDEV_UNREGISTER:
diff --git a/net/8021q/vlan.h b/net/8021q/vlan.h
index c7ffe591d59366..c41caaf94095f3 100644
--- a/net/8021q/vlan.h
+++ b/net/8021q/vlan.h
@@ -125,6 +125,17 @@ static inline netdev_features_t vlan_tnl_features(struct net_device *real_dev)
int vlan_filter_push_vids(struct vlan_info *vlan_info, __be16 proto);
void vlan_filter_drop_vids(struct vlan_info *vlan_info, __be16 proto);
+/* netdev_work events propagated from the real device, see vlan_dev_work(). */
+enum {
+ VLAN_WORK_LINK_STATE = BIT(0), /* sync up/down with real_dev */
+ VLAN_WORK_MTU = BIT(1), /* clamp mtu to real_dev's */
+ VLAN_WORK_FEATURES = BIT(2), /* re-inherit real_dev features */
+};
+
+void vlan_stacked_transfer_operstate(const struct net_device *rootdev,
+ struct net_device *dev,
+ struct vlan_dev_priv *vlan);
+
/* found in vlan_dev.c */
void vlan_dev_set_ingress_priority(const struct net_device *dev,
u32 skb_prio, u16 vlan_prio);
diff --git a/net/8021q/vlan_dev.c b/net/8021q/vlan_dev.c
index 7aa3af8b10ead0..ec2569b3f8dac6 100644
--- a/net/8021q/vlan_dev.c
+++ b/net/8021q/vlan_dev.c
@@ -270,6 +270,9 @@ static int vlan_dev_open(struct net_device *dev)
!(vlan->flags & VLAN_FLAG_LOOSE_BINDING))
return -ENETDOWN;
+ /* The explicit open supersedes any deferred link-state sync */
+ netdev_work_cancel(dev, VLAN_WORK_LINK_STATE);
+
if (!ether_addr_equal(dev->dev_addr, real_dev->dev_addr) &&
!vlan_dev_inherit_address(dev, real_dev)) {
err = dev_uc_add(real_dev, dev->dev_addr);
@@ -300,6 +303,9 @@ static int vlan_dev_stop(struct net_device *dev)
struct vlan_dev_priv *vlan = vlan_dev_priv(dev);
struct net_device *real_dev = vlan->real_dev;
+ /* The explicit close supersedes any deferred link-state sync */
+ netdev_work_cancel(dev, VLAN_WORK_LINK_STATE);
+
dev_mc_unsync(real_dev, dev);
dev_uc_unsync(real_dev, dev);
@@ -1016,6 +1022,59 @@ static const struct ethtool_ops vlan_ethtool_ops = {
.get_ts_info = vlan_ethtool_get_ts_info,
};
+static void vlan_transfer_features(struct net_device *dev,
+ struct net_device *vlandev)
+{
+ struct vlan_dev_priv *vlan = vlan_dev_priv(vlandev);
+
+ netif_inherit_tso_max(vlandev, dev);
+
+ if (vlan_hw_offload_capable(dev->features, vlan->vlan_proto))
+ vlandev->hard_header_len = dev->hard_header_len;
+ else
+ vlandev->hard_header_len = dev->hard_header_len + VLAN_HLEN;
+
+#if IS_ENABLED(CONFIG_FCOE)
+ vlandev->fcoe_ddp_xid = dev->fcoe_ddp_xid;
+#endif
+
+ vlandev->priv_flags &= ~IFF_XMIT_DST_RELEASE;
+ vlandev->priv_flags |= (vlan->real_dev->priv_flags & IFF_XMIT_DST_RELEASE);
+ vlandev->hw_enc_features = vlan_tnl_features(vlan->real_dev);
+
+ netdev_update_features(vlandev);
+}
+
+static void vlan_dev_work(struct net_device *vlandev, unsigned long events)
+{
+ struct vlan_dev_priv *vlan = vlan_dev_priv(vlandev);
+ struct net_device *real_dev = vlan->real_dev;
+ bool loose = vlan->flags & VLAN_FLAG_LOOSE_BINDING;
+ unsigned int flgs;
+
+ if (events & VLAN_WORK_LINK_STATE) {
+ flgs = netif_get_flags(vlandev);
+ if (real_dev->flags & IFF_UP) {
+ if (!(flgs & IFF_UP)) {
+ if (!loose)
+ netif_change_flags(vlandev,
+ flgs | IFF_UP, NULL);
+ vlan_stacked_transfer_operstate(real_dev,
+ vlandev, vlan);
+ }
+ } else if ((flgs & IFF_UP) && !loose) {
+ netif_change_flags(vlandev, flgs & ~IFF_UP, NULL);
+ vlan_stacked_transfer_operstate(real_dev, vlandev, vlan);
+ }
+ }
+
+ if ((events & VLAN_WORK_MTU) && vlandev->mtu > real_dev->mtu)
+ netif_set_mtu(vlandev, real_dev->mtu);
+
+ if (events & VLAN_WORK_FEATURES)
+ vlan_transfer_features(real_dev, vlandev);
+}
+
static const struct net_device_ops vlan_netdev_ops = {
.ndo_change_mtu = vlan_dev_change_mtu,
.ndo_init = vlan_dev_init,
@@ -1027,6 +1086,7 @@ static const struct net_device_ops vlan_netdev_ops = {
.ndo_set_mac_address = vlan_dev_set_mac_address,
.ndo_set_rx_mode = vlan_dev_set_rx_mode,
.ndo_change_rx_flags = vlan_dev_change_rx_flags,
+ .ndo_work = vlan_dev_work,
.ndo_eth_ioctl = vlan_dev_ioctl,
.ndo_neigh_setup = vlan_dev_neigh_setup,
.ndo_get_stats64 = vlan_dev_get_stats64,
diff --git a/net/core/Makefile b/net/core/Makefile
index dc17c5a61e9a4a..b3fdcb4e355fa6 100644
--- a/net/core/Makefile
+++ b/net/core/Makefile
@@ -13,7 +13,7 @@ obj-y += dev.o dev_api.o dev_addr_lists.o dst.o netevent.o \
neighbour.o rtnetlink.o utils.o link_watch.o filter.o \
sock_diag.o dev_ioctl.o tso.o sock_reuseport.o \
fib_notifier.o xdp.o flow_offload.o gro.o \
- netdev-genl.o netdev-genl-gen.o gso.o
+ netdev-genl.o netdev-genl-gen.o netdev_work.o gso.o
obj-$(CONFIG_NETDEV_ADDR_LIST_TEST) += dev_addr_lists_test.o
diff --git a/net/core/dev.c b/net/core/dev.c
index 5c01dfaa6c4450..4b3d5cfdf6e00f 100644
--- a/net/core/dev.c
+++ b/net/core/dev.c
@@ -9822,6 +9822,7 @@ int netif_change_flags(struct net_device *dev, unsigned int flags,
__dev_notify_flags(dev, old_flags, changes, 0, NULL);
return ret;
}
+EXPORT_SYMBOL(netif_change_flags);
int __netif_set_mtu(struct net_device *dev, int new_mtu)
{
@@ -12093,6 +12094,7 @@ struct net_device *alloc_netdev_mqs(int sizeof_priv, const char *name,
INIT_LIST_HEAD(&dev->ptype_all);
INIT_LIST_HEAD(&dev->ptype_specific);
INIT_LIST_HEAD(&dev->net_notifier_list);
+ INIT_LIST_HEAD(&dev->work_node);
#ifdef CONFIG_NET_SCHED
hash_init(dev->qdisc_hash);
#endif
diff --git a/net/core/dev.h b/net/core/dev.h
index 4121c50e7c88be..5d0b0305d3baaf 100644
--- a/net/core/dev.h
+++ b/net/core/dev.h
@@ -167,10 +167,19 @@ int dev_change_carrier(struct net_device *dev, bool new_carrier);
void __dev_set_rx_mode(struct net_device *dev);
int __dev_set_promiscuity(struct net_device *dev, int inc, bool notify);
void netif_rx_mode_init(struct net_device *dev);
-bool netif_rx_mode_clean(struct net_device *dev);
+void netif_rx_mode_run(struct net_device *dev);
void netif_rx_mode_sync(struct net_device *dev);
void netif_rx_mode_cancel_retry(struct net_device *dev);
+/* Events for the async netdev work, tracked in netdev->work_core_pending. */
+enum netdev_work_core {
+ NETDEV_WORK_RX_MODE = BIT(0), /* run the rx_mode update */
+};
+
+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 __dev_notify_flags(struct net_device *dev, unsigned int old_flags,
unsigned int gchanges, u32 portid,
const struct nlmsghdr *nlh);
diff --git a/net/core/dev_addr_lists.c b/net/core/dev_addr_lists.c
index e17f64a65e17be..08528ca0a8b314 100644
--- a/net/core/dev_addr_lists.c
+++ b/net/core/dev_addr_lists.c
@@ -12,17 +12,10 @@
#include <linux/export.h>
#include <linux/list.h>
#include <linux/spinlock.h>
-#include <linux/workqueue.h>
#include <kunit/visibility.h>
#include "dev.h"
-static void netdev_rx_mode_work(struct work_struct *work);
-
-static LIST_HEAD(rx_mode_list);
-static DEFINE_SPINLOCK(rx_mode_lock);
-static DECLARE_WORK(rx_mode_work, netdev_rx_mode_work);
-
/*
* General list handling functions
*/
@@ -1281,7 +1274,7 @@ void netif_rx_mode_cancel_retry(struct net_device *dev)
dev->rx_mode_retry_count = 0;
}
-static void netif_rx_mode_run(struct net_device *dev)
+void netif_rx_mode_run(struct net_device *dev)
{
struct netdev_hw_addr_list uc_snap, mc_snap, uc_ref, mc_ref;
const struct net_device_ops *ops = dev->netdev_ops;
@@ -1339,49 +1332,9 @@ static void netif_rx_mode_run(struct net_device *dev)
}
}
-static void netdev_rx_mode_work(struct work_struct *work)
-{
- struct net_device *dev;
-
- rtnl_lock();
-
- while (true) {
- spin_lock_bh(&rx_mode_lock);
- if (list_empty(&rx_mode_list)) {
- spin_unlock_bh(&rx_mode_lock);
- break;
- }
- dev = list_first_entry(&rx_mode_list, struct net_device,
- rx_mode_node);
- list_del_init(&dev->rx_mode_node);
- /* We must free netdev tracker under
- * the spinlock protection.
- */
- netdev_tracker_free(dev, &dev->rx_mode_tracker);
- spin_unlock_bh(&rx_mode_lock);
-
- netdev_lock_ops(dev);
- netif_rx_mode_run(dev);
- netdev_unlock_ops(dev);
- /* Use __dev_put() because netdev_tracker_free() was already
- * called above. Must be after netdev_unlock_ops() to prevent
- * netdev_run_todo() from freeing the device while still in use.
- */
- __dev_put(dev);
- }
-
- rtnl_unlock();
-}
-
static void netif_rx_mode_queue(struct net_device *dev)
{
- spin_lock_bh(&rx_mode_lock);
- if (list_empty(&dev->rx_mode_node)) {
- list_add_tail(&dev->rx_mode_node, &rx_mode_list);
- netdev_hold(dev, &dev->rx_mode_tracker, GFP_ATOMIC);
- }
- spin_unlock_bh(&rx_mode_lock);
- schedule_work(&rx_mode_work);
+ __netdev_work_core_sched(dev, NETDEV_WORK_RX_MODE);
}
static void netif_rx_mode_retry(struct timer_list *t)
@@ -1394,7 +1347,6 @@ static void netif_rx_mode_retry(struct timer_list *t)
void netif_rx_mode_init(struct net_device *dev)
{
- INIT_LIST_HEAD(&dev->rx_mode_node);
__hw_addr_init(&dev->rx_mode_addr_cache);
timer_setup(&dev->rx_mode_retry_timer, netif_rx_mode_retry, 0);
}
@@ -1442,24 +1394,6 @@ void dev_set_rx_mode(struct net_device *dev)
netif_addr_unlock_bh(dev);
}
-bool netif_rx_mode_clean(struct net_device *dev)
-{
- bool clean = false;
-
- spin_lock_bh(&rx_mode_lock);
- if (!list_empty(&dev->rx_mode_node)) {
- list_del_init(&dev->rx_mode_node);
- clean = true;
- /* We must release netdev tracker under
- * the spinlock protection.
- */
- netdev_tracker_free(dev, &dev->rx_mode_tracker);
- }
- spin_unlock_bh(&rx_mode_lock);
-
- return clean;
-}
-
/**
* netif_rx_mode_sync() - sync rx mode inline
* @dev: network device
@@ -1473,11 +1407,6 @@ bool netif_rx_mode_clean(struct net_device *dev)
*/
void netif_rx_mode_sync(struct net_device *dev)
{
- if (netif_rx_mode_clean(dev)) {
+ if (__netdev_work_core_cancel(dev, NETDEV_WORK_RX_MODE))
netif_rx_mode_run(dev);
- /* Use __dev_put() because netdev_tracker_free() was already
- * called inside netif_rx_mode_clean().
- */
- __dev_put(dev);
- }
}
diff --git a/net/core/netdev_work.c b/net/core/netdev_work.c
new file mode 100644
index 00000000000000..3109fae132ad18
--- /dev/null
+++ b/net/core/netdev_work.c
@@ -0,0 +1,162 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <linux/export.h>
+#include <linux/list.h>
+#include <linux/netdevice.h>
+#include <linux/rtnetlink.h>
+#include <linux/spinlock.h>
+#include <linux/workqueue.h>
+#include <net/netdev_lock.h>
+
+#include "dev.h"
+
+static void netdev_work_proc(struct work_struct *work);
+
+/* @netdev_work_lock protects:
+ * - @netdev_work_list
+ * - within the list entries (struct net_device fields):
+ * - work_node
+ * - work_tracker
+ * - work_pending
+ * - work_core_pending
+ */
+static LIST_HEAD(netdev_work_list);
+static DEFINE_SPINLOCK(netdev_work_lock);
+static DECLARE_WORK(netdev_work, netdev_work_proc);
+
+static void netdev_work_enqueue(struct net_device *dev, unsigned long events,
+ unsigned long core)
+{
+ if (!events && !core)
+ return;
+
+ spin_lock_bh(&netdev_work_lock);
+ if (list_empty(&dev->work_node)) {
+ list_add_tail(&dev->work_node, &netdev_work_list);
+ netdev_hold(dev, &dev->work_tracker, GFP_ATOMIC);
+ }
+ dev->work_pending |= events;
+ dev->work_core_pending |= core;
+ spin_unlock_bh(&netdev_work_lock);
+
+ schedule_work(&netdev_work);
+}
+
+static unsigned long
+netdev_work_dequeue(struct net_device *dev, unsigned long *pending,
+ unsigned long mask)
+{
+ unsigned long events;
+
+ spin_lock_bh(&netdev_work_lock);
+ events = *pending & mask;
+ *pending &= ~events;
+ if (!list_empty(&dev->work_node) &&
+ !dev->work_pending && !dev->work_core_pending) {
+ list_del_init(&dev->work_node);
+ netdev_put(dev, &dev->work_tracker);
+ }
+ spin_unlock_bh(&netdev_work_lock);
+
+ return events;
+}
+
+void netdev_work_sched(struct net_device *dev, unsigned long events)
+{
+ netdev_work_enqueue(dev, events, 0);
+}
+EXPORT_SYMBOL(netdev_work_sched);
+
+/**
+ * netdev_work_cancel() - cancel selected work for a netdev
+ * @dev: net_device
+ * @mask: events to cancel
+ *
+ * Clear @mask from the device's work pending mask. If no work is left pending
+ * the device is dequeued and its ndo_work won't be called.
+ *
+ * No expectations on locking, but also no guarantees provided. If the caller
+ * wants to touch @dev afterwards (e.g. call the work that got canceled)
+ * they have to ensure @dev does not get freed.
+ *
+ * Returns: the subset of @mask that was actually pending, so the caller can run
+ * those events inline.
+ */
+unsigned long netdev_work_cancel(struct net_device *dev, unsigned long mask)
+{
+ return netdev_work_dequeue(dev, &dev->work_pending, mask);
+}
+EXPORT_SYMBOL(netdev_work_cancel);
+
+void __netdev_work_core_sched(struct net_device *dev, unsigned long events)
+{
+ netdev_work_enqueue(dev, 0, events);
+}
+
+unsigned long
+__netdev_work_core_cancel(struct net_device *dev, unsigned long mask)
+{
+ return netdev_work_dequeue(dev, &dev->work_core_pending, mask);
+}
+
+static void netdev_work_run(struct net_device *dev, unsigned long events,
+ unsigned long core)
+{
+ if (!netif_device_present(dev))
+ return;
+
+ if (core & NETDEV_WORK_RX_MODE)
+ netif_rx_mode_run(dev);
+ if (events && dev->netdev_ops->ndo_work)
+ dev->netdev_ops->ndo_work(dev, events);
+}
+
+static void netdev_work_proc(struct work_struct *work)
+{
+ rtnl_lock();
+
+ while (true) {
+ unsigned long events = 0, core = 0;
+ netdevice_tracker tracker;
+ struct net_device *dev;
+
+ spin_lock_bh(&netdev_work_lock);
+ if (list_empty(&netdev_work_list)) {
+ spin_unlock_bh(&netdev_work_lock);
+ break;
+ }
+ dev = list_first_entry(&netdev_work_list, struct net_device,
+ work_node);
+ /* Take a temporary reference so @dev can't be freed while we
+ * drop the lock to grab its ops lock; the work reference is
+ * only released once we claim the work below.
+ * The re-locking dance is to ensure that ops lock is enough
+ * to ensure canceling work is not racy with dequeue.
+ */
+ netdev_hold(dev, &tracker, GFP_ATOMIC);
+ spin_unlock_bh(&netdev_work_lock);
+
+ netdev_lock_ops(dev);
+ spin_lock_bh(&netdev_work_lock);
+ if (!list_empty(&dev->work_node)) {
+ list_del_init(&dev->work_node);
+ core = dev->work_core_pending;
+ dev->work_core_pending = 0;
+ events = dev->work_pending;
+ dev->work_pending = 0;
+ /* We took another ref above */
+ netdev_put(dev, &dev->work_tracker);
+
+ if (!dev_isalive(dev))
+ core = events = 0;
+ }
+ spin_unlock_bh(&netdev_work_lock);
+
+ netdev_work_run(dev, events, core);
+ netdev_unlock_ops(dev);
+
+ netdev_put(dev, &tracker);
+ }
+
+ rtnl_unlock();
+}
diff --git a/tools/testing/selftests/drivers/net/bonding/Makefile b/tools/testing/selftests/drivers/net/bonding/Makefile
index be130bf585a419..6364ca02642d68 100644
--- a/tools/testing/selftests/drivers/net/bonding/Makefile
+++ b/tools/testing/selftests/drivers/net/bonding/Makefile
@@ -13,6 +13,7 @@ TEST_PROGS := \
bond_options.sh \
bond_passive_lacp.sh \
bond_stacked_header_parse.sh \
+ bond_vlan_real_dev.sh \
dev_addr_lists.sh \
mode-1-recovery-updelay.sh \
mode-2-recovery-updelay.sh \
diff --git a/tools/testing/selftests/drivers/net/bonding/bond_vlan_real_dev.sh b/tools/testing/selftests/drivers/net/bonding/bond_vlan_real_dev.sh
new file mode 100755
index 00000000000000..542d9ffc4819f9
--- /dev/null
+++ b/tools/testing/selftests/drivers/net/bonding/bond_vlan_real_dev.sh
@@ -0,0 +1,180 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+#
+# Test propagation of a real device's state to the VLANs stacked on top of it
+# when the real device is (or becomes) a bond member.
+#
+# The kernel mirrors a real device's UP/DOWN, MTU and feature changes onto its
+# VLANs. This is done asynchronously (netdev_work): doing it synchronously from
+# the real device's notifier could deadlock. If the real device is brought up
+# while enslaved to a bond - so its instance lock is held across NETDEV_UP - and
+# a VLAN on top of it is itself a bond member, the synchronous propagation
+# re-entered the stack and tried to take the same instance lock again.
+#
+# Cover both halves:
+# - the deferred UP/DOWN, MTU and feature propagation actually lands on the
+# VLAN (link state and MTU use an ops-locked dummy, i.e. the deferral path),
+# - the deadlock-prone topology - a VLAN on a dummy, with the VLAN and the
+# dummy each enslaved to a different bond - can be built without hanging.
+
+ALL_TESTS="
+ vlan_link_state
+ vlan_mtu
+ vlan_features
+ vlan_real_dev_enslave
+"
+
+REQUIRE_MZ=no
+NUM_NETIFS=0
+lib_dir=$(dirname "$0")
+source "$lib_dir"/../../../net/forwarding/lib.sh
+
+# Return 0 if $dev in netns $ns has flag $flag set (e.g. UP) in its <...> flags.
+link_has_flag()
+{
+ local ns=$1 dev=$2 flag=$3
+
+ ip -n "$ns" link show dev "$dev" 2>/dev/null | grep -q "[<,]${flag}[,>]"
+}
+
+link_lacks_flag()
+{
+ ! link_has_flag "$@"
+}
+
+link_mtu_is()
+{
+ local ns=$1 dev=$2 want=$3 cur
+
+ cur=$(ip -n "$ns" link show dev "$dev" 2>/dev/null | \
+ sed -n 's/.* mtu \([0-9]\+\).*/\1/p')
+ [ "$cur" = "$want" ]
+}
+
+vlan_feature_is()
+{
+ local ns=$1 dev=$2 feature=$3 value=$4
+
+ ip netns exec "$ns" ethtool -k "$dev" 2>/dev/null | \
+ grep -q "^$feature: $value"
+}
+
+link_has_master()
+{
+ local ns=$1 dev=$2 master=$3
+
+ ip -n "$ns" -o link show dev "$dev" 2>/dev/null | grep -q "master $master"
+}
+
+vlan_link_state()
+{
+ RET=0
+
+ ip -n "$NS" link add ls_dummy type dummy
+ ip -n "$NS" link add link ls_dummy name ls_vlan type vlan id 100
+
+ # Bringing the real device up must propagate UP to the VLAN.
+ ip -n "$NS" link set ls_dummy up
+ busywait "$BUSYWAIT_TIMEOUT" link_has_flag "$NS" ls_vlan UP
+ check_err $? "VLAN did not go UP after the real device went UP"
+
+ # ... and likewise for DOWN.
+ ip -n "$NS" link set ls_dummy down
+ busywait "$BUSYWAIT_TIMEOUT" link_lacks_flag "$NS" ls_vlan UP
+ check_err $? "VLAN did not go DOWN after the real device went DOWN"
+
+ ip -n "$NS" link del ls_vlan
+ ip -n "$NS" link del ls_dummy
+
+ log_test "VLAN link state follows the real device"
+}
+
+vlan_mtu()
+{
+ RET=0
+
+ # The VLAN inherits the real device's MTU (2000) at creation time.
+ ip -n "$NS" link add mtu_dummy mtu 2000 type dummy
+ ip -n "$NS" link add link mtu_dummy name mtu_vlan type vlan id 100
+
+ # Shrinking the real device's MTU must clamp the VLAN's MTU.
+ ip -n "$NS" link set mtu_dummy mtu 1500
+ busywait "$BUSYWAIT_TIMEOUT" link_mtu_is "$NS" mtu_vlan 1500
+ check_err $? "VLAN MTU not clamped after the real device's MTU shrank"
+
+ ip -n "$NS" link del mtu_vlan
+ ip -n "$NS" link del mtu_dummy
+
+ log_test "VLAN MTU clamped to the real device"
+}
+
+vlan_features()
+{
+ RET=0
+
+ # Use veth as the real device: unlike dummy it exports vlan_features, so
+ # the VLAN actually inherits a toggleable offload to assert on.
+ ip -n "$NS" link add ft_veth type veth peer name ft_veth_pr
+ ip -n "$NS" link add link ft_veth name ft_vlan type vlan id 100
+
+ vlan_feature_is "$NS" ft_vlan scatter-gather on
+ check_err $? "VLAN did not inherit scatter-gather from the real device"
+
+ # Toggling the offload on the real device must propagate to the VLAN.
+ ip netns exec "$NS" ethtool -K ft_veth sg off
+ busywait "$BUSYWAIT_TIMEOUT" \
+ vlan_feature_is "$NS" ft_vlan scatter-gather off
+ check_err $? "VLAN scatter-gather still on after disabling it on real dev"
+
+ ip netns exec "$NS" ethtool -K ft_veth sg on
+ busywait "$BUSYWAIT_TIMEOUT" \
+ vlan_feature_is "$NS" ft_vlan scatter-gather on
+ check_err $? "VLAN scatter-gather still off after enabling it on real dev"
+
+ ip -n "$NS" link del ft_vlan
+ ip -n "$NS" link del ft_veth
+
+ log_test "VLAN features follow the real device"
+}
+
+vlan_real_dev_enslave()
+{
+ RET=0
+
+ # dummy <- VLAN -> bond0, then enslave the dummy itself to bond1. The
+ # last step brings the dummy up under bond1's instance lock, which used
+ # to deadlock while synchronously propagating UP to the (bond-enslaved)
+ # VLAN on top.
+ ip -n "$NS" link add dl_dummy type dummy
+ ip -n "$NS" link set dl_dummy up
+ ip -n "$NS" link add link dl_dummy name dl_vlan type vlan id 100
+
+ ip -n "$NS" link add dl_bond0 type bond mode active-backup
+ ip -n "$NS" link set dl_vlan down
+ ip -n "$NS" link set dl_vlan master dl_bond0
+ check_err $? "could not enslave the VLAN to bond0"
+
+ ip -n "$NS" link add dl_bond1 type bond mode active-backup
+ ip -n "$NS" link set dl_dummy down
+ ip -n "$NS" link set dl_dummy master dl_bond1
+ check_err $? "could not enslave the real device to bond1"
+
+ # If we got here the kernel did not deadlock; make sure it is still
+ # responsive and the enslave really took effect.
+ link_has_master "$NS" dl_dummy dl_bond1
+ check_err $? "real device not enslaved to bond1"
+
+ ip -n "$NS" link del dl_bond1
+ ip -n "$NS" link del dl_bond0
+ ip -n "$NS" link del dl_vlan
+ ip -n "$NS" link del dl_dummy
+
+ log_test "VLAN real device enslaved to a second bond"
+}
+
+setup_ns NS
+trap 'cleanup_ns $NS' EXIT
+
+tests_run
+
+exit "$EXIT_STATUS"
|
| 3/2 | 2026/06/24 18:45 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 13008 output: 36 thoughts: 583 Thoughts: **Analyzing a Patch for Deferred Network Device Work** Okay, so I'm looking at a patch here, and it's making some pretty fundamental changes to how we handle network device events, specifically concerning VLANs and the core networking code. The goal here is to introduce a deferred work mechanism to avoid some nasty potential deadlocks that can arise from nested notifications, particularly around things like `NETDEV_DOWN`, `NETDEV_UP`, `NETDEV_CHANGEMTU`, and `NETDEV_FEAT_CHANGE`. Essentially, we're replacing the synchronous execution with asynchronous work to be done later. The changes are fairly widespread, touching files in `include/linux/netdevice.h`, various VLAN-related files (`vlan.c`, `vlan.h`, `vlan_dev.c`), core networking components (`dev.c`, `dev.h`, `dev_addr_lists.c`, `Makefile`), and even a new file specifically for this deferred work (`netdev_work.c`) – which makes sense. I see a new selftest script, `bond_vlan_real_dev.sh`, which is a good sign; that's great for verifying that these changes work as intended. This is a functional change, and given its impact on the core networking stack, this patch is *definitely* worth fuzzing and further scrutiny. I want to make sure we're not introducing any regressions or unexpected behavior. Now, let's identify the core symbols involved. I see a few key functions: `netdev_work_sched`, `netdev_work_cancel`, `__netdev_work_core_sched`, `__netdev_work_core_cancel`, `netdev_work_run`, `netdev_work_proc`, and of course the VLAN-specific `vlan_dev_work`. This gives me a good entrypoint to look for callers and their behavior. I want to look for dependencies and configuration. I know the changes are mostly in `net/core/` and `net/8021q/`. `net/core/` is pretty much always included, but the VLAN code is going to depend on `CONFIG_VLAN_8021Q`. I should verify that that's the only relevant config, so I'll double-check `net/8021q/Kconfig` and `net/Makefile` just to be absolutely sure. |
| 4/2 | 2026/06/24 18:45 | tool | grepper |
0mArgs:{
"Expression": "VLAN_8021Q",
"PathPrefix": "net/8021q/"
}Results: {
"Output": "net/8021q/Kconfig-5-\nnet/8021q/Kconfig:6:config VLAN_8021Q\nnet/8021q/Kconfig-7-\ttristate \"802.1Q/802.1ad VLAN Support\"\n--\nnet/8021q/Kconfig-21-\nnet/8021q/Kconfig:22:config VLAN_8021Q_GVRP\nnet/8021q/Kconfig-23-\tbool \"GVRP (GARP VLAN Registration Protocol) support\"\nnet/8021q/Kconfig:24:\tdepends on VLAN_8021Q\nnet/8021q/Kconfig-25-\tselect GARP\n--\nnet/8021q/Kconfig-31-\nnet/8021q/Kconfig:32:config VLAN_8021Q_MVRP\nnet/8021q/Kconfig-33-\tbool \"MVRP (Multiple VLAN Registration Protocol) support\"\nnet/8021q/Kconfig:34:\tdepends on VLAN_8021Q\nnet/8021q/Kconfig-35-\tselect MRP\n--\nnet/8021q/Makefile-4-#\nnet/8021q/Makefile:5:obj-$(subst m,y,$(CONFIG_VLAN_8021Q))\t+= vlan_core.o\nnet/8021q/Makefile:6:obj-$(CONFIG_VLAN_8021Q)\t\t+= 8021q.o\nnet/8021q/Makefile-7-\nnet/8021q/Makefile-8-8021q-y\t\t\t\t\t:= vlan.o vlan_dev.o vlan_netlink.o\nnet/8021q/Makefile:9:8021q-$(CONFIG_VLAN_8021Q_GVRP)\t\t+= vlan_gvrp.o\nnet/8021q/Makefile:10:8021q-$(CONFIG_VLAN_8021Q_MVRP)\t\t+= vlan_mvrp.o\nnet/8021q/Makefile-11-8021q-$(CONFIG_PROC_FS)\t\t\t+= vlanproc.o\n--\nnet/8021q/vlan.h=158=static inline u32 vlan_get_ingress_priority(struct net_device *dev,\n--\nnet/8021q/vlan.h-165-\nnet/8021q/vlan.h:166:#ifdef CONFIG_VLAN_8021Q_GVRP\nnet/8021q/vlan.h-167-int vlan_gvrp_request_join(const struct net_device *dev);\n--\nnet/8021q/vlan.h=179=static inline void vlan_gvrp_uninit(void) {}\n--\nnet/8021q/vlan.h-181-\nnet/8021q/vlan.h:182:#ifdef CONFIG_VLAN_8021Q_MVRP\nnet/8021q/vlan.h-183-int vlan_mvrp_request_join(const struct net_device *dev);\n"
} |
| 5/2 | 2026/06/24 18:45 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 14984 output: 211 thoughts: 588 Thoughts: **Fuzzing Strategy for Network Device Asynchronous Work Mechanism** Okay, so the config under consideration here is `VLAN_8021Q`. My initial thought is to quickly check for any other relevant configurations, but I suspect that this is the primary focus. Given that the test framework leverages bonding, `BONDING` could be useful to enable, although the core logic I'm interested in is clearly in `netdev_work.c` and `vlan_dev.c`. I'll add both `VLAN_8021Q` and `BONDING` to `EnableConfigs`. Now, I need to pinpoint the critical functions. I've identified the following symbols as potential targets for fuzzing: `netdev_work_sched`, `netdev_work_cancel`, `__netdev_work_core_sched`, `__netdev_work_core_cancel`, `netdev_work_run`, `netdev_work_proc`, and `vlan_dev_work`. Let's quickly verify the visibility of these symbols, and whether they are static or exported. Okay, `netdev_work_proc`, `netdev_work_run` and `vlan_dev_work` are static, while `netdev_work_sched` and `netdev_work_cancel` are exported. The `__netdev_work_core_*` symbols are global, but not exported. My fuzz target list will consist of: * `netdev_work_sched` * `netdev_work_cancel` * `netdev_work_proc` * `netdev_work_run` * `vlan_dev_work` * `vlan_device_event` (which was modified in the patch) * `vlan_stacked_transfer_operstate` (another modified function) I'll confirm that `netdev_work_proc` is indeed the workqueue callback. Yes, it is. The rationale here is that this patch introduces a new asynchronous work mechanism (`netdev_work`) for network devices and integrates it with the VLAN driver. The goal is to handle state propagation (MTU, features, link state) more efficiently and avoid potential deadlocks. This is a crucial change to the core network and VLAN logic. Critically, these functions are accessible from userspace through netlink and ioctls. Therefore, I need to fuzz this implementation thoroughly to identify any potential race conditions, use-after-free vulnerabilities, or concurrency bugs that this new asynchronous mechanism might introduce. The focus symbols look good to me. This change is absolutely "WorthFuzzing: true". |
| 6/2 | 2026/06/24 18:45 | tool | set-results |
0mArgs:{
"EnableConfigs": [
"VLAN_8021Q",
"BONDING"
],
"FocusSymbols": [
"netdev_work_sched",
"netdev_work_cancel",
"netdev_work_proc",
"netdev_work_run",
"vlan_dev_work",
"vlan_device_event"
],
"Reasoning": "This patch introduces a new asynchronous work mechanism (`netdev_work`) for network devices and modifies the VLAN driver to use it for state propagation (MTU, features, link state) to avoid deadlocks during nested notifications. This is a significant functional change to core networking and VLAN logic, reachable from userspace by modifying device states (e.g., bringing devices up/down, changing MTU). It should be fuzzed to ensure the new asynchronous mechanism doesn't introduce race conditions, use-after-free, or other concurrency bugs.",
"WorthFuzzing": true
}Results: {
"EnableConfigs": [
"VLAN_8021Q",
"BONDING"
],
"FocusSymbols": [
"netdev_work_sched",
"netdev_work_cancel",
"netdev_work_proc",
"netdev_work_run",
"vlan_dev_work",
"vlan_device_event"
],
"Reasoning": "This patch introduces a new asynchronous work mechanism (`netdev_work`) for network devices and modifies the VLAN driver to use it for state propagation (MTU, features, link state) to avoid deadlocks during nested notifications. This is a significant functional change to core networking and VLAN logic, reachable from userspace by modifying device states (e.g., bringing devices up/down, changing MTU). It should be fuzzed to ensure the new asynchronous mechanism doesn't introduce race conditions, use-after-free, or other concurrency bugs.",
"WorthFuzzing": true
} |
| Total Calls | Total Tokens | Avg Tokens | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|
| Total Calls | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|