batman-adv: Avoid double-rtnl_lock ELP metric worker

batadv_v_elp_get_throughput() might be called when the RTNL lock is already
held. This could be problematic when the work queue item is cancelled via
cancel_delayed_work_sync() in batadv_v_elp_iface_disable(). In this case,
an rtnl_lock() would cause a deadlock.

To avoid this, rtnl_trylock() was used in this function to skip the
retrieval of the ethtool information in case the RTNL lock was already
held.

But for cfg80211 interfaces, batadv_get_real_netdev() was called - which
also uses rtnl_lock(). The approach for __ethtool_get_link_ksettings() must
also be used instead and the lockless version __batadv_get_real_netdev()
has to be called.

Cc: stable@vger.kernel.org
Fixes: 8c8ecc98f5 ("batman-adv: Drop unmanaged ELP metric worker")
Reported-by: Christian Schmidbauer <github@grische.xyz>
Signed-off-by: Sven Eckelmann <sven@narfation.org>
Tested-by: Sören Skaarup <freifunk_nordm4nn@gmx.de>
Signed-off-by: Simon Wunderlich <sw@simonwunderlich.de>
master
Sven Eckelmann 2026-02-16 11:20:29 +01:00 committed by Simon Wunderlich
parent 8f7aa3d3c7
commit cfc83a3c71
3 changed files with 14 additions and 5 deletions

View File

@ -111,7 +111,15 @@ static bool batadv_v_elp_get_throughput(struct batadv_hardif_neigh_node *neigh,
/* unsupported WiFi driver version */
goto default_throughput;
real_netdev = batadv_get_real_netdev(hard_iface->net_dev);
/* only use rtnl_trylock because the elp worker will be cancelled while
* the rntl_lock is held. the cancel_delayed_work_sync() would otherwise
* wait forever when the elp work_item was started and it is then also
* trying to rtnl_lock
*/
if (!rtnl_trylock())
return false;
real_netdev = __batadv_get_real_netdev(hard_iface->net_dev);
rtnl_unlock();
if (!real_netdev)
goto default_throughput;

View File

@ -204,7 +204,7 @@ static bool batadv_is_valid_iface(const struct net_device *net_dev)
}
/**
* batadv_get_real_netdevice() - check if the given netdev struct is a virtual
* __batadv_get_real_netdev() - check if the given netdev struct is a virtual
* interface on top of another 'real' interface
* @netdev: the device to check
*
@ -214,7 +214,7 @@ static bool batadv_is_valid_iface(const struct net_device *net_dev)
* Return: the 'real' net device or the original net device and NULL in case
* of an error.
*/
static struct net_device *batadv_get_real_netdevice(struct net_device *netdev)
struct net_device *__batadv_get_real_netdev(struct net_device *netdev)
{
struct batadv_hard_iface *hard_iface = NULL;
struct net_device *real_netdev = NULL;
@ -267,7 +267,7 @@ struct net_device *batadv_get_real_netdev(struct net_device *net_device)
struct net_device *real_netdev;
rtnl_lock();
real_netdev = batadv_get_real_netdevice(net_device);
real_netdev = __batadv_get_real_netdev(net_device);
rtnl_unlock();
return real_netdev;
@ -336,7 +336,7 @@ static u32 batadv_wifi_flags_evaluate(struct net_device *net_device)
if (batadv_is_cfg80211_netdev(net_device))
wifi_flags |= BATADV_HARDIF_WIFI_CFG80211_DIRECT;
real_netdev = batadv_get_real_netdevice(net_device);
real_netdev = __batadv_get_real_netdev(net_device);
if (!real_netdev)
return wifi_flags;

View File

@ -67,6 +67,7 @@ enum batadv_hard_if_bcast {
extern struct notifier_block batadv_hard_if_notifier;
struct net_device *__batadv_get_real_netdev(struct net_device *net_device);
struct net_device *batadv_get_real_netdev(struct net_device *net_device);
bool batadv_is_cfg80211_hardif(struct batadv_hard_iface *hard_iface);
bool batadv_is_wifi_hardif(struct batadv_hard_iface *hard_iface);