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

Sven Eckelmann <[email protected]> Fri, 24 Jul 2026 08:51:39 +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 an originator 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 orig_learned, and a limit in orig_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/bat_iv_ogm.c     | 15 +++++++++++++--
 net/batman-adv/bat_v_ogm.c      | 15 +++++++++++++--
 net/batman-adv/mesh-interface.c |  3 +++
 net/batman-adv/netlink.c        | 10 ++++++++++
 net/batman-adv/originator.c     |  3 +++
 net/batman-adv/types.h          |  6 ++++++
 7 files changed, 54 insertions(+), 4 deletions(-)

diff --git a/include/uapi/linux/batman_adv.h b/include/uapi/linux/batman_adv.h
index addb6092..325cd687 100644
--- a/include/uapi/linux/batman_adv.h
+++ b/include/uapi/linux/batman_adv.h
@@ -487,6 +487,12 @@ enum batadv_nl_attrs {
 	 */
 	BATADV_ATTR_NEIGH_MAX_LEARNED,
 
+	/**
+	 * @BATADV_ATTR_ORIG_MAX_LEARNED: defines the maximum number of originators
+	 * which can be learned in parallel
+	 */
+	BATADV_ATTR_ORIG_MAX_LEARNED,
+
 	/* add attributes above here, update the policy in netlink.c */
 
 	/**
diff --git a/net/batman-adv/bat_iv_ogm.c b/net/batman-adv/bat_iv_ogm.c
index aff279b8..0efba915 100644
--- a/net/batman-adv/bat_iv_ogm.c
+++ b/net/batman-adv/bat_iv_ogm.c
@@ -141,15 +141,26 @@ static struct batadv_orig_node *
 batadv_iv_ogm_orig_get(struct batadv_priv *bat_priv, const u8 *addr)
 {
 	struct batadv_orig_node *orig_node;
+	u32 orig_max_learned;
+	int orig_learned;
 	int hash_added;
 
 	orig_node = batadv_orig_hash_find(bat_priv, addr);
 	if (orig_node)
 		return orig_node;
 
-	orig_node = batadv_orig_node_new(bat_priv, addr);
-	if (!orig_node)
+	orig_max_learned = READ_ONCE(bat_priv->orig_max_learned);
+	orig_learned = atomic_inc_return(&bat_priv->orig_learned);
+	if (orig_max_learned && orig_learned > orig_max_learned) {
+		atomic_dec(&bat_priv->orig_learned);
 		return NULL;
+	}
+
+	orig_node = batadv_orig_node_new(bat_priv, addr);
+	if (!orig_node) {
+		atomic_dec(&bat_priv->orig_learned);
+		return NULL;
+	}
 
 	spin_lock_init(&orig_node->bat_iv.ogm_cnt_lock);
 
diff --git a/net/batman-adv/bat_v_ogm.c b/net/batman-adv/bat_v_ogm.c
index 70846c99..c1354291 100644
--- a/net/batman-adv/bat_v_ogm.c
+++ b/net/batman-adv/bat_v_ogm.c
@@ -56,15 +56,26 @@ struct batadv_orig_node *batadv_v_ogm_orig_get(struct batadv_priv *bat_priv,
 					       const u8 *addr)
 {
 	struct batadv_orig_node *orig_node;
+	u32 orig_max_learned;
+	int orig_learned;
 	int hash_added;
 
 	orig_node = batadv_orig_hash_find(bat_priv, addr);
 	if (orig_node)
 		return orig_node;
 
-	orig_node = batadv_orig_node_new(bat_priv, addr);
-	if (!orig_node)
+	orig_max_learned = READ_ONCE(bat_priv->orig_max_learned);
+	orig_learned = atomic_inc_return(&bat_priv->orig_learned);
+	if (orig_max_learned && orig_learned > orig_max_learned) {
+		atomic_dec(&bat_priv->orig_learned);
 		return NULL;
+	}
+
+	orig_node = batadv_orig_node_new(bat_priv, addr);
+	if (!orig_node) {
+		atomic_dec(&bat_priv->orig_learned);
+		return NULL;
+	}
 
 	kref_get(&orig_node->refcount);
 	hash_added = batadv_hash_add(bat_priv->orig_hash, batadv_compare_orig,
diff --git a/net/batman-adv/mesh-interface.c b/net/batman-adv/mesh-interface.c
index 70e13150..e9155ff3 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->neigh_learned, 0);
 	WRITE_ONCE(bat_priv->neigh_max_learned, 0);
 
+	atomic_set(&bat_priv->orig_learned, 0);
+	WRITE_ONCE(bat_priv->orig_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 64bec41a..41ed7aaf 100644
--- a/net/batman-adv/netlink.c
+++ b/net/batman-adv/netlink.c
@@ -155,6 +155,7 @@ static const struct nla_policy batadv_netlink_policy[NUM_BATADV_ATTR] = {
 	[BATADV_ATTR_ELP_INTERVAL]		= { .type = NLA_U32 },
 	[BATADV_ATTR_THROUGHPUT_OVERRIDE]	= { .type = NLA_U32 },
 	[BATADV_ATTR_NEIGH_MAX_LEARNED]		= { .type = NLA_U32 },
+	[BATADV_ATTR_ORIG_MAX_LEARNED]		= { .type = NLA_U32 },
 };
 
 /**
@@ -360,6 +361,10 @@ static int batadv_netlink_mesh_fill(struct sk_buff *msg,
 			READ_ONCE(bat_priv->neigh_max_learned)))
 		goto nla_put_failure;
 
+	if (nla_put_u32(msg, BATADV_ATTR_ORIG_MAX_LEARNED,
+			READ_ONCE(bat_priv->orig_max_learned)))
+		goto nla_put_failure;
+
 	batadv_hardif_put(primary_if);
 
 	genlmsg_end(msg, hdr);
@@ -610,6 +615,11 @@ static int batadv_netlink_set_mesh(struct sk_buff *skb, struct genl_info *info)
 			   nla_get_u32(info->attrs[BATADV_ATTR_NEIGH_MAX_LEARNED]));
 	}
 
+	if (info->attrs[BATADV_ATTR_ORIG_MAX_LEARNED]) {
+		WRITE_ONCE(bat_priv->orig_max_learned,
+			   nla_get_u32(info->attrs[BATADV_ATTR_ORIG_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 d6a5bd7c..f2910366 100644
--- a/net/batman-adv/originator.c
+++ b/net/batman-adv/originator.c
@@ -876,9 +876,11 @@ void batadv_orig_node_release(struct kref *ref)
 	struct batadv_neigh_node *neigh_node;
 	struct batadv_orig_node *orig_node;
 	struct batadv_orig_node_vlan *vlan;
+	struct batadv_priv *bat_priv;
 	struct hlist_node *node_tmp;
 
 	orig_node = container_of(ref, struct batadv_orig_node, refcount);
+	bat_priv = orig_node->bat_priv;
 
 	spin_lock_bh(&orig_node->neigh_list_lock);
 
@@ -910,6 +912,7 @@ void batadv_orig_node_release(struct kref *ref)
 
 	batadv_mcast_purge_orig(orig_node);
 
+	atomic_dec(&bat_priv->orig_learned);
 	call_rcu(&orig_node->rcu, batadv_orig_node_free_rcu);
 }
 
diff --git a/net/batman-adv/types.h b/net/batman-adv/types.h
index e404a6b4..66096bfe 100644
--- a/net/batman-adv/types.h
+++ b/net/batman-adv/types.h
@@ -1709,6 +1709,12 @@ struct batadv_priv {
 	/** @neigh_learned: current number of learned neighbors */
 	atomic_t neigh_learned;
 
+	/** @orig_max_learned: Maximum number of originators */
+	u32 orig_max_learned;
+
+	/** @orig_learned: current number of learned originators */
+	atomic_t orig_learned;
+
 #ifdef CONFIG_BATMAN_ADV_BLA
 	/** @bla: bridge loop avoidance data */
 	struct batadv_priv_bla bla;

-- 
2.47.3