batman-adv: bound BLA claim and backbone gateway table growth
Subsystems (2)
PATCH 1/1 batman-adv: bound BLA claim and backbone gateway table growth
batadv_bla_add_claim() and batadv_bla_get_backbone_gw() each kzalloc a new hash entry (struct batadv_bla_claim / struct batadv_bla_backbone_gw) for every distinct (mac, vid) / (orig, vid) pair carried in a BLA-group-conforming CLAIM frame, and insert it into bat_priv->bla.claim_hash / backbone_hash. There is no maximum-entry cap on either table -- entries are only ever removed by the timeout-driven periodic purge (batadv_bla_purge_claims() / batadv_bla_purge_backbone_gw(), BATADV_BLA_CLAIM_TIMEOUT / BATADV_BLA_BACKBONE_TIMEOUT, on the order of 100 s).
The BLA group a frame must carry is htons(crc16(primary_hardif_mac)), which is trivially observable/derivable by any node already on the mesh soft-interface (batadv_check_claim_group()). A single on-mesh sender can therefore emit CLAIM frames with an incrementing source MAC and force one kzalloc(GFP_ATOMIC) per frame on both hot paths, growing kernel memory without bound for as long as the attacker keeps sending -- uncontrolled resource allocation. Both allocations are GFP_ATOMIC with a NULL check, so this is a graceful memory-pressure DoS, not a crash: there is no OOB access.
batman-adv already has an established pattern for capping an attacker-/peer-influenced, unbounded-growth per-mesh-interface
resource: the TP-meter session list bounds concurrent sessions with a fixed ceiling and an atomic_add_unless() admission check that rejects new allocations once the cap is hit, logging and freeing/decrementing on the abort path (net/batman-adv/tp_meter.c, BATADV_TP_MAX_NUM, bat_priv->tp_num, "Meter: too many ongoing sessions, aborting").
Apply the same pattern to BLA: add two atomic_t counters, bat_priv->bla.num_claims and bat_priv->bla.num_backbone_gws, each capped at a new BATADV_BLA_MAX_CLAIMS / BATADV_BLA_MAX_BACKBONE_GW limit (4096 / 256 -- generous for any real bridged LAN/VLAN population, several orders of magnitude below what would need to be sprayed to threaten memory availability). batadv_bla_add_claim() and batadv_bla_get_backbone_gw() reserve a slot with atomic_add_unless() before allocating; on cap-hit the frame is dropped (matching existing "drop silently, let the sender resync/backoff" BLA behaviour) instead of allocating. The reservation is released on every existing early-out (kzalloc failure, hash_add failure) and in the kref release paths (batadv_claim_release(), batadv_backbone_gw_release()), where the counters are decremented right before the objects are freed. No locking changes are needed: the counters are only ever touched via atomic ops, mirroring tp_num.
This does not change on-the-wire behaviour, hash table sizing, or timeout-based purging; it only stops a single on-mesh peer from growing the tables past a bounded ceiling.
Verified by code review rather than by driving either counter to its cap at runtime: the atomic_add_unless()/atomic_dec() pairing was checked against every existing early-out (kzalloc failure, hash_add failure) and against both kref release callbacks, confirming exactly one reservation and one release per entry, mirroring the same tp_num accounting in tp_meter.c. A loopback CLAIM-frame reproducer was used earlier to confirm the pre-fix unbounded growth itself (distinct claim_hash/backbone_hash entries scale linearly with the number of distinct (mac, vid) pairs sent), but reaching the new 4096 / 256 caps with that same reproducer is impractical: entries age out via the existing timeout-driven purge faster than a single-host reproducer can accumulate enough distinct pairs to hit the ceiling, so the cap-hit and slot-release paths were exercised by inspection, not by a live saturation run.
--- net/batman-adv/bridge_loop_avoidance.c | 38 ++++++++++++++++++++++++-- net/batman-adv/main.h | 3 ++ net/batman-adv/types.h | 6 ++++ 3 files changed, 45 insertions(+), 2 deletions(-)
net/batman-adv/bridge_loop_avoidance.c +36 -2
@@ -154,6 +154,9 @@ static void batadv_backbone_gw_release(struct kref *ref) backbone_gw = container_of(ref, struct batadv_bla_backbone_gw, refcount); +/* release the slot reserved in batadv_bla_get_backbone_gw() */ +atomic_dec (&backbone_gw->bat_priv->bla.num_backbone_gws); +kfree_rcu (backbone_gw, rcu); }
@@ -179,6 +182,7 @@ static void batadv_claim_release(struct kref *ref) {struct batadv_bla_claim *claim;struct batadv_bla_backbone_gw *old_backbone_gw; +struct batadv_priv *bat_priv; claim = container_of(ref, struct batadv_bla_claim, refcount);
@@ -187,12 +191,18 @@ static void batadv_claim_release(struct kref *ref) claim->backbone_gw =NULL ;spin_unlock_bh (&claim->backbone_lock); +/* stash bat_priv before dropping our reference on old_backbone_gw */ + bat_priv = old_backbone_gw->bat_priv; +spin_lock_bh (&old_backbone_gw->crc_lock); old_backbone_gw->crc ^=crc16 (0 , claim->addr,ETH_ALEN );spin_unlock_bh (&old_backbone_gw->crc_lock);batadv_backbone_gw_put (old_backbone_gw); +/* release the slot reserved in batadv_bla_add_claim() */ +atomic_dec (&bat_priv->bla.num_claims); +kfree_rcu (claim, rcu); }
@@ -508,9 +518,20 @@ 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 (!atomic_add_unless (&bat_priv->bla.num_backbone_gws,1 , +BATADV_BLA_MAX_BACKBONE_GW )) { +batadv_dbg (BATADV_DBG_BLA , bat_priv, +"%s(): too many backbone gateways (limit %d), dropping (%pM, %d)\n" , + __func__,BATADV_BLA_MAX_BACKBONE_GW , orig, +batadv_print_vid (vid)); +return NULL ; + } + entry =kzalloc_obj (*entry,GFP_ATOMIC ); -if (!entry) +if (!entry) { +atomic_dec (&bat_priv->bla.num_backbone_gws);return NULL ; + } entry->vid = vid;WRITE_ONCE (entry->lasttime, jiffies);
@@ -531,6 +552,7 @@ batadv_bla_get_backbone_gw(struct batadv_priv *bat_priv, const u8 *orig,if (unlikely (hash_added !=0 )) {/* hash failed, free the structure */ +atomic_dec (&bat_priv->bla.num_backbone_gws);kfree (entry);return NULL ; }
@@ -708,9 +730,20 @@ static void batadv_bla_add_claim(struct batadv_priv *bat_priv,/* create a new claim entry if it does not exist yet. */ if (!claim) { +if (!atomic_add_unless (&bat_priv->bla.num_claims,1 , +BATADV_BLA_MAX_CLAIMS )) { +batadv_dbg (BATADV_DBG_BLA , bat_priv, +"%s(): too many claims (limit %d), dropping %pM, vid %d\n" , + __func__,BATADV_BLA_MAX_CLAIMS , mac, +batadv_print_vid (vid)); +return ; + } + claim =kzalloc_obj (*claim,GFP_ATOMIC ); -if (!claim) +if (!claim) { +atomic_dec (&bat_priv->bla.num_claims);return ; + }ether_addr_copy (claim->addr, mac);spin_lock_init (&claim->backbone_lock);
@@ -732,6 +765,7 @@ static void batadv_bla_add_claim(struct batadv_priv *bat_priv,if (unlikely (hash_added !=0 )) {/* only local changes happened. */ +atomic_dec (&bat_priv->bla.num_claims);batadv_backbone_gw_put (backbone_gw);kfree (claim);return ;
net/batman-adv/main.h +3 -0
@@ -110,6 +110,9 @@#define BATADV_BLA_WAIT_PERIODS 3#define BATADV_BLA_LOOPDETECT_PERIODS 6#define BATADV_BLA_LOOPDETECT_TIMEOUT 3000/* 3 seconds */ +/* upper bound on claim_hash / backbone_hash entries per mesh interface */ +#define BATADV_BLA_MAX_CLAIMS 4096 +#define BATADV_BLA_MAX_BACKBONE_GW 256#define BATADV_DUPLIST_SIZE 16#define BATADV_DUPLIST_TIMEOUT 500/* 500 ms */
net/batman-adv/types.h +6 -0
@@ -1077,6 +1077,12 @@ struct batadv_priv_bla {*/ spinlock_t num_requests_lock; +/** @num_claims: number of entries currently in @claim_hash */ +atomic_t num_claims; + +/** @num_backbone_gws: number of entries currently in @backbone_hash */ +atomic_t num_backbone_gws; +/** * @claim_hash: hash table containing mesh nodes this host has claimed */
Signature · 1 line
2.50.1 (Apple Git-155)
Sven Eckelmann
maintainerQuoted 67 lines · On Friday, 10 July 2026 18:52:24 CEST Ibrahim Hashimov wrote:
batadv_bla_add_claim()andbatadv_bla_get_backbone_gw()each kzalloc a new hash entry (structbatadv_bla_claim/ structbatadv_bla_backbone_gw) for every distinct (mac, vid) / (orig, vid) pair carried in a BLA-group-conforming CLAIM frame, and insert it intobat_priv->bla.claim_hash/backbone_hash. There is no maximum-entry cap on either table -- entries are only ever removed by the timeout-driven periodic purge (batadv_bla_purge_claims()/batadv_bla_purge_backbone_gw(),BATADV_BLA_CLAIM_TIMEOUT/BATADV_BLA_BACKBONE_TIMEOUT, on the order of 100 s).The BLA group a frame must carry is htons(crc16(primary_hardif_mac)), which is trivially observable/derivable by any node already on the mesh soft-interface (
batadv_check_claim_group()). A single on-mesh sender can therefore emit CLAIM frames with an incrementing source MAC and force onekzalloc(GFP_ATOMIC)per frame on both hot paths, growing kernel memory without bound for as long as the attacker keeps sending -- uncontrolled resource allocation. Both allocations areGFP_ATOMICwith a NULL check, so this is a graceful memory-pressure DoS, not a crash: there is no OOB access.batman-adv already has an established pattern for capping an attacker-/peer-influenced, unbounded-growth per-mesh-interface
resource: the TP-meter session list bounds concurrent sessions with a fixed ceiling and anatomic_add_unless()admission check that rejects new allocations once the cap is hit, logging and freeing/decrementing on the abort path (net/batman-adv/tp_meter.c,BATADV_TP_MAX_NUM,bat_priv->tp_num, "Meter: too many ongoing sessions, aborting").Apply the same pattern to BLA: add two
atomic_tcounters,bat_priv->bla.num_claimsandbat_priv->bla.num_backbone_gws, each capped at a newBATADV_BLA_MAX_CLAIMS/BATADV_BLA_MAX_BACKBONE_GWlimit (4096 / 256 -- generous for any real bridged LAN/VLAN population, several orders of magnitude below what would need to be sprayed to threaten memory availability).batadv_bla_add_claim()andbatadv_bla_get_backbone_gw()reserve a slot withatomic_add_unless()before allocating; on cap-hit the frame is dropped (matching existing "drop silently, let the sender resync/backoff" BLA behaviour) instead of allocating. The reservation is released on every existing early-out (kzalloc failure,hash_addfailure) and in the kref release paths (batadv_claim_release(),batadv_backbone_gw_release()), where the counters are decremented right before the objects are freed. No locking changes are needed: the counters are only ever touched via atomic ops, mirroringtp_num.This does not change on-the-wire behaviour, hash table sizing, or timeout-based purging; it only stops a single on-mesh peer from growing the tables past a bounded ceiling.
Verified by code review rather than by driving either counter to its cap at runtime: the atomic_add_unless()/atomic_dec() pairing was checked against every existing early-out (kzalloc failure,
hash_addfailure) and against both kref release callbacks, confirming exactly one reservation and one release per entry, mirroring the sametp_numaccounting intp_meter.c. A loopback CLAIM-frame reproducer was used earlier to confirm the pre-fix unbounded growth itself (distinct claim_hash/backbone_hash entries scale linearly with the number of distinct (mac, vid) pairs sent), but reaching the new 4096 / 256 caps with that same reproducer is impractical: entries age out via the existing timeout-driven purge faster than a single-host reproducer can accumulate enough distinct pairs to hit the ceiling, so the cap-hit and slot-release paths were exercised by inspection, not by a live saturation run.Fixes 23721387c409 ("batman-adv: add basic bridge loop avoidance code")Signed-off-by Ibrahim Hashimov <[email protected]>Assisted-by AuditCode-AI:2026.07
Quoted 5 lines
--- net/batman-adv/bridge_loop_avoidance.c | 38 ++++++++++++++++++++++++-- net/batman-adv/main.h | 3 ++ net/batman-adv/types.h | 6 ++++ 3 files changed, 45 insertions(+), 2 deletions(-)
Consider this rejected:
* the target tree should be batadv and not "net"
* patch doesn't even apply to batadv.git
$ git am [email protected] Applying: batman-adv: bound BLA claim and backbone gateway table growth error: patch failed: net/batman-adv/bridge_loop_avoidance.c:179 error: net/batman-adv/bridge_loop_avoidance.c: patch does not apply Patch failed at 0001 batman-adv: bound BLA claim and backbone gateway table growth hint: Use 'git am --show-current-patch=diff' to see the failed patch hint: When you have resolved this problem, run "git am --continue". hint: If you prefer to skip this patch, run "git am --skip" instead. hint: To restore the original branch and stop patching, run "git am --abort". hint: Disable this message with "git config advice.mergeConflict false"
* there are already patches for these for review [1]
* breaks existing setups and doesn't allow users to adjust limits according to their requirements
* own backbones must never be prevented
* I start to get tired by all these overlong AI generated patch summaries which meander around completely irrelevant detail information - or information which are not even relevant here
Regards,
Sven
[1] https://patchwork.open-mesh.org/project/b.a.t.m.a.n./list/?series=726
https://git.open-mesh.org/pub/ecsv/batman-adv.git/log/?h=b4/resource-limit