forked from mirrors/linux
		
	net: hold instance lock during NETDEV_CHANGE
Cosmin reports an issue with ipv6_add_dev being called from
NETDEV_CHANGE notifier:
[ 3455.008776]  ? ipv6_add_dev+0x370/0x620
[ 3455.010097]  ipv6_find_idev+0x96/0xe0
[ 3455.010725]  addrconf_add_dev+0x1e/0xa0
[ 3455.011382]  addrconf_init_auto_addrs+0xb0/0x720
[ 3455.013537]  addrconf_notify+0x35f/0x8d0
[ 3455.014214]  notifier_call_chain+0x38/0xf0
[ 3455.014903]  netdev_state_change+0x65/0x90
[ 3455.015586]  linkwatch_do_dev+0x5a/0x70
[ 3455.016238]  rtnl_getlink+0x241/0x3e0
[ 3455.019046]  rtnetlink_rcv_msg+0x177/0x5e0
Similarly, linkwatch might get to ipv6_add_dev without ops lock:
[ 3456.656261]  ? ipv6_add_dev+0x370/0x620
[ 3456.660039]  ipv6_find_idev+0x96/0xe0
[ 3456.660445]  addrconf_add_dev+0x1e/0xa0
[ 3456.660861]  addrconf_init_auto_addrs+0xb0/0x720
[ 3456.661803]  addrconf_notify+0x35f/0x8d0
[ 3456.662236]  notifier_call_chain+0x38/0xf0
[ 3456.662676]  netdev_state_change+0x65/0x90
[ 3456.663112]  linkwatch_do_dev+0x5a/0x70
[ 3456.663529]  __linkwatch_run_queue+0xeb/0x200
[ 3456.663990]  linkwatch_event+0x21/0x30
[ 3456.664399]  process_one_work+0x211/0x610
[ 3456.664828]  worker_thread+0x1cc/0x380
[ 3456.665691]  kthread+0xf4/0x210
Reclassify NETDEV_CHANGE as a notifier that consistently runs under the
instance lock.
Link: https://lore.kernel.org/netdev/aac073de8beec3e531c86c101b274d434741c28e.camel@nvidia.com/
Reported-by: Cosmin Ratiu <cratiu@nvidia.com>
Tested-by: Cosmin Ratiu <cratiu@nvidia.com>
Fixes: ad7c7b2172 ("net: hold netdev instance lock during sysfs operations")
Signed-off-by: Stanislav Fomichev <sdf@fomichev.me>
Link: https://patch.msgid.link/20250404161122.3907628-1-sdf@fomichev.me
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
			
			
This commit is contained in:
		
							parent
							
								
									54f5fafcce
								
							
						
					
					
						commit
						04efcee6ef
					
				
					 10 changed files with 63 additions and 31 deletions
				
			
		|  | @ -338,10 +338,11 @@ operations directly under the netdev instance lock. | ||||||
| Devices drivers are encouraged to rely on the instance lock where possible. | Devices drivers are encouraged to rely on the instance lock where possible. | ||||||
| 
 | 
 | ||||||
| For the (mostly software) drivers that need to interact with the core stack, | For the (mostly software) drivers that need to interact with the core stack, | ||||||
| there are two sets of interfaces: ``dev_xxx`` and ``netif_xxx`` (e.g., | there are two sets of interfaces: ``dev_xxx``/``netdev_xxx`` and ``netif_xxx`` | ||||||
| ``dev_set_mtu`` and ``netif_set_mtu``). The ``dev_xxx`` functions handle | (e.g., ``dev_set_mtu`` and ``netif_set_mtu``). The ``dev_xxx``/``netdev_xxx`` | ||||||
| acquiring the instance lock themselves, while the ``netif_xxx`` functions | functions handle acquiring the instance lock themselves, while the | ||||||
| assume that the driver has already acquired the instance lock. | ``netif_xxx`` functions assume that the driver has already acquired | ||||||
|  | the instance lock. | ||||||
| 
 | 
 | ||||||
| Notifiers and netdev instance lock | Notifiers and netdev instance lock | ||||||
| ================================== | ================================== | ||||||
|  | @ -354,6 +355,7 @@ For devices with locked ops, currently only the following notifiers are | ||||||
| running under the lock: | running under the lock: | ||||||
| * ``NETDEV_REGISTER`` | * ``NETDEV_REGISTER`` | ||||||
| * ``NETDEV_UP`` | * ``NETDEV_UP`` | ||||||
|  | * ``NETDEV_CHANGE`` | ||||||
| 
 | 
 | ||||||
