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

Sven Eckelmann <[email protected]> Fri, 24 Jul 2026 08:51:38 +0200
Newsgroups org.open-mesh.lists.batman
Message-ID <[email protected]>
A malicious actor behind one hardif may spam the kernel with OGMs using a
random source MAC address, each of which will create a neighbor node entry,
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 neigh_learned, and a limit in neigh_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/mesh-interface.c |  3 +++
 net/batman-adv/netlink.c        | 10 ++++++++++
 net/batman-adv/originator.c     | 19 +++++++++++++++++--
 net/batman-adv/types.h          |  9 +++++++++
 5 files changed, 45 insertions(+), 2 deletions(-)

diff --git a/include/uapi/linux/batman_adv.h b/include/uapi/linux/batman_adv.h
index 936bcac2..addb6092 100644
--- a/include/uapi/linux/batman_adv.h
+++ b/include/uapi/linux/batman_adv.h
@@ -481,6 +481,12 @@ enum batadv_nl_attrs {
 	 */
 	BATADV_ATTR_MULTICAST_FANOUT,
 
+	/**
+	 * @BATADV_ATTR_NEIGH_MAX_LEARNED: defines the maximum number of neighbors
+	 * which can be learned in parallel
+	 */
+	BATADV_ATTR_NEIGH_MAX_LEARNED,
+
 	/* add attributes above here, update the policy in netlink.c */
 
 	/**
diff --git a/net/batman-adv/mesh-interface.c b/net/batman-adv/mesh-interface.c
index f1bfe2a5..70e13150 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)
 #endif
 	atomic_set(&bat_priv->tp_num, 0);
 
+	atomic_set(&bat_priv->neigh_learned, 0);
+	WRITE_ONCE(bat_priv->neigh_max_learned, 0);
+
 	WRITE_ONCE(bat_priv->tt.local_changes, 0);
 	bat_priv->tt.last_changeset = NULL;
 	bat_priv->tt.last_changeset_len = 0;
diff --git a/net/batman-adv/netlink.c b/net/batman-adv/netlink.c
index bb1ef3ee..64bec41a 100644
--- a/net/batman-adv/netlink.c
+++ b/net/batman-adv/netlink.c
@@ -154,6 +154,7 @@ static const struct nla_policy batadv_netlink_policy[NUM_BATADV_ATTR] = {
 	[BATADV_ATTR_ORIG_INTERVAL]		= { .type = NLA_U32 },
 	[BATADV_ATTR_ELP_INTERVAL]		= { .type = NLA_U32 },
 	[BATADV_ATTR_THROUGHPUT_OVERRIDE]	= { .type = NLA_U32 },
+	[BATADV_ATTR_NEIGH_MAX_LEARNED]		= { .type = NLA_U32 },
 };
 
 /**
@@ -355,6 +356,10 @@ static int batadv_netlink_mesh_fill(struct sk_buff *msg,
 			READ_ONCE(bat_priv->orig_interval)))
 		goto nla_put_failure;
 
+	if (nla_put_u32(msg, BATADV_ATTR_NEIGH_MAX_LEARNED,
+			READ_ONCE(bat_priv->neigh_max_learned)))
+		goto nla_put_failure;
+
 	batadv_hardif_put(primary_if);
 
 	genlmsg_end(msg, hdr);
@@ -600,6 +605,11 @@ static int batadv_netlink_set_mesh(struct sk_buff *skb, struct genl_info *info)
 		WRITE_ONCE(bat_priv->orig_interval, orig_interval);
 	}
 
+	if (info->attrs[BATADV_ATTR_NEIGH_MAX_LEARNED]) {
+		WRITE_ONCE(bat_priv->neigh_max_learned,
+			   nla_get_u32(info->attrs[BATADV_ATTR_NEIGH_MAX_LEARNED]));
+	}
+
 	batadv_netlink_notify_mesh(bat_priv);
 
 	return 0;
diff --git a/net/batman-adv/originator.c b/net/batman-adv/originator.c
index f06583ef..d6a5bd7c 100644
--- a/net/batman-adv/originator.c
+++ b/net/batman-adv/originator.c
@@ -299,6 +299,7 @@ void batadv_neigh_node_release(struct kref *ref)
 
 	batadv_hardif_put(neigh_node->if_incoming);
 
+	atomic_dec(&neigh_node->bat_priv->neigh_learned);
 	kfree_rcu(neigh_node, rcu);
 }
 
@@ -675,6 +676,8 @@ batadv_neigh_node_create(struct batadv_orig_node *orig_node,
 {
 	struct batadv_hardif_neigh_node *hardif_neigh = NULL;
 	struct batadv_neigh_node *neigh_node;
+	u32 neigh_max_learned;
+	int neigh_learned;
 
 	spin_lock_bh(&orig_node->neigh_list_lock);
 
@@ -682,14 +685,25 @@ batadv_neigh_node_create(struct batadv_orig_node *orig_node,
 	if (neigh_node)
 		goto out;
 
+	neigh_max_learned = READ_ONCE(orig_node->bat_priv->neigh_max_learned);
+	neigh_learned = atomic_inc_return(&orig_node->bat_priv->neigh_learned);
+	if (neigh_max_learned && neigh_learned > neigh_max_learned) {
+		atomic_dec(&orig_node->bat_priv->neigh_learned);
+		goto out;
+	}
+
 	hardif_neigh = batadv_hardif_neigh_get_or_create(hard_iface,
 							 neigh_addr, orig_node);
-	if (!hardif_neigh)
+	if (!hardif_neigh) {
+		atomic_dec(&orig_node->bat_priv->neigh_learned);
 		goto out;
+	}
 
 	neigh_node = kzalloc_obj(*neigh_node, GFP_ATOMIC);
-	if (!neigh_node)
+	if (!neigh_node) {
+		atomic_dec(&orig_node->bat_priv->neigh_learned);
 		goto out;
+	}
 
 	INIT_HLIST_NODE(&neigh_node->list);
 	INIT_HLIST_HEAD(&neigh_node->ifinfo_list);
@@ -699,6 +713,7 @@ batadv_neigh_node_create(struct batadv_orig_node *orig_node,
 	ether_addr_copy(neigh_node->addr, neigh_addr);
 	neigh_node->if_incoming = hard_iface;
 	neigh_node->last_seen = jiffies;
+	neigh_node->bat_priv = orig_node->bat_priv;
 
 #ifdef CONFIG_BATMAN_ADV_BATMAN_V
 	ACCESS_PRIVATE(neigh_node, orig_node_id) = orig_node;
diff --git a/net/batman-adv/types.h b/net/batman-adv/types.h
index d6e15d46..e404a6b4 100644
--- a/net/batman-adv/types.h
+++ b/net/batman-adv/types.h
@@ -679,6 +679,9 @@ struct batadv_neigh_node {
 	/** @addr: the MAC address of the neighboring interface */
 	u8 addr[ETH_ALEN];
 
+	/** @bat_priv: pointer to the mesh object */
+	struct batadv_priv *bat_priv;
+
 	/** @ifinfo_list: list for routing metrics per outgoing interface */
 	struct hlist_head ifinfo_list;
 
@@ -1700,6 +1703,12 @@ struct batadv_priv {
 	/** @meshif_vlan_list_lock: lock protecting meshif_vlan_list */
 	spinlock_t meshif_vlan_list_lock;
 
+	/** @neigh_max_learned: Maximum number of neighbors */
+	u32 neigh_max_learned;
+
+	/** @neigh_learned: current number of learned neighbors */
+	atomic_t neigh_learned;
+
 #ifdef CONFIG_BATMAN_ADV_BLA
 	/** @bla: bridge loop avoidance data */
 	struct batadv_priv_bla bla;

-- 
2.47.3