[PATCH batadv v3 3/5] batman-adv: limit number of parallel learned DAT entries

Sven Eckelmann <[email protected]> Fri, 24 Jul 2026 08:51:40 +0200
Newsgroups org.open-mesh.lists.batman
Message-ID <[email protected]>
A malicious actor behind one bridge port may spam the kernel with ARP/DHCP
using a random MAC address, causing the creation of a DAT 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 dat.learned, and a limit in dat.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/distributed-arp-table.c | 17 +++++++++++++++--
 net/batman-adv/mesh-interface.c        |  3 +++
 net/batman-adv/netlink.c               | 10 ++++++++++
 net/batman-adv/types.h                 |  6 ++++++
 5 files changed, 40 insertions(+), 2 deletions(-)

diff --git a/include/uapi/linux/batman_adv.h b/include/uapi/linux/batman_adv.h
index 325cd687..184f2bc8 100644
--- a/include/uapi/linux/batman_adv.h
+++ b/include/uapi/linux/batman_adv.h
@@ -493,6 +493,12 @@ enum batadv_nl_attrs {
 	 */
 	BATADV_ATTR_ORIG_MAX_LEARNED,
 
+	/**
+	 * @BATADV_ATTR_DAT_MAX_LEARNED: defines the maximum number of DAT entries
+	 * which can be learned in parallel
+	 */
+	BATADV_ATTR_DAT_MAX_LEARNED,
+
 	/* add attributes above here, update the policy in netlink.c */
 
 	/**
diff --git a/net/batman-adv/distributed-arp-table.c b/net/batman-adv/distributed-arp-table.c
index bedd0cc7..9820a783 100644
--- a/net/batman-adv/distributed-arp-table.c
+++ b/net/batman-adv/distributed-arp-table.c
@@ -248,6 +248,7 @@ static void __batadv_dat_purge(struct batadv_priv *bat_priv,
 
 			hlist_del_rcu(&dat_entry->hash_entry);
 			batadv_dat_entry_put(dat_entry);
+			atomic_dec(&bat_priv->dat.learned);
 		}
 		spin_unlock_bh(list_lock);
 	}
@@ -446,6 +447,8 @@ static void batadv_dat_entry_add(struct batadv_priv *bat_priv, __be32 ip,
 {
 	u64 u64_mac = ether_addr_to_u64(mac_addr);
 	struct batadv_dat_entry *dat_entry;
+	u32 dat_max_learned;
+	int dat_learned;
 	int hash_added;
 
 	dat_entry = batadv_dat_entry_hash_find(bat_priv, ip, vid);
@@ -461,9 +464,18 @@ static void batadv_dat_entry_add(struct batadv_priv *bat_priv, __be32 ip,
 		goto out;
 	}
 
-	dat_entry = kmalloc_obj(*dat_entry, GFP_ATOMIC);
-	if (!dat_entry)
+	dat_max_learned = READ_ONCE(bat_priv->dat.max_learned);
+	dat_learned = atomic_inc_return(&bat_priv->dat.learned);
+	if (dat_max_learned && dat_learned > dat_max_learned) {
+		atomic_dec(&bat_priv->dat.learned);
 		goto out;
+	}
+
+	dat_entry = kmalloc_obj(*dat_entry, GFP_ATOMIC);
+	if (!dat_entry) {
+		atomic_dec(&bat_priv->dat.learned);
+		goto out;
+	}
 
 	dat_entry->ip = ip;
 	dat_entry->vid = vid;
@@ -479,6 +491,7 @@ static void batadv_dat_entry_add(struct batadv_priv *bat_priv, __be32 ip,
 	if (unlikely(hash_added != 0)) {
 		/* remove the reference for the hash */
 		batadv_dat_entry_put(dat_entry);
+		atomic_dec(&bat_priv->dat.learned);
 		goto out;
 	}
 
diff --git a/net/batman-adv/mesh-interface.c b/net/batman-adv/mesh-interface.c
index e9155ff3..a34fdd41 100644
--- a/net/batman-adv/mesh-interface.c
+++ b/net/batman-adv/mesh-interface.c
@@ -814,6 +814,9 @@ static int batadv_meshif_init_late(struct net_device *dev)
 #endif
 #ifdef CONFIG_BATMAN_ADV_DAT
 	WRITE_ONCE(bat_priv->distributed_arp_table, 1);
+
+	atomic_set(&bat_priv->dat.learned, 0);
+	WRITE_ONCE(bat_priv->dat.max_learned, 0);
 #endif
 #ifdef CONFIG_BATMAN_ADV_MCAST
 	WRITE_ONCE(bat_priv->multicast_mode, 1);
diff --git a/net/batman-adv/netlink.c b/net/batman-adv/netlink.c
index 41ed7aaf..d146d7c7 100644
--- a/net/batman-adv/netlink.c
+++ b/net/batman-adv/netlink.c
@@ -156,6 +156,7 @@ static const struct nla_policy batadv_netlink_policy[NUM_BATADV_ATTR] = {
 	[BATADV_ATTR_THROUGHPUT_OVERRIDE]	= { .type = NLA_U32 },
 	[BATADV_ATTR_NEIGH_MAX_LEARNED]		= { .type = NLA_U32 },
 	[BATADV_ATTR_ORIG_MAX_LEARNED]		= { .type = NLA_U32 },
+	[BATADV_ATTR_DAT_MAX_LEARNED]		= { .type = NLA_U32 },
 };
 
 /**
@@ -305,6 +306,10 @@ static int batadv_netlink_mesh_fill(struct sk_buff *msg,
 	if (nla_put_u8(msg, BATADV_ATTR_DISTRIBUTED_ARP_TABLE_ENABLED,
 		       !!READ_ONCE(bat_priv->distributed_arp_table)))
 		goto nla_put_failure;
+
+	if (nla_put_u32(msg, BATADV_ATTR_DAT_MAX_LEARNED,
+			READ_ONCE(bat_priv->dat.max_learned)))
+		goto nla_put_failure;
 #endif /* CONFIG_BATMAN_ADV_DAT */
 
 	if (nla_put_u8(msg, BATADV_ATTR_FRAGMENTATION_ENABLED,
@@ -495,6 +500,11 @@ static int batadv_netlink_set_mesh(struct sk_buff *skb, struct genl_info *info)
 			   !!nla_get_u8(attr));
 		batadv_dat_status_update(bat_priv->mesh_iface);
 	}
+
+	if (info->attrs[BATADV_ATTR_DAT_MAX_LEARNED]) {
+		WRITE_ONCE(bat_priv->dat.max_learned,
+			   nla_get_u32(info->attrs[BATADV_ATTR_DAT_MAX_LEARNED]));
+	}
 #endif /* CONFIG_BATMAN_ADV_DAT */
 
 	if (info->attrs[BATADV_ATTR_FRAGMENTATION_ENABLED]) {
diff --git a/net/batman-adv/types.h b/net/batman-adv/types.h
index 66096bfe..6001273b 100644
--- a/net/batman-adv/types.h
+++ b/net/batman-adv/types.h
@@ -1200,6 +1200,12 @@ struct batadv_priv_dat {
 
 	/** @work: work queue callback item for cache purging */
 	struct delayed_work work;
+
+	/** @max_learned: Maximum number of DAT entries */
+	u32 max_learned;
+
+	/** @learned: current number of learned DAT entries */
+	atomic_t learned;
 };
 #endif
 

-- 
2.47.3