| The following notifiers are running without the lock: | The following notifiers are running without the lock: | ||||||
| * ``NETDEV_UNREGISTER`` | * ``NETDEV_UNREGISTER`` | ||||||
|  |  | ||||||
|  | @ -4429,6 +4429,7 @@ void linkwatch_fire_event(struct net_device *dev); | ||||||
|  * pending work list (if queued). |  * pending work list (if queued). | ||||||
|  */ |  */ | ||||||
| void linkwatch_sync_dev(struct net_device *dev); | void linkwatch_sync_dev(struct net_device *dev); | ||||||
|  | void __linkwatch_sync_dev(struct net_device *dev); | ||||||
| 
 | 
 | ||||||
| /**
 | /**
 | ||||||
|  *	netif_carrier_ok - test if carrier present |  *	netif_carrier_ok - test if carrier present | ||||||
|  | @ -4974,6 +4975,7 @@ void dev_set_rx_mode(struct net_device *dev); | ||||||
| int dev_set_promiscuity(struct net_device *dev, int inc); | int dev_set_promiscuity(struct net_device *dev, int inc); | ||||||
| int netif_set_allmulti(struct net_device *dev, int inc, bool notify); | int netif_set_allmulti(struct net_device *dev, int inc, bool notify); | ||||||
| int dev_set_allmulti(struct net_device *dev, int inc); | int dev_set_allmulti(struct net_device *dev, int inc); | ||||||
|  | void netif_state_change(struct net_device *dev); | ||||||
| void netdev_state_change(struct net_device *dev); | void netdev_state_change(struct net_device *dev); | ||||||
| void __netdev_notify_peers(struct net_device *dev); | void __netdev_notify_peers(struct net_device *dev); | ||||||
| void netdev_notify_peers(struct net_device *dev); | void netdev_notify_peers(struct net_device *dev); | ||||||
|  |  | ||||||
|  | @ -240,6 +240,6 @@ rtnl_notify_needed(const struct net *net, u16 nlflags, u32 group) | ||||||
| 	return (nlflags & NLM_F_ECHO) || rtnl_has_listeners(net, group); | 	return (nlflags & NLM_F_ECHO) || rtnl_has_listeners(net, group); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void netdev_set_operstate(struct net_device *dev, int newstate); | void netif_set_operstate(struct net_device *dev, int newstate); | ||||||
| 
 | 
 | ||||||
| #endif	/* __LINUX_RTNETLINK_H */ | #endif	/* __LINUX_RTNETLINK_H */ | ||||||
|  |  | ||||||
|  | @ -1518,15 +1518,7 @@ void netdev_features_change(struct net_device *dev) | ||||||
| } | } | ||||||
| EXPORT_SYMBOL(netdev_features_change); | EXPORT_SYMBOL(netdev_features_change); | ||||||
| 
 | 
 | ||||||
| /**
 | void netif_state_change(struct net_device *dev) | ||||||
|  *	netdev_state_change - device changes state |  | ||||||
|  *	@dev: device to cause notification |  | ||||||
|  * |  | ||||||
|  *	Called to indicate a device has changed state. This function calls |  | ||||||
|  *	the notifier chains for netdev_chain and sends a NEWLINK message |  | ||||||
|  *	to the routing socket. |  | ||||||
|  */ |  | ||||||
| void netdev_state_change(struct net_device *dev) |  | ||||||
| { | { | ||||||
| 	if (dev->flags & IFF_UP) { | 	if (dev->flags & IFF_UP) { | ||||||
| 		struct netdev_notifier_change_info change_info = { | 		struct netdev_notifier_change_info change_info = { | ||||||
|  | @ -1538,7 +1530,6 @@ void netdev_state_change(struct net_device *dev) | ||||||
| 		rtmsg_ifinfo(RTM_NEWLINK, dev, 0, GFP_KERNEL, 0, NULL); | 		rtmsg_ifinfo(RTM_NEWLINK, dev, 0, GFP_KERNEL, 0, NULL); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| EXPORT_SYMBOL(netdev_state_change); |  | ||||||
| 
 | 
 | ||||||
| /**
 | /**
 | ||||||
|  * __netdev_notify_peers - notify network peers about existence of @dev, |  * __netdev_notify_peers - notify network peers about existence of @dev, | ||||||
|  |  | ||||||
|  | @ -327,3 +327,19 @@ int dev_xdp_propagate(struct net_device *dev, struct netdev_bpf *bpf) | ||||||
| 	return ret; | 	return ret; | ||||||
| } | } | ||||||
| EXPORT_SYMBOL_GPL(dev_xdp_propagate); | EXPORT_SYMBOL_GPL(dev_xdp_propagate); | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * netdev_state_change() - device changes state | ||||||
|  |  * @dev: device to cause notification | ||||||
|  |  * | ||||||
|  |  * Called to indicate a device has changed state. This function calls | ||||||
|  |  * the notifier chains for netdev_chain and sends a NEWLINK message | ||||||
|  |  * to the routing socket. | ||||||
|  |  */ | ||||||
|  | void netdev_state_change(struct net_device *dev) | ||||||
|  | { | ||||||
|  | 	netdev_lock_ops(dev); | ||||||
|  | 	netif_state_change(dev); | ||||||
|  | 	netdev_unlock_ops(dev); | ||||||
|  | } | ||||||
|  | EXPORT_SYMBOL(netdev_state_change); | ||||||
|  |  | ||||||
|  | @ -183,7 +183,7 @@ static void linkwatch_do_dev(struct net_device *dev) | ||||||
| 		else | 		else | ||||||
| 			dev_deactivate(dev); | 			dev_deactivate(dev); | ||||||
| 
 | 
 | ||||||
| 		netdev_state_change(dev); | 		netif_state_change(dev); | ||||||
| 	} | 	} | ||||||
| 	/* Note: our callers are responsible for calling netdev_tracker_free().
 | 	/* Note: our callers are responsible for calling netdev_tracker_free().
 | ||||||
| 	 * This is the reason we use __dev_put() instead of dev_put(). | 	 * This is the reason we use __dev_put() instead of dev_put(). | ||||||
|  | @ -240,7 +240,9 @@ static void __linkwatch_run_queue(int urgent_only) | ||||||
| 		 */ | 		 */ | ||||||
| 		netdev_tracker_free(dev, &dev->linkwatch_dev_tracker); | 		netdev_tracker_free(dev, &dev->linkwatch_dev_tracker); | ||||||
| 		spin_unlock_irq(&lweventlist_lock); | 		spin_unlock_irq(&lweventlist_lock); | ||||||
|  | 		netdev_lock_ops(dev); | ||||||
| 		linkwatch_do_dev(dev); | 		linkwatch_do_dev(dev); | ||||||
|  | 		netdev_unlock_ops(dev); | ||||||
| 		do_dev--; | 		do_dev--; | ||||||
| 		spin_lock_irq(&lweventlist_lock); | 		spin_lock_irq(&lweventlist_lock); | ||||||
| 	} | 	} | ||||||
|  | @ -253,25 +255,41 @@ static void __linkwatch_run_queue(int urgent_only) | ||||||
| 	spin_unlock_irq(&lweventlist_lock); | 	spin_unlock_irq(&lweventlist_lock); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void linkwatch_sync_dev(struct net_device *dev) | static bool linkwatch_clean_dev(struct net_device *dev) | ||||||
| { | { | ||||||
| 	unsigned long flags; | 	unsigned long flags; | ||||||
| 	int clean = 0; | 	bool clean = false; | ||||||
| 
 | 
 | ||||||
| 	spin_lock_irqsave(&lweventlist_lock, flags); | 	spin_lock_irqsave(&lweventlist_lock, flags); | ||||||
| 	if (!list_empty(&dev->link_watch_list)) { | 	if (!list_empty(&dev->link_watch_list)) { | ||||||
| 		list_del_init(&dev->link_watch_list); | 		list_del_init(&dev->link_watch_list); | ||||||
| 		clean = 1; | 		clean = true; | ||||||
| 		/* We must release netdev tracker under
 | 		/* We must release netdev tracker under
 | ||||||
| 		 * the spinlock protection. | 		 * the spinlock protection. | ||||||
| 		 */ | 		 */ | ||||||
| 		netdev_tracker_free(dev, &dev->linkwatch_dev_tracker); | 		netdev_tracker_free(dev, &dev->linkwatch_dev_tracker); | ||||||
| 	} | 	} | ||||||
| 	spin_unlock_irqrestore(&lweventlist_lock, flags); | 	spin_unlock_irqrestore(&lweventlist_lock, flags); | ||||||
| 	if (clean) | 
 | ||||||
|  | 	return clean; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void __linkwatch_sync_dev(struct net_device *dev) | ||||||
|  | { | ||||||
|  | 	netdev_ops_assert_locked(dev); | ||||||
|  | 
 | ||||||
|  | 	if (linkwatch_clean_dev(dev)) | ||||||
| 		linkwatch_do_dev(dev); | 		linkwatch_do_dev(dev); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | void linkwatch_sync_dev(struct net_device *dev) | ||||||
|  | { | ||||||
|  | 	if (linkwatch_clean_dev(dev)) { | ||||||
|  | 		netdev_lock_ops(dev); | ||||||
|  | 		linkwatch_do_dev(dev); | ||||||
|  | 		netdev_unlock_ops(dev); | ||||||
|  | 	} | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| /* Must be called with the rtnl semaphore held */ | /* Must be called with the rtnl semaphore held */ | ||||||
| void linkwatch_run_queue(void) | void linkwatch_run_queue(void) | ||||||
|  |  | ||||||
|  | @ -20,11 +20,11 @@ int netdev_debug_event(struct notifier_block *nb, unsigned long event, | ||||||
| 	switch (cmd) { | 	switch (cmd) { | ||||||
| 	case NETDEV_REGISTER: | 	case NETDEV_REGISTER: | ||||||
| 	case NETDEV_UP: | 	case NETDEV_UP: | ||||||
|  | 	case NETDEV_CHANGE: | ||||||
| 		netdev_ops_assert_locked(dev); | 		netdev_ops_assert_locked(dev); | ||||||
| 		fallthrough; | 		fallthrough; | ||||||
| 	case NETDEV_DOWN: | 	case NETDEV_DOWN: | ||||||
| 	case NETDEV_REBOOT: | 	case NETDEV_REBOOT: | ||||||
| 	case NETDEV_CHANGE: |  | ||||||
| 	case NETDEV_UNREGISTER: | 	case NETDEV_UNREGISTER: | ||||||
| 	case NETDEV_CHANGEMTU: | 	case NETDEV_CHANGEMTU: | ||||||
| 	case NETDEV_CHANGEADDR: | 	case NETDEV_CHANGEADDR: | ||||||
|  |  | ||||||
|  | @ -1043,7 +1043,7 @@ int rtnl_put_cacheinfo(struct sk_buff *skb, struct dst_entry *dst, u32 id, | ||||||
| } | } | ||||||
| EXPORT_SYMBOL_GPL(rtnl_put_cacheinfo); | EXPORT_SYMBOL_GPL(rtnl_put_cacheinfo); | ||||||
| 
 | 
 | ||||||
| void netdev_set_operstate(struct net_device *dev, int newstate) | void netif_set_operstate(struct net_device *dev, int newstate) | ||||||
| { | { | ||||||
| 	unsigned int old = READ_ONCE(dev->operstate); | 	unsigned int old = READ_ONCE(dev->operstate); | ||||||
| 
 | 
 | ||||||
|  | @ -1052,9 +1052,9 @@ void netdev_set_operstate(struct net_device *dev, int newstate) | ||||||
| 			return; | 			return; | ||||||
| 	} while (!try_cmpxchg(&dev->operstate, &old, newstate)); | 	} while (!try_cmpxchg(&dev->operstate, &old, newstate)); | ||||||
| 
 | 
 | ||||||
| 	netdev_state_change(dev); | 	netif_state_change(dev); | ||||||
| } | } | ||||||
| EXPORT_SYMBOL(netdev_set_operstate); | EXPORT_SYMBOL(netif_set_operstate); | ||||||
| 
 | 
 | ||||||
| static void set_operstate(struct net_device *dev, unsigned char transition) | static void set_operstate(struct net_device *dev, unsigned char transition) | ||||||
| { | { | ||||||
|  | @ -1080,7 +1080,7 @@ static void set_operstate(struct net_device *dev, unsigned char transition) | ||||||
| 		break; | 		break; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	netdev_set_operstate(dev, operstate); | 	netif_set_operstate(dev, operstate); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static unsigned int rtnl_dev_get_flags(const struct net_device *dev) | static unsigned int rtnl_dev_get_flags(const struct net_device *dev) | ||||||
|  | @ -3396,7 +3396,7 @@ static int do_setlink(const struct sk_buff *skb, struct net_device *dev, | ||||||
| errout: | errout: | ||||||
| 	if (status & DO_SETLINK_MODIFIED) { | 	if (status & DO_SETLINK_MODIFIED) { | ||||||
| 		if ((status & DO_SETLINK_NOTIFY) == DO_SETLINK_NOTIFY) | 		if ((status & DO_SETLINK_NOTIFY) == DO_SETLINK_NOTIFY) | ||||||
| 			netdev_state_change(dev); | 			netif_state_change(dev); | ||||||
| 
 | 
 | ||||||
| 		if (err < 0) | 		if (err < 0) | ||||||
| 			net_warn_ratelimited("A link change request failed with some changes committed already. Interface %s may have been left with an inconsistent configuration, please check.\n", | 			net_warn_ratelimited("A link change request failed with some changes committed already. Interface %s may have been left with an inconsistent configuration, please check.\n", | ||||||
|  | @ -3676,8 +3676,11 @@ struct net_device *rtnl_create_link(struct net *net, const char *ifname, | ||||||
| 				nla_len(tb[IFLA_BROADCAST])); | 				nla_len(tb[IFLA_BROADCAST])); | ||||||
| 	if (tb[IFLA_TXQLEN]) | 	if (tb[IFLA_TXQLEN]) | ||||||
| 		dev->tx_queue_len = nla_get_u32(tb[IFLA_TXQLEN]); | 		dev->tx_queue_len = nla_get_u32(tb[IFLA_TXQLEN]); | ||||||
| 	if (tb[IFLA_OPERSTATE]) | 	if (tb[IFLA_OPERSTATE]) { | ||||||
|  | 		netdev_lock_ops(dev); | ||||||
| 		set_operstate(dev, nla_get_u8(tb[IFLA_OPERSTATE])); | 		set_operstate(dev, nla_get_u8(tb[IFLA_OPERSTATE])); | ||||||
|  | 		netdev_unlock_ops(dev); | ||||||
|  | 	} | ||||||
| 	if (tb[IFLA_LINKMODE]) | 	if (tb[IFLA_LINKMODE]) | ||||||
| 		dev->link_mode = nla_get_u8(tb[IFLA_LINKMODE]); | 		dev->link_mode = nla_get_u8(tb[IFLA_LINKMODE]); | ||||||
| 	if (tb[IFLA_GROUP]) | 	if (tb[IFLA_GROUP]) | ||||||
|  |  | ||||||
|  | @ -60,7 +60,7 @@ static struct devlink *netdev_to_devlink_get(struct net_device *dev) | ||||||
| u32 ethtool_op_get_link(struct net_device *dev) | u32 ethtool_op_get_link(struct net_device *dev) | ||||||
| { | { | ||||||
| 	/* Synchronize carrier state with link watch, see also rtnl_getlink() */ | 	/* Synchronize carrier state with link watch, see also rtnl_getlink() */ | ||||||
| 	linkwatch_sync_dev(dev); | 	__linkwatch_sync_dev(dev); | ||||||
| 
 | 
 | ||||||
| 	return netif_carrier_ok(dev) ? 1 : 0; | 	return netif_carrier_ok(dev) ? 1 : 0; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -33,14 +33,14 @@ static void hsr_set_operstate(struct hsr_port *master, bool has_carrier) | ||||||
| 	struct net_device *dev = master->dev; | 	struct net_device *dev = master->dev; | ||||||
| 
 | 
 | ||||||
| 	if (!is_admin_up(dev)) { | 	if (!is_admin_up(dev)) { | ||||||
| 		netdev_set_operstate(dev, IF_OPER_DOWN); | 		netif_set_operstate(dev, IF_OPER_DOWN); | ||||||
| 		return; | 		return; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if (has_carrier) | 	if (has_carrier) | ||||||
| 		netdev_set_operstate(dev, IF_OPER_UP); | 		netif_set_operstate(dev, IF_OPER_UP); | ||||||
| 	else | 	else | ||||||
| 		netdev_set_operstate(dev, IF_OPER_LOWERLAYERDOWN); | 		netif_set_operstate(dev, IF_OPER_LOWERLAYERDOWN); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static bool hsr_check_carrier(struct hsr_port *master) | static bool hsr_check_carrier(struct hsr_port *master) | ||||||
|  |  | ||||||
		Loading…
	
		Reference in a new issue
	
	 Stanislav Fomichev
						Stanislav Fomichev