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 canceled 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 called.

Fixes: 405d49a20a20 ("batman-adv: Drop unmanaged ELP metric worker")
Signed-off-by: Sven Eckelmann <[email protected]>
---
 net/batman-adv/bat_v_elp.c      | 10 +++++++++-
 net/batman-adv/hard-interface.c |  8 ++++----
 net/batman-adv/hard-interface.h |  1 +
 3 files changed, 14 insertions(+), 5 deletions(-)

diff --git a/net/batman-adv/bat_v_elp.c b/net/batman-adv/bat_v_elp.c
index cb16c1ed..fe832093 100644
--- a/net/batman-adv/bat_v_elp.c
+++ b/net/batman-adv/bat_v_elp.c
@@ -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;
 
diff --git a/net/batman-adv/hard-interface.c b/net/batman-adv/hard-interface.c
index 5113f879..1c488049 100644
--- a/net/batman-adv/hard-interface.c
+++ b/net/batman-adv/hard-interface.c
@@ -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;
 
diff --git a/net/batman-adv/hard-interface.h b/net/batman-adv/hard-interface.h
index 9db8a310..9ba8fb2b 100644
--- a/net/batman-adv/hard-interface.h
+++ b/net/batman-adv/hard-interface.h
@@ -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);

---
base-commit: 35208d1234584c536db431441445ce82f09273f6
change-id: 20260216-double-rtnl-lock-throughput-21b36bca629c

Best regards,
-- 
Sven Eckelmann <[email protected]>

Reply via email to