]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
batman-adv: bla: fix report_work leak on backbone_gw purge
authorSven Eckelmann <sven@narfation.org>
Sun, 10 May 2026 09:43:20 +0000 (11:43 +0200)
committerSven Eckelmann <sven@narfation.org>
Tue, 19 May 2026 07:09:34 +0000 (09:09 +0200)
batadv_bla_purge_backbone_gw() removes stale backbone gateway entries,
but fails to properly handle their associated report_work:

- If report_work is running, the purge must wait for it to finish before
  freeing the backbone_gw, otherwise the worker may access freed memory
  (e.g. bat_priv).
- If report_work is pending, the purge must cancel it and release the
  reference held for that pending work item.

The previous implementation called hlist_for_each_entry_safe() inside a
spin_lock_bh() section, but cancel_work_sync() may sleep and therefore
cannot be called from within a spinlock-protected region.

Restructure the loop to handle one entry per spinlock critical section:
acquire the lock, find the next entry to purge, remove it from the hash
list, then release the lock before calling cancel_work_sync() and
dropping the hash_entry reference. Repeat until no more entries require
purging.

Cc: stable@kernel.org
Fixes: 23721387c409 ("batman-adv: add basic bridge loop avoidance code")
Reviewed-by: Simon Wunderlich <sw@simonwunderlich.de>
Signed-off-by: Sven Eckelmann <sven@narfation.org>
net/batman-adv/bridge_loop_avoidance.c

index cec11f1251d66a8b49587281abfded56ae7d278c..df1dfdf4a1a1255cee23bcf47fc556662a82c239 100644 (file)
@@ -1224,6 +1224,7 @@ static void batadv_bla_purge_backbone_gw(struct batadv_priv *bat_priv, int now)
        struct hlist_head *head;
        struct batadv_hashtable *hash;
        spinlock_t *list_lock;  /* protects write access to the hash lists */
+       bool purged;
        int i;
 
        hash = bat_priv->bla.backbone_hash;
@@ -1234,30 +1235,45 @@ static void batadv_bla_purge_backbone_gw(struct batadv_priv *bat_priv, int now)
                head = &hash->table[i];
                list_lock = &hash->list_locks[i];
 
-               spin_lock_bh(list_lock);
-               hlist_for_each_entry_safe(backbone_gw, node_tmp,
-                                         head, hash_entry) {
-                       if (now)
-                               goto purge_now;
-                       if (!batadv_has_timed_out(backbone_gw->lasttime,
-                                                 BATADV_BLA_BACKBONE_TIMEOUT))
-                               continue;
+               do {
+                       purged = false;
 
-                       batadv_dbg(BATADV_DBG_BLA, backbone_gw->bat_priv,
-                                  "%s(): backbone gw %pM timed out\n",
-                                  __func__, backbone_gw->orig);
+                       spin_lock_bh(list_lock);
+                       hlist_for_each_entry_safe(backbone_gw, node_tmp,
+                                                 head, hash_entry) {
+                               if (now)
+                                       goto purge_now;
+                               if (!batadv_has_timed_out(backbone_gw->lasttime,
+                                                         BATADV_BLA_BACKBONE_TIMEOUT))
+                                       continue;
+
+                               batadv_dbg(BATADV_DBG_BLA, backbone_gw->bat_priv,
+                                          "%s(): backbone gw %pM timed out\n",
+                                          __func__, backbone_gw->orig);
 
 purge_now:
-                       /* don't wait for the pending request anymore */
-                       if (atomic_read(&backbone_gw->request_sent))
-                               atomic_dec(&bat_priv->bla.num_requests);
+                               purged = true;
 
-                       batadv_bla_del_backbone_claims(backbone_gw);
+                               /* don't wait for the pending request anymore */
+                               if (atomic_read(&backbone_gw->request_sent))
+                                       atomic_dec(&bat_priv->bla.num_requests);
 
-                       hlist_del_rcu(&backbone_gw->hash_entry);
-                       batadv_backbone_gw_put(backbone_gw);
-               }
-               spin_unlock_bh(list_lock);
+                               batadv_bla_del_backbone_claims(backbone_gw);
+
+                               hlist_del_rcu(&backbone_gw->hash_entry);
+                               break;
+                       }
+                       spin_unlock_bh(list_lock);
+
+                       if (purged) {
+                               /* reference for pending report_work */
+                               if (cancel_work_sync(&backbone_gw->report_work))
+                                       batadv_backbone_gw_put(backbone_gw);
+
+                               /* reference for hash_entry */
+                               batadv_backbone_gw_put(backbone_gw);
+                       }
+               } while (purged);
        }
 }