mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/
synced 2025-04-19 20:58:31 +09:00
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: ad7c7b2172c3 ("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
@ -338,10 +338,11 @@ operations directly under the netdev instance lock.
|
||||
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,
|
||||
there are two sets of interfaces: ``dev_xxx`` and ``netif_xxx`` (e.g.,
|
||||
``dev_set_mtu`` and ``netif_set_mtu``). The ``dev_xxx`` functions handle
|
||||
acquiring the instance lock themselves, while the ``netif_xxx`` functions
|
||||
assume that the driver has already acquired the instance lock.
|
||||
there are two sets of interfaces: ``dev_xxx``/``netdev_xxx`` and ``netif_xxx``
|
||||
(e.g., ``dev_set_mtu`` and ``netif_set_mtu``). The ``dev_xxx``/``netdev_xxx``
|
||||
functions handle acquiring the instance lock themselves, while the
|
||||
``netif_xxx`` functions assume that the driver has already acquired
|
||||
the 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:
|
||||
* ``NETDEV_REGISTER``
|
||||
* ``NETDEV_UP``
|
||||
* ``NETDEV_CHANGE``
|
||||
|
||||
The following notifiers are running without the lock:
|
||||
* ``NETDEV_UNREGISTER``
|
||||
|
@ -4429,6 +4429,7 @@ void linkwatch_fire_event(struct net_device *dev);
|
||||
* pending work list (if queued).
|
||||
*/
|
||||
void linkwatch_sync_dev(struct net_device *dev);
|
||||
void __linkwatch_sync_dev(struct net_device *dev);
|
||||
|
||||
/**
|
||||
* 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 netif_set_allmulti(struct net_device *dev, int inc, bool notify);
|
||||
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_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);
|
||||
}
|
||||
|
||||
void netdev_set_operstate(struct net_device *dev, int newstate);
|
||||
void netif_set_operstate(struct net_device *dev, int newstate);
|
||||
|
||||
#endif /* __LINUX_RTNETLINK_H */
|
||||
|
@ -1518,15 +1518,7 @@ void netdev_features_change(struct net_device *dev)
|
||||
}
|
||||
EXPORT_SYMBOL(netdev_features_change);
|
||||
|
||||
/**
|
||||
* 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)
|
||||
void netif_state_change(struct net_device *dev)
|
||||
{
|
||||
if (dev->flags & IFF_UP) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL(netdev_state_change);
|
||||
|
||||
/**
|
||||
* __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;
|
||||
}
|
||||
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
|
||||
dev_deactivate(dev);
|
||||
|
||||
netdev_state_change(dev);
|
||||
netif_state_change(dev);
|
||||
}
|
||||
/* Note: our callers are responsible for calling netdev_tracker_free().
|
||||
* 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);
|
||||
spin_unlock_irq(&lweventlist_lock);
|
||||
netdev_lock_ops(dev);
|
||||
linkwatch_do_dev(dev);
|
||||
netdev_unlock_ops(dev);
|
||||
do_dev--;
|
||||
spin_lock_irq(&lweventlist_lock);
|
||||
}
|
||||
@ -253,25 +255,41 @@ static void __linkwatch_run_queue(int urgent_only)
|
||||
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;
|
||||
int clean = 0;
|
||||
bool clean = false;
|
||||
|
||||
spin_lock_irqsave(&lweventlist_lock, flags);
|
||||
if (!list_empty(&dev->link_watch_list)) {
|
||||
list_del_init(&dev->link_watch_list);
|
||||
clean = 1;
|
||||
clean = true;
|
||||
/* We must release netdev tracker under
|
||||
* the spinlock protection.
|
||||
*/
|
||||
netdev_tracker_free(dev, &dev->linkwatch_dev_tracker);
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
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 */
|
||||
void linkwatch_run_queue(void)
|
||||
|
@ -20,11 +20,11 @@ int netdev_debug_event(struct notifier_block *nb, unsigned long event,
|
||||
switch (cmd) {
|
||||
case NETDEV_REGISTER:
|
||||
case NETDEV_UP:
|
||||
case NETDEV_CHANGE:
|
||||
netdev_ops_assert_locked(dev);
|
||||
fallthrough;
|
||||
case NETDEV_DOWN:
|
||||
case NETDEV_REBOOT:
|
||||
case NETDEV_CHANGE:
|
||||
case NETDEV_UNREGISTER:
|
||||
case NETDEV_CHANGEMTU:
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
@ -1052,9 +1052,9 @@ void netdev_set_operstate(struct net_device *dev, int newstate)
|
||||
return;
|
||||
} 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)
|
||||
{
|
||||
@ -1080,7 +1080,7 @@ static void set_operstate(struct net_device *dev, unsigned char transition)
|
||||
break;
|
||||
}
|
||||
|
||||
netdev_set_operstate(dev, operstate);
|
||||
netif_set_operstate(dev, operstate);
|
||||
}
|
||||
|
||||
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:
|
||||
if (status & DO_SETLINK_MODIFIED) {
|
||||
if ((status & DO_SETLINK_NOTIFY) == DO_SETLINK_NOTIFY)
|
||||
netdev_state_change(dev);
|
||||
netif_state_change(dev);
|
||||
|
||||
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",
|
||||
@ -3676,8 +3676,11 @@ struct net_device *rtnl_create_link(struct net *net, const char *ifname,
|
||||
nla_len(tb[IFLA_BROADCAST]));
|
||||
if (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]));
|
||||
netdev_unlock_ops(dev);
|
||||
}
|
||||
if (tb[IFLA_LINKMODE])
|
||||
dev->link_mode = nla_get_u8(tb[IFLA_LINKMODE]);
|
||||
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)
|
||||
{
|
||||
/* 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;
|
||||
}
|
||||
|
@ -33,14 +33,14 @@ static void hsr_set_operstate(struct hsr_port *master, bool has_carrier)
|
||||
struct net_device *dev = master->dev;
|
||||
|
||||
if (!is_admin_up(dev)) {
|
||||
netdev_set_operstate(dev, IF_OPER_DOWN);
|
||||
netif_set_operstate(dev, IF_OPER_DOWN);
|
||||
return;
|
||||
}
|
||||
|
||||
if (has_carrier)
|
||||
netdev_set_operstate(dev, IF_OPER_UP);
|
||||
netif_set_operstate(dev, IF_OPER_UP);
|
||||
else
|
||||
netdev_set_operstate(dev, IF_OPER_LOWERLAYERDOWN);
|
||||
netif_set_operstate(dev, IF_OPER_LOWERLAYERDOWN);
|
||||
}
|
||||
|
||||
static bool hsr_check_carrier(struct hsr_port *master)
|
||||
|
Loading…
x
Reference in New Issue
Block a user