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

Sven Eckelmann <[email protected]> Fri, 24 Jul 2026 08:51:41 +0200
Newsgroups org.open-mesh.lists.batman
Message-ID <[email protected]>
A malicious actor on the backbone Ethernet may spam the kernel with BLA
announcements 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
backbone, 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.backbone_learned, and a limit in
bla.backbone_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 | 25 ++++++++++++++++++++++++-
 net/batman-adv/mesh-interface.c        |  3 +++
 net/batman-adv/netlink.c               | 10 ++++++++++
 net/batman-adv/types.h                 |  9 +++++++++
 5 files changed, 52 insertions(+), 1 deletion(-)

diff --git a/include/uapi/linux/batman_adv.h b/include/uapi/linux/batman_adv.h
index 184f2bc8..33049818 100644
--- a/include/uapi/linux/batman_adv.h
+++ b/include/uapi/linux/batman_adv.h
@@ -499,6 +499,12 @@ enum batadv_nl_attrs {
 	 */
 	BATADV_ATTR_DAT_MAX_LEARNED,
 
+	/**
+	 * @BATADV_ATTR_BLA_BACKBONE_MAX_LEARNED: defines the maximum number of BLA backbones
+	 * which can be learned in parallel
+	 */
+	BATADV_ATTR_BLA_BACKBONE_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 f231afbe..949c887b 100644
--- a/net/batman-adv/bridge_loop_avoidance.c
+++ b/net/batman-adv/bridge_loop_avoidance.c
@@ -154,6 +154,9 @@ static void batadv_backbone_gw_release(struct kref *ref)
 	backbone_gw = container_of(ref, struct batadv_bla_backbone_gw,
 				   refcount);
 
+	if (!backbone_gw->own)
+		atomic_dec(&backbone_gw->bat_priv->bla.backbone_learned);
+
 	kfree_rcu(backbone_gw, rcu);
 }
 
@@ -488,6 +491,8 @@ batadv_bla_get_backbone_gw(struct batadv_priv *bat_priv, const u8 *orig,
 {
 	struct batadv_bla_backbone_gw *entry;
 	struct batadv_orig_node *orig_node;
+	u32 bla_backbone_max_learned;
+	int bla_backbone_learned;
 	int hash_added;
 
 	entry = batadv_backbone_hash_find(bat_priv, orig, vid);
@@ -499,11 +504,25 @@ batadv_bla_get_backbone_gw(struct batadv_priv *bat_priv, const u8 *orig,
 		   "%s(): not found (%pM, %d), creating new entry\n", __func__,
 		   orig, batadv_print_vid(vid));
 
+	if (!own_backbone) {
+		bla_backbone_max_learned = READ_ONCE(bat_priv->bla.backbone_max_learned);
+		bla_backbone_learned = atomic_inc_return(&bat_priv->bla.backbone_learned);
+		if (bla_backbone_max_learned && bla_backbone_learned > bla_backbone_max_learned) {
+			atomic_dec(&bat_priv->bla.backbone_learned);
+			return NULL;
+		}
+	}
+
 	entry = kzalloc_obj(*entry, GFP_ATOMIC);
-	if (!entry)
+	if (!entry) {
+		if (!own_backbone)
+			atomic_dec(&bat_priv->bla.backbone_learned);
+
 		return NULL;
+	}
 
 	entry->vid = vid;
+	entry->own = own_backbone;
 	WRITE_ONCE(entry->lasttime, jiffies);
 	entry->crc = BATADV_BLA_CRC_INIT;
 	entry->bat_priv = bat_priv;
@@ -523,6 +542,10 @@ batadv_bla_get_backbone_gw(struct batadv_priv *bat_priv, const u8 *orig,
 	if (unlikely(hash_added != 0)) {
 		/* hash failed, free the structure */
 		kfree(entry);
+
+		if (!own_backbone)
+			atomic_dec(&bat_priv->bla.backbone_learned);
+
 		return NULL;
 	}
 
diff --git a/net/batman-adv/mesh-interface.c b/net/batman-adv/mesh-interface.c
index a34fdd41..0e313ac8 100644
--- a/net/batman-adv/mesh-interface.c
+++ b/net/batman-adv/mesh-interface.c
@@ -846,6 +846,9 @@ static int batadv_meshif_init_late(struct net_device *dev)
 #ifdef CONFIG_BATMAN_ADV_BLA
 	atomic_set(&bat_priv->bla.num_requests, 0);
 	spin_lock_init(&bat_priv->bla.num_requests_lock);
+
+	atomic_set(&bat_priv->bla.backbone_learned, 0);
+	WRITE_ONCE(bat_priv->bla.backbone_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 d146d7c7..f49d7a5e 100644
--- a/net/batman-adv/netlink.c
+++ b/net/batman-adv/netlink.c
@@ -157,6 +157,7 @@ static const struct nla_policy batadv_netlink_policy[NUM_BATADV_ATTR] = {
 	[BATADV_ATTR_NEIGH_MAX_LEARNED]		= { .type = NLA_U32 },
 	[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 },
 };
 
 /**
@@ -300,6 +301,10 @@ static int batadv_netlink_mesh_fill(struct sk_buff *msg,
 	if (nla_put_u8(msg, BATADV_ATTR_BRIDGE_LOOP_AVOIDANCE_ENABLED,
 		       !!READ_ONCE(bat_priv->bridge_loop_avoidance)))
 		goto nla_put_failure;
+
+	if (nla_put_u32(msg, BATADV_ATTR_BLA_BACKBONE_MAX_LEARNED,
+			READ_ONCE(bat_priv->bla.backbone_max_learned)))
+		goto nla_put_failure;
 #endif /* CONFIG_BATMAN_ADV_BLA */
 
 #ifdef CONFIG_BATMAN_ADV_DAT
@@ -490,6 +495,11 @@ static int batadv_netlink_set_mesh(struct sk_buff *skb, struct genl_info *info)
 			   !!nla_get_u8(attr));
 		batadv_bla_status_update(bat_priv->mesh_iface);
 	}
+
+	if (info->attrs[BATADV_ATTR_BLA_BACKBONE_MAX_LEARNED]) {
+		WRITE_ONCE(bat_priv->bla.backbone_max_learned,
+			   nla_get_u32(info->attrs[BATADV_ATTR_BLA_BACKBONE_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 6001273b..22d47509 100644
--- a/net/batman-adv/types.h
+++ b/net/batman-adv/types.h
@@ -1071,6 +1071,12 @@ struct batadv_priv_bla {
 	/** @num_requests: number of bla requests in flight */
 	atomic_t num_requests;
 
+	/** @backbone_max_learned: Maximum number of backbone_gw */
+	u32 backbone_max_learned;
+
+	/** @backbone_learned: current number of learned backbone_gw entries */
+	atomic_t backbone_learned;
+
 	/**
 	 * @num_requests_lock: locks update num_requests +
 	 * batadv_backbone_gw::state + batadv_backbone_gw::wait_periods update
@@ -1791,6 +1797,9 @@ struct batadv_bla_backbone_gw {
 	/** @vid: vlan id this gateway was detected on */
 	unsigned short vid;
 
+	/** @own: it is the local backbone gateway */
+	bool own:1;
+
 	/** @hash_entry: hlist node for &batadv_priv_bla.backbone_hash */
 	struct hlist_node hash_entry;
 

-- 
2.47.3