[PATCH batadv v3 5/5] batman-adv: limit number of parallel learned BLA claims

Sven Eckelmann <[email protected]> Fri, 24 Jul 2026 08:51:42 +0200
Newsgroups org.open-mesh.lists.batman
Message-ID <[email protected]>
A malicious actor on the backbone Ethernet may spam the kernel with BLA
claims using a random source MAC address and a valid group ID (local group
ID, or one that exists on the mesh), each of which will create a BLA claim,
a dynamic allocation in the kernel. This will at some point exhaust the
available memory.

Mitigate this by maintaining a per meshif count of those automatically
generated entries in bla.claim_learned, and a limit in
bla.claim_max_learned. If the limit is hit, new entries are not learned
anymore.

For backwards compatibility, the default setting of 0 disables the limit.

Signed-off-by: Sven Eckelmann <[email protected]>
---
 include/uapi/linux/batman_adv.h        |  6 ++++++
 net/batman-adv/bridge_loop_avoidance.c | 21 ++++++++++++++++++---
 net/batman-adv/mesh-interface.c        |  3 +++
 net/batman-adv/netlink.c               | 10 ++++++++++
 net/batman-adv/types.h                 |  6 ++++++
 5 files changed, 43 insertions(+), 3 deletions(-)

diff --git a/include/uapi/linux/batman_adv.h b/include/uapi/linux/batman_adv.h
index 33049818..460e75c4 100644
--- a/include/uapi/linux/batman_adv.h
+++ b/include/uapi/linux/batman_adv.h
@@ -505,6 +505,12 @@ enum batadv_nl_attrs {
 	 */
 	BATADV_ATTR_BLA_BACKBONE_MAX_LEARNED,
 
+	/**
+	 * @BATADV_ATTR_BLA_CLAIM_MAX_LEARNED: defines the maximum number of BLA claims
+	 * which can be learned in parallel
+	 */
+	BATADV_ATTR_BLA_CLAIM_MAX_LEARNED,
+
 	/* add attributes above here, update the policy in netlink.c */
 
 	/**
diff --git a/net/batman-adv/bridge_loop_avoidance.c b/net/batman-adv/bridge_loop_avoidance.c
index 949c887b..d8e7d7ad 100644
--- a/net/batman-adv/bridge_loop_avoidance.c
+++ b/net/batman-adv/bridge_loop_avoidance.c
@@ -182,6 +182,7 @@ static void batadv_claim_release(struct kref *ref)
 {
 	struct batadv_bla_backbone_gw *old_backbone_gw;
 	struct batadv_bla_claim *claim;
+	struct batadv_priv *bat_priv;
 
 	claim = container_of(ref, struct batadv_bla_claim, refcount);
 
@@ -194,8 +195,10 @@ static void batadv_claim_release(struct kref *ref)
 	old_backbone_gw->crc ^= crc16(0, claim->addr, ETH_ALEN);
 	spin_unlock_bh(&old_backbone_gw->crc_lock);
 
+	bat_priv = old_backbone_gw->bat_priv;
 	batadv_backbone_gw_put(old_backbone_gw);
 
+	atomic_dec(&bat_priv->bla.claim_learned);
 	kfree_rcu(claim, rcu);
 }
 
@@ -713,6 +716,8 @@ static void batadv_bla_add_claim(struct batadv_priv *bat_priv,
 	struct batadv_bla_backbone_gw *old_backbone_gw;
 	struct batadv_bla_claim search_claim;
 	struct batadv_bla_claim *claim;
+	u32 bla_claim_max_learned;
+	int bla_claim_learned;
 	int hash_added;
 	u16 claim_crc;
 	bool changed;
@@ -724,9 +729,18 @@ static void batadv_bla_add_claim(struct batadv_priv *bat_priv,
 
 	/* create a new claim entry if it does not exist yet. */
 	if (!claim) {
-		claim = kzalloc_obj(*claim, GFP_ATOMIC);
-		if (!claim)
+		bla_claim_max_learned = READ_ONCE(bat_priv->bla.claim_max_learned);
+		bla_claim_learned = atomic_inc_return(&bat_priv->bla.claim_learned);
+		if (bla_claim_max_learned && bla_claim_learned > bla_claim_max_learned) {
+			atomic_dec(&bat_priv->bla.claim_learned);
 			return;
+		}
+
+		claim = kzalloc_obj(*claim, GFP_ATOMIC);
+		if (!claim) {
+			atomic_dec(&bat_priv->bla.claim_learned);
+			return;
+		}
 
 		ether_addr_copy(claim->addr, mac);
 		spin_lock_init(&claim->backbone_lock);
@@ -747,8 +761,9 @@ static void batadv_bla_add_claim(struct batadv_priv *bat_priv,
 					     &claim->hash_entry);
 
 		if (unlikely(hash_added != 0)) {
-			/* only local changes happened. */
 			batadv_backbone_gw_put(backbone_gw);
+			atomic_dec(&bat_priv->bla.claim_learned);
+			/* only local changes happened. */
 			kfree(claim);
 			return;
 		}
diff --git a/net/batman-adv/mesh-interface.c b/net/batman-adv/mesh-interface.c
index 0e313ac8..4d83be55 100644
--- a/net/batman-adv/mesh-interface.c
+++ b/net/batman-adv/mesh-interface.c
@@ -849,6 +849,9 @@ static int batadv_meshif_init_late(struct net_device *dev)
 
 	atomic_set(&bat_priv->bla.backbone_learned, 0);
 	WRITE_ONCE(bat_priv->bla.backbone_max_learned, 0);
+
+	atomic_set(&bat_priv->bla.claim_learned, 0);
+	WRITE_ONCE(bat_priv->bla.claim_max_learned, 0);
 #endif
 	atomic_set(&bat_priv->tp_num, 0);
 
diff --git a/net/batman-adv/netlink.c b/net/batman-adv/netlink.c
index f49d7a5e..f2c7fd8d 100644
--- a/net/batman-adv/netlink.c
+++ b/net/batman-adv/netlink.c
@@ -158,6 +158,7 @@ static const struct nla_policy batadv_netlink_policy[NUM_BATADV_ATTR] = {
 	[BATADV_ATTR_ORIG_MAX_LEARNED]		= { .type = NLA_U32 },
 	[BATADV_ATTR_DAT_MAX_LEARNED]		= { .type = NLA_U32 },
 	[BATADV_ATTR_BLA_BACKBONE_MAX_LEARNED]	= { .type = NLA_U32 },
+	[BATADV_ATTR_BLA_CLAIM_MAX_LEARNED]	= { .type = NLA_U32 },
 };
 
 /**
@@ -305,6 +306,10 @@ static int batadv_netlink_mesh_fill(struct sk_buff *msg,
 	if (nla_put_u32(msg, BATADV_ATTR_BLA_BACKBONE_MAX_LEARNED,
 			READ_ONCE(bat_priv->bla.backbone_max_learned)))
 		goto nla_put_failure;
+
+	if (nla_put_u32(msg, BATADV_ATTR_BLA_CLAIM_MAX_LEARNED,
+			READ_ONCE(bat_priv->bla.claim_max_learned)))
+		goto nla_put_failure;
 #endif /* CONFIG_BATMAN_ADV_BLA */
 
 #ifdef CONFIG_BATMAN_ADV_DAT
@@ -500,6 +505,11 @@ static int batadv_netlink_set_mesh(struct sk_buff *skb, struct genl_info *info)
 		WRITE_ONCE(bat_priv->bla.backbone_max_learned,
 			   nla_get_u32(info->attrs[BATADV_ATTR_BLA_BACKBONE_MAX_LEARNED]));
 	}
+
+	if (info->attrs[BATADV_ATTR_BLA_CLAIM_MAX_LEARNED]) {
+		WRITE_ONCE(bat_priv->bla.claim_max_learned,
+			   nla_get_u32(info->attrs[BATADV_ATTR_BLA_CLAIM_MAX_LEARNED]));
+	}
 #endif /* CONFIG_BATMAN_ADV_BLA */
 
 #ifdef CONFIG_BATMAN_ADV_DAT
diff --git a/net/batman-adv/types.h b/net/batman-adv/types.h
index 22d47509..df2d5ccf 100644
--- a/net/batman-adv/types.h
+++ b/net/batman-adv/types.h
@@ -1077,6 +1077,12 @@ struct batadv_priv_bla {
 	/** @backbone_learned: current number of learned backbone_gw entries */
 	atomic_t backbone_learned;
 
+	/** @claim_max_learned: Maximum number of claims */
+	u32 claim_max_learned;
+
+	/** @claim_learned: current number of learned claim entries */
+	atomic_t claim_learned;
+
 	/**
 	 * @num_requests_lock: locks update num_requests +
 	 * batadv_backbone_gw::state + batadv_backbone_gw::wait_periods update

-- 
2.47.3