--- /dev/null
+From 836f1b3edeffe28bc8547cde960bed26f4936979 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:09:38 +0200
+Subject: batman-adv: bla: annotate lasttime access with READ/WRITE_ONCE
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 98b0fb191c878a64cbaebfe231d96d57576acf8c upstream.
+
+The lasttime field for claim, backbone_gw, and loopdetect tracks the
+jiffies value of the most recent activity and is used to detect timeouts.
+These accesses are not consistently protected by a lock, so
+READ_ONCE/WRITE_ONCE must be used to prevent data races caused by compiler
+optimizations.
+
+Cc: stable@kernel.org
+Fixes: 23721387c409 ("batman-adv: add basic bridge loop avoidance code")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/bridge_loop_avoidance.c | 28 +++++++++++++-------------
+ 1 file changed, 14 insertions(+), 14 deletions(-)
+
+diff --git a/net/batman-adv/bridge_loop_avoidance.c b/net/batman-adv/bridge_loop_avoidance.c
+index 00185ed4940f23..5593ac0aae378a 100644
+--- a/net/batman-adv/bridge_loop_avoidance.c
++++ b/net/batman-adv/bridge_loop_avoidance.c
+@@ -517,7 +517,7 @@ batadv_bla_get_backbone_gw(struct batadv_priv *bat_priv, u8 *orig,
+ return NULL;
+
+ entry->vid = vid;
+- entry->lasttime = jiffies;
++ WRITE_ONCE(entry->lasttime, jiffies);
+ entry->crc = BATADV_BLA_CRC_INIT;
+ entry->bat_priv = bat_priv;
+ spin_lock_init(&entry->crc_lock);
+@@ -585,7 +585,7 @@ batadv_bla_update_own_backbone_gw(struct batadv_priv *bat_priv,
+ if (unlikely(!backbone_gw))
+ return;
+
+- backbone_gw->lasttime = jiffies;
++ WRITE_ONCE(backbone_gw->lasttime, jiffies);
+ batadv_backbone_gw_put(backbone_gw);
+ }
+
+@@ -719,7 +719,7 @@ static void batadv_bla_add_claim(struct batadv_priv *bat_priv,
+ ether_addr_copy(claim->addr, mac);
+ spin_lock_init(&claim->backbone_lock);
+ claim->vid = vid;
+- claim->lasttime = jiffies;
++ WRITE_ONCE(claim->lasttime, jiffies);
+ kref_get(&backbone_gw->refcount);
+ claim->backbone_gw = backbone_gw;
+ kref_init(&claim->refcount);
+@@ -741,7 +741,7 @@ static void batadv_bla_add_claim(struct batadv_priv *bat_priv,
+ return;
+ }
+ } else {
+- claim->lasttime = jiffies;
++ WRITE_ONCE(claim->lasttime, jiffies);
+ if (claim->backbone_gw == backbone_gw)
+ /* no need to register a new backbone */
+ goto claim_free_ref;
+@@ -774,7 +774,7 @@ static void batadv_bla_add_claim(struct batadv_priv *bat_priv,
+ spin_lock_bh(&backbone_gw->crc_lock);
+ backbone_gw->crc ^= crc16(0, claim->addr, ETH_ALEN);
+ spin_unlock_bh(&backbone_gw->crc_lock);
+- backbone_gw->lasttime = jiffies;
++ WRITE_ONCE(backbone_gw->lasttime, jiffies);
+
+ claim_free_ref:
+ batadv_claim_put(claim);
+@@ -863,7 +863,7 @@ static bool batadv_handle_announce(struct batadv_priv *bat_priv, u8 *an_addr,
+ return true;
+
+ /* handle as ANNOUNCE frame */
+- backbone_gw->lasttime = jiffies;
++ WRITE_ONCE(backbone_gw->lasttime, jiffies);
+ crc = ntohs(*((__force __be16 *)(&an_addr[4])));
+
+ batadv_dbg(BATADV_DBG_BLA, bat_priv,
+@@ -1258,7 +1258,7 @@ static void batadv_bla_purge_backbone_gw(struct batadv_priv *bat_priv, int now)
+ head, hash_entry) {
+ if (now)
+ goto purge_now;
+- if (!batadv_has_timed_out(backbone_gw->lasttime,
++ if (!batadv_has_timed_out(READ_ONCE(backbone_gw->lasttime),
+ BATADV_BLA_BACKBONE_TIMEOUT))
+ continue;
+
+@@ -1339,7 +1339,7 @@ static void batadv_bla_purge_claims(struct batadv_priv *bat_priv,
+ primary_if->net_dev->dev_addr))
+ goto skip;
+
+- if (!batadv_has_timed_out(claim->lasttime,
++ if (!batadv_has_timed_out(READ_ONCE(claim->lasttime),
+ BATADV_BLA_CLAIM_TIMEOUT))
+ goto skip;
+
+@@ -1499,7 +1499,7 @@ static void batadv_bla_periodic_work(struct work_struct *work)
+ eth_random_addr(bat_priv->bla.loopdetect_addr);
+ bat_priv->bla.loopdetect_addr[0] = 0xba;
+ bat_priv->bla.loopdetect_addr[1] = 0xbe;
+- bat_priv->bla.loopdetect_lasttime = jiffies;
++ WRITE_ONCE(bat_priv->bla.loopdetect_lasttime, jiffies);
+ atomic_set(&bat_priv->bla.loopdetect_next,
+ BATADV_BLA_LOOPDETECT_PERIODS);
+
+@@ -1520,7 +1520,7 @@ static void batadv_bla_periodic_work(struct work_struct *work)
+ primary_if->net_dev->dev_addr))
+ continue;
+
+- backbone_gw->lasttime = jiffies;
++ WRITE_ONCE(backbone_gw->lasttime, jiffies);
+
+ batadv_bla_send_announce(bat_priv, backbone_gw);
+ if (send_loopdetect)
+@@ -1907,7 +1907,7 @@ batadv_bla_loopdetect_check(struct batadv_priv *bat_priv, struct sk_buff *skb,
+ /* If the packet came too late, don't forward it on the mesh
+ * but don't consider that as loop. It might be a coincidence.
+ */
+- if (batadv_has_timed_out(bat_priv->bla.loopdetect_lasttime,
++ if (batadv_has_timed_out(READ_ONCE(bat_priv->bla.loopdetect_lasttime),
+ BATADV_BLA_LOOPDETECT_TIMEOUT))
+ return true;
+
+@@ -2023,7 +2023,7 @@ bool batadv_bla_rx(struct batadv_priv *bat_priv, struct sk_buff *skb,
+
+ if (own_claim) {
+ /* ... allow it in any case */
+- claim->lasttime = jiffies;
++ WRITE_ONCE(claim->lasttime, jiffies);
+ goto allow;
+ }
+
+@@ -2127,7 +2127,7 @@ bool batadv_bla_tx(struct batadv_priv *bat_priv, struct sk_buff *skb,
+ /* if yes, the client has roamed and we have
+ * to unclaim it.
+ */
+- if (batadv_has_timed_out(claim->lasttime, 100)) {
++ if (batadv_has_timed_out(READ_ONCE(claim->lasttime), 100)) {
+ /* only unclaim if the last claim entry is
+ * older than 100 ms to make sure we really
+ * have a roaming client here.
+@@ -2513,7 +2513,7 @@ batadv_bla_backbone_dump_entry(struct sk_buff *msg, u32 portid,
+ backbone_crc = backbone_gw->crc;
+ spin_unlock_bh(&backbone_gw->crc_lock);
+
+- msecs = jiffies_to_msecs(jiffies - backbone_gw->lasttime);
++ msecs = jiffies_to_msecs(jiffies - READ_ONCE(backbone_gw->lasttime));
+
+ if (is_own)
+ if (nla_put_flag(msg, BATADV_ATTR_BLA_OWN)) {
+--
+2.53.0
+
--- /dev/null
+From d4205c11455b8ac4b18ee868a6d8dacf11d0dc13 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:09:50 +0200
+Subject: batman-adv: dat: prevent false sharing between VLANs
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 20d7658b74169f86d4ac01b9185b3eadddf71f28 upstream.
+
+The local hash of DAT entries is supposed to be VLAN (VID) aware. But
+the adding to the hash and the search in the hash were not checking the VID
+information of the hash entries. The entries would therefore only be
+correctly separated when batadv_hash_dat() didn't select the same buckets
+for different VIDs.
+
+Cc: stable@kernel.org
+Fixes: be1db4f6615b ("batman-adv: make the Distributed ARP Table vlan aware")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/distributed-arp-table.c | 12 +++++++++---
+ 1 file changed, 9 insertions(+), 3 deletions(-)
+
+diff --git a/net/batman-adv/distributed-arp-table.c b/net/batman-adv/distributed-arp-table.c
+index 9d27d7d7b2b4b4..592b61a2e7911e 100644
+--- a/net/batman-adv/distributed-arp-table.c
++++ b/net/batman-adv/distributed-arp-table.c
+@@ -216,10 +216,13 @@ static void batadv_dat_purge(struct work_struct *work)
+ */
+ static bool batadv_compare_dat(const struct hlist_node *node, const void *data2)
+ {
+- const void *data1 = container_of(node, struct batadv_dat_entry,
+- hash_entry);
++ const struct batadv_dat_entry *entry1;
++ const struct batadv_dat_entry *entry2;
+
+- return memcmp(data1, data2, sizeof(__be32)) == 0;
++ entry1 = container_of(node, struct batadv_dat_entry, hash_entry);
++ entry2 = data2;
++
++ return entry1->ip == entry2->ip && entry1->vid == entry2->vid;
+ }
+
+ /**
+@@ -346,6 +349,9 @@ batadv_dat_entry_hash_find(struct batadv_priv *bat_priv, __be32 ip,
+ if (dat_entry->ip != ip)
+ continue;
+
++ if (dat_entry->vid != vid)
++ continue;
++
+ if (!kref_get_unless_zero(&dat_entry->refcount))
+ continue;
+
+--
+2.53.0
+
--- /dev/null
+From 6a5ed9c966eba4121f22365627df5eb721c4825e Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:09:42 +0200
+Subject: batman-adv: frag: avoid underflow of TTL
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 493d9d2528e1a09b090e4b37f0f553def7bd5ce9 upstream.
+
+Packets with a TTL are using it to limit the amount of time this packet can
+be forwarded. But for batadv_frag_packet, the TTL was always only reduced
+but it was never evaluated. It could even underflow without any effect.
+
+Check the TTL in batadv_frag_skb_fwd() before attempting to prepare it for
+forwarding. This keeps it in sync with the not fragmented unicast packet.
+
+Cc: stable@kernel.org
+Fixes: 610bfc6bc99b ("batman-adv: Receive fragmented packets and merge")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/fragmentation.c | 7 +++++++
+ 1 file changed, 7 insertions(+)
+
+diff --git a/net/batman-adv/fragmentation.c b/net/batman-adv/fragmentation.c
+index 95c88bbdbcbee0..43dfe86f07a9b1 100644
+--- a/net/batman-adv/fragmentation.c
++++ b/net/batman-adv/fragmentation.c
+@@ -421,6 +421,13 @@ bool batadv_frag_skb_fwd(struct sk_buff *skb,
+ */
+ total_size = ntohs(packet->total_size);
+ if (total_size > neigh_node->if_incoming->net_dev->mtu) {
++ if (packet->ttl < 2) {
++ kfree_skb(skb);
++ *rx_result = NET_RX_DROP;
++ ret = true;
++ goto out;
++ }
++
+ if (skb_cow(skb, ETH_HLEN) < 0) {
+ kfree_skb(skb);
+ *rx_result = NET_RX_DROP;
+--
+2.53.0
+
--- /dev/null
+From 5b751cd0ff9e9ba5ef494a22562e732cf0e28e10 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:09:41 +0200
+Subject: batman-adv: frag: ensure fragment is writable before modifying TTL
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit b7293c6e8c15b2db77809b25cf8389e35331b27a upstream.
+
+Before batman-adv is allowed to write to an skb, it either has to have its
+own copy of the skb or use skb_cow() to ensure that the data part is not
+shared. But batadv_frag_skb_fwd() modifies the TTL even when it is shared.
+
+Adding a skb_cow() right before this operation avoids this and can at the
+same time prepare it for the modifications required to forward the
+fragment.
+
+Cc: stable@kernel.org
+Fixes: 610bfc6bc99b ("batman-adv: Receive fragmented packets and merge")
+[ Context ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/fragmentation.c | 15 ++++++++++++++-
+ net/batman-adv/fragmentation.h | 3 ++-
+ net/batman-adv/routing.c | 3 +--
+ 3 files changed, 17 insertions(+), 4 deletions(-)
+
+diff --git a/net/batman-adv/fragmentation.c b/net/batman-adv/fragmentation.c
+index 724e06e3e799f4..95c88bbdbcbee0 100644
+--- a/net/batman-adv/fragmentation.c
++++ b/net/batman-adv/fragmentation.c
+@@ -386,6 +386,8 @@ bool batadv_frag_skb_buffer(struct sk_buff **skb,
+ * @skb: skb to forward
+ * @recv_if: interface that the skb is received on
+ * @orig_node_src: originator that the skb is received from
++ * @rx_result: set to NET_RX_SUCCESS when the fragment was forwarded and
++ * NET_RX_DROP when it was dropped; only valid when true is returned
+ *
+ * Look up the next-hop of the fragments payload and check if the merged packet
+ * will exceed the MTU towards the next-hop. If so, the fragment is forwarded
+@@ -395,7 +397,8 @@ bool batadv_frag_skb_buffer(struct sk_buff **skb,
+ */
+ bool batadv_frag_skb_fwd(struct sk_buff *skb,
+ struct batadv_hard_iface *recv_if,
+- struct batadv_orig_node *orig_node_src)
++ struct batadv_orig_node *orig_node_src,
++ int *rx_result)
+ {
+ struct batadv_priv *bat_priv = netdev_priv(recv_if->soft_iface);
+ struct batadv_orig_node *orig_node_dst;
+@@ -418,12 +421,22 @@ bool batadv_frag_skb_fwd(struct sk_buff *skb,
+ */
+ total_size = ntohs(packet->total_size);
+ if (total_size > neigh_node->if_incoming->net_dev->mtu) {
++ if (skb_cow(skb, ETH_HLEN) < 0) {
++ kfree_skb(skb);
++ *rx_result = NET_RX_DROP;
++ ret = true;
++ goto out;
++ }
++
++ packet = (struct batadv_frag_packet *)skb->data;
++
+ batadv_inc_counter(bat_priv, BATADV_CNT_FRAG_FWD);
+ batadv_add_counter(bat_priv, BATADV_CNT_FRAG_FWD_BYTES,
+ skb->len + ETH_HLEN);
+
+ packet->ttl--;
+ batadv_send_unicast_skb(skb, neigh_node);
++ *rx_result = NET_RX_SUCCESS;
+ ret = true;
+ }
+
+diff --git a/net/batman-adv/fragmentation.h b/net/batman-adv/fragmentation.h
+index 881ef328b6cd7e..aba6eecb7d0e76 100644
+--- a/net/batman-adv/fragmentation.h
++++ b/net/batman-adv/fragmentation.h
+@@ -19,7 +19,8 @@ void batadv_frag_purge_orig(struct batadv_orig_node *orig,
+ bool (*check_cb)(struct batadv_frag_table_entry *));
+ bool batadv_frag_skb_fwd(struct sk_buff *skb,
+ struct batadv_hard_iface *recv_if,
+- struct batadv_orig_node *orig_node_src);
++ struct batadv_orig_node *orig_node_src,
++ int *rx_result);
+ bool batadv_frag_skb_buffer(struct sk_buff **skb,
+ struct batadv_orig_node *orig_node);
+ int batadv_frag_send_packet(struct sk_buff *skb,
+diff --git a/net/batman-adv/routing.c b/net/batman-adv/routing.c
+index 9e5c71e406ffc6..f6df66a534c41e 100644
+--- a/net/batman-adv/routing.c
++++ b/net/batman-adv/routing.c
+@@ -1143,10 +1143,9 @@ int batadv_recv_frag_packet(struct sk_buff *skb,
+
+ /* Route the fragment if it is not for us and too big to be merged. */
+ if (!batadv_is_my_mac(bat_priv, frag_packet->dest) &&
+- batadv_frag_skb_fwd(skb, recv_if, orig_node_src)) {
++ batadv_frag_skb_fwd(skb, recv_if, orig_node_src, &ret)) {
+ /* skb was consumed */
+ skb = NULL;
+- ret = NET_RX_SUCCESS;
+ goto put_orig_node;
+ }
+
+--
+2.53.0
+
--- /dev/null
+From 19eb8cbdb04997e62a78049f820257384df1ab95 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:09:39 +0200
+Subject: batman-adv: prevent ELP transmission interval underflow
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 5e50d4b8ae3ea622122d3c6a38d7f6fe68dfddca upstream.
+
+batadv_v_elp_start_timer() enqeues a delayed work. The time when it starts
+is randomly chosen between (elp_interval - BATADV_JITTER) and
+(elp_interval + BATADV_JITTER). The configured elp_interval must therefore
+be larger or equal to BATADV_JITTER to avoid that it causes an underflow of
+the unsigned integer. If this would happen, then a "fast" ELP interval
+would turn into a "day long" delay.
+
+At the same time, it must not be larger than the maximum value the variable
+can store.
+
+Cc: stable@kernel.org
+Fixes: a10800829040 ("batman-adv: Add elp_interval hardif genl configuration")
+[ Context ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/netlink.c | 8 +++++++-
+ 1 file changed, 7 insertions(+), 1 deletion(-)
+
+diff --git a/net/batman-adv/netlink.c b/net/batman-adv/netlink.c
+index 0b5cb03859b25b..e5a0b2c97162d2 100644
+--- a/net/batman-adv/netlink.c
++++ b/net/batman-adv/netlink.c
+@@ -939,9 +939,15 @@ static int batadv_netlink_set_hardif(struct sk_buff *skb,
+ #ifdef CONFIG_BATMAN_ADV_BATMAN_V
+
+ if (info->attrs[BATADV_ATTR_ELP_INTERVAL]) {
++ u32 elp_interval;
++
+ attr = info->attrs[BATADV_ATTR_ELP_INTERVAL];
++ elp_interval = nla_get_u32(attr);
++
++ elp_interval = min_t(u32, elp_interval, INT_MAX);
++ elp_interval = max_t(u32, elp_interval, BATADV_JITTER);
+
+- atomic_set(&hard_iface->bat_v.elp_interval, nla_get_u32(attr));
++ atomic_set(&hard_iface->bat_v.elp_interval, elp_interval);
+ }
+
+ if (info->attrs[BATADV_ATTR_THROUGHPUT_OVERRIDE]) {
+--
+2.53.0
+
--- /dev/null
+From 3fa68ff67e2280c13d117673f9723013154072b7 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:09:37 +0200
+Subject: batman-adv: tp_meter: add only finished tp_vars to lists
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 15ccbf685222274f5add1387af58c2a41a95f81e upstream.
+
+When the receiver variables (aka "session") are initialized, then they are
+added to the list of sessions before the timer is set up. A RCU protected
+reader could therefore find the entry and run mod_setup before
+batadv_tp_init_recv() finished the timer initialization.
+
+The same is true for batadv_tp_start(), which must first initialize the
+finish_work and the test_length to avoid a similar problem.
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 18 +++++++++---------
+ 1 file changed, 9 insertions(+), 9 deletions(-)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index a4df826e4ece71..a015ff73812f5e 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -1106,21 +1106,21 @@ void batadv_tp_start(struct batadv_priv *bat_priv, const u8 *dst,
+ tp_vars->prerandom_offset = 0;
+ spin_lock_init(&tp_vars->prerandom_lock);
+
+- kref_get(&tp_vars->refcount);
+- hlist_add_head_rcu(&tp_vars->list, &bat_priv->tp_list);
+- spin_unlock_bh(&bat_priv->tp_list_lock);
+-
+ tp_vars->test_length = test_length;
+ if (!tp_vars->test_length)
+ tp_vars->test_length = BATADV_TP_DEF_TEST_LENGTH;
+
++ /* init work item for finished tp tests */
++ INIT_DELAYED_WORK(&tp_vars->finish_work, batadv_tp_sender_finish);
++
++ kref_get(&tp_vars->refcount);
++ hlist_add_head_rcu(&tp_vars->list, &bat_priv->tp_list);
++ spin_unlock_bh(&bat_priv->tp_list_lock);
++
+ batadv_dbg(BATADV_DBG_TP_METER, bat_priv,
+ "Meter: starting throughput meter towards %pM (length=%ums)\n",
+ dst, test_length);
+
+- /* init work item for finished tp tests */
+- INIT_DELAYED_WORK(&tp_vars->finish_work, batadv_tp_sender_finish);
+-
+ /* start tp kthread. This way the write() call issued from userspace can
+ * happily return and avoid to block
+ */
+@@ -1438,10 +1438,10 @@ batadv_tp_init_recv(struct batadv_priv *bat_priv,
+ INIT_LIST_HEAD(&tp_vars->unacked_list);
+
+ kref_get(&tp_vars->refcount);
+- hlist_add_head_rcu(&tp_vars->list, &bat_priv->tp_list);
++ timer_setup(&tp_vars->timer, batadv_tp_receiver_shutdown, 0);
+
+ kref_get(&tp_vars->refcount);
+- timer_setup(&tp_vars->timer, batadv_tp_receiver_shutdown, 0);
++ hlist_add_head_rcu(&tp_vars->list, &bat_priv->tp_list);
+
+ batadv_tp_reset_receiver_timer(tp_vars);
+
+--
+2.53.0
+
--- /dev/null
+From b6e51ec44a0c883b75f7e67cbe4d9601b984d298 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:09:45 +0200
+Subject: batman-adv: tp_meter: annotate last_recv_time access with
+ READ/WRITE_ONCE
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit d67c728f07fca2ee6ffdc6dd4421cf2e8691f4d1 upstream.
+
+The last_recv_time field for batadv_tp_receiver tracks the jiffies value of
+the most recent activity and is used to detect timeouts. These accesses are
+not consistently protected by a lock, so READ_ONCE/WRITE_ONCE must be used
+to prevent data races caused by compiler optimizations.
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 8 ++++----
+ 1 file changed, 4 insertions(+), 4 deletions(-)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index c7de8dfe9b65bd..91392e48514d64 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -1193,7 +1193,7 @@ static void batadv_tp_receiver_shutdown(struct timer_list *t)
+ bat_priv = tp_vars->bat_priv;
+
+ /* if there is recent activity rearm the timer */
+- if (!batadv_has_timed_out(tp_vars->last_recv_time,
++ if (!batadv_has_timed_out(READ_ONCE(tp_vars->last_recv_time),
+ BATADV_TP_RECV_TIMEOUT)) {
+ /* reset the receiver shutdown timer */
+ batadv_tp_reset_receiver_timer(tp_vars);
+@@ -1433,7 +1433,7 @@ batadv_tp_init_recv(struct batadv_priv *bat_priv,
+ tp_vars = batadv_tp_list_find_session(bat_priv, icmp->orig,
+ icmp->session, BATADV_TP_RECEIVER);
+ if (tp_vars) {
+- tp_vars->last_recv_time = jiffies;
++ WRITE_ONCE(tp_vars->last_recv_time, jiffies);
+ goto out_unlock;
+ }
+
+@@ -1463,7 +1463,7 @@ batadv_tp_init_recv(struct batadv_priv *bat_priv,
+ kref_get(&tp_vars->refcount);
+ timer_setup(&tp_vars->timer, batadv_tp_receiver_shutdown, 0);
+
+- tp_vars->last_recv_time = jiffies;
++ WRITE_ONCE(tp_vars->last_recv_time, jiffies);
+
+ kref_get(&tp_vars->refcount);
+ hlist_add_head_rcu(&tp_vars->list, &bat_priv->tp_list);
+@@ -1514,7 +1514,7 @@ static void batadv_tp_recv_msg(struct batadv_priv *bat_priv,
+ goto out;
+ }
+
+- tp_vars->last_recv_time = jiffies;
++ WRITE_ONCE(tp_vars->last_recv_time, jiffies);
+ }
+
+ /* if the packet is a duplicate, it may be the case that an ACK has been
+--
+2.53.0
+
--- /dev/null
+From 089534cc33b4073c018a8ad5c383cd365a8ce1ec Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:09:34 +0200
+Subject: batman-adv: tp_meter: avoid divide-by-zero for dec_cwnd
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 33ccd52f3cc9ed46ce395199f89aa3234dc83314 upstream.
+
+The cwnd is always MSS <= cwnd <= 0x20000000. But the calculation in
+batadv_tp_update_cwnd() assumes unsigned 32 bit arithmetics.
+
+ ((mss * 8) ** 2) / (cwnd * 8)
+
+In case cwnd is actually 0x20000000, it will be shifted by 3 bit to the
+left end up at 0x100000000 or U32_MAX + 1. It will therefore wrap around
+and be 0 - resulting in:
+
+ ((mss * 8) ** 2) / 0
+
+This is of course invalid and cannot be calculated. The calculation should
+must be simplified to avoid this overflow:
+
+ (mss ** 2) * 8 / cwnd
+
+It will keep the precision enhancement from the scaling (by 8) but avoid
+the overflow in the divisor.
+
+In theory, there could still be an overflow in the dividend. It is at the
+moment fixed to BATADV_TP_PLEN in batadv_tp_recv_ack() - so it is not an
+imminent problem. But allowing it to use the whole u32 bit range, would
+mean that it can still use up to 67 bits. To keep this calculation safe for
+32 bit arithmetic, mss must never use more than floor((32 - 3) / 2) bits -
+or in other words: must never be larger than 16383.
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 5 ++++-
+ 1 file changed, 4 insertions(+), 1 deletion(-)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index 10953096996ab1..118cb5dd285c2d 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -153,9 +153,12 @@ static void batadv_tp_update_cwnd(struct batadv_tp_vars *tp_vars, u32 mss)
+ return;
+ }
+
++ /* prevent overflow in (mss * mss) << 3 */
++ mss = min_t(u32, mss, (1U << 14) - 1);
++
+ /* increment CWND at least of 1 (section 3.1 of RFC5681) */
+ tp_vars->dec_cwnd += max_t(u32, 1U << 3,
+- ((mss * mss) << 6) / (tp_vars->cwnd << 3));
++ ((mss * mss) << 3) / tp_vars->cwnd);
+ if (tp_vars->dec_cwnd < (mss << 3)) {
+ spin_unlock_bh(&tp_vars->cwnd_lock);
+ return;
+--
+2.53.0
+
--- /dev/null
+From 9f8607e45084aa12a17ab12b562ba613bc0dda79 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:09:33 +0200
+Subject: batman-adv: tp_meter: avoid window underflow
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 765947b81fb54b6ebb0bc1cfe55c0fa399e002b8 upstream.
+
+In batadv_tp_avail(), win_left is calculated with 32-bit unsigned
+arithmetic: win_left = win_limit - tp_vars->last_sent;
+
+During Fast Recovery, cwnd is inflated and last_sent advances rapidly. When
+Fast Recovery ends, cwnd drops abruptly back to ss_threshold. If the newly
+shrunk win_limit is less than last_sent, the unsigned subtraction will
+underflow, wrapping to a massive positive value. Instead of returning that
+the window is full (unavailable), it returns that the sender can continue
+sending.
+
+To handle this situation, it must be checked whether the windows end
+sequence number (win_limit) has to be compared with the last sent sequence
+number. If it would be before the last sent sequence number, then more acks
+are needed before the transmission can be started again.
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 7 ++++++-
+ 1 file changed, 6 insertions(+), 1 deletion(-)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index d8ad58ccef2608..10953096996ab1 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -825,10 +825,15 @@ static void batadv_tp_recv_ack(struct batadv_priv *bat_priv,
+ static bool batadv_tp_avail(struct batadv_tp_vars *tp_vars,
+ size_t payload_len)
+ {
++ u32 last_sent = READ_ONCE(tp_vars->last_sent);
+ u32 win_left, win_limit;
+
+ win_limit = atomic_read(&tp_vars->last_acked) + tp_vars->cwnd;
+- win_left = win_limit - tp_vars->last_sent;
++
++ if (batadv_seq_before(last_sent, win_limit))
++ win_left = win_limit - last_sent;
++ else
++ win_left = 0;
+
+ return win_left >= payload_len;
+ }
+--
+2.53.0
+
--- /dev/null
+From 7e3e1a906a157788ca4d4dd490fdc3ad82f70a09 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:09:35 +0200
+Subject: batman-adv: tp_meter: fix fast recovery precondition
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 2b0d08f08ed3b2174f05c43089ec65f3543a025b upstream.
+
+The fast recovery precondition checks if the recover (initialized to
+BATADV_TP_FIRST_SEQ) is bigger than the received ack. But since recover is
+only updated when this check is successful, it will never enter the fast
+recovery mode.
+
+According to RFC6582 Section 3.2 step 2, the check should actually be
+different:
+
+> When the third duplicate ACK is received, the TCP sender first
+> checks the value of recover to see if the Cumulative
+> Acknowledgment field covers more than recover
+
+The precondition must therefore check if recover is smaller than the
+received ack - basically swapping the operands of the current check.
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index 118cb5dd285c2d..cd196842478686 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -738,7 +738,7 @@ static void batadv_tp_recv_ack(struct batadv_priv *bat_priv,
+ if (atomic_read(&tp_vars->dup_acks) != 3)
+ goto out;
+
+- if (recv_ack >= tp_vars->recover)
++ if (tp_vars->recover >= recv_ack)
+ goto out;
+
+ /* if this is the third duplicate ACK do Fast Retransmit */
+--
+2.53.0
+
--- /dev/null
+From a80b9f4c4ce2e7969b2e23ca2f4724152cda9226 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:09:47 +0200
+Subject: batman-adv: tp_meter: handle overlapping packets
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit cbde75c38b21f022891525078622587ad557b7c1 upstream.
+
+If the size of the packets would change during the transmission, it could
+happen that some retries of packets are overlapping. In this case, precise
+comparisons of sequence numbers by the receiver would be wrong. It is then
+necessary to check if the start sequence number to the end sequence number
+("seqno + length") would contain a new range.
+
+If this is the case then this is enough to accept this packet. In all other
+cases, the packet still has to be dropped (and not acked).
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+[ Switch to pre-splitted tp_vars structure names ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 25 +++++++++++--------------
+ 1 file changed, 11 insertions(+), 14 deletions(-)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index c01ff6e72b5da5..3a1561f5791987 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -1293,7 +1293,8 @@ static int batadv_tp_send_ack(struct batadv_priv *bat_priv, const u8 *dst,
+ /**
+ * batadv_tp_handle_out_of_order() - store an out of order packet
+ * @tp_vars: the private data of the current TP meter session
+- * @skb: the buffer containing the received packet
++ * @seqno: sequence number of new received packet
++ * @payload_len: length of the received packet
+ *
+ * Store the out of order packet in the unacked list for late processing. This
+ * packets are kept in this list so that they can be ACKed at once as soon as
+@@ -1302,22 +1303,17 @@ static int batadv_tp_send_ack(struct batadv_priv *bat_priv, const u8 *dst,
+ * Return: true if the packed has been successfully processed, false otherwise
+ */
+ static bool batadv_tp_handle_out_of_order(struct batadv_tp_vars *tp_vars,
+- const struct sk_buff *skb)
++ u32 seqno, u32 payload_len)
+ __must_hold(&tp_vars->unacked_lock)
+ {
+- const struct batadv_icmp_tp_packet *icmp;
+ struct batadv_tp_unacked *un, *new;
+- u32 payload_len;
+ bool added = false;
+
+ new = kmalloc(sizeof(*new), GFP_ATOMIC);
+ if (unlikely(!new))
+ return false;
+
+- icmp = (struct batadv_icmp_tp_packet *)skb->data;
+-
+- new->seqno = ntohl(icmp->seqno);
+- payload_len = skb->len - sizeof(struct batadv_unicast_packet);
++ new->seqno = seqno;
+ new->len = payload_len;
+
+ /* if the list is empty immediately attach this new object */
+@@ -1484,7 +1480,7 @@ static void batadv_tp_recv_msg(struct batadv_priv *bat_priv,
+ {
+ const struct batadv_icmp_tp_packet *icmp;
+ struct batadv_tp_vars *tp_vars;
+- size_t packet_size;
++ u32 payload_len;
+ u32 to_ack;
+ u32 seqno;
+
+@@ -1519,15 +1515,17 @@ static void batadv_tp_recv_msg(struct batadv_priv *bat_priv,
+ /* if the packet is a duplicate, it may be the case that an ACK has been
+ * lost. Resend the ACK
+ */
+- if (batadv_seq_before(seqno, tp_vars->last_recv))
++ payload_len = skb->len - sizeof(struct batadv_unicast_packet);
++ to_ack = seqno + payload_len;
++ if (batadv_seq_before(to_ack, tp_vars->last_recv))
+ goto send_ack;
+
+ /* if the packet is out of order enqueue it */
+- if (ntohl(icmp->seqno) != tp_vars->last_recv) {
++ if (batadv_seq_before(tp_vars->last_recv, seqno)) {
+ /* exit immediately (and do not send any ACK) if the packet has
+ * not been enqueued correctly
+ */
+- if (!batadv_tp_handle_out_of_order(tp_vars, skb)) {
++ if (!batadv_tp_handle_out_of_order(tp_vars, seqno, payload_len)) {
+ spin_unlock_bh(&tp_vars->unacked_lock);
+ goto out;
+ }
+@@ -1537,8 +1535,7 @@ static void batadv_tp_recv_msg(struct batadv_priv *bat_priv,
+ }
+
+ /* if everything was fine count the ACKed bytes */
+- packet_size = skb->len - sizeof(struct batadv_unicast_packet);
+- tp_vars->last_recv += packet_size;
++ tp_vars->last_recv = to_ack;
+
+ /* check if this ordered message filled a gap.... */
+ batadv_tp_ack_unordered(tp_vars);
+--
+2.53.0
+
--- /dev/null
+From b25b3f5c58ef759f60de8fbd9e1debdd98a2fe82 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:09:36 +0200
+Subject: batman-adv: tp_meter: handle seqno wrap-around for fast recovery
+ detection
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit f54c85ed42a1b27a516cf2a4728f5a612b799e07 upstream.
+
+The recover variable and the last_sent sequence number are initialized on
+purpose as a really high value which will wrap-around after the first 2000
+bytes. The fast recovery precondition must therefore not use simple integer
+comparisons but use helpers which are aware of the sequence number
+wrap-arounds.
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index cd196842478686..a4df826e4ece71 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -738,7 +738,7 @@ static void batadv_tp_recv_ack(struct batadv_priv *bat_priv,
+ if (atomic_read(&tp_vars->dup_acks) != 3)
+ goto out;
+
+- if (tp_vars->recover >= recv_ack)
++ if (!batadv_seq_before(tp_vars->recover, recv_ack))
+ goto out;
+
+ /* if this is the third duplicate ACK do Fast Retransmit */
+--
+2.53.0
+
--- /dev/null
+From cdb8f527e0043cdd1f8257b7c98126e11d6bc309 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:09:32 +0200
+Subject: batman-adv: tp_meter: initialize dec_cwnd explicitly
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit febfb1b86224489535312296ecfa3d4bf467f339 upstream.
+
+When batadv_tp_update_cwnd() is called, dec_cwnd is increased. But dec_cwnd
+is only initialixed (to 0) when a duplicate Ack was received or when cwnd
+is below the ss_threshold.
+
+Just initialize the cwnd during the initialization to avoid any potential
+access of uninitialized data.
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index 1699fb8f8c82d4..d8ad58ccef2608 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -1065,6 +1065,8 @@ void batadv_tp_start(struct batadv_priv *bat_priv, const u8 *dst,
+ * soft_interface, hence its MTU
+ */
+ tp_vars->cwnd = BATADV_TP_PLEN * 3;
++ tp_vars->dec_cwnd = 0;
++
+ /* at the beginning initialise the SS threshold to the biggest possible
+ * window size, hence the AWND size
+ */
+--
+2.53.0
+
--- /dev/null
+From 3033c1b305408031acd97f766e20f0fbde27474a Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:09:31 +0200
+Subject: batman-adv: tp_meter: initialize dup_acks explicitly
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit b2b68b32a715e0328662801576974aa37b942b00 upstream.
+
+When an ack with a sequence number equal to the last_acked is received, the
+dup_acks counter is increased to decide whether fast retransmit should be
+performed. Only when the sequence numbers are not equal, the dup_acks is
+set to the initial value (0).
+
+But if the initial packet would have the sequence number
+BATADV_TP_FIRST_SEQ, dup_acks would not be initialized and atomic_inc would
+operate on an undefined starting value. It is therefore required to have it
+explicitly initialized during the start of the sender session.
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index c837a91879ac2f..1699fb8f8c82d4 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -1055,6 +1055,7 @@ void batadv_tp_start(struct batadv_priv *bat_priv, const u8 *dst,
+ tp_vars->icmp_uid = icmp_uid;
+
+ tp_vars->last_sent = BATADV_TP_FIRST_SEQ;
++ atomic_set(&tp_vars->dup_acks, 0);
+ atomic_set(&tp_vars->last_acked, BATADV_TP_FIRST_SEQ);
+ tp_vars->fast_recovery = false;
+ tp_vars->recover = BATADV_TP_FIRST_SEQ;
+--
+2.53.0
+
--- /dev/null
+From e126b797fc3919ca9c90015d9ea74e6193dea658 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:09:40 +0200
+Subject: batman-adv: tp_meter: initialize last_recv_time during init
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 811cb00fa8cdc3f0a7f6eefc000a6888367c8c8f upstream.
+
+The last_recv_time is the most important indicator for a receiver session
+to figure out whether a session timed out or not. But this information was
+only initialized after the session was added to the tp_receiver_list and
+after the timer was started.
+
+In the worst case, the timer (function) could have tried to access this
+information before the actual initialization was reached. Like rest of the
+variables of the tp_meter receiver session, this field has to be filled out
+before any other (parallel running) context has the chance to access it.
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+[ Context ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 10 +++++++---
+ 1 file changed, 7 insertions(+), 3 deletions(-)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index a015ff73812f5e..ea5640242ddc2e 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -1412,8 +1412,10 @@ batadv_tp_init_recv(struct batadv_priv *bat_priv,
+
+ tp_vars = batadv_tp_list_find_session(bat_priv, icmp->orig,
+ icmp->session, BATADV_TP_RECEIVER);
+- if (tp_vars)
++ if (tp_vars) {
++ tp_vars->last_recv_time = jiffies;
+ goto out_unlock;
++ }
+
+ if (!atomic_add_unless(&bat_priv->tp_num, 1, BATADV_TP_MAX_NUM)) {
+ batadv_dbg(BATADV_DBG_TP_METER, bat_priv,
+@@ -1440,6 +1442,8 @@ batadv_tp_init_recv(struct batadv_priv *bat_priv,
+ kref_get(&tp_vars->refcount);
+ timer_setup(&tp_vars->timer, batadv_tp_receiver_shutdown, 0);
+
++ tp_vars->last_recv_time = jiffies;
++
+ kref_get(&tp_vars->refcount);
+ hlist_add_head_rcu(&tp_vars->list, &bat_priv->tp_list);
+
+@@ -1488,9 +1492,9 @@ static void batadv_tp_recv_msg(struct batadv_priv *bat_priv,
+ icmp->orig);
+ goto out;
+ }
+- }
+
+- tp_vars->last_recv_time = jiffies;
++ tp_vars->last_recv_time = jiffies;
++ }
+
+ /* if the packet is a duplicate, it may be the case that an ACK has been
+ * lost. Resend the ACK
+--
+2.53.0
+
--- /dev/null
+From 2ec7e4f20d84a902f39fe2057c31c2d6942853d3 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:09:30 +0200
+Subject: batman-adv: tp_meter: keep unacked list in ascending ordered
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 5aa8651527ea0b610e7a09fb3b8204c1398b9525 upstream.
+
+When batadv_tp_handle_out_of_order inserts a new entry in the list of
+unacked (out of order) packets, it searches from the entry with the newest
+sequence number towards oldest sequence number. If an entry is found which
+is older than the newly entry, the new entry has to be added after the
+found one to keep the ascending order.
+
+But for this operation list_add_tail() was used. But this function adds an
+entry _before_ another one. As result, the list would contain a lot of
+swapped sequence numbers. The consumer of this list
+(batadv_tp_ack_unordered()) would then fail to correctly ack packets.
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index fc2f0b49e5a06e..c837a91879ac2f 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -1334,7 +1334,7 @@ static bool batadv_tp_handle_out_of_order(struct batadv_tp_vars *tp_vars,
+ * one is attached _after_ it. In this way the list is kept in
+ * ascending order
+ */
+- list_add_tail(&new->list, &un->list);
++ list_add(&new->list, &un->list);
+ added = true;
+ break;
+ }
+--
+2.53.0
+
--- /dev/null
+From e28be785410fc412794c83b8096704f83ead84e3 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:09:46 +0200
+Subject: batman-adv: tp_meter: prevent parallel modifications of last_recv
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 6dde0cfcb36e4d5b3de35b75696937478441eed4 upstream.
+
+When last_recv is updated to store the last receive sequence number, it is
+assuming that nothing is modifying in parallel while:
+
+* check for outdated packets is done
+* out of order check is performed (and packets are stored in out-of-order
+ queue)
+* the out-of-order queue was searched for closed gaps
+* sequence number for next ack is calculated
+
+Nothing of that was actually protected. It could therefore happen that the
+last_recv was updated multiple times in parallel and the final sequence
+number was calculated with deltas which had no connection to the sequence
+number they were added to.
+
+Lock this whole region with the same lock which was already used to protect
+the unacked (out-of-order) list.
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+[ Switch to pre-splitted tp_vars structure names ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 22 +++++++++++++---------
+ net/batman-adv/types.h | 2 +-
+ 2 files changed, 14 insertions(+), 10 deletions(-)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index 91392e48514d64..c01ff6e72b5da5 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -1303,6 +1303,7 @@ static int batadv_tp_send_ack(struct batadv_priv *bat_priv, const u8 *dst,
+ */
+ static bool batadv_tp_handle_out_of_order(struct batadv_tp_vars *tp_vars,
+ const struct sk_buff *skb)
++ __must_hold(&tp_vars->unacked_lock)
+ {
+ const struct batadv_icmp_tp_packet *icmp;
+ struct batadv_tp_unacked *un, *new;
+@@ -1319,12 +1320,11 @@ static bool batadv_tp_handle_out_of_order(struct batadv_tp_vars *tp_vars,
+ payload_len = skb->len - sizeof(struct batadv_unicast_packet);
+ new->len = payload_len;
+
+- spin_lock_bh(&tp_vars->unacked_lock);
+ /* if the list is empty immediately attach this new object */
+ if (list_empty(&tp_vars->unacked_list)) {
+ list_add(&new->list, &tp_vars->unacked_list);
+ tp_vars->unacked_count++;
+- goto out;
++ return true;
+ }
+
+ /* otherwise loop over the list and either drop the packet because this
+@@ -1373,9 +1373,6 @@ static bool batadv_tp_handle_out_of_order(struct batadv_tp_vars *tp_vars,
+ tp_vars->unacked_count--;
+ }
+
+-out:
+- spin_unlock_bh(&tp_vars->unacked_lock);
+-
+ return true;
+ }
+
+@@ -1385,6 +1382,7 @@ static bool batadv_tp_handle_out_of_order(struct batadv_tp_vars *tp_vars,
+ * @tp_vars: the private data of the current TP meter session
+ */
+ static void batadv_tp_ack_unordered(struct batadv_tp_vars *tp_vars)
++ __must_hold(&tp_vars->unacked_lock)
+ {
+ struct batadv_tp_unacked *un, *safe;
+ u32 to_ack;
+@@ -1392,7 +1390,6 @@ static void batadv_tp_ack_unordered(struct batadv_tp_vars *tp_vars)
+ /* go through the unacked packet list and possibly ACK them as
+ * well
+ */
+- spin_lock_bh(&tp_vars->unacked_lock);
+ list_for_each_entry_safe(un, safe, &tp_vars->unacked_list, list) {
+ /* the list is ordered, therefore it is possible to stop as soon
+ * there is a gap between the last acked seqno and the seqno of
+@@ -1410,7 +1407,6 @@ static void batadv_tp_ack_unordered(struct batadv_tp_vars *tp_vars)
+ kfree(un);
+ tp_vars->unacked_count--;
+ }
+- spin_unlock_bh(&tp_vars->unacked_lock);
+ }
+
+ /**
+@@ -1489,6 +1485,7 @@ static void batadv_tp_recv_msg(struct batadv_priv *bat_priv,
+ const struct batadv_icmp_tp_packet *icmp;
+ struct batadv_tp_vars *tp_vars;
+ size_t packet_size;
++ u32 to_ack;
+ u32 seqno;
+
+ icmp = (struct batadv_icmp_tp_packet *)skb->data;
+@@ -1517,6 +1514,8 @@ static void batadv_tp_recv_msg(struct batadv_priv *bat_priv,
+ WRITE_ONCE(tp_vars->last_recv_time, jiffies);
+ }
+
++ spin_lock_bh(&tp_vars->unacked_lock);
++
+ /* if the packet is a duplicate, it may be the case that an ACK has been
+ * lost. Resend the ACK
+ */
+@@ -1528,8 +1527,10 @@ static void batadv_tp_recv_msg(struct batadv_priv *bat_priv,
+ /* exit immediately (and do not send any ACK) if the packet has
+ * not been enqueued correctly
+ */
+- if (!batadv_tp_handle_out_of_order(tp_vars, skb))
++ if (!batadv_tp_handle_out_of_order(tp_vars, skb)) {
++ spin_unlock_bh(&tp_vars->unacked_lock);
+ goto out;
++ }
+
+ /* send a duplicate ACK */
+ goto send_ack;
+@@ -1543,11 +1544,14 @@ static void batadv_tp_recv_msg(struct batadv_priv *bat_priv,
+ batadv_tp_ack_unordered(tp_vars);
+
+ send_ack:
++ to_ack = tp_vars->last_recv;
++ spin_unlock_bh(&tp_vars->unacked_lock);
++
+ /* send the ACK. If the received packet was out of order, the ACK that
+ * is going to be sent is a duplicate (the sender will count them and
+ * possibly enter Fast Retransmit as soon as it has reached 3)
+ */
+- batadv_tp_send_ack(bat_priv, icmp->orig, tp_vars->last_recv,
++ batadv_tp_send_ack(bat_priv, icmp->orig, to_ack,
+ icmp->timestamp, icmp->session, icmp->uid);
+ out:
+ if (likely(tp_vars))
+diff --git a/net/batman-adv/types.h b/net/batman-adv/types.h
+index b12b0851df6e7e..437d651a1cffe3 100644
+--- a/net/batman-adv/types.h
++++ b/net/batman-adv/types.h
+@@ -1505,7 +1505,7 @@ struct batadv_tp_vars {
+ /** @unacked_list: list of unacked packets (meta-info only) */
+ struct list_head unacked_list;
+
+- /** @unacked_lock: protect unacked_list */
++ /** @unacked_lock: protect unacked_list + &batadv_tp_receiver.last_recv */
+ spinlock_t unacked_lock;
+
+ /** @unacked_count: number of unacked entries */
+--
+2.53.0
+
--- /dev/null
+From 5ea688ad68a89be8b5ccbdc69c8f7c9751c4843d Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:09:44 +0200
+Subject: batman-adv: tp_meter: restrict number of unacked list entries
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit e7c775110e1858e5a7471a23a9c9658c0af9df89 upstream.
+
+When the unacked_list is unbound, an attacker could send messages with
+small lengths and appropriated seqno + gaps to force the receiver to
+allocate more and more unacked_list entries. And the end either causing an
+out-of-memory situation or increase the management overhead for the (large)
+list that significant portions of CPU cycles are wasted in searching
+through the list.
+
+When limiting the list to a specific number, it is important to still
+correctly add a new entry to the list. But if the list became larger than
+the limit, the last entry of the list (with the highest seqno) must be
+dropped to still allow the earlier seqnos to finish and therefore to
+continue the process. Otherwise, the process might get stuck with too high
+seqnos which are not handled by batadv_tp_ack_unordered().
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+[ Switch to pre-splitted tp_vars structure names ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 23 ++++++++++++++++++++++-
+ net/batman-adv/types.h | 3 +++
+ 2 files changed, 25 insertions(+), 1 deletion(-)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index ea5640242ddc2e..c7de8dfe9b65bd 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -86,6 +86,11 @@
+ #define BATADV_TP_PLEN (BATADV_TP_PACKET_LEN - ETH_HLEN - \
+ sizeof(struct batadv_unicast_packet))
+
++/**
++ * BATADV_TP_MAX_UNACKED - maximum number of packets a receiver didn't yet ack
++ */
++#define BATADV_TP_MAX_UNACKED 100
++
+ static u8 batadv_tp_prerandom[4096] __read_mostly;
+
+ /**
+@@ -1205,6 +1210,7 @@ static void batadv_tp_receiver_shutdown(struct timer_list *t)
+ list_for_each_entry_safe(un, safe, &tp_vars->unacked_list, list) {
+ list_del(&un->list);
+ kfree(un);
++ tp_vars->unacked_count--;
+ }
+ spin_unlock_bh(&tp_vars->unacked_lock);
+
+@@ -1317,6 +1323,7 @@ static bool batadv_tp_handle_out_of_order(struct batadv_tp_vars *tp_vars,
+ /* if the list is empty immediately attach this new object */
+ if (list_empty(&tp_vars->unacked_list)) {
+ list_add(&new->list, &tp_vars->unacked_list);
++ tp_vars->unacked_count++;
+ goto out;
+ }
+
+@@ -1347,12 +1354,24 @@ static bool batadv_tp_handle_out_of_order(struct batadv_tp_vars *tp_vars,
+ */
+ list_add(&new->list, &un->list);
+ added = true;
++ tp_vars->unacked_count++;
+ break;
+ }
+
+ /* received packet with smallest seqno out of order; add it to front */
+- if (!added)
++ if (!added) {
+ list_add(&new->list, &tp_vars->unacked_list);
++ tp_vars->unacked_count++;
++ }
++
++ /* remove the last (biggest) unacked seqno when list is too large */
++ if (tp_vars->unacked_count > BATADV_TP_MAX_UNACKED) {
++ un = list_last_entry(&tp_vars->unacked_list,
++ struct batadv_tp_unacked, list);
++ list_del(&un->list);
++ kfree(un);
++ tp_vars->unacked_count--;
++ }
+
+ out:
+ spin_unlock_bh(&tp_vars->unacked_lock);
+@@ -1389,6 +1408,7 @@ static void batadv_tp_ack_unordered(struct batadv_tp_vars *tp_vars)
+
+ list_del(&un->list);
+ kfree(un);
++ tp_vars->unacked_count--;
+ }
+ spin_unlock_bh(&tp_vars->unacked_lock);
+ }
+@@ -1438,6 +1458,7 @@ batadv_tp_init_recv(struct batadv_priv *bat_priv,
+
+ spin_lock_init(&tp_vars->unacked_lock);
+ INIT_LIST_HEAD(&tp_vars->unacked_list);
++ tp_vars->unacked_count = 0;
+
+ kref_get(&tp_vars->refcount);
+ timer_setup(&tp_vars->timer, batadv_tp_receiver_shutdown, 0);
+diff --git a/net/batman-adv/types.h b/net/batman-adv/types.h
+index 28f239421f74a3..b12b0851df6e7e 100644
+--- a/net/batman-adv/types.h
++++ b/net/batman-adv/types.h
+@@ -1508,6 +1508,9 @@ struct batadv_tp_vars {
+ /** @unacked_lock: protect unacked_list */
+ spinlock_t unacked_lock;
+
++ /** @unacked_count: number of unacked entries */
++ size_t unacked_count;
++
+ /** @last_recv_time: time (jiffies) a msg was received */
+ unsigned long last_recv_time;
+
+--
+2.53.0
+
--- /dev/null
+From e25b407d71aa2945e9d8d63eba360cad2f7a40d2 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:09:48 +0200
+Subject: batman-adv: tt: don't merge change entries with different VIDs
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit f08e06c2d5c3e2434e7c773f2213f4a7dce6bc1e upstream.
+
+batadv_tt_local_event() merges/cancels events for the same client which
+would conflict or be duplicates. The matching of the queued events only
+compares the MAC address - the VLAN ID stored in each event is ignored.
+
+If a MAC would now appear on multiple VID, the two ADD change events (for
+VID 1 and VID 2) would be merged to a single vid event. The remote can
+therefore not calculate the correct TT table and desync. A full translation
+table exchange is required to recover from this state.
+
+A check of VID is therefore necessary to avoid such wrong merges/cancels.
+
+Cc: stable@kernel.org
+Fixes: c018ad3de61a ("batman-adv: add the VLAN ID attribute to the TT entry")
+[ Context ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/translation-table.c | 3 +++
+ 1 file changed, 3 insertions(+)
+
+diff --git a/net/batman-adv/translation-table.c b/net/batman-adv/translation-table.c
+index 79da90b9cf0659..c323660897d43d 100644
+--- a/net/batman-adv/translation-table.c
++++ b/net/batman-adv/translation-table.c
+@@ -486,6 +486,9 @@ static void batadv_tt_local_event(struct batadv_priv *bat_priv,
+ if (!batadv_compare_eth(entry->change.addr, common->addr))
+ continue;
+
++ if (entry->change.vid != tt_change_node->change.vid)
++ continue;
++
+ /* DEL+ADD in the same orig interval have no effect and can be
+ * removed to avoid silly behaviour on the receiver side. The
+ * other way around (ADD+DEL) can happen in case of roaming of
+--
+2.53.0
+
--- /dev/null
+From bf37b2bb7138f9d53e9df7185103e5d620b76d4b Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:09:49 +0200
+Subject: batman-adv: tt: track roam count per VID
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 12407d5f61c2653a64f2ff4b22f3c267f8420ef1 upstream.
+
+batadv_tt_check_roam_count() is supposed to track roaming of a TT entry.
+But TT entries are for a MAC + VID. The VID was completely missed and thus
+leads to incorrect detection of ROAM counts when a client MAC exists in
+multiple VLANs.
+
+Cc: stable@kernel.org
+Fixes: c018ad3de61a ("batman-adv: add the VLAN ID attribute to the TT entry")
+[ Context ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/translation-table.c | 9 +++++++--
+ net/batman-adv/types.h | 3 +++
+ 2 files changed, 10 insertions(+), 2 deletions(-)
+
+diff --git a/net/batman-adv/translation-table.c b/net/batman-adv/translation-table.c
+index c323660897d43d..4b3f39d6ddaf5b 100644
+--- a/net/batman-adv/translation-table.c
++++ b/net/batman-adv/translation-table.c
+@@ -3753,6 +3753,7 @@ static void batadv_tt_roam_purge(struct batadv_priv *bat_priv)
+ * batadv_tt_check_roam_count() - check if a client has roamed too frequently
+ * @bat_priv: the bat priv with all the soft interface information
+ * @client: mac address of the roaming client
++ * @vid: VLAN identifier
+ *
+ * This function checks whether the client already reached the
+ * maximum number of possible roaming phases. In this case the ROAMING_ADV
+@@ -3760,7 +3761,7 @@ static void batadv_tt_roam_purge(struct batadv_priv *bat_priv)
+ *
+ * Return: true if the ROAMING_ADV can be sent, false otherwise
+ */
+-static bool batadv_tt_check_roam_count(struct batadv_priv *bat_priv, u8 *client)
++static bool batadv_tt_check_roam_count(struct batadv_priv *bat_priv, u8 *client, u16 vid)
+ {
+ struct batadv_tt_roam_node *tt_roam_node;
+ bool ret = false;
+@@ -3773,6 +3774,9 @@ static bool batadv_tt_check_roam_count(struct batadv_priv *bat_priv, u8 *client)
+ if (!batadv_compare_eth(tt_roam_node->addr, client))
+ continue;
+
++ if (tt_roam_node->vid != vid)
++ continue;
++
+ if (batadv_has_timed_out(tt_roam_node->first_time,
+ BATADV_ROAMING_MAX_TIME))
+ continue;
+@@ -3794,6 +3798,7 @@ static bool batadv_tt_check_roam_count(struct batadv_priv *bat_priv, u8 *client)
+ atomic_set(&tt_roam_node->counter,
+ BATADV_ROAMING_MAX_COUNT - 1);
+ ether_addr_copy(tt_roam_node->addr, client);
++ tt_roam_node->vid = vid;
+
+ list_add(&tt_roam_node->list, &bat_priv->tt.roam_list);
+ ret = true;
+@@ -3830,7 +3835,7 @@ static void batadv_send_roam_adv(struct batadv_priv *bat_priv, u8 *client,
+ /* before going on we have to check whether the client has
+ * already roamed to us too many times
+ */
+- if (!batadv_tt_check_roam_count(bat_priv, client))
++ if (!batadv_tt_check_roam_count(bat_priv, client, vid))
+ goto out;
+
+ batadv_dbg(BATADV_DBG_TT, bat_priv,
+diff --git a/net/batman-adv/types.h b/net/batman-adv/types.h
+index 437d651a1cffe3..88831374e81747 100644
+--- a/net/batman-adv/types.h
++++ b/net/batman-adv/types.h
+@@ -2059,6 +2059,9 @@ struct batadv_tt_roam_node {
+ /** @addr: mac address of the client in the roaming phase */
+ u8 addr[ETH_ALEN];
+
++ /** @vid: VLAN identifier */
++ u16 vid;
++
+ /**
+ * @counter: number of allowed roaming events per client within a single
+ * OGM interval (changes are committed with each OGM)
+--
+2.53.0
+
--- /dev/null
+From 45e167d455f760a3395459b8f99b3bfbf459f436 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:09:52 +0200
+Subject: batman-adv: tvlv: avoid race of cifsnotfound handler state
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit edb557b2ba38fea2c5eb710cf366c797e187218c upstream.
+
+TVLV handlers can have the flag BATADV_TVLV_HANDLER_OGM_CIFNOTFND set to
+signal that the OGM handler should be called (with NULL for data) when the
+specific TVLV container was not found in the OGM. This is used by:
+
+* DAT
+* GW
+* Multicast (OGM + Tracker)
+
+The state whether the handler was executed was stored in the struct
+batadv_tvlv_handler. But the TVLV processing is started without any lock.
+Multiple parallel contexts processing TVLVs would therefore overwrite each
+others BATADV_TVLV_HANDLER_OGM_CALLED flag in the shared
+batadv_tvlv_handler.
+
+Drop the shared BATADV_TVLV_HANDLER_OGM_CALLED flag and instead determine,
+per TVLV buffer, whether a matching container was present by scanning the
+packet's buffer.
+
+Cc: stable@kernel.org
+Fixes: ef26157747d4 ("batman-adv: tvlv - basic infrastructure")
+[ Context ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tvlv.c | 63 ++++++++++++++++++++++++++++++++++++++----
+ net/batman-adv/types.h | 7 -----
+ 2 files changed, 57 insertions(+), 13 deletions(-)
+
+diff --git a/net/batman-adv/tvlv.c b/net/batman-adv/tvlv.c
+index 84f0fb175b3313..2c224951d0195c 100644
+--- a/net/batman-adv/tvlv.c
++++ b/net/batman-adv/tvlv.c
+@@ -394,7 +394,6 @@ static int batadv_tvlv_call_handler(struct batadv_priv *bat_priv,
+ tvlv_handler->ogm_handler(bat_priv, orig_node,
+ BATADV_NO_FLAGS,
+ tvlv_value, tvlv_value_len);
+- tvlv_handler->flags |= BATADV_TVLV_HANDLER_OGM_CALLED;
+ } else {
+ if (!src)
+ return NET_RX_SUCCESS;
+@@ -413,6 +412,48 @@ static int batadv_tvlv_call_handler(struct batadv_priv *bat_priv,
+ return NET_RX_SUCCESS;
+ }
+
++/**
++ * batadv_tvlv_containers_contain() - check if a tvlv buffer holds a container
++ * @tvlv_value: tvlv content
++ * @tvlv_value_len: tvlv content length
++ * @type: tvlv container type to look for
++ * @version: tvlv container version to look for
++ *
++ * Return: true if a container of the given type and version is present in the
++ * tvlv buffer, false otherwise.
++ */
++static bool batadv_tvlv_containers_contain(void *tvlv_value,
++ u16 tvlv_value_len, u8 type,
++ u8 version)
++{
++ struct batadv_tvlv_hdr *tvlv_hdr;
++ u16 tvlv_value_cont_len;
++
++ while (tvlv_value_len >= sizeof(*tvlv_hdr)) {
++ tvlv_hdr = tvlv_value;
++ tvlv_value_cont_len = ntohs(tvlv_hdr->len);
++ tvlv_value = tvlv_hdr + 1;
++ tvlv_value_len -= sizeof(*tvlv_hdr);
++
++ if (tvlv_value_cont_len > tvlv_value_len)
++ break;
++
++ /* the next tvlv header is accessed assuming (at least) 2-byte
++ * alignment, so it must start at an even offset.
++ */
++ if (tvlv_value_cont_len & 1)
++ break;
++
++ if (tvlv_hdr->type == type && tvlv_hdr->version == version)
++ return true;
++
++ tvlv_value = (u8 *)tvlv_value + tvlv_value_cont_len;
++ tvlv_value_len -= tvlv_value_cont_len;
++ }
++
++ return false;
++}
++
+ /**
+ * batadv_tvlv_containers_process() - parse the given tvlv buffer to call the
+ * appropriate handlers
+@@ -433,7 +474,9 @@ int batadv_tvlv_containers_process(struct batadv_priv *bat_priv,
+ u8 *src, u8 *dst,
+ void *tvlv_value, u16 tvlv_value_len)
+ {
++ u16 tvlv_value_start_len = tvlv_value_len;
+ struct batadv_tvlv_handler *tvlv_handler;
++ void *tvlv_value_start = tvlv_value;
+ struct batadv_tvlv_hdr *tvlv_hdr;
+ u16 tvlv_value_cont_len;
+ u8 cifnotfound = BATADV_TVLV_HANDLER_OGM_CIFNOTFND;
+@@ -474,12 +517,20 @@ int batadv_tvlv_containers_process(struct batadv_priv *bat_priv,
+ rcu_read_lock();
+ hlist_for_each_entry_rcu(tvlv_handler,
+ &bat_priv->tvlv.handler_list, list) {
+- if ((tvlv_handler->flags & BATADV_TVLV_HANDLER_OGM_CIFNOTFND) &&
+- !(tvlv_handler->flags & BATADV_TVLV_HANDLER_OGM_CALLED))
+- tvlv_handler->ogm_handler(bat_priv, orig_node,
+- cifnotfound, NULL, 0);
++ if (!(tvlv_handler->flags & BATADV_TVLV_HANDLER_OGM_CIFNOTFND))
++ continue;
+
+- tvlv_handler->flags &= ~BATADV_TVLV_HANDLER_OGM_CALLED;
++ /* if the corresponding container was present then the handler
++ * was already called from the loop above
++ */
++ if (batadv_tvlv_containers_contain(tvlv_value_start,
++ tvlv_value_start_len,
++ tvlv_handler->type,
++ tvlv_handler->version))
++ continue;
++
++ tvlv_handler->ogm_handler(bat_priv, orig_node,
++ cifnotfound, NULL, 0);
+ }
+ rcu_read_unlock();
+
+diff --git a/net/batman-adv/types.h b/net/batman-adv/types.h
+index 88831374e81747..79ffd25fd4eba2 100644
+--- a/net/batman-adv/types.h
++++ b/net/batman-adv/types.h
+@@ -2492,13 +2492,6 @@ enum batadv_tvlv_handler_flags {
+ * will call this handler even if its type was not found (with no data)
+ */
+ BATADV_TVLV_HANDLER_OGM_CIFNOTFND = BIT(1),
+-
+- /**
+- * @BATADV_TVLV_HANDLER_OGM_CALLED: interval tvlv handling flag - the
+- * API marks a handler as being called, so it won't be called if the
+- * BATADV_TVLV_HANDLER_OGM_CIFNOTFND flag was set
+- */
+- BATADV_TVLV_HANDLER_OGM_CALLED = BIT(2),
+ };
+
+ /**
+--
+2.53.0
+
--- /dev/null
+From 617eab49d1ecb5560d3742c791fc337ffcf694df Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:09:51 +0200
+Subject: batman-adv: tvlv: enforce 2-byte alignment
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 32a6799255525d6ea4da0f7e9e0e521ad9560a46 upstream.
+
+The fields of an aggregated OGM(v2) are accessed assuming (at least) 2-byte
+alignment, so a following OGM must start at an even offset. As the header
+length is even, an odd tvlv_len would misalign it and trigger unaligned
+accesses on strict-alignment architectures.
+
+Such a misaligned TVLV/OGM/OGMv2 is not created by a normal participant in
+the mesh. Therefore, reject such malformed packets.
+
+Cc: stable@kernel.org
+Fixes: ef26157747d4 ("batman-adv: tvlv - basic infrastructure")
+[ Drop change for non-existing mcast handling ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/bat_iv_ogm.c | 11 ++++++++++-
+ net/batman-adv/bat_v_ogm.c | 11 ++++++++++-
+ net/batman-adv/tvlv.c | 6 ++++++
+ 3 files changed, 26 insertions(+), 2 deletions(-)
+
+diff --git a/net/batman-adv/bat_iv_ogm.c b/net/batman-adv/bat_iv_ogm.c
+index 93c2c5f6facc0d..cc4fbfd0b75530 100644
+--- a/net/batman-adv/bat_iv_ogm.c
++++ b/net/batman-adv/bat_iv_ogm.c
+@@ -312,14 +312,23 @@ batadv_iv_ogm_aggr_packet(int buff_pos, int packet_len,
+ const struct batadv_ogm_packet *ogm_packet)
+ {
+ int next_buff_pos = 0;
++ u16 tvlv_len;
+
+ /* check if there is enough space for the header */
+ next_buff_pos += buff_pos + sizeof(*ogm_packet);
+ if (next_buff_pos > packet_len)
+ return false;
+
++ tvlv_len = ntohs(ogm_packet->tvlv_len);
++
++ /* the fields of an aggregated OGM are accessed assuming (at least)
++ * 2-byte alignment, so a following OGM must start at an even offset.
++ */
++ if (tvlv_len & 1)
++ return false;
++
+ /* check if there is enough space for the optional TVLV */
+- next_buff_pos += ntohs(ogm_packet->tvlv_len);
++ next_buff_pos += tvlv_len;
+
+ return next_buff_pos <= packet_len;
+ }
+diff --git a/net/batman-adv/bat_v_ogm.c b/net/batman-adv/bat_v_ogm.c
+index 12de9b44064dd4..2a62ab13bf87bb 100644
+--- a/net/batman-adv/bat_v_ogm.c
++++ b/net/batman-adv/bat_v_ogm.c
+@@ -864,14 +864,23 @@ batadv_v_ogm_aggr_packet(int buff_pos, int packet_len,
+ const struct batadv_ogm2_packet *ogm2_packet)
+ {
+ int next_buff_pos = 0;
++ u16 tvlv_len;
+
+ /* check if there is enough space for the header */
+ next_buff_pos += buff_pos + sizeof(*ogm2_packet);
+ if (next_buff_pos > packet_len)
+ return false;
+
++ tvlv_len = ntohs(ogm2_packet->tvlv_len);
++
++ /* the fields of an aggregated OGMv2 are accessed assuming (at least)
++ * 2-byte alignment, so a following OGMv2 must start at an even offset.
++ */
++ if (tvlv_len & 1)
++ return false;
++
+ /* check if there is enough space for the optional TVLV */
+- next_buff_pos += ntohs(ogm2_packet->tvlv_len);
++ next_buff_pos += tvlv_len;
+
+ return next_buff_pos <= packet_len;
+ }
+diff --git a/net/batman-adv/tvlv.c b/net/batman-adv/tvlv.c
+index 8da8184a2ebdfc..84f0fb175b3313 100644
+--- a/net/batman-adv/tvlv.c
++++ b/net/batman-adv/tvlv.c
+@@ -448,6 +448,12 @@ int batadv_tvlv_containers_process(struct batadv_priv *bat_priv,
+ if (tvlv_value_cont_len > tvlv_value_len)
+ break;
+
++ /* the next tvlv header is accessed assuming (at least) 2-byte
++ * alignment, so it must start at an even offset.
++ */
++ if (tvlv_value_cont_len & 1)
++ break;
++
+ tvlv_handler = batadv_tvlv_handler_get(bat_priv,
+ tvlv_hdr->type,
+ tvlv_hdr->version);
+--
+2.53.0
+
--- /dev/null
+From cefdcf236b82687578ad4d6ed69410651ac18441 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:09:43 +0200
+Subject: batman-adv: v: prevent OGM aggregation on disabled hardif
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit d11c00b95b2a3b3934007fc003dccc6fdcc061ad upstream.
+
+When an interface gets disabled, the worker is correctly disabled by
+batadv_hardif_disable_interface() -> ... -> batadv_v_ogm_iface_disable().
+In this process, the skb aggr_list is also freed.
+
+But batadv_v_ogm_send_meshif() can still queue new skbs (via
+batadv_v_ogm_queue_on_if()) to the aggr_list. This will only stop after all
+cores can no longer find the RCU protected list of hard interfaces. These
+queued skbs will never be freed or consumed by batadv_v_ogm_aggr_work.
+
+The batadv_v_ogm_iface_disable() function must block
+batadv_v_ogm_queue_on_if() to avoid leak of skbs.
+
+Cc: stable@kernel.org
+Fixes: f89255a02f1d ("batman-adv: BATMAN_V: introduce per hard-iface OGMv2 queues")
+[ Context ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/bat_v.c | 1 +
+ net/batman-adv/bat_v_ogm.c | 12 ++++++++++++
+ net/batman-adv/types.h | 6 ++++++
+ 3 files changed, 19 insertions(+)
+
+diff --git a/net/batman-adv/bat_v.c b/net/batman-adv/bat_v.c
+index 6dc39fc0350e6a..7b9bc8a4bd48f5 100644
+--- a/net/batman-adv/bat_v.c
++++ b/net/batman-adv/bat_v.c
+@@ -1084,6 +1084,7 @@ void batadv_v_hardif_init(struct batadv_hard_iface *hard_iface)
+
+ hard_iface->bat_v.aggr_len = 0;
+ skb_queue_head_init(&hard_iface->bat_v.aggr_list);
++ hard_iface->bat_v.aggr_list_enabled = false;
+ INIT_DELAYED_WORK(&hard_iface->bat_v.aggr_wq,
+ batadv_v_ogm_aggr_work);
+ }
+diff --git a/net/batman-adv/bat_v_ogm.c b/net/batman-adv/bat_v_ogm.c
+index 939aa4b303ad98..12de9b44064dd4 100644
+--- a/net/batman-adv/bat_v_ogm.c
++++ b/net/batman-adv/bat_v_ogm.c
+@@ -255,11 +255,18 @@ static void batadv_v_ogm_queue_on_if(struct batadv_priv *bat_priv,
+ }
+
+ spin_lock_bh(&hard_iface->bat_v.aggr_list.lock);
++ if (!hard_iface->bat_v.aggr_list_enabled) {
++ kfree_skb(skb);
++ goto unlock;
++ }
++
+ if (!batadv_v_ogm_queue_left(skb, hard_iface))
+ batadv_v_ogm_aggr_send(bat_priv, hard_iface);
+
+ hard_iface->bat_v.aggr_len += batadv_v_ogm_len(skb);
+ __skb_queue_tail(&hard_iface->bat_v.aggr_list, skb);
++
++unlock:
+ spin_unlock_bh(&hard_iface->bat_v.aggr_list.lock);
+ }
+
+@@ -422,6 +429,10 @@ int batadv_v_ogm_iface_enable(struct batadv_hard_iface *hard_iface)
+ {
+ struct batadv_priv *bat_priv = netdev_priv(hard_iface->soft_iface);
+
++ spin_lock_bh(&hard_iface->bat_v.aggr_list.lock);
++ hard_iface->bat_v.aggr_list_enabled = true;
++ spin_unlock_bh(&hard_iface->bat_v.aggr_list.lock);
++
+ batadv_v_ogm_start_queue_timer(hard_iface);
+ batadv_v_ogm_start_timer(bat_priv);
+
+@@ -437,6 +448,7 @@ void batadv_v_ogm_iface_disable(struct batadv_hard_iface *hard_iface)
+ cancel_delayed_work_sync(&hard_iface->bat_v.aggr_wq);
+
+ spin_lock_bh(&hard_iface->bat_v.aggr_list.lock);
++ hard_iface->bat_v.aggr_list_enabled = false;
+ batadv_v_ogm_aggr_list_free(hard_iface);
+ spin_unlock_bh(&hard_iface->bat_v.aggr_list.lock);
+ }
+diff --git a/net/batman-adv/types.h b/net/batman-adv/types.h
+index 238d9824c2d62f..28f239421f74a3 100644
+--- a/net/batman-adv/types.h
++++ b/net/batman-adv/types.h
+@@ -131,6 +131,12 @@ struct batadv_hard_iface_bat_v {
+ /** @aggr_list: queue for to be aggregated OGM packets */
+ struct sk_buff_head aggr_list;
+
++ /**
++ * @aggr_list_enabled: aggr_list is active and new skbs can be
++ * enqueued. Protected by aggr_list.lock after initialization
++ */
++ bool aggr_list_enabled:1;
++
+ /** @aggr_len: size of the OGM aggregate (excluding ethernet header) */
+ unsigned int aggr_len;
+
+--
+2.53.0
+
crypto-qat-return-pointer-directly-in-adf_ctl_alloc_.patch
crypto-qat-remove-unused-character-device-and-ioctls.patch
net-sched-act_pedit-fix-action-bind-logic.patch
+batman-adv-tp_meter-keep-unacked-list-in-ascending-o.patch
+batman-adv-tp_meter-initialize-dup_acks-explicitly.patch
+batman-adv-tp_meter-initialize-dec_cwnd-explicitly.patch
+batman-adv-tp_meter-avoid-window-underflow.patch
+batman-adv-tp_meter-avoid-divide-by-zero-for-dec_cwn.patch
+batman-adv-tp_meter-fix-fast-recovery-precondition.patch
+batman-adv-tp_meter-handle-seqno-wrap-around-for-fas.patch
+batman-adv-tp_meter-add-only-finished-tp_vars-to-lis.patch
+batman-adv-bla-annotate-lasttime-access-with-read-wr.patch
+batman-adv-prevent-elp-transmission-interval-underfl.patch
+batman-adv-tp_meter-initialize-last_recv_time-during.patch
+batman-adv-frag-ensure-fragment-is-writable-before-m.patch
+batman-adv-frag-avoid-underflow-of-ttl.patch
+batman-adv-v-prevent-ogm-aggregation-on-disabled-har.patch
+batman-adv-tp_meter-restrict-number-of-unacked-list-.patch
+batman-adv-tp_meter-annotate-last_recv_time-access-w.patch
+batman-adv-tp_meter-prevent-parallel-modifications-o.patch
+batman-adv-tp_meter-handle-overlapping-packets.patch
+batman-adv-tt-don-t-merge-change-entries-with-differ.patch
+batman-adv-tt-track-roam-count-per-vid.patch
+batman-adv-dat-prevent-false-sharing-between-vlans.patch
+batman-adv-tvlv-enforce-2-byte-alignment.patch
+batman-adv-tvlv-avoid-race-of-cifsnotfound-handler-s.patch
--- /dev/null
+From 2906041178585aacd76f532e70461277492b14b2 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:10:48 +0200
+Subject: batman-adv: bla: annotate lasttime access with READ/WRITE_ONCE
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 98b0fb191c878a64cbaebfe231d96d57576acf8c upstream.
+
+The lasttime field for claim, backbone_gw, and loopdetect tracks the
+jiffies value of the most recent activity and is used to detect timeouts.
+These accesses are not consistently protected by a lock, so
+READ_ONCE/WRITE_ONCE must be used to prevent data races caused by compiler
+optimizations.
+
+Cc: stable@kernel.org
+Fixes: 23721387c409 ("batman-adv: add basic bridge loop avoidance code")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/bridge_loop_avoidance.c | 28 +++++++++++++-------------
+ 1 file changed, 14 insertions(+), 14 deletions(-)
+
+diff --git a/net/batman-adv/bridge_loop_avoidance.c b/net/batman-adv/bridge_loop_avoidance.c
+index 452e78fd70c039..0b5222ac4f59a1 100644
+--- a/net/batman-adv/bridge_loop_avoidance.c
++++ b/net/batman-adv/bridge_loop_avoidance.c
+@@ -511,7 +511,7 @@ batadv_bla_get_backbone_gw(struct batadv_priv *bat_priv, u8 *orig,
+ return NULL;
+
+ entry->vid = vid;
+- entry->lasttime = jiffies;
++ WRITE_ONCE(entry->lasttime, jiffies);
+ entry->crc = BATADV_BLA_CRC_INIT;
+ entry->bat_priv = bat_priv;
+ spin_lock_init(&entry->crc_lock);
+@@ -579,7 +579,7 @@ batadv_bla_update_own_backbone_gw(struct batadv_priv *bat_priv,
+ if (unlikely(!backbone_gw))
+ return;
+
+- backbone_gw->lasttime = jiffies;
++ WRITE_ONCE(backbone_gw->lasttime, jiffies);
+ batadv_backbone_gw_put(backbone_gw);
+ }
+
+@@ -713,7 +713,7 @@ static void batadv_bla_add_claim(struct batadv_priv *bat_priv,
+ ether_addr_copy(claim->addr, mac);
+ spin_lock_init(&claim->backbone_lock);
+ claim->vid = vid;
+- claim->lasttime = jiffies;
++ WRITE_ONCE(claim->lasttime, jiffies);
+ kref_get(&backbone_gw->refcount);
+ claim->backbone_gw = backbone_gw;
+ kref_init(&claim->refcount);
+@@ -735,7 +735,7 @@ static void batadv_bla_add_claim(struct batadv_priv *bat_priv,
+ return;
+ }
+ } else {
+- claim->lasttime = jiffies;
++ WRITE_ONCE(claim->lasttime, jiffies);
+ if (claim->backbone_gw == backbone_gw)
+ /* no need to register a new backbone */
+ goto claim_free_ref;
+@@ -768,7 +768,7 @@ static void batadv_bla_add_claim(struct batadv_priv *bat_priv,
+ spin_lock_bh(&backbone_gw->crc_lock);
+ backbone_gw->crc ^= crc16(0, claim->addr, ETH_ALEN);
+ spin_unlock_bh(&backbone_gw->crc_lock);
+- backbone_gw->lasttime = jiffies;
++ WRITE_ONCE(backbone_gw->lasttime, jiffies);
+
+ claim_free_ref:
+ batadv_claim_put(claim);
+@@ -857,7 +857,7 @@ static bool batadv_handle_announce(struct batadv_priv *bat_priv, u8 *an_addr,
+ return true;
+
+ /* handle as ANNOUNCE frame */
+- backbone_gw->lasttime = jiffies;
++ WRITE_ONCE(backbone_gw->lasttime, jiffies);
+ crc = ntohs(*((__force __be16 *)(&an_addr[4])));
+
+ batadv_dbg(BATADV_DBG_BLA, bat_priv,
+@@ -1252,7 +1252,7 @@ static void batadv_bla_purge_backbone_gw(struct batadv_priv *bat_priv, int now)
+ head, hash_entry) {
+ if (now)
+ goto purge_now;
+- if (!batadv_has_timed_out(backbone_gw->lasttime,
++ if (!batadv_has_timed_out(READ_ONCE(backbone_gw->lasttime),
+ BATADV_BLA_BACKBONE_TIMEOUT))
+ continue;
+
+@@ -1333,7 +1333,7 @@ static void batadv_bla_purge_claims(struct batadv_priv *bat_priv,
+ primary_if->net_dev->dev_addr))
+ goto skip;
+
+- if (!batadv_has_timed_out(claim->lasttime,
++ if (!batadv_has_timed_out(READ_ONCE(claim->lasttime),
+ BATADV_BLA_CLAIM_TIMEOUT))
+ goto skip;
+
+@@ -1493,7 +1493,7 @@ static void batadv_bla_periodic_work(struct work_struct *work)
+ eth_random_addr(bat_priv->bla.loopdetect_addr);
+ bat_priv->bla.loopdetect_addr[0] = 0xba;
+ bat_priv->bla.loopdetect_addr[1] = 0xbe;
+- bat_priv->bla.loopdetect_lasttime = jiffies;
++ WRITE_ONCE(bat_priv->bla.loopdetect_lasttime, jiffies);
+ atomic_set(&bat_priv->bla.loopdetect_next,
+ BATADV_BLA_LOOPDETECT_PERIODS);
+
+@@ -1514,7 +1514,7 @@ static void batadv_bla_periodic_work(struct work_struct *work)
+ primary_if->net_dev->dev_addr))
+ continue;
+
+- backbone_gw->lasttime = jiffies;
++ WRITE_ONCE(backbone_gw->lasttime, jiffies);
+
+ batadv_bla_send_announce(bat_priv, backbone_gw);
+ if (send_loopdetect)
+@@ -1899,7 +1899,7 @@ batadv_bla_loopdetect_check(struct batadv_priv *bat_priv, struct sk_buff *skb,
+ /* If the packet came too late, don't forward it on the mesh
+ * but don't consider that as loop. It might be a coincidence.
+ */
+- if (batadv_has_timed_out(bat_priv->bla.loopdetect_lasttime,
++ if (batadv_has_timed_out(READ_ONCE(bat_priv->bla.loopdetect_lasttime),
+ BATADV_BLA_LOOPDETECT_TIMEOUT))
+ return true;
+
+@@ -2015,7 +2015,7 @@ bool batadv_bla_rx(struct batadv_priv *bat_priv, struct sk_buff *skb,
+
+ if (own_claim) {
+ /* ... allow it in any case */
+- claim->lasttime = jiffies;
++ WRITE_ONCE(claim->lasttime, jiffies);
+ goto allow;
+ }
+
+@@ -2117,7 +2117,7 @@ bool batadv_bla_tx(struct batadv_priv *bat_priv, struct sk_buff *skb,
+ /* if yes, the client has roamed and we have
+ * to unclaim it.
+ */
+- if (batadv_has_timed_out(claim->lasttime, 100)) {
++ if (batadv_has_timed_out(READ_ONCE(claim->lasttime), 100)) {
+ /* only unclaim if the last claim entry is
+ * older than 100 ms to make sure we really
+ * have a roaming client here.
+@@ -2371,7 +2371,7 @@ batadv_bla_backbone_dump_entry(struct sk_buff *msg, u32 portid,
+ backbone_crc = backbone_gw->crc;
+ spin_unlock_bh(&backbone_gw->crc_lock);
+
+- msecs = jiffies_to_msecs(jiffies - backbone_gw->lasttime);
++ msecs = jiffies_to_msecs(jiffies - READ_ONCE(backbone_gw->lasttime));
+
+ if (is_own)
+ if (nla_put_flag(msg, BATADV_ATTR_BLA_OWN)) {
+--
+2.53.0
+
--- /dev/null
+From eb233b47819142a2b5c2d28e31bf8fd28ac14c6f Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:02 +0200
+Subject: batman-adv: dat: prevent false sharing between VLANs
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 20d7658b74169f86d4ac01b9185b3eadddf71f28 upstream.
+
+The local hash of DAT entries is supposed to be VLAN (VID) aware. But
+the adding to the hash and the search in the hash were not checking the VID
+information of the hash entries. The entries would therefore only be
+correctly separated when batadv_hash_dat() didn't select the same buckets
+for different VIDs.
+
+Cc: stable@kernel.org
+Fixes: be1db4f6615b ("batman-adv: make the Distributed ARP Table vlan aware")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/distributed-arp-table.c | 12 +++++++++---
+ 1 file changed, 9 insertions(+), 3 deletions(-)
+
+diff --git a/net/batman-adv/distributed-arp-table.c b/net/batman-adv/distributed-arp-table.c
+index 9f561ea95f730d..b8d471dae2888c 100644
+--- a/net/batman-adv/distributed-arp-table.c
++++ b/net/batman-adv/distributed-arp-table.c
+@@ -215,10 +215,13 @@ static void batadv_dat_purge(struct work_struct *work)
+ */
+ static bool batadv_compare_dat(const struct hlist_node *node, const void *data2)
+ {
+- const void *data1 = container_of(node, struct batadv_dat_entry,
+- hash_entry);
++ const struct batadv_dat_entry *entry1;
++ const struct batadv_dat_entry *entry2;
+
+- return memcmp(data1, data2, sizeof(__be32)) == 0;
++ entry1 = container_of(node, struct batadv_dat_entry, hash_entry);
++ entry2 = data2;
++
++ return entry1->ip == entry2->ip && entry1->vid == entry2->vid;
+ }
+
+ /**
+@@ -345,6 +348,9 @@ batadv_dat_entry_hash_find(struct batadv_priv *bat_priv, __be32 ip,
+ if (dat_entry->ip != ip)
+ continue;
+
++ if (dat_entry->vid != vid)
++ continue;
++
+ if (!kref_get_unless_zero(&dat_entry->refcount))
+ continue;
+
+--
+2.53.0
+
--- /dev/null
+From a417ea7a6294239dbbcf64e4eca9d2d22dae1b7c Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:10:51 +0200
+Subject: batman-adv: ensure bcast is writable before modifying TTL
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 4cd6d3a4b96a8576f1fed8f9f9f17c2dc2978e0c upstream.
+
+Before batman-adv is allowed to write to an skb, it either has to have its
+own copy of the skb or used skb_cow() to ensure that the data part is not
+shared.
+
+The old implementation used a shared queue and created copies before
+attempting to write to it. But with the new implementation, the broadcast
+packet is already modified when it gets received. Potentially writing to
+shared buffers in this process.
+
+Adding a skb_cow() right before this operation avoids this and can at the
+same time prepare it for the modifications required to rebroadcast the
+packet.
+
+Cc: stable@kernel.org
+Fixes: 3f69339068f9 ("batman-adv: bcast: queue per interface, if needed")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/routing.c | 6 ++++++
+ 1 file changed, 6 insertions(+)
+
+diff --git a/net/batman-adv/routing.c b/net/batman-adv/routing.c
+index 970d0d7ccc981a..503c8c9381ebe9 100644
+--- a/net/batman-adv/routing.c
++++ b/net/batman-adv/routing.c
+@@ -1198,6 +1198,12 @@ int batadv_recv_bcast_packet(struct sk_buff *skb,
+ if (batadv_is_my_mac(bat_priv, bcast_packet->orig))
+ goto free_skb;
+
++ /* create a copy of the skb, if needed, to modify it. */
++ if (skb_cow(skb, ETH_HLEN) < 0)
++ goto free_skb;
++
++ bcast_packet = (struct batadv_bcast_packet *)skb->data;
++
+ if (bcast_packet->ttl-- < 2)
+ goto free_skb;
+
+--
+2.53.0
+
--- /dev/null
+From 82593888262b84a5910c07b4546692e42980d3e0 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:10:52 +0200
+Subject: batman-adv: fix (m|b)cast csum after decrementing TTL
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit e728bbdf32660c8f32b8f5e8d09427a2c131ad60 upstream.
+
+The broadcast and multicast packets can be received at the same time by the
+local system and forwarded to other nodes. Both are simply decrementing the
+TTL at the beginning of the receive path - independent of chosen paths
+(receive/forward). But such a modification of the data conflicts with the
+hw csum. This is not a problem when the packet is directly forwarded but
+can cause errors in the local receive path.
+
+Such a problem can then trigger a "hw csum failure". The receiver path must
+therefore ensure that the csum is fixed for each modification of the
+payload before batadv_interface_rx() is reached.
+
+Since all batman-adv packet types with a ttl have it as u8 at offset 2, a
+helper can be used for all of them. But it is only used at the moment for
+batadv_bcast_packet and batadv_mcast_packet because they are the only ones
+which deliver the packet locally but unconditionally modify the TTL.
+
+Cc: stable@kernel.org
+Fixes: 3f69339068f9 ("batman-adv: bcast: queue per interface, if needed")
+Fixes: 07afe1ba288c ("batman-adv: mcast: implement multicast packet reception and forwarding")
+[ Context, Drop change for non-existing mcast handling ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/routing.c | 55 +++++++++++++++++++++++++++++++++++++++-
+ 1 file changed, 54 insertions(+), 1 deletion(-)
+
+diff --git a/net/batman-adv/routing.c b/net/batman-adv/routing.c
+index 503c8c9381ebe9..0fb37993fd4604 100644
+--- a/net/batman-adv/routing.c
++++ b/net/batman-adv/routing.c
+@@ -8,6 +8,7 @@
+ #include "main.h"
+
+ #include <linux/atomic.h>
++#include <linux/build_bug.h>
+ #include <linux/byteorder/generic.h>
+ #include <linux/compiler.h>
+ #include <linux/errno.h>
+@@ -205,6 +206,58 @@ bool batadv_check_management_packet(struct sk_buff *skb,
+ return true;
+ }
+
++/**
++ * batadv_skb_decrement_ttl() - decrement ttl in a batman-adv header, csum-safe
++ * @skb: the received packet with @skb->data pointing to the batman-adv header
++ *
++ * Supports the following packet types, all of which carry the TTL at offset 2:
++ *
++ * - batadv_ogm_packet
++ * - batadv_ogm2_packet
++ * - batadv_icmp_header
++ * - batadv_icmp_packet
++ * - batadv_icmp_tp_packet
++ * - batadv_icmp_packet_rr
++ * - batadv_unicast_packet
++ * - batadv_frag_packet
++ * - batadv_bcast_packet
++ * - batadv_mcast_packet
++ * - batadv_coded_packet
++ * - batadv_unicast_tvlv_packet
++ *
++ * Return: true if the packet may be forwarded (ttl decremented),
++ * false if it must be dropped (ttl would expire)
++ */
++static bool batadv_skb_decrement_ttl(struct sk_buff *skb)
++{
++ static const size_t ttl_offset = 2;
++ u8 *ttl_pos;
++
++ BUILD_BUG_ON(offsetof(struct batadv_ogm_packet, ttl) != ttl_offset);
++ BUILD_BUG_ON(offsetof(struct batadv_ogm2_packet, ttl) != ttl_offset);
++ BUILD_BUG_ON(offsetof(struct batadv_icmp_header, ttl) != ttl_offset);
++ BUILD_BUG_ON(offsetof(struct batadv_icmp_packet, ttl) != ttl_offset);
++ BUILD_BUG_ON(offsetof(struct batadv_icmp_tp_packet, ttl) != ttl_offset);
++ BUILD_BUG_ON(offsetof(struct batadv_icmp_packet_rr, ttl) != ttl_offset);
++ BUILD_BUG_ON(offsetof(struct batadv_unicast_packet, ttl) != ttl_offset);
++ BUILD_BUG_ON(offsetof(struct batadv_frag_packet, ttl) != ttl_offset);
++ BUILD_BUG_ON(offsetof(struct batadv_bcast_packet, ttl) != ttl_offset);
++ BUILD_BUG_ON(offsetof(struct batadv_coded_packet, ttl) != ttl_offset);
++ BUILD_BUG_ON(offsetof(struct batadv_unicast_tvlv_packet, ttl) != ttl_offset);
++
++ ttl_pos = skb->data + ttl_offset;
++
++ /* would expire on this hop -> drop, leave header + csum untouched */
++ if (*ttl_pos < 2)
++ return false;
++
++ skb_postpull_rcsum(skb, ttl_pos, 1);
++ (*ttl_pos)--;
++ skb_postpush_rcsum(skb, ttl_pos, 1);
++
++ return true;
++}
++
+ /**
+ * batadv_recv_my_icmp_packet() - receive an icmp packet locally
+ * @bat_priv: the bat priv with all the soft interface information
+@@ -1204,7 +1257,7 @@ int batadv_recv_bcast_packet(struct sk_buff *skb,
+
+ bcast_packet = (struct batadv_bcast_packet *)skb->data;
+
+- if (bcast_packet->ttl-- < 2)
++ if (!batadv_skb_decrement_ttl(skb))
+ goto free_skb;
+
+ orig_node = batadv_orig_hash_find(bat_priv, bcast_packet->orig);
+--
+2.53.0
+
--- /dev/null
+From f54f596f8a17b4218aa235f494ef561c23fdbb44 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:10:54 +0200
+Subject: batman-adv: frag: avoid underflow of TTL
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 493d9d2528e1a09b090e4b37f0f553def7bd5ce9 upstream.
+
+Packets with a TTL are using it to limit the amount of time this packet can
+be forwarded. But for batadv_frag_packet, the TTL was always only reduced
+but it was never evaluated. It could even underflow without any effect.
+
+Check the TTL in batadv_frag_skb_fwd() before attempting to prepare it for
+forwarding. This keeps it in sync with the not fragmented unicast packet.
+
+Cc: stable@kernel.org
+Fixes: 610bfc6bc99b ("batman-adv: Receive fragmented packets and merge")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/fragmentation.c | 7 +++++++
+ 1 file changed, 7 insertions(+)
+
+diff --git a/net/batman-adv/fragmentation.c b/net/batman-adv/fragmentation.c
+index fbf030c57ac04d..0138b0953e10b5 100644
+--- a/net/batman-adv/fragmentation.c
++++ b/net/batman-adv/fragmentation.c
+@@ -420,6 +420,13 @@ bool batadv_frag_skb_fwd(struct sk_buff *skb,
+ */
+ total_size = ntohs(packet->total_size);
+ if (total_size > neigh_node->if_incoming->net_dev->mtu) {
++ if (packet->ttl < 2) {
++ kfree_skb(skb);
++ *rx_result = NET_RX_DROP;
++ ret = true;
++ goto out;
++ }
++
+ if (skb_cow(skb, ETH_HLEN) < 0) {
+ kfree_skb(skb);
+ *rx_result = NET_RX_DROP;
+--
+2.53.0
+
--- /dev/null
+From ce3d7d051bd7a1bec2e392eb92fca7b5cb7a7938 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:10:53 +0200
+Subject: batman-adv: frag: ensure fragment is writable before modifying TTL
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit b7293c6e8c15b2db77809b25cf8389e35331b27a upstream.
+
+Before batman-adv is allowed to write to an skb, it either has to have its
+own copy of the skb or use skb_cow() to ensure that the data part is not
+shared. But batadv_frag_skb_fwd() modifies the TTL even when it is shared.
+
+Adding a skb_cow() right before this operation avoids this and can at the
+same time prepare it for the modifications required to forward the
+fragment.
+
+Cc: stable@kernel.org
+Fixes: 610bfc6bc99b ("batman-adv: Receive fragmented packets and merge")
+[ Context ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/fragmentation.c | 15 ++++++++++++++-
+ net/batman-adv/fragmentation.h | 3 ++-
+ net/batman-adv/routing.c | 3 +--
+ 3 files changed, 17 insertions(+), 4 deletions(-)
+
+diff --git a/net/batman-adv/fragmentation.c b/net/batman-adv/fragmentation.c
+index 4c193194034155..fbf030c57ac04d 100644
+--- a/net/batman-adv/fragmentation.c
++++ b/net/batman-adv/fragmentation.c
+@@ -385,6 +385,8 @@ bool batadv_frag_skb_buffer(struct sk_buff **skb,
+ * @skb: skb to forward
+ * @recv_if: interface that the skb is received on
+ * @orig_node_src: originator that the skb is received from
++ * @rx_result: set to NET_RX_SUCCESS when the fragment was forwarded and
++ * NET_RX_DROP when it was dropped; only valid when true is returned
+ *
+ * Look up the next-hop of the fragments payload and check if the merged packet
+ * will exceed the MTU towards the next-hop. If so, the fragment is forwarded
+@@ -394,7 +396,8 @@ bool batadv_frag_skb_buffer(struct sk_buff **skb,
+ */
+ bool batadv_frag_skb_fwd(struct sk_buff *skb,
+ struct batadv_hard_iface *recv_if,
+- struct batadv_orig_node *orig_node_src)
++ struct batadv_orig_node *orig_node_src,
++ int *rx_result)
+ {
+ struct batadv_priv *bat_priv = netdev_priv(recv_if->soft_iface);
+ struct batadv_orig_node *orig_node_dst;
+@@ -417,12 +420,22 @@ bool batadv_frag_skb_fwd(struct sk_buff *skb,
+ */
+ total_size = ntohs(packet->total_size);
+ if (total_size > neigh_node->if_incoming->net_dev->mtu) {
++ if (skb_cow(skb, ETH_HLEN) < 0) {
++ kfree_skb(skb);
++ *rx_result = NET_RX_DROP;
++ ret = true;
++ goto out;
++ }
++
++ packet = (struct batadv_frag_packet *)skb->data;
++
+ batadv_inc_counter(bat_priv, BATADV_CNT_FRAG_FWD);
+ batadv_add_counter(bat_priv, BATADV_CNT_FRAG_FWD_BYTES,
+ skb->len + ETH_HLEN);
+
+ packet->ttl--;
+ batadv_send_unicast_skb(skb, neigh_node);
++ *rx_result = NET_RX_SUCCESS;
+ ret = true;
+ }
+
+diff --git a/net/batman-adv/fragmentation.h b/net/batman-adv/fragmentation.h
+index dbf0871f870303..51e281027ab630 100644
+--- a/net/batman-adv/fragmentation.h
++++ b/net/batman-adv/fragmentation.h
+@@ -19,7 +19,8 @@ void batadv_frag_purge_orig(struct batadv_orig_node *orig,
+ bool (*check_cb)(struct batadv_frag_table_entry *));
+ bool batadv_frag_skb_fwd(struct sk_buff *skb,
+ struct batadv_hard_iface *recv_if,
+- struct batadv_orig_node *orig_node_src);
++ struct batadv_orig_node *orig_node_src,
++ int *rx_result);
+ bool batadv_frag_skb_buffer(struct sk_buff **skb,
+ struct batadv_orig_node *orig_node);
+ int batadv_frag_send_packet(struct sk_buff *skb,
+diff --git a/net/batman-adv/routing.c b/net/batman-adv/routing.c
+index 0fb37993fd4604..596e4cc47046a2 100644
+--- a/net/batman-adv/routing.c
++++ b/net/batman-adv/routing.c
+@@ -1174,10 +1174,9 @@ int batadv_recv_frag_packet(struct sk_buff *skb,
+
+ /* Route the fragment if it is not for us and too big to be merged. */
+ if (!batadv_is_my_mac(bat_priv, frag_packet->dest) &&
+- batadv_frag_skb_fwd(skb, recv_if, orig_node_src)) {
++ batadv_frag_skb_fwd(skb, recv_if, orig_node_src, &ret)) {
+ /* skb was consumed */
+ skb = NULL;
+- ret = NET_RX_SUCCESS;
+ goto put_orig_node;
+ }
+
+--
+2.53.0
+
--- /dev/null
+From d1211e333781f3bc9b976568f3edeaf888a94325 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:10:49 +0200
+Subject: batman-adv: prevent ELP transmission interval underflow
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 5e50d4b8ae3ea622122d3c6a38d7f6fe68dfddca upstream.
+
+batadv_v_elp_start_timer() enqeues a delayed work. The time when it starts
+is randomly chosen between (elp_interval - BATADV_JITTER) and
+(elp_interval + BATADV_JITTER). The configured elp_interval must therefore
+be larger or equal to BATADV_JITTER to avoid that it causes an underflow of
+the unsigned integer. If this would happen, then a "fast" ELP interval
+would turn into a "day long" delay.
+
+At the same time, it must not be larger than the maximum value the variable
+can store.
+
+Cc: stable@kernel.org
+Fixes: a10800829040 ("batman-adv: Add elp_interval hardif genl configuration")
+[ Context ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/netlink.c | 8 +++++++-
+ 1 file changed, 7 insertions(+), 1 deletion(-)
+
+diff --git a/net/batman-adv/netlink.c b/net/batman-adv/netlink.c
+index bbd6ecf1678c97..cb50b46cb8d30d 100644
+--- a/net/batman-adv/netlink.c
++++ b/net/batman-adv/netlink.c
+@@ -941,9 +941,15 @@ static int batadv_netlink_set_hardif(struct sk_buff *skb,
+ #ifdef CONFIG_BATMAN_ADV_BATMAN_V
+
+ if (info->attrs[BATADV_ATTR_ELP_INTERVAL]) {
++ u32 elp_interval;
++
+ attr = info->attrs[BATADV_ATTR_ELP_INTERVAL];
++ elp_interval = nla_get_u32(attr);
++
++ elp_interval = min_t(u32, elp_interval, INT_MAX);
++ elp_interval = max_t(u32, elp_interval, BATADV_JITTER);
+
+- atomic_set(&hard_iface->bat_v.elp_interval, nla_get_u32(attr));
++ atomic_set(&hard_iface->bat_v.elp_interval, elp_interval);
+ }
+
+ if (info->attrs[BATADV_ATTR_THROUGHPUT_OVERRIDE]) {
+--
+2.53.0
+
--- /dev/null
+From ee45ff9e66206c819dbe2d926138fdae02a0a9b0 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:10:47 +0200
+Subject: batman-adv: tp_meter: add only finished tp_vars to lists
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 15ccbf685222274f5add1387af58c2a41a95f81e upstream.
+
+When the receiver variables (aka "session") are initialized, then they are
+added to the list of sessions before the timer is set up. A RCU protected
+reader could therefore find the entry and run mod_setup before
+batadv_tp_init_recv() finished the timer initialization.
+
+The same is true for batadv_tp_start(), which must first initialize the
+finish_work and the test_length to avoid a similar problem.
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 18 +++++++++---------
+ 1 file changed, 9 insertions(+), 9 deletions(-)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index 7385cd02abf9f0..257f8d042d158e 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -1096,21 +1096,21 @@ void batadv_tp_start(struct batadv_priv *bat_priv, const u8 *dst,
+ tp_vars->prerandom_offset = 0;
+ spin_lock_init(&tp_vars->prerandom_lock);
+
+- kref_get(&tp_vars->refcount);
+- hlist_add_head_rcu(&tp_vars->list, &bat_priv->tp_list);
+- spin_unlock_bh(&bat_priv->tp_list_lock);
+-
+ tp_vars->test_length = test_length;
+ if (!tp_vars->test_length)
+ tp_vars->test_length = BATADV_TP_DEF_TEST_LENGTH;
+
++ /* init work item for finished tp tests */
++ INIT_DELAYED_WORK(&tp_vars->finish_work, batadv_tp_sender_finish);
++
++ kref_get(&tp_vars->refcount);
++ hlist_add_head_rcu(&tp_vars->list, &bat_priv->tp_list);
++ spin_unlock_bh(&bat_priv->tp_list_lock);
++
+ batadv_dbg(BATADV_DBG_TP_METER, bat_priv,
+ "Meter: starting throughput meter towards %pM (length=%ums)\n",
+ dst, test_length);
+
+- /* init work item for finished tp tests */
+- INIT_DELAYED_WORK(&tp_vars->finish_work, batadv_tp_sender_finish);
+-
+ /* start tp kthread. This way the write() call issued from userspace can
+ * happily return and avoid to block
+ */
+@@ -1430,10 +1430,10 @@ batadv_tp_init_recv(struct batadv_priv *bat_priv,
+ INIT_LIST_HEAD(&tp_vars->unacked_list);
+
+ kref_get(&tp_vars->refcount);
+- hlist_add_head_rcu(&tp_vars->list, &bat_priv->tp_list);
++ timer_setup(&tp_vars->timer, batadv_tp_receiver_shutdown, 0);
+
+ kref_get(&tp_vars->refcount);
+- timer_setup(&tp_vars->timer, batadv_tp_receiver_shutdown, 0);
++ hlist_add_head_rcu(&tp_vars->list, &bat_priv->tp_list);
+
+ batadv_tp_reset_receiver_timer(tp_vars);
+
+--
+2.53.0
+
--- /dev/null
+From 584a72aa30797ddbe2e61b73e0d668c7012222c4 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:10:57 +0200
+Subject: batman-adv: tp_meter: annotate last_recv_time access with
+ READ/WRITE_ONCE
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit d67c728f07fca2ee6ffdc6dd4421cf2e8691f4d1 upstream.
+
+The last_recv_time field for batadv_tp_receiver tracks the jiffies value of
+the most recent activity and is used to detect timeouts. These accesses are
+not consistently protected by a lock, so READ_ONCE/WRITE_ONCE must be used
+to prevent data races caused by compiler optimizations.
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 8 ++++----
+ 1 file changed, 4 insertions(+), 4 deletions(-)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index 105cda5b0b2cda..503819e9839c62 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -1183,7 +1183,7 @@ static void batadv_tp_receiver_shutdown(struct timer_list *t)
+ bat_priv = tp_vars->bat_priv;
+
+ /* if there is recent activity rearm the timer */
+- if (!batadv_has_timed_out(tp_vars->last_recv_time,
++ if (!batadv_has_timed_out(READ_ONCE(tp_vars->last_recv_time),
+ BATADV_TP_RECV_TIMEOUT)) {
+ /* reset the receiver shutdown timer */
+ batadv_tp_reset_receiver_timer(tp_vars);
+@@ -1424,7 +1424,7 @@ batadv_tp_init_recv(struct batadv_priv *bat_priv,
+ tp_vars = batadv_tp_list_find_session(bat_priv, icmp->orig,
+ icmp->session, BATADV_TP_RECEIVER);
+ if (tp_vars) {
+- tp_vars->last_recv_time = jiffies;
++ WRITE_ONCE(tp_vars->last_recv_time, jiffies);
+ goto out_unlock;
+ }
+
+@@ -1455,7 +1455,7 @@ batadv_tp_init_recv(struct batadv_priv *bat_priv,
+ kref_get(&tp_vars->refcount);
+ timer_setup(&tp_vars->timer, batadv_tp_receiver_shutdown, 0);
+
+- tp_vars->last_recv_time = jiffies;
++ WRITE_ONCE(tp_vars->last_recv_time, jiffies);
+
+ kref_get(&tp_vars->refcount);
+ hlist_add_head_rcu(&tp_vars->list, &bat_priv->tp_list);
+@@ -1506,7 +1506,7 @@ static void batadv_tp_recv_msg(struct batadv_priv *bat_priv,
+ goto out;
+ }
+
+- tp_vars->last_recv_time = jiffies;
++ WRITE_ONCE(tp_vars->last_recv_time, jiffies);
+ }
+
+ /* if the packet is a duplicate, it may be the case that an ACK has been
+--
+2.53.0
+
--- /dev/null
+From ca58056f2de5dcf4ab6996cdf7d6860e34aac5da Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:10:44 +0200
+Subject: batman-adv: tp_meter: avoid divide-by-zero for dec_cwnd
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 33ccd52f3cc9ed46ce395199f89aa3234dc83314 upstream.
+
+The cwnd is always MSS <= cwnd <= 0x20000000. But the calculation in
+batadv_tp_update_cwnd() assumes unsigned 32 bit arithmetics.
+
+ ((mss * 8) ** 2) / (cwnd * 8)
+
+In case cwnd is actually 0x20000000, it will be shifted by 3 bit to the
+left end up at 0x100000000 or U32_MAX + 1. It will therefore wrap around
+and be 0 - resulting in:
+
+ ((mss * 8) ** 2) / 0
+
+This is of course invalid and cannot be calculated. The calculation should
+must be simplified to avoid this overflow:
+
+ (mss ** 2) * 8 / cwnd
+
+It will keep the precision enhancement from the scaling (by 8) but avoid
+the overflow in the divisor.
+
+In theory, there could still be an overflow in the dividend. It is at the
+moment fixed to BATADV_TP_PLEN in batadv_tp_recv_ack() - so it is not an
+imminent problem. But allowing it to use the whole u32 bit range, would
+mean that it can still use up to 67 bits. To keep this calculation safe for
+32 bit arithmetic, mss must never use more than floor((32 - 3) / 2) bits -
+or in other words: must never be larger than 16383.
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 5 ++++-
+ 1 file changed, 4 insertions(+), 1 deletion(-)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index 3fd46999f36745..f961beac12282e 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -154,9 +154,12 @@ static void batadv_tp_update_cwnd(struct batadv_tp_vars *tp_vars, u32 mss)
+ return;
+ }
+
++ /* prevent overflow in (mss * mss) << 3 */
++ mss = min_t(u32, mss, (1U << 14) - 1);
++
+ /* increment CWND at least of 1 (section 3.1 of RFC5681) */
+ tp_vars->dec_cwnd += max_t(u32, 1U << 3,
+- ((mss * mss) << 6) / (tp_vars->cwnd << 3));
++ ((mss * mss) << 3) / tp_vars->cwnd);
+ if (tp_vars->dec_cwnd < (mss << 3)) {
+ spin_unlock_bh(&tp_vars->cwnd_lock);
+ return;
+--
+2.53.0
+
--- /dev/null
+From 049569def25e4a9f5821e48e92e2a69812e37942 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:10:43 +0200
+Subject: batman-adv: tp_meter: avoid window underflow
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 765947b81fb54b6ebb0bc1cfe55c0fa399e002b8 upstream.
+
+In batadv_tp_avail(), win_left is calculated with 32-bit unsigned
+arithmetic: win_left = win_limit - tp_vars->last_sent;
+
+During Fast Recovery, cwnd is inflated and last_sent advances rapidly. When
+Fast Recovery ends, cwnd drops abruptly back to ss_threshold. If the newly
+shrunk win_limit is less than last_sent, the unsigned subtraction will
+underflow, wrapping to a massive positive value. Instead of returning that
+the window is full (unavailable), it returns that the sender can continue
+sending.
+
+To handle this situation, it must be checked whether the windows end
+sequence number (win_limit) has to be compared with the last sent sequence
+number. If it would be before the last sent sequence number, then more acks
+are needed before the transmission can be started again.
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 7 ++++++-
+ 1 file changed, 6 insertions(+), 1 deletion(-)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index f7b0ce3f8e2aef..3fd46999f36745 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -817,10 +817,15 @@ static void batadv_tp_recv_ack(struct batadv_priv *bat_priv,
+ static bool batadv_tp_avail(struct batadv_tp_vars *tp_vars,
+ size_t payload_len)
+ {
++ u32 last_sent = READ_ONCE(tp_vars->last_sent);
+ u32 win_left, win_limit;
+
+ win_limit = atomic_read(&tp_vars->last_acked) + tp_vars->cwnd;
+- win_left = win_limit - tp_vars->last_sent;
++
++ if (batadv_seq_before(last_sent, win_limit))
++ win_left = win_limit - last_sent;
++ else
++ win_left = 0;
+
+ return win_left >= payload_len;
+ }
+--
+2.53.0
+
--- /dev/null
+From 1e20d87ae2704e3b2d4bf5892f1ee18421ca42d4 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:10:45 +0200
+Subject: batman-adv: tp_meter: fix fast recovery precondition
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 2b0d08f08ed3b2174f05c43089ec65f3543a025b upstream.
+
+The fast recovery precondition checks if the recover (initialized to
+BATADV_TP_FIRST_SEQ) is bigger than the received ack. But since recover is
+only updated when this check is successful, it will never enter the fast
+recovery mode.
+
+According to RFC6582 Section 3.2 step 2, the check should actually be
+different:
+
+> When the third duplicate ACK is received, the TCP sender first
+> checks the value of recover to see if the Cumulative
+> Acknowledgment field covers more than recover
+
+The precondition must therefore check if recover is smaller than the
+received ack - basically swapping the operands of the current check.
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index f961beac12282e..1fc8d6e6f99b30 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -733,7 +733,7 @@ static void batadv_tp_recv_ack(struct batadv_priv *bat_priv,
+ if (atomic_read(&tp_vars->dup_acks) != 3)
+ goto out;
+
+- if (recv_ack >= tp_vars->recover)
++ if (tp_vars->recover >= recv_ack)
+ goto out;
+
+ /* if this is the third duplicate ACK do Fast Retransmit */
+--
+2.53.0
+
--- /dev/null
+From 18d62c85f98b28e7de10db80f1857980c7d159f2 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:10:59 +0200
+Subject: batman-adv: tp_meter: handle overlapping packets
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit cbde75c38b21f022891525078622587ad557b7c1 upstream.
+
+If the size of the packets would change during the transmission, it could
+happen that some retries of packets are overlapping. In this case, precise
+comparisons of sequence numbers by the receiver would be wrong. It is then
+necessary to check if the start sequence number to the end sequence number
+("seqno + length") would contain a new range.
+
+If this is the case then this is enough to accept this packet. In all other
+cases, the packet still has to be dropped (and not acked).
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+[ Switch to pre-splitted tp_vars structure names ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 25 +++++++++++--------------
+ 1 file changed, 11 insertions(+), 14 deletions(-)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index 83749d7b4fd112..5514fef54b0a9a 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -1284,7 +1284,8 @@ static int batadv_tp_send_ack(struct batadv_priv *bat_priv, const u8 *dst,
+ /**
+ * batadv_tp_handle_out_of_order() - store an out of order packet
+ * @tp_vars: the private data of the current TP meter session
+- * @skb: the buffer containing the received packet
++ * @seqno: sequence number of new received packet
++ * @payload_len: length of the received packet
+ *
+ * Store the out of order packet in the unacked list for late processing. This
+ * packets are kept in this list so that they can be ACKed at once as soon as
+@@ -1293,22 +1294,17 @@ static int batadv_tp_send_ack(struct batadv_priv *bat_priv, const u8 *dst,
+ * Return: true if the packed has been successfully processed, false otherwise
+ */
+ static bool batadv_tp_handle_out_of_order(struct batadv_tp_vars *tp_vars,
+- const struct sk_buff *skb)
++ u32 seqno, u32 payload_len)
+ __must_hold(&tp_vars->unacked_lock)
+ {
+- const struct batadv_icmp_tp_packet *icmp;
+ struct batadv_tp_unacked *un, *new;
+- u32 payload_len;
+ bool added = false;
+
+ new = kmalloc(sizeof(*new), GFP_ATOMIC);
+ if (unlikely(!new))
+ return false;
+
+- icmp = (struct batadv_icmp_tp_packet *)skb->data;
+-
+- new->seqno = ntohl(icmp->seqno);
+- payload_len = skb->len - sizeof(struct batadv_unicast_packet);
++ new->seqno = seqno;
+ new->len = payload_len;
+
+ /* if the list is empty immediately attach this new object */
+@@ -1476,7 +1472,7 @@ static void batadv_tp_recv_msg(struct batadv_priv *bat_priv,
+ {
+ const struct batadv_icmp_tp_packet *icmp;
+ struct batadv_tp_vars *tp_vars;
+- size_t packet_size;
++ u32 payload_len;
+ u32 to_ack;
+ u32 seqno;
+
+@@ -1511,15 +1507,17 @@ static void batadv_tp_recv_msg(struct batadv_priv *bat_priv,
+ /* if the packet is a duplicate, it may be the case that an ACK has been
+ * lost. Resend the ACK
+ */
+- if (batadv_seq_before(seqno, tp_vars->last_recv))
++ payload_len = skb->len - sizeof(struct batadv_unicast_packet);
++ to_ack = seqno + payload_len;
++ if (batadv_seq_before(to_ack, tp_vars->last_recv))
+ goto send_ack;
+
+ /* if the packet is out of order enqueue it */
+- if (ntohl(icmp->seqno) != tp_vars->last_recv) {
++ if (batadv_seq_before(tp_vars->last_recv, seqno)) {
+ /* exit immediately (and do not send any ACK) if the packet has
+ * not been enqueued correctly
+ */
+- if (!batadv_tp_handle_out_of_order(tp_vars, skb)) {
++ if (!batadv_tp_handle_out_of_order(tp_vars, seqno, payload_len)) {
+ spin_unlock_bh(&tp_vars->unacked_lock);
+ goto out;
+ }
+@@ -1529,8 +1527,7 @@ static void batadv_tp_recv_msg(struct batadv_priv *bat_priv,
+ }
+
+ /* if everything was fine count the ACKed bytes */
+- packet_size = skb->len - sizeof(struct batadv_unicast_packet);
+- tp_vars->last_recv += packet_size;
++ tp_vars->last_recv = to_ack;
+
+ /* check if this ordered message filled a gap.... */
+ batadv_tp_ack_unordered(tp_vars);
+--
+2.53.0
+
--- /dev/null
+From 5fdc6dd0ccb9afc5fe090618e841aba968c13545 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:10:46 +0200
+Subject: batman-adv: tp_meter: handle seqno wrap-around for fast recovery
+ detection
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit f54c85ed42a1b27a516cf2a4728f5a612b799e07 upstream.
+
+The recover variable and the last_sent sequence number are initialized on
+purpose as a really high value which will wrap-around after the first 2000
+bytes. The fast recovery precondition must therefore not use simple integer
+comparisons but use helpers which are aware of the sequence number
+wrap-arounds.
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index 1fc8d6e6f99b30..7385cd02abf9f0 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -733,7 +733,7 @@ static void batadv_tp_recv_ack(struct batadv_priv *bat_priv,
+ if (atomic_read(&tp_vars->dup_acks) != 3)
+ goto out;
+
+- if (tp_vars->recover >= recv_ack)
++ if (!batadv_seq_before(tp_vars->recover, recv_ack))
+ goto out;
+
+ /* if this is the third duplicate ACK do Fast Retransmit */
+--
+2.53.0
+
--- /dev/null
+From 0d496b785dde951ccba9bef8260da9ffa0c1ddfe Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:10:42 +0200
+Subject: batman-adv: tp_meter: initialize dec_cwnd explicitly
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit febfb1b86224489535312296ecfa3d4bf467f339 upstream.
+
+When batadv_tp_update_cwnd() is called, dec_cwnd is increased. But dec_cwnd
+is only initialixed (to 0) when a duplicate Ack was received or when cwnd
+is below the ss_threshold.
+
+Just initialize the cwnd during the initialization to avoid any potential
+access of uninitialized data.
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index 2b68eacd7bf51d..f7b0ce3f8e2aef 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -1055,6 +1055,8 @@ void batadv_tp_start(struct batadv_priv *bat_priv, const u8 *dst,
+ * soft_interface, hence its MTU
+ */
+ tp_vars->cwnd = BATADV_TP_PLEN * 3;
++ tp_vars->dec_cwnd = 0;
++
+ /* at the beginning initialise the SS threshold to the biggest possible
+ * window size, hence the AWND size
+ */
+--
+2.53.0
+
--- /dev/null
+From 26d78a9abbff74958875494c37408f37f111b6b0 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:10:41 +0200
+Subject: batman-adv: tp_meter: initialize dup_acks explicitly
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit b2b68b32a715e0328662801576974aa37b942b00 upstream.
+
+When an ack with a sequence number equal to the last_acked is received, the
+dup_acks counter is increased to decide whether fast retransmit should be
+performed. Only when the sequence numbers are not equal, the dup_acks is
+set to the initial value (0).
+
+But if the initial packet would have the sequence number
+BATADV_TP_FIRST_SEQ, dup_acks would not be initialized and atomic_inc would
+operate on an undefined starting value. It is therefore required to have it
+explicitly initialized during the start of the sender session.
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index e8792e1e4eedfa..2b68eacd7bf51d 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -1045,6 +1045,7 @@ void batadv_tp_start(struct batadv_priv *bat_priv, const u8 *dst,
+ tp_vars->icmp_uid = icmp_uid;
+
+ tp_vars->last_sent = BATADV_TP_FIRST_SEQ;
++ atomic_set(&tp_vars->dup_acks, 0);
+ atomic_set(&tp_vars->last_acked, BATADV_TP_FIRST_SEQ);
+ tp_vars->fast_recovery = false;
+ tp_vars->recover = BATADV_TP_FIRST_SEQ;
+--
+2.53.0
+
--- /dev/null
+From 2f594743543b43d26878981f25380c6758124313 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:10:50 +0200
+Subject: batman-adv: tp_meter: initialize last_recv_time during init
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 811cb00fa8cdc3f0a7f6eefc000a6888367c8c8f upstream.
+
+The last_recv_time is the most important indicator for a receiver session
+to figure out whether a session timed out or not. But this information was
+only initialized after the session was added to the tp_receiver_list and
+after the timer was started.
+
+In the worst case, the timer (function) could have tried to access this
+information before the actual initialization was reached. Like rest of the
+variables of the tp_meter receiver session, this field has to be filled out
+before any other (parallel running) context has the chance to access it.
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+[ Context ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 10 +++++++---
+ 1 file changed, 7 insertions(+), 3 deletions(-)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index 257f8d042d158e..9e31d86c4b87f5 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -1403,8 +1403,10 @@ batadv_tp_init_recv(struct batadv_priv *bat_priv,
+
+ tp_vars = batadv_tp_list_find_session(bat_priv, icmp->orig,
+ icmp->session, BATADV_TP_RECEIVER);
+- if (tp_vars)
++ if (tp_vars) {
++ tp_vars->last_recv_time = jiffies;
+ goto out_unlock;
++ }
+
+ if (!atomic_add_unless(&bat_priv->tp_num, 1, BATADV_TP_MAX_NUM)) {
+ batadv_dbg(BATADV_DBG_TP_METER, bat_priv,
+@@ -1432,6 +1434,8 @@ batadv_tp_init_recv(struct batadv_priv *bat_priv,
+ kref_get(&tp_vars->refcount);
+ timer_setup(&tp_vars->timer, batadv_tp_receiver_shutdown, 0);
+
++ tp_vars->last_recv_time = jiffies;
++
+ kref_get(&tp_vars->refcount);
+ hlist_add_head_rcu(&tp_vars->list, &bat_priv->tp_list);
+
+@@ -1480,9 +1484,9 @@ static void batadv_tp_recv_msg(struct batadv_priv *bat_priv,
+ icmp->orig);
+ goto out;
+ }
+- }
+
+- tp_vars->last_recv_time = jiffies;
++ tp_vars->last_recv_time = jiffies;
++ }
+
+ /* if the packet is a duplicate, it may be the case that an ACK has been
+ * lost. Resend the ACK
+--
+2.53.0
+
--- /dev/null
+From bbdabf1821e04892e99790640f6eacf39c8c8ff5 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:10:40 +0200
+Subject: batman-adv: tp_meter: keep unacked list in ascending ordered
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 5aa8651527ea0b610e7a09fb3b8204c1398b9525 upstream.
+
+When batadv_tp_handle_out_of_order inserts a new entry in the list of
+unacked (out of order) packets, it searches from the entry with the newest
+sequence number towards oldest sequence number. If an entry is found which
+is older than the newly entry, the new entry has to be added after the
+found one to keep the ascending order.
+
+But for this operation list_add_tail() was used. But this function adds an
+entry _before_ another one. As result, the list would contain a lot of
+swapped sequence numbers. The consumer of this list
+(batadv_tp_ack_unordered()) would then fail to correctly ack packets.
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index c7b48d51e2aed2..e8792e1e4eedfa 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -1325,7 +1325,7 @@ static bool batadv_tp_handle_out_of_order(struct batadv_tp_vars *tp_vars,
+ * one is attached _after_ it. In this way the list is kept in
+ * ascending order
+ */
+- list_add_tail(&new->list, &un->list);
++ list_add(&new->list, &un->list);
+ added = true;
+ break;
+ }
+--
+2.53.0
+
--- /dev/null
+From 8284d148758d64bf934574dd56ee614a1fdf2198 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:10:58 +0200
+Subject: batman-adv: tp_meter: prevent parallel modifications of last_recv
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 6dde0cfcb36e4d5b3de35b75696937478441eed4 upstream.
+
+When last_recv is updated to store the last receive sequence number, it is
+assuming that nothing is modifying in parallel while:
+
+* check for outdated packets is done
+* out of order check is performed (and packets are stored in out-of-order
+ queue)
+* the out-of-order queue was searched for closed gaps
+* sequence number for next ack is calculated
+
+Nothing of that was actually protected. It could therefore happen that the
+last_recv was updated multiple times in parallel and the final sequence
+number was calculated with deltas which had no connection to the sequence
+number they were added to.
+
+Lock this whole region with the same lock which was already used to protect
+the unacked (out-of-order) list.
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+[ Switch to pre-splitted tp_vars structure names ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 22 +++++++++++++---------
+ net/batman-adv/types.h | 2 +-
+ 2 files changed, 14 insertions(+), 10 deletions(-)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index 503819e9839c62..83749d7b4fd112 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -1294,6 +1294,7 @@ static int batadv_tp_send_ack(struct batadv_priv *bat_priv, const u8 *dst,
+ */
+ static bool batadv_tp_handle_out_of_order(struct batadv_tp_vars *tp_vars,
+ const struct sk_buff *skb)
++ __must_hold(&tp_vars->unacked_lock)
+ {
+ const struct batadv_icmp_tp_packet *icmp;
+ struct batadv_tp_unacked *un, *new;
+@@ -1310,12 +1311,11 @@ static bool batadv_tp_handle_out_of_order(struct batadv_tp_vars *tp_vars,
+ payload_len = skb->len - sizeof(struct batadv_unicast_packet);
+ new->len = payload_len;
+
+- spin_lock_bh(&tp_vars->unacked_lock);
+ /* if the list is empty immediately attach this new object */
+ if (list_empty(&tp_vars->unacked_list)) {
+ list_add(&new->list, &tp_vars->unacked_list);
+ tp_vars->unacked_count++;
+- goto out;
++ return true;
+ }
+
+ /* otherwise loop over the list and either drop the packet because this
+@@ -1364,9 +1364,6 @@ static bool batadv_tp_handle_out_of_order(struct batadv_tp_vars *tp_vars,
+ tp_vars->unacked_count--;
+ }
+
+-out:
+- spin_unlock_bh(&tp_vars->unacked_lock);
+-
+ return true;
+ }
+
+@@ -1376,6 +1373,7 @@ static bool batadv_tp_handle_out_of_order(struct batadv_tp_vars *tp_vars,
+ * @tp_vars: the private data of the current TP meter session
+ */
+ static void batadv_tp_ack_unordered(struct batadv_tp_vars *tp_vars)
++ __must_hold(&tp_vars->unacked_lock)
+ {
+ struct batadv_tp_unacked *un, *safe;
+ u32 to_ack;
+@@ -1383,7 +1381,6 @@ static void batadv_tp_ack_unordered(struct batadv_tp_vars *tp_vars)
+ /* go through the unacked packet list and possibly ACK them as
+ * well
+ */
+- spin_lock_bh(&tp_vars->unacked_lock);
+ list_for_each_entry_safe(un, safe, &tp_vars->unacked_list, list) {
+ /* the list is ordered, therefore it is possible to stop as soon
+ * there is a gap between the last acked seqno and the seqno of
+@@ -1401,7 +1398,6 @@ static void batadv_tp_ack_unordered(struct batadv_tp_vars *tp_vars)
+ kfree(un);
+ tp_vars->unacked_count--;
+ }
+- spin_unlock_bh(&tp_vars->unacked_lock);
+ }
+
+ /**
+@@ -1481,6 +1477,7 @@ static void batadv_tp_recv_msg(struct batadv_priv *bat_priv,
+ const struct batadv_icmp_tp_packet *icmp;
+ struct batadv_tp_vars *tp_vars;
+ size_t packet_size;
++ u32 to_ack;
+ u32 seqno;
+
+ icmp = (struct batadv_icmp_tp_packet *)skb->data;
+@@ -1509,6 +1506,8 @@ static void batadv_tp_recv_msg(struct batadv_priv *bat_priv,
+ WRITE_ONCE(tp_vars->last_recv_time, jiffies);
+ }
+
++ spin_lock_bh(&tp_vars->unacked_lock);
++
+ /* if the packet is a duplicate, it may be the case that an ACK has been
+ * lost. Resend the ACK
+ */
+@@ -1520,8 +1519,10 @@ static void batadv_tp_recv_msg(struct batadv_priv *bat_priv,
+ /* exit immediately (and do not send any ACK) if the packet has
+ * not been enqueued correctly
+ */
+- if (!batadv_tp_handle_out_of_order(tp_vars, skb))
++ if (!batadv_tp_handle_out_of_order(tp_vars, skb)) {
++ spin_unlock_bh(&tp_vars->unacked_lock);
+ goto out;
++ }
+
+ /* send a duplicate ACK */
+ goto send_ack;
+@@ -1535,11 +1536,14 @@ static void batadv_tp_recv_msg(struct batadv_priv *bat_priv,
+ batadv_tp_ack_unordered(tp_vars);
+
+ send_ack:
++ to_ack = tp_vars->last_recv;
++ spin_unlock_bh(&tp_vars->unacked_lock);
++
+ /* send the ACK. If the received packet was out of order, the ACK that
+ * is going to be sent is a duplicate (the sender will count them and
+ * possibly enter Fast Retransmit as soon as it has reached 3)
+ */
+- batadv_tp_send_ack(bat_priv, icmp->orig, tp_vars->last_recv,
++ batadv_tp_send_ack(bat_priv, icmp->orig, to_ack,
+ icmp->timestamp, icmp->session, icmp->uid);
+ out:
+ batadv_tp_vars_put(tp_vars);
+diff --git a/net/batman-adv/types.h b/net/batman-adv/types.h
+index d7fb3046d04412..e1ca1359415bc7 100644
+--- a/net/batman-adv/types.h
++++ b/net/batman-adv/types.h
+@@ -1491,7 +1491,7 @@ struct batadv_tp_vars {
+ /** @unacked_list: list of unacked packets (meta-info only) */
+ struct list_head unacked_list;
+
+- /** @unacked_lock: protect unacked_list */
++ /** @unacked_lock: protect unacked_list + &batadv_tp_receiver.last_recv */
+ spinlock_t unacked_lock;
+
+ /** @unacked_count: number of unacked entries */
+--
+2.53.0
+
--- /dev/null
+From df7081f69f2061e11ca745b2452ca6c98c96f3f7 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:10:56 +0200
+Subject: batman-adv: tp_meter: restrict number of unacked list entries
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit e7c775110e1858e5a7471a23a9c9658c0af9df89 upstream.
+
+When the unacked_list is unbound, an attacker could send messages with
+small lengths and appropriated seqno + gaps to force the receiver to
+allocate more and more unacked_list entries. And the end either causing an
+out-of-memory situation or increase the management overhead for the (large)
+list that significant portions of CPU cycles are wasted in searching
+through the list.
+
+When limiting the list to a specific number, it is important to still
+correctly add a new entry to the list. But if the list became larger than
+the limit, the last entry of the list (with the highest seqno) must be
+dropped to still allow the earlier seqnos to finish and therefore to
+continue the process. Otherwise, the process might get stuck with too high
+seqnos which are not handled by batadv_tp_ack_unordered().
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+[ Switch to pre-splitted tp_vars structure names ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 23 ++++++++++++++++++++++-
+ net/batman-adv/types.h | 3 +++
+ 2 files changed, 25 insertions(+), 1 deletion(-)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index 9e31d86c4b87f5..105cda5b0b2cda 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -87,6 +87,11 @@
+ #define BATADV_TP_PLEN (BATADV_TP_PACKET_LEN - ETH_HLEN - \
+ sizeof(struct batadv_unicast_packet))
+
++/**
++ * BATADV_TP_MAX_UNACKED - maximum number of packets a receiver didn't yet ack
++ */
++#define BATADV_TP_MAX_UNACKED 100
++
+ static u8 batadv_tp_prerandom[4096] __read_mostly;
+
+ /**
+@@ -1195,6 +1200,7 @@ static void batadv_tp_receiver_shutdown(struct timer_list *t)
+ list_for_each_entry_safe(un, safe, &tp_vars->unacked_list, list) {
+ list_del(&un->list);
+ kfree(un);
++ tp_vars->unacked_count--;
+ }
+ spin_unlock_bh(&tp_vars->unacked_lock);
+
+@@ -1308,6 +1314,7 @@ static bool batadv_tp_handle_out_of_order(struct batadv_tp_vars *tp_vars,
+ /* if the list is empty immediately attach this new object */
+ if (list_empty(&tp_vars->unacked_list)) {
+ list_add(&new->list, &tp_vars->unacked_list);
++ tp_vars->unacked_count++;
+ goto out;
+ }
+
+@@ -1338,12 +1345,24 @@ static bool batadv_tp_handle_out_of_order(struct batadv_tp_vars *tp_vars,
+ */
+ list_add(&new->list, &un->list);
+ added = true;
++ tp_vars->unacked_count++;
+ break;
+ }
+
+ /* received packet with smallest seqno out of order; add it to front */
+- if (!added)
++ if (!added) {
+ list_add(&new->list, &tp_vars->unacked_list);
++ tp_vars->unacked_count++;
++ }
++
++ /* remove the last (biggest) unacked seqno when list is too large */
++ if (tp_vars->unacked_count > BATADV_TP_MAX_UNACKED) {
++ un = list_last_entry(&tp_vars->unacked_list,
++ struct batadv_tp_unacked, list);
++ list_del(&un->list);
++ kfree(un);
++ tp_vars->unacked_count--;
++ }
+
+ out:
+ spin_unlock_bh(&tp_vars->unacked_lock);
+@@ -1380,6 +1399,7 @@ static void batadv_tp_ack_unordered(struct batadv_tp_vars *tp_vars)
+
+ list_del(&un->list);
+ kfree(un);
++ tp_vars->unacked_count--;
+ }
+ spin_unlock_bh(&tp_vars->unacked_lock);
+ }
+@@ -1430,6 +1450,7 @@ batadv_tp_init_recv(struct batadv_priv *bat_priv,
+
+ spin_lock_init(&tp_vars->unacked_lock);
+ INIT_LIST_HEAD(&tp_vars->unacked_list);
++ tp_vars->unacked_count = 0;
+
+ kref_get(&tp_vars->refcount);
+ timer_setup(&tp_vars->timer, batadv_tp_receiver_shutdown, 0);
+diff --git a/net/batman-adv/types.h b/net/batman-adv/types.h
+index d298a3983fab97..d7fb3046d04412 100644
+--- a/net/batman-adv/types.h
++++ b/net/batman-adv/types.h
+@@ -1494,6 +1494,9 @@ struct batadv_tp_vars {
+ /** @unacked_lock: protect unacked_list */
+ spinlock_t unacked_lock;
+
++ /** @unacked_count: number of unacked entries */
++ size_t unacked_count;
++
+ /** @last_recv_time: time (jiffies) a msg was received */
+ unsigned long last_recv_time;
+
+--
+2.53.0
+
--- /dev/null
+From a17c210562764a37e97b52b5137dbf30b08c25e4 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:00 +0200
+Subject: batman-adv: tt: don't merge change entries with different VIDs
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit f08e06c2d5c3e2434e7c773f2213f4a7dce6bc1e upstream.
+
+batadv_tt_local_event() merges/cancels events for the same client which
+would conflict or be duplicates. The matching of the queued events only
+compares the MAC address - the VLAN ID stored in each event is ignored.
+
+If a MAC would now appear on multiple VID, the two ADD change events (for
+VID 1 and VID 2) would be merged to a single vid event. The remote can
+therefore not calculate the correct TT table and desync. A full translation
+table exchange is required to recover from this state.
+
+A check of VID is therefore necessary to avoid such wrong merges/cancels.
+
+Cc: stable@kernel.org
+Fixes: c018ad3de61a ("batman-adv: add the VLAN ID attribute to the TT entry")
+[ Context ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/translation-table.c | 3 +++
+ 1 file changed, 3 insertions(+)
+
+diff --git a/net/batman-adv/translation-table.c b/net/batman-adv/translation-table.c
+index 8ab1257bade048..4bc73aceeb89f6 100644
+--- a/net/batman-adv/translation-table.c
++++ b/net/batman-adv/translation-table.c
+@@ -485,6 +485,9 @@ static void batadv_tt_local_event(struct batadv_priv *bat_priv,
+ if (!batadv_compare_eth(entry->change.addr, common->addr))
+ continue;
+
++ if (entry->change.vid != tt_change_node->change.vid)
++ continue;
++
+ /* DEL+ADD in the same orig interval have no effect and can be
+ * removed to avoid silly behaviour on the receiver side. The
+ * other way around (ADD+DEL) can happen in case of roaming of
+--
+2.53.0
+
--- /dev/null
+From 674b41f57012ff41b1bd6b64ae87a79ea87fa6f1 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:01 +0200
+Subject: batman-adv: tt: track roam count per VID
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 12407d5f61c2653a64f2ff4b22f3c267f8420ef1 upstream.
+
+batadv_tt_check_roam_count() is supposed to track roaming of a TT entry.
+But TT entries are for a MAC + VID. The VID was completely missed and thus
+leads to incorrect detection of ROAM counts when a client MAC exists in
+multiple VLANs.
+
+Cc: stable@kernel.org
+Fixes: c018ad3de61a ("batman-adv: add the VLAN ID attribute to the TT entry")
+[ Context ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/translation-table.c | 9 +++++++--
+ net/batman-adv/types.h | 3 +++
+ 2 files changed, 10 insertions(+), 2 deletions(-)
+
+diff --git a/net/batman-adv/translation-table.c b/net/batman-adv/translation-table.c
+index 4bc73aceeb89f6..53ac49ca2c2f1d 100644
+--- a/net/batman-adv/translation-table.c
++++ b/net/batman-adv/translation-table.c
+@@ -3517,6 +3517,7 @@ static void batadv_tt_roam_purge(struct batadv_priv *bat_priv)
+ * batadv_tt_check_roam_count() - check if a client has roamed too frequently
+ * @bat_priv: the bat priv with all the soft interface information
+ * @client: mac address of the roaming client
++ * @vid: VLAN identifier
+ *
+ * This function checks whether the client already reached the
+ * maximum number of possible roaming phases. In this case the ROAMING_ADV
+@@ -3524,7 +3525,7 @@ static void batadv_tt_roam_purge(struct batadv_priv *bat_priv)
+ *
+ * Return: true if the ROAMING_ADV can be sent, false otherwise
+ */
+-static bool batadv_tt_check_roam_count(struct batadv_priv *bat_priv, u8 *client)
++static bool batadv_tt_check_roam_count(struct batadv_priv *bat_priv, u8 *client, u16 vid)
+ {
+ struct batadv_tt_roam_node *tt_roam_node;
+ bool ret = false;
+@@ -3537,6 +3538,9 @@ static bool batadv_tt_check_roam_count(struct batadv_priv *bat_priv, u8 *client)
+ if (!batadv_compare_eth(tt_roam_node->addr, client))
+ continue;
+
++ if (tt_roam_node->vid != vid)
++ continue;
++
+ if (batadv_has_timed_out(tt_roam_node->first_time,
+ BATADV_ROAMING_MAX_TIME))
+ continue;
+@@ -3558,6 +3562,7 @@ static bool batadv_tt_check_roam_count(struct batadv_priv *bat_priv, u8 *client)
+ atomic_set(&tt_roam_node->counter,
+ BATADV_ROAMING_MAX_COUNT - 1);
+ ether_addr_copy(tt_roam_node->addr, client);
++ tt_roam_node->vid = vid;
+
+ list_add(&tt_roam_node->list, &bat_priv->tt.roam_list);
+ ret = true;
+@@ -3594,7 +3599,7 @@ static void batadv_send_roam_adv(struct batadv_priv *bat_priv, u8 *client,
+ /* before going on we have to check whether the client has
+ * already roamed to us too many times
+ */
+- if (!batadv_tt_check_roam_count(bat_priv, client))
++ if (!batadv_tt_check_roam_count(bat_priv, client, vid))
+ goto out;
+
+ batadv_dbg(BATADV_DBG_TT, bat_priv,
+diff --git a/net/batman-adv/types.h b/net/batman-adv/types.h
+index e1ca1359415bc7..f0d5701265ef48 100644
+--- a/net/batman-adv/types.h
++++ b/net/batman-adv/types.h
+@@ -2034,6 +2034,9 @@ struct batadv_tt_roam_node {
+ /** @addr: mac address of the client in the roaming phase */
+ u8 addr[ETH_ALEN];
+
++ /** @vid: VLAN identifier */
++ u16 vid;
++
+ /**
+ * @counter: number of allowed roaming events per client within a single
+ * OGM interval (changes are committed with each OGM)
+--
+2.53.0
+
--- /dev/null
+From 792d8b5b518a080dc39a800ee7ce47f591dfcf58 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:04 +0200
+Subject: batman-adv: tvlv: avoid race of cifsnotfound handler state
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit edb557b2ba38fea2c5eb710cf366c797e187218c upstream.
+
+TVLV handlers can have the flag BATADV_TVLV_HANDLER_OGM_CIFNOTFND set to
+signal that the OGM handler should be called (with NULL for data) when the
+specific TVLV container was not found in the OGM. This is used by:
+
+* DAT
+* GW
+* Multicast (OGM + Tracker)
+
+The state whether the handler was executed was stored in the struct
+batadv_tvlv_handler. But the TVLV processing is started without any lock.
+Multiple parallel contexts processing TVLVs would therefore overwrite each
+others BATADV_TVLV_HANDLER_OGM_CALLED flag in the shared
+batadv_tvlv_handler.
+
+Drop the shared BATADV_TVLV_HANDLER_OGM_CALLED flag and instead determine,
+per TVLV buffer, whether a matching container was present by scanning the
+packet's buffer.
+
+Cc: stable@kernel.org
+Fixes: ef26157747d4 ("batman-adv: tvlv - basic infrastructure")
+[ Context ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tvlv.c | 63 ++++++++++++++++++++++++++++++++++++++----
+ net/batman-adv/types.h | 7 -----
+ 2 files changed, 57 insertions(+), 13 deletions(-)
+
+diff --git a/net/batman-adv/tvlv.c b/net/batman-adv/tvlv.c
+index 66c2043dc1df91..6eb95bd7a4bc36 100644
+--- a/net/batman-adv/tvlv.c
++++ b/net/batman-adv/tvlv.c
+@@ -394,7 +394,6 @@ static int batadv_tvlv_call_handler(struct batadv_priv *bat_priv,
+ tvlv_handler->ogm_handler(bat_priv, orig_node,
+ BATADV_NO_FLAGS,
+ tvlv_value, tvlv_value_len);
+- tvlv_handler->flags |= BATADV_TVLV_HANDLER_OGM_CALLED;
+ } else {
+ if (!src)
+ return NET_RX_SUCCESS;
+@@ -413,6 +412,48 @@ static int batadv_tvlv_call_handler(struct batadv_priv *bat_priv,
+ return NET_RX_SUCCESS;
+ }
+
++/**
++ * batadv_tvlv_containers_contain() - check if a tvlv buffer holds a container
++ * @tvlv_value: tvlv content
++ * @tvlv_value_len: tvlv content length
++ * @type: tvlv container type to look for
++ * @version: tvlv container version to look for
++ *
++ * Return: true if a container of the given type and version is present in the
++ * tvlv buffer, false otherwise.
++ */
++static bool batadv_tvlv_containers_contain(void *tvlv_value,
++ u16 tvlv_value_len, u8 type,
++ u8 version)
++{
++ struct batadv_tvlv_hdr *tvlv_hdr;
++ u16 tvlv_value_cont_len;
++
++ while (tvlv_value_len >= sizeof(*tvlv_hdr)) {
++ tvlv_hdr = tvlv_value;
++ tvlv_value_cont_len = ntohs(tvlv_hdr->len);
++ tvlv_value = tvlv_hdr + 1;
++ tvlv_value_len -= sizeof(*tvlv_hdr);
++
++ if (tvlv_value_cont_len > tvlv_value_len)
++ break;
++
++ /* the next tvlv header is accessed assuming (at least) 2-byte
++ * alignment, so it must start at an even offset.
++ */
++ if (tvlv_value_cont_len & 1)
++ break;
++
++ if (tvlv_hdr->type == type && tvlv_hdr->version == version)
++ return true;
++
++ tvlv_value = (u8 *)tvlv_value + tvlv_value_cont_len;
++ tvlv_value_len -= tvlv_value_cont_len;
++ }
++
++ return false;
++}
++
+ /**
+ * batadv_tvlv_containers_process() - parse the given tvlv buffer to call the
+ * appropriate handlers
+@@ -433,7 +474,9 @@ int batadv_tvlv_containers_process(struct batadv_priv *bat_priv,
+ u8 *src, u8 *dst,
+ void *tvlv_value, u16 tvlv_value_len)
+ {
++ u16 tvlv_value_start_len = tvlv_value_len;
+ struct batadv_tvlv_handler *tvlv_handler;
++ void *tvlv_value_start = tvlv_value;
+ struct batadv_tvlv_hdr *tvlv_hdr;
+ u16 tvlv_value_cont_len;
+ u8 cifnotfound = BATADV_TVLV_HANDLER_OGM_CIFNOTFND;
+@@ -473,12 +516,20 @@ int batadv_tvlv_containers_process(struct batadv_priv *bat_priv,
+ rcu_read_lock();
+ hlist_for_each_entry_rcu(tvlv_handler,
+ &bat_priv->tvlv.handler_list, list) {
+- if ((tvlv_handler->flags & BATADV_TVLV_HANDLER_OGM_CIFNOTFND) &&
+- !(tvlv_handler->flags & BATADV_TVLV_HANDLER_OGM_CALLED))
+- tvlv_handler->ogm_handler(bat_priv, orig_node,
+- cifnotfound, NULL, 0);
++ if (!(tvlv_handler->flags & BATADV_TVLV_HANDLER_OGM_CIFNOTFND))
++ continue;
+
+- tvlv_handler->flags &= ~BATADV_TVLV_HANDLER_OGM_CALLED;
++ /* if the corresponding container was present then the handler
++ * was already called from the loop above
++ */
++ if (batadv_tvlv_containers_contain(tvlv_value_start,
++ tvlv_value_start_len,
++ tvlv_handler->type,
++ tvlv_handler->version))
++ continue;
++
++ tvlv_handler->ogm_handler(bat_priv, orig_node,
++ cifnotfound, NULL, 0);
+ }
+ rcu_read_unlock();
+
+diff --git a/net/batman-adv/types.h b/net/batman-adv/types.h
+index f0d5701265ef48..586679ad574f72 100644
+--- a/net/batman-adv/types.h
++++ b/net/batman-adv/types.h
+@@ -2447,13 +2447,6 @@ enum batadv_tvlv_handler_flags {
+ * will call this handler even if its type was not found (with no data)
+ */
+ BATADV_TVLV_HANDLER_OGM_CIFNOTFND = BIT(1),
+-
+- /**
+- * @BATADV_TVLV_HANDLER_OGM_CALLED: interval tvlv handling flag - the
+- * API marks a handler as being called, so it won't be called if the
+- * BATADV_TVLV_HANDLER_OGM_CIFNOTFND flag was set
+- */
+- BATADV_TVLV_HANDLER_OGM_CALLED = BIT(2),
+ };
+
+ #endif /* _NET_BATMAN_ADV_TYPES_H_ */
+--
+2.53.0
+
--- /dev/null
+From 289176ea3b5875d64153e062f8e313f3b7317877 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:03 +0200
+Subject: batman-adv: tvlv: enforce 2-byte alignment
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 32a6799255525d6ea4da0f7e9e0e521ad9560a46 upstream.
+
+The fields of an aggregated OGM(v2) are accessed assuming (at least) 2-byte
+alignment, so a following OGM must start at an even offset. As the header
+length is even, an odd tvlv_len would misalign it and trigger unaligned
+accesses on strict-alignment architectures.
+
+Such a misaligned TVLV/OGM/OGMv2 is not created by a normal participant in
+the mesh. Therefore, reject such malformed packets.
+
+Cc: stable@kernel.org
+Fixes: ef26157747d4 ("batman-adv: tvlv - basic infrastructure")
+[ Drop change for non-existing mcast handling ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/bat_iv_ogm.c | 11 ++++++++++-
+ net/batman-adv/bat_v_ogm.c | 11 ++++++++++-
+ net/batman-adv/tvlv.c | 6 ++++++
+ 3 files changed, 26 insertions(+), 2 deletions(-)
+
+diff --git a/net/batman-adv/bat_iv_ogm.c b/net/batman-adv/bat_iv_ogm.c
+index 669f77eed073a0..a424c5b7a2462c 100644
+--- a/net/batman-adv/bat_iv_ogm.c
++++ b/net/batman-adv/bat_iv_ogm.c
+@@ -311,14 +311,23 @@ batadv_iv_ogm_aggr_packet(int buff_pos, int packet_len,
+ const struct batadv_ogm_packet *ogm_packet)
+ {
+ int next_buff_pos = 0;
++ u16 tvlv_len;
+
+ /* check if there is enough space for the header */
+ next_buff_pos += buff_pos + sizeof(*ogm_packet);
+ if (next_buff_pos > packet_len)
+ return false;
+
++ tvlv_len = ntohs(ogm_packet->tvlv_len);
++
++ /* the fields of an aggregated OGM are accessed assuming (at least)
++ * 2-byte alignment, so a following OGM must start at an even offset.
++ */
++ if (tvlv_len & 1)
++ return false;
++
+ /* check if there is enough space for the optional TVLV */
+- next_buff_pos += ntohs(ogm_packet->tvlv_len);
++ next_buff_pos += tvlv_len;
+
+ return next_buff_pos <= packet_len;
+ }
+diff --git a/net/batman-adv/bat_v_ogm.c b/net/batman-adv/bat_v_ogm.c
+index c7d15887c47c8e..641f3dbde1bde2 100644
+--- a/net/batman-adv/bat_v_ogm.c
++++ b/net/batman-adv/bat_v_ogm.c
+@@ -854,14 +854,23 @@ batadv_v_ogm_aggr_packet(int buff_pos, int packet_len,
+ const struct batadv_ogm2_packet *ogm2_packet)
+ {
+ int next_buff_pos = 0;
++ u16 tvlv_len;
+
+ /* check if there is enough space for the header */
+ next_buff_pos += buff_pos + sizeof(*ogm2_packet);
+ if (next_buff_pos > packet_len)
+ return false;
+
++ tvlv_len = ntohs(ogm2_packet->tvlv_len);
++
++ /* the fields of an aggregated OGMv2 are accessed assuming (at least)
++ * 2-byte alignment, so a following OGMv2 must start at an even offset.
++ */
++ if (tvlv_len & 1)
++ return false;
++
+ /* check if there is enough space for the optional TVLV */
+- next_buff_pos += ntohs(ogm2_packet->tvlv_len);
++ next_buff_pos += tvlv_len;
+
+ return next_buff_pos <= packet_len;
+ }
+diff --git a/net/batman-adv/tvlv.c b/net/batman-adv/tvlv.c
+index de0c139426839c..66c2043dc1df91 100644
+--- a/net/batman-adv/tvlv.c
++++ b/net/batman-adv/tvlv.c
+@@ -448,6 +448,12 @@ int batadv_tvlv_containers_process(struct batadv_priv *bat_priv,
+ if (tvlv_value_cont_len > tvlv_value_len)
+ break;
+
++ /* the next tvlv header is accessed assuming (at least) 2-byte
++ * alignment, so it must start at an even offset.
++ */
++ if (tvlv_value_cont_len & 1)
++ break;
++
+ tvlv_handler = batadv_tvlv_handler_get(bat_priv,
+ tvlv_hdr->type,
+ tvlv_hdr->version);
+--
+2.53.0
+
--- /dev/null
+From 6fcda727f2744048d3e2bcb36b04d68ae6c7e5cc Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:10:55 +0200
+Subject: batman-adv: v: prevent OGM aggregation on disabled hardif
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit d11c00b95b2a3b3934007fc003dccc6fdcc061ad upstream.
+
+When an interface gets disabled, the worker is correctly disabled by
+batadv_hardif_disable_interface() -> ... -> batadv_v_ogm_iface_disable().
+In this process, the skb aggr_list is also freed.
+
+But batadv_v_ogm_send_meshif() can still queue new skbs (via
+batadv_v_ogm_queue_on_if()) to the aggr_list. This will only stop after all
+cores can no longer find the RCU protected list of hard interfaces. These
+queued skbs will never be freed or consumed by batadv_v_ogm_aggr_work.
+
+The batadv_v_ogm_iface_disable() function must block
+batadv_v_ogm_queue_on_if() to avoid leak of skbs.
+
+Cc: stable@kernel.org
+Fixes: f89255a02f1d ("batman-adv: BATMAN_V: introduce per hard-iface OGMv2 queues")
+[ Context ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/bat_v.c | 1 +
+ net/batman-adv/bat_v_ogm.c | 12 ++++++++++++
+ net/batman-adv/types.h | 6 ++++++
+ 3 files changed, 19 insertions(+)
+
+diff --git a/net/batman-adv/bat_v.c b/net/batman-adv/bat_v.c
+index 651e01b86141e3..34874942ae8d03 100644
+--- a/net/batman-adv/bat_v.c
++++ b/net/batman-adv/bat_v.c
+@@ -838,6 +838,7 @@ void batadv_v_hardif_init(struct batadv_hard_iface *hard_iface)
+
+ hard_iface->bat_v.aggr_len = 0;
+ skb_queue_head_init(&hard_iface->bat_v.aggr_list);
++ hard_iface->bat_v.aggr_list_enabled = false;
+ INIT_DELAYED_WORK(&hard_iface->bat_v.aggr_wq,
+ batadv_v_ogm_aggr_work);
+ }
+diff --git a/net/batman-adv/bat_v_ogm.c b/net/batman-adv/bat_v_ogm.c
+index 63337e02cf2f11..c7d15887c47c8e 100644
+--- a/net/batman-adv/bat_v_ogm.c
++++ b/net/batman-adv/bat_v_ogm.c
+@@ -255,11 +255,18 @@ static void batadv_v_ogm_queue_on_if(struct batadv_priv *bat_priv,
+ }
+
+ spin_lock_bh(&hard_iface->bat_v.aggr_list.lock);
++ if (!hard_iface->bat_v.aggr_list_enabled) {
++ kfree_skb(skb);
++ goto unlock;
++ }
++
+ if (!batadv_v_ogm_queue_left(skb, hard_iface))
+ batadv_v_ogm_aggr_send(bat_priv, hard_iface);
+
+ hard_iface->bat_v.aggr_len += batadv_v_ogm_len(skb);
+ __skb_queue_tail(&hard_iface->bat_v.aggr_list, skb);
++
++unlock:
+ spin_unlock_bh(&hard_iface->bat_v.aggr_list.lock);
+ }
+
+@@ -422,6 +429,10 @@ int batadv_v_ogm_iface_enable(struct batadv_hard_iface *hard_iface)
+ {
+ struct batadv_priv *bat_priv = netdev_priv(hard_iface->soft_iface);
+
++ spin_lock_bh(&hard_iface->bat_v.aggr_list.lock);
++ hard_iface->bat_v.aggr_list_enabled = true;
++ spin_unlock_bh(&hard_iface->bat_v.aggr_list.lock);
++
+ batadv_v_ogm_start_queue_timer(hard_iface);
+ batadv_v_ogm_start_timer(bat_priv);
+
+@@ -437,6 +448,7 @@ void batadv_v_ogm_iface_disable(struct batadv_hard_iface *hard_iface)
+ cancel_delayed_work_sync(&hard_iface->bat_v.aggr_wq);
+
+ spin_lock_bh(&hard_iface->bat_v.aggr_list.lock);
++ hard_iface->bat_v.aggr_list_enabled = false;
+ batadv_v_ogm_aggr_list_free(hard_iface);
+ spin_unlock_bh(&hard_iface->bat_v.aggr_list.lock);
+ }
+diff --git a/net/batman-adv/types.h b/net/batman-adv/types.h
+index f1a835edd115c7..d298a3983fab97 100644
+--- a/net/batman-adv/types.h
++++ b/net/batman-adv/types.h
+@@ -130,6 +130,12 @@ struct batadv_hard_iface_bat_v {
+ /** @aggr_list: queue for to be aggregated OGM packets */
+ struct sk_buff_head aggr_list;
+
++ /**
++ * @aggr_list_enabled: aggr_list is active and new skbs can be
++ * enqueued. Protected by aggr_list.lock after initialization
++ */
++ bool aggr_list_enabled:1;
++
+ /** @aggr_len: size of the OGM aggregate (excluding ethernet header) */
+ unsigned int aggr_len;
+
+--
+2.53.0
+
kvm-x86-fix-shadow-paging-use-after-free-due-to-unex.patch-23529
kvm-x86-mmu-ensure-hugepage-is-in-by-slot-before-che.patch
kselftest-arm64-signal-skip-sve-signal-test-if-not-e.patch
+batman-adv-tp_meter-keep-unacked-list-in-ascending-o.patch
+batman-adv-tp_meter-initialize-dup_acks-explicitly.patch
+batman-adv-tp_meter-initialize-dec_cwnd-explicitly.patch
+batman-adv-tp_meter-avoid-window-underflow.patch
+batman-adv-tp_meter-avoid-divide-by-zero-for-dec_cwn.patch
+batman-adv-tp_meter-fix-fast-recovery-precondition.patch
+batman-adv-tp_meter-handle-seqno-wrap-around-for-fas.patch
+batman-adv-tp_meter-add-only-finished-tp_vars-to-lis.patch
+batman-adv-bla-annotate-lasttime-access-with-read-wr.patch
+batman-adv-prevent-elp-transmission-interval-underfl.patch
+batman-adv-tp_meter-initialize-last_recv_time-during.patch
+batman-adv-ensure-bcast-is-writable-before-modifying.patch
+batman-adv-fix-m-b-cast-csum-after-decrementing-ttl.patch
+batman-adv-frag-ensure-fragment-is-writable-before-m.patch
+batman-adv-frag-avoid-underflow-of-ttl.patch
+batman-adv-v-prevent-ogm-aggregation-on-disabled-har.patch
+batman-adv-tp_meter-restrict-number-of-unacked-list-.patch
+batman-adv-tp_meter-annotate-last_recv_time-access-w.patch
+batman-adv-tp_meter-prevent-parallel-modifications-o.patch
+batman-adv-tp_meter-handle-overlapping-packets.patch
+batman-adv-tt-don-t-merge-change-entries-with-differ.patch
+batman-adv-tt-track-roam-count-per-vid.patch
+batman-adv-dat-prevent-false-sharing-between-vlans.patch
+batman-adv-tvlv-enforce-2-byte-alignment.patch
+batman-adv-tvlv-avoid-race-of-cifsnotfound-handler-s.patch
--- /dev/null
+From 5a52ae127a87d497c511fc702390e9affad4a1bf Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:07 +0200
+Subject: batman-adv: bla: annotate lasttime access with READ/WRITE_ONCE
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 98b0fb191c878a64cbaebfe231d96d57576acf8c upstream.
+
+The lasttime field for claim, backbone_gw, and loopdetect tracks the
+jiffies value of the most recent activity and is used to detect timeouts.
+These accesses are not consistently protected by a lock, so
+READ_ONCE/WRITE_ONCE must be used to prevent data races caused by compiler
+optimizations.
+
+Cc: stable@kernel.org
+Fixes: 23721387c409 ("batman-adv: add basic bridge loop avoidance code")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/bridge_loop_avoidance.c | 28 +++++++++++++-------------
+ 1 file changed, 14 insertions(+), 14 deletions(-)
+
+diff --git a/net/batman-adv/bridge_loop_avoidance.c b/net/batman-adv/bridge_loop_avoidance.c
+index cfb1eb25c6ac4d..ac12c06f11b89e 100644
+--- a/net/batman-adv/bridge_loop_avoidance.c
++++ b/net/batman-adv/bridge_loop_avoidance.c
+@@ -512,7 +512,7 @@ batadv_bla_get_backbone_gw(struct batadv_priv *bat_priv, const u8 *orig,
+ return NULL;
+
+ entry->vid = vid;
+- entry->lasttime = jiffies;
++ WRITE_ONCE(entry->lasttime, jiffies);
+ entry->crc = BATADV_BLA_CRC_INIT;
+ entry->bat_priv = bat_priv;
+ spin_lock_init(&entry->crc_lock);
+@@ -580,7 +580,7 @@ batadv_bla_update_own_backbone_gw(struct batadv_priv *bat_priv,
+ if (unlikely(!backbone_gw))
+ return;
+
+- backbone_gw->lasttime = jiffies;
++ WRITE_ONCE(backbone_gw->lasttime, jiffies);
+ batadv_backbone_gw_put(backbone_gw);
+ }
+
+@@ -714,7 +714,7 @@ static void batadv_bla_add_claim(struct batadv_priv *bat_priv,
+ ether_addr_copy(claim->addr, mac);
+ spin_lock_init(&claim->backbone_lock);
+ claim->vid = vid;
+- claim->lasttime = jiffies;
++ WRITE_ONCE(claim->lasttime, jiffies);
+ kref_get(&backbone_gw->refcount);
+ claim->backbone_gw = backbone_gw;
+ kref_init(&claim->refcount);
+@@ -736,7 +736,7 @@ static void batadv_bla_add_claim(struct batadv_priv *bat_priv,
+ return;
+ }
+ } else {
+- claim->lasttime = jiffies;
++ WRITE_ONCE(claim->lasttime, jiffies);
+ if (claim->backbone_gw == backbone_gw)
+ /* no need to register a new backbone */
+ goto claim_free_ref;
+@@ -769,7 +769,7 @@ static void batadv_bla_add_claim(struct batadv_priv *bat_priv,
+ spin_lock_bh(&backbone_gw->crc_lock);
+ backbone_gw->crc ^= crc16(0, claim->addr, ETH_ALEN);
+ spin_unlock_bh(&backbone_gw->crc_lock);
+- backbone_gw->lasttime = jiffies;
++ WRITE_ONCE(backbone_gw->lasttime, jiffies);
+
+ claim_free_ref:
+ batadv_claim_put(claim);
+@@ -858,7 +858,7 @@ static bool batadv_handle_announce(struct batadv_priv *bat_priv, u8 *an_addr,
+ return true;
+
+ /* handle as ANNOUNCE frame */
+- backbone_gw->lasttime = jiffies;
++ WRITE_ONCE(backbone_gw->lasttime, jiffies);
+ crc = ntohs(*((__force __be16 *)(&an_addr[4])));
+
+ batadv_dbg(BATADV_DBG_BLA, bat_priv,
+@@ -1253,7 +1253,7 @@ static void batadv_bla_purge_backbone_gw(struct batadv_priv *bat_priv, int now)
+ head, hash_entry) {
+ if (now)
+ goto purge_now;
+- if (!batadv_has_timed_out(backbone_gw->lasttime,
++ if (!batadv_has_timed_out(READ_ONCE(backbone_gw->lasttime),
+ BATADV_BLA_BACKBONE_TIMEOUT))
+ continue;
+
+@@ -1334,7 +1334,7 @@ static void batadv_bla_purge_claims(struct batadv_priv *bat_priv,
+ primary_if->net_dev->dev_addr))
+ goto skip;
+
+- if (!batadv_has_timed_out(claim->lasttime,
++ if (!batadv_has_timed_out(READ_ONCE(claim->lasttime),
+ BATADV_BLA_CLAIM_TIMEOUT))
+ goto skip;
+
+@@ -1494,7 +1494,7 @@ static void batadv_bla_periodic_work(struct work_struct *work)
+ eth_random_addr(bat_priv->bla.loopdetect_addr);
+ bat_priv->bla.loopdetect_addr[0] = 0xba;
+ bat_priv->bla.loopdetect_addr[1] = 0xbe;
+- bat_priv->bla.loopdetect_lasttime = jiffies;
++ WRITE_ONCE(bat_priv->bla.loopdetect_lasttime, jiffies);
+ atomic_set(&bat_priv->bla.loopdetect_next,
+ BATADV_BLA_LOOPDETECT_PERIODS);
+
+@@ -1515,7 +1515,7 @@ static void batadv_bla_periodic_work(struct work_struct *work)
+ primary_if->net_dev->dev_addr))
+ continue;
+
+- backbone_gw->lasttime = jiffies;
++ WRITE_ONCE(backbone_gw->lasttime, jiffies);
+
+ batadv_bla_send_announce(bat_priv, backbone_gw);
+ if (send_loopdetect)
+@@ -1900,7 +1900,7 @@ batadv_bla_loopdetect_check(struct batadv_priv *bat_priv, struct sk_buff *skb,
+ /* If the packet came too late, don't forward it on the mesh
+ * but don't consider that as loop. It might be a coincidence.
+ */
+- if (batadv_has_timed_out(bat_priv->bla.loopdetect_lasttime,
++ if (batadv_has_timed_out(READ_ONCE(bat_priv->bla.loopdetect_lasttime),
+ BATADV_BLA_LOOPDETECT_TIMEOUT))
+ return true;
+
+@@ -2016,7 +2016,7 @@ bool batadv_bla_rx(struct batadv_priv *bat_priv, struct sk_buff *skb,
+
+ if (own_claim) {
+ /* ... allow it in any case */
+- claim->lasttime = jiffies;
++ WRITE_ONCE(claim->lasttime, jiffies);
+ goto allow;
+ }
+
+@@ -2118,7 +2118,7 @@ bool batadv_bla_tx(struct batadv_priv *bat_priv, struct sk_buff *skb,
+ /* if yes, the client has roamed and we have
+ * to unclaim it.
+ */
+- if (batadv_has_timed_out(claim->lasttime, 100)) {
++ if (batadv_has_timed_out(READ_ONCE(claim->lasttime), 100)) {
+ /* only unclaim if the last claim entry is
+ * older than 100 ms to make sure we really
+ * have a roaming client here.
+@@ -2372,7 +2372,7 @@ batadv_bla_backbone_dump_entry(struct sk_buff *msg, u32 portid,
+ backbone_crc = backbone_gw->crc;
+ spin_unlock_bh(&backbone_gw->crc_lock);
+
+- msecs = jiffies_to_msecs(jiffies - backbone_gw->lasttime);
++ msecs = jiffies_to_msecs(jiffies - READ_ONCE(backbone_gw->lasttime));
+
+ if (is_own)
+ if (nla_put_flag(msg, BATADV_ATTR_BLA_OWN)) {
+--
+2.53.0
+
--- /dev/null
+From 27049ec18ed615ee2fbd658140d74246d7a81057 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:21 +0200
+Subject: batman-adv: dat: prevent false sharing between VLANs
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 20d7658b74169f86d4ac01b9185b3eadddf71f28 upstream.
+
+The local hash of DAT entries is supposed to be VLAN (VID) aware. But
+the adding to the hash and the search in the hash were not checking the VID
+information of the hash entries. The entries would therefore only be
+correctly separated when batadv_hash_dat() didn't select the same buckets
+for different VIDs.
+
+Cc: stable@kernel.org
+Fixes: be1db4f6615b ("batman-adv: make the Distributed ARP Table vlan aware")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/distributed-arp-table.c | 12 +++++++++---
+ 1 file changed, 9 insertions(+), 3 deletions(-)
+
+diff --git a/net/batman-adv/distributed-arp-table.c b/net/batman-adv/distributed-arp-table.c
+index e8d706d86aa82a..36462d9288c7ee 100644
+--- a/net/batman-adv/distributed-arp-table.c
++++ b/net/batman-adv/distributed-arp-table.c
+@@ -215,10 +215,13 @@ static void batadv_dat_purge(struct work_struct *work)
+ */
+ static bool batadv_compare_dat(const struct hlist_node *node, const void *data2)
+ {
+- const void *data1 = container_of(node, struct batadv_dat_entry,
+- hash_entry);
++ const struct batadv_dat_entry *entry1;
++ const struct batadv_dat_entry *entry2;
+
+- return memcmp(data1, data2, sizeof(__be32)) == 0;
++ entry1 = container_of(node, struct batadv_dat_entry, hash_entry);
++ entry2 = data2;
++
++ return entry1->ip == entry2->ip && entry1->vid == entry2->vid;
+ }
+
+ /**
+@@ -345,6 +348,9 @@ batadv_dat_entry_hash_find(struct batadv_priv *bat_priv, __be32 ip,
+ if (dat_entry->ip != ip)
+ continue;
+
++ if (dat_entry->vid != vid)
++ continue;
++
+ if (!kref_get_unless_zero(&dat_entry->refcount))
+ continue;
+
+--
+2.53.0
+
--- /dev/null
+From 6632a50cba14b1852f5517c0df65ee5faddbcd25 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:10 +0200
+Subject: batman-adv: ensure bcast is writable before modifying TTL
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 4cd6d3a4b96a8576f1fed8f9f9f17c2dc2978e0c upstream.
+
+Before batman-adv is allowed to write to an skb, it either has to have its
+own copy of the skb or used skb_cow() to ensure that the data part is not
+shared.
+
+The old implementation used a shared queue and created copies before
+attempting to write to it. But with the new implementation, the broadcast
+packet is already modified when it gets received. Potentially writing to
+shared buffers in this process.
+
+Adding a skb_cow() right before this operation avoids this and can at the
+same time prepare it for the modifications required to rebroadcast the
+packet.
+
+Cc: stable@kernel.org
+Fixes: 3f69339068f9 ("batman-adv: bcast: queue per interface, if needed")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/routing.c | 6 ++++++
+ 1 file changed, 6 insertions(+)
+
+diff --git a/net/batman-adv/routing.c b/net/batman-adv/routing.c
+index 83f31494ea4d92..0709f42c54b496 100644
+--- a/net/batman-adv/routing.c
++++ b/net/batman-adv/routing.c
+@@ -1199,6 +1199,12 @@ int batadv_recv_bcast_packet(struct sk_buff *skb,
+ if (batadv_is_my_mac(bat_priv, bcast_packet->orig))
+ goto free_skb;
+
++ /* create a copy of the skb, if needed, to modify it. */
++ if (skb_cow(skb, ETH_HLEN) < 0)
++ goto free_skb;
++
++ bcast_packet = (struct batadv_bcast_packet *)skb->data;
++
+ if (bcast_packet->ttl-- < 2)
+ goto free_skb;
+
+--
+2.53.0
+
--- /dev/null
+From 97713caef9bd3e682405f26528a5983714af0dad Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:11 +0200
+Subject: batman-adv: fix (m|b)cast csum after decrementing TTL
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit e728bbdf32660c8f32b8f5e8d09427a2c131ad60 upstream.
+
+The broadcast and multicast packets can be received at the same time by the
+local system and forwarded to other nodes. Both are simply decrementing the
+TTL at the beginning of the receive path - independent of chosen paths
+(receive/forward). But such a modification of the data conflicts with the
+hw csum. This is not a problem when the packet is directly forwarded but
+can cause errors in the local receive path.
+
+Such a problem can then trigger a "hw csum failure". The receiver path must
+therefore ensure that the csum is fixed for each modification of the
+payload before batadv_interface_rx() is reached.
+
+Since all batman-adv packet types with a ttl have it as u8 at offset 2, a
+helper can be used for all of them. But it is only used at the moment for
+batadv_bcast_packet and batadv_mcast_packet because they are the only ones
+which deliver the packet locally but unconditionally modify the TTL.
+
+Cc: stable@kernel.org
+Fixes: 3f69339068f9 ("batman-adv: bcast: queue per interface, if needed")
+Fixes: 07afe1ba288c ("batman-adv: mcast: implement multicast packet reception and forwarding")
+[ Context, Drop change for non-existing mcast handling ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/routing.c | 55 +++++++++++++++++++++++++++++++++++++++-
+ 1 file changed, 54 insertions(+), 1 deletion(-)
+
+diff --git a/net/batman-adv/routing.c b/net/batman-adv/routing.c
+index 0709f42c54b496..e9347b25d2087a 100644
+--- a/net/batman-adv/routing.c
++++ b/net/batman-adv/routing.c
+@@ -8,6 +8,7 @@
+ #include "main.h"
+
+ #include <linux/atomic.h>
++#include <linux/build_bug.h>
+ #include <linux/byteorder/generic.h>
+ #include <linux/compiler.h>
+ #include <linux/errno.h>
+@@ -205,6 +206,58 @@ bool batadv_check_management_packet(struct sk_buff *skb,
+ return true;
+ }
+
++/**
++ * batadv_skb_decrement_ttl() - decrement ttl in a batman-adv header, csum-safe
++ * @skb: the received packet with @skb->data pointing to the batman-adv header
++ *
++ * Supports the following packet types, all of which carry the TTL at offset 2:
++ *
++ * - batadv_ogm_packet
++ * - batadv_ogm2_packet
++ * - batadv_icmp_header
++ * - batadv_icmp_packet
++ * - batadv_icmp_tp_packet
++ * - batadv_icmp_packet_rr
++ * - batadv_unicast_packet
++ * - batadv_frag_packet
++ * - batadv_bcast_packet
++ * - batadv_mcast_packet
++ * - batadv_coded_packet
++ * - batadv_unicast_tvlv_packet
++ *
++ * Return: true if the packet may be forwarded (ttl decremented),
++ * false if it must be dropped (ttl would expire)
++ */
++static bool batadv_skb_decrement_ttl(struct sk_buff *skb)
++{
++ static const size_t ttl_offset = 2;
++ u8 *ttl_pos;
++
++ BUILD_BUG_ON(offsetof(struct batadv_ogm_packet, ttl) != ttl_offset);
++ BUILD_BUG_ON(offsetof(struct batadv_ogm2_packet, ttl) != ttl_offset);
++ BUILD_BUG_ON(offsetof(struct batadv_icmp_header, ttl) != ttl_offset);
++ BUILD_BUG_ON(offsetof(struct batadv_icmp_packet, ttl) != ttl_offset);
++ BUILD_BUG_ON(offsetof(struct batadv_icmp_tp_packet, ttl) != ttl_offset);
++ BUILD_BUG_ON(offsetof(struct batadv_icmp_packet_rr, ttl) != ttl_offset);
++ BUILD_BUG_ON(offsetof(struct batadv_unicast_packet, ttl) != ttl_offset);
++ BUILD_BUG_ON(offsetof(struct batadv_frag_packet, ttl) != ttl_offset);
++ BUILD_BUG_ON(offsetof(struct batadv_bcast_packet, ttl) != ttl_offset);
++ BUILD_BUG_ON(offsetof(struct batadv_coded_packet, ttl) != ttl_offset);
++ BUILD_BUG_ON(offsetof(struct batadv_unicast_tvlv_packet, ttl) != ttl_offset);
++
++ ttl_pos = skb->data + ttl_offset;
++
++ /* would expire on this hop -> drop, leave header + csum untouched */
++ if (*ttl_pos < 2)
++ return false;
++
++ skb_postpull_rcsum(skb, ttl_pos, 1);
++ (*ttl_pos)--;
++ skb_postpush_rcsum(skb, ttl_pos, 1);
++
++ return true;
++}
++
+ /**
+ * batadv_recv_my_icmp_packet() - receive an icmp packet locally
+ * @bat_priv: the bat priv with all the soft interface information
+@@ -1205,7 +1258,7 @@ int batadv_recv_bcast_packet(struct sk_buff *skb,
+
+ bcast_packet = (struct batadv_bcast_packet *)skb->data;
+
+- if (bcast_packet->ttl-- < 2)
++ if (!batadv_skb_decrement_ttl(skb))
+ goto free_skb;
+
+ orig_node = batadv_orig_hash_find(bat_priv, bcast_packet->orig);
+--
+2.53.0
+
--- /dev/null
+From 314a46c28a91be49dc3f83322f498ba03c6906df Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:13 +0200
+Subject: batman-adv: frag: avoid underflow of TTL
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 493d9d2528e1a09b090e4b37f0f553def7bd5ce9 upstream.
+
+Packets with a TTL are using it to limit the amount of time this packet can
+be forwarded. But for batadv_frag_packet, the TTL was always only reduced
+but it was never evaluated. It could even underflow without any effect.
+
+Check the TTL in batadv_frag_skb_fwd() before attempting to prepare it for
+forwarding. This keeps it in sync with the not fragmented unicast packet.
+
+Cc: stable@kernel.org
+Fixes: 610bfc6bc99b ("batman-adv: Receive fragmented packets and merge")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/fragmentation.c | 7 +++++++
+ 1 file changed, 7 insertions(+)
+
+diff --git a/net/batman-adv/fragmentation.c b/net/batman-adv/fragmentation.c
+index fbf030c57ac04d..0138b0953e10b5 100644
+--- a/net/batman-adv/fragmentation.c
++++ b/net/batman-adv/fragmentation.c
+@@ -420,6 +420,13 @@ bool batadv_frag_skb_fwd(struct sk_buff *skb,
+ */
+ total_size = ntohs(packet->total_size);
+ if (total_size > neigh_node->if_incoming->net_dev->mtu) {
++ if (packet->ttl < 2) {
++ kfree_skb(skb);
++ *rx_result = NET_RX_DROP;
++ ret = true;
++ goto out;
++ }
++
+ if (skb_cow(skb, ETH_HLEN) < 0) {
+ kfree_skb(skb);
+ *rx_result = NET_RX_DROP;
+--
+2.53.0
+
--- /dev/null
+From b22fa2d5eb7111575d51e5b4d3cf58b60d2542ce Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:12 +0200
+Subject: batman-adv: frag: ensure fragment is writable before modifying TTL
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit b7293c6e8c15b2db77809b25cf8389e35331b27a upstream.
+
+Before batman-adv is allowed to write to an skb, it either has to have its
+own copy of the skb or use skb_cow() to ensure that the data part is not
+shared. But batadv_frag_skb_fwd() modifies the TTL even when it is shared.
+
+Adding a skb_cow() right before this operation avoids this and can at the
+same time prepare it for the modifications required to forward the
+fragment.
+
+Cc: stable@kernel.org
+Fixes: 610bfc6bc99b ("batman-adv: Receive fragmented packets and merge")
+[ Context ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/fragmentation.c | 15 ++++++++++++++-
+ net/batman-adv/fragmentation.h | 3 ++-
+ net/batman-adv/routing.c | 3 +--
+ 3 files changed, 17 insertions(+), 4 deletions(-)
+
+diff --git a/net/batman-adv/fragmentation.c b/net/batman-adv/fragmentation.c
+index 4c193194034155..fbf030c57ac04d 100644
+--- a/net/batman-adv/fragmentation.c
++++ b/net/batman-adv/fragmentation.c
+@@ -385,6 +385,8 @@ bool batadv_frag_skb_buffer(struct sk_buff **skb,
+ * @skb: skb to forward
+ * @recv_if: interface that the skb is received on
+ * @orig_node_src: originator that the skb is received from
++ * @rx_result: set to NET_RX_SUCCESS when the fragment was forwarded and
++ * NET_RX_DROP when it was dropped; only valid when true is returned
+ *
+ * Look up the next-hop of the fragments payload and check if the merged packet
+ * will exceed the MTU towards the next-hop. If so, the fragment is forwarded
+@@ -394,7 +396,8 @@ bool batadv_frag_skb_buffer(struct sk_buff **skb,
+ */
+ bool batadv_frag_skb_fwd(struct sk_buff *skb,
+ struct batadv_hard_iface *recv_if,
+- struct batadv_orig_node *orig_node_src)
++ struct batadv_orig_node *orig_node_src,
++ int *rx_result)
+ {
+ struct batadv_priv *bat_priv = netdev_priv(recv_if->soft_iface);
+ struct batadv_orig_node *orig_node_dst;
+@@ -417,12 +420,22 @@ bool batadv_frag_skb_fwd(struct sk_buff *skb,
+ */
+ total_size = ntohs(packet->total_size);
+ if (total_size > neigh_node->if_incoming->net_dev->mtu) {
++ if (skb_cow(skb, ETH_HLEN) < 0) {
++ kfree_skb(skb);
++ *rx_result = NET_RX_DROP;
++ ret = true;
++ goto out;
++ }
++
++ packet = (struct batadv_frag_packet *)skb->data;
++
+ batadv_inc_counter(bat_priv, BATADV_CNT_FRAG_FWD);
+ batadv_add_counter(bat_priv, BATADV_CNT_FRAG_FWD_BYTES,
+ skb->len + ETH_HLEN);
+
+ packet->ttl--;
+ batadv_send_unicast_skb(skb, neigh_node);
++ *rx_result = NET_RX_SUCCESS;
+ ret = true;
+ }
+
+diff --git a/net/batman-adv/fragmentation.h b/net/batman-adv/fragmentation.h
+index dbf0871f870303..51e281027ab630 100644
+--- a/net/batman-adv/fragmentation.h
++++ b/net/batman-adv/fragmentation.h
+@@ -19,7 +19,8 @@ void batadv_frag_purge_orig(struct batadv_orig_node *orig,
+ bool (*check_cb)(struct batadv_frag_table_entry *));
+ bool batadv_frag_skb_fwd(struct sk_buff *skb,
+ struct batadv_hard_iface *recv_if,
+- struct batadv_orig_node *orig_node_src);
++ struct batadv_orig_node *orig_node_src,
++ int *rx_result);
+ bool batadv_frag_skb_buffer(struct sk_buff **skb,
+ struct batadv_orig_node *orig_node);
+ int batadv_frag_send_packet(struct sk_buff *skb,
+diff --git a/net/batman-adv/routing.c b/net/batman-adv/routing.c
+index e9347b25d2087a..49cce0cbdff8b6 100644
+--- a/net/batman-adv/routing.c
++++ b/net/batman-adv/routing.c
+@@ -1175,10 +1175,9 @@ int batadv_recv_frag_packet(struct sk_buff *skb,
+
+ /* Route the fragment if it is not for us and too big to be merged. */
+ if (!batadv_is_my_mac(bat_priv, frag_packet->dest) &&
+- batadv_frag_skb_fwd(skb, recv_if, orig_node_src)) {
++ batadv_frag_skb_fwd(skb, recv_if, orig_node_src, &ret)) {
+ /* skb was consumed */
+ skb = NULL;
+- ret = NET_RX_SUCCESS;
+ goto put_orig_node;
+ }
+
+--
+2.53.0
+
--- /dev/null
+From f5f9cf61085ff7967aaacbf7bc7824f342b2ffca Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:08 +0200
+Subject: batman-adv: prevent ELP transmission interval underflow
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 5e50d4b8ae3ea622122d3c6a38d7f6fe68dfddca upstream.
+
+batadv_v_elp_start_timer() enqeues a delayed work. The time when it starts
+is randomly chosen between (elp_interval - BATADV_JITTER) and
+(elp_interval + BATADV_JITTER). The configured elp_interval must therefore
+be larger or equal to BATADV_JITTER to avoid that it causes an underflow of
+the unsigned integer. If this would happen, then a "fast" ELP interval
+would turn into a "day long" delay.
+
+At the same time, it must not be larger than the maximum value the variable
+can store.
+
+Cc: stable@kernel.org
+Fixes: a10800829040 ("batman-adv: Add elp_interval hardif genl configuration")
+[ Context ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/netlink.c | 8 +++++++-
+ 1 file changed, 7 insertions(+), 1 deletion(-)
+
+diff --git a/net/batman-adv/netlink.c b/net/batman-adv/netlink.c
+index 86e0664e0511bb..7ff48c968a13fe 100644
+--- a/net/batman-adv/netlink.c
++++ b/net/batman-adv/netlink.c
+@@ -941,9 +941,15 @@ static int batadv_netlink_set_hardif(struct sk_buff *skb,
+ #ifdef CONFIG_BATMAN_ADV_BATMAN_V
+
+ if (info->attrs[BATADV_ATTR_ELP_INTERVAL]) {
++ u32 elp_interval;
++
+ attr = info->attrs[BATADV_ATTR_ELP_INTERVAL];
++ elp_interval = nla_get_u32(attr);
++
++ elp_interval = min_t(u32, elp_interval, INT_MAX);
++ elp_interval = max_t(u32, elp_interval, BATADV_JITTER);
+
+- atomic_set(&hard_iface->bat_v.elp_interval, nla_get_u32(attr));
++ atomic_set(&hard_iface->bat_v.elp_interval, elp_interval);
+ }
+
+ if (info->attrs[BATADV_ATTR_THROUGHPUT_OVERRIDE]) {
+--
+2.53.0
+
--- /dev/null
+From 48b7c9045ff6b24ec88ce85aee844255527a7244 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:06 +0200
+Subject: batman-adv: tp_meter: add only finished tp_vars to lists
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 15ccbf685222274f5add1387af58c2a41a95f81e upstream.
+
+When the receiver variables (aka "session") are initialized, then they are
+added to the list of sessions before the timer is set up. A RCU protected
+reader could therefore find the entry and run mod_setup before
+batadv_tp_init_recv() finished the timer initialization.
+
+The same is true for batadv_tp_start(), which must first initialize the
+finish_work and the test_length to avoid a similar problem.
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 18 +++++++++---------
+ 1 file changed, 9 insertions(+), 9 deletions(-)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index 089ac7cc9fbebc..2bba53fc6da5c0 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -1096,21 +1096,21 @@ void batadv_tp_start(struct batadv_priv *bat_priv, const u8 *dst,
+ tp_vars->prerandom_offset = 0;
+ spin_lock_init(&tp_vars->prerandom_lock);
+
+- kref_get(&tp_vars->refcount);
+- hlist_add_head_rcu(&tp_vars->list, &bat_priv->tp_list);
+- spin_unlock_bh(&bat_priv->tp_list_lock);
+-
+ tp_vars->test_length = test_length;
+ if (!tp_vars->test_length)
+ tp_vars->test_length = BATADV_TP_DEF_TEST_LENGTH;
+
++ /* init work item for finished tp tests */
++ INIT_DELAYED_WORK(&tp_vars->finish_work, batadv_tp_sender_finish);
++
++ kref_get(&tp_vars->refcount);
++ hlist_add_head_rcu(&tp_vars->list, &bat_priv->tp_list);
++ spin_unlock_bh(&bat_priv->tp_list_lock);
++
+ batadv_dbg(BATADV_DBG_TP_METER, bat_priv,
+ "Meter: starting throughput meter towards %pM (length=%ums)\n",
+ dst, test_length);
+
+- /* init work item for finished tp tests */
+- INIT_DELAYED_WORK(&tp_vars->finish_work, batadv_tp_sender_finish);
+-
+ /* start tp kthread. This way the write() call issued from userspace can
+ * happily return and avoid to block
+ */
+@@ -1430,10 +1430,10 @@ batadv_tp_init_recv(struct batadv_priv *bat_priv,
+ INIT_LIST_HEAD(&tp_vars->unacked_list);
+
+ kref_get(&tp_vars->refcount);
+- hlist_add_head_rcu(&tp_vars->list, &bat_priv->tp_list);
++ timer_setup(&tp_vars->timer, batadv_tp_receiver_shutdown, 0);
+
+ kref_get(&tp_vars->refcount);
+- timer_setup(&tp_vars->timer, batadv_tp_receiver_shutdown, 0);
++ hlist_add_head_rcu(&tp_vars->list, &bat_priv->tp_list);
+
+ batadv_tp_reset_receiver_timer(tp_vars);
+
+--
+2.53.0
+
--- /dev/null
+From ede0391be39ad21e7d159985fd99dbc756db8db3 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:16 +0200
+Subject: batman-adv: tp_meter: annotate last_recv_time access with
+ READ/WRITE_ONCE
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit d67c728f07fca2ee6ffdc6dd4421cf2e8691f4d1 upstream.
+
+The last_recv_time field for batadv_tp_receiver tracks the jiffies value of
+the most recent activity and is used to detect timeouts. These accesses are
+not consistently protected by a lock, so READ_ONCE/WRITE_ONCE must be used
+to prevent data races caused by compiler optimizations.
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 8 ++++----
+ 1 file changed, 4 insertions(+), 4 deletions(-)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index 638dd438a6c052..b882919a868ca0 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -1183,7 +1183,7 @@ static void batadv_tp_receiver_shutdown(struct timer_list *t)
+ bat_priv = tp_vars->bat_priv;
+
+ /* if there is recent activity rearm the timer */
+- if (!batadv_has_timed_out(tp_vars->last_recv_time,
++ if (!batadv_has_timed_out(READ_ONCE(tp_vars->last_recv_time),
+ BATADV_TP_RECV_TIMEOUT)) {
+ /* reset the receiver shutdown timer */
+ batadv_tp_reset_receiver_timer(tp_vars);
+@@ -1424,7 +1424,7 @@ batadv_tp_init_recv(struct batadv_priv *bat_priv,
+ tp_vars = batadv_tp_list_find_session(bat_priv, icmp->orig,
+ icmp->session, BATADV_TP_RECEIVER);
+ if (tp_vars) {
+- tp_vars->last_recv_time = jiffies;
++ WRITE_ONCE(tp_vars->last_recv_time, jiffies);
+ goto out_unlock;
+ }
+
+@@ -1455,7 +1455,7 @@ batadv_tp_init_recv(struct batadv_priv *bat_priv,
+ kref_get(&tp_vars->refcount);
+ timer_setup(&tp_vars->timer, batadv_tp_receiver_shutdown, 0);
+
+- tp_vars->last_recv_time = jiffies;
++ WRITE_ONCE(tp_vars->last_recv_time, jiffies);
+
+ kref_get(&tp_vars->refcount);
+ hlist_add_head_rcu(&tp_vars->list, &bat_priv->tp_list);
+@@ -1506,7 +1506,7 @@ static void batadv_tp_recv_msg(struct batadv_priv *bat_priv,
+ goto out;
+ }
+
+- tp_vars->last_recv_time = jiffies;
++ WRITE_ONCE(tp_vars->last_recv_time, jiffies);
+ }
+
+ /* if the packet is a duplicate, it may be the case that an ACK has been
+--
+2.53.0
+
--- /dev/null
+From 7749dcd68bed6dc3142962efd15fae37974c5bfd Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:03 +0200
+Subject: batman-adv: tp_meter: avoid divide-by-zero for dec_cwnd
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 33ccd52f3cc9ed46ce395199f89aa3234dc83314 upstream.
+
+The cwnd is always MSS <= cwnd <= 0x20000000. But the calculation in
+batadv_tp_update_cwnd() assumes unsigned 32 bit arithmetics.
+
+ ((mss * 8) ** 2) / (cwnd * 8)
+
+In case cwnd is actually 0x20000000, it will be shifted by 3 bit to the
+left end up at 0x100000000 or U32_MAX + 1. It will therefore wrap around
+and be 0 - resulting in:
+
+ ((mss * 8) ** 2) / 0
+
+This is of course invalid and cannot be calculated. The calculation should
+must be simplified to avoid this overflow:
+
+ (mss ** 2) * 8 / cwnd
+
+It will keep the precision enhancement from the scaling (by 8) but avoid
+the overflow in the divisor.
+
+In theory, there could still be an overflow in the dividend. It is at the
+moment fixed to BATADV_TP_PLEN in batadv_tp_recv_ack() - so it is not an
+imminent problem. But allowing it to use the whole u32 bit range, would
+mean that it can still use up to 67 bits. To keep this calculation safe for
+32 bit arithmetic, mss must never use more than floor((32 - 3) / 2) bits -
+or in other words: must never be larger than 16383.
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 5 ++++-
+ 1 file changed, 4 insertions(+), 1 deletion(-)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index 0fdcafca3aa02b..4ff80e4214ff0a 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -154,9 +154,12 @@ static void batadv_tp_update_cwnd(struct batadv_tp_vars *tp_vars, u32 mss)
+ return;
+ }
+
++ /* prevent overflow in (mss * mss) << 3 */
++ mss = min_t(u32, mss, (1U << 14) - 1);
++
+ /* increment CWND at least of 1 (section 3.1 of RFC5681) */
+ tp_vars->dec_cwnd += max_t(u32, 1U << 3,
+- ((mss * mss) << 6) / (tp_vars->cwnd << 3));
++ ((mss * mss) << 3) / tp_vars->cwnd);
+ if (tp_vars->dec_cwnd < (mss << 3)) {
+ spin_unlock_bh(&tp_vars->cwnd_lock);
+ return;
+--
+2.53.0
+
--- /dev/null
+From 4aca6456212d50fc31e6ff514ef1c61b8a003c8e Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:02 +0200
+Subject: batman-adv: tp_meter: avoid window underflow
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 765947b81fb54b6ebb0bc1cfe55c0fa399e002b8 upstream.
+
+In batadv_tp_avail(), win_left is calculated with 32-bit unsigned
+arithmetic: win_left = win_limit - tp_vars->last_sent;
+
+During Fast Recovery, cwnd is inflated and last_sent advances rapidly. When
+Fast Recovery ends, cwnd drops abruptly back to ss_threshold. If the newly
+shrunk win_limit is less than last_sent, the unsigned subtraction will
+underflow, wrapping to a massive positive value. Instead of returning that
+the window is full (unavailable), it returns that the sender can continue
+sending.
+
+To handle this situation, it must be checked whether the windows end
+sequence number (win_limit) has to be compared with the last sent sequence
+number. If it would be before the last sent sequence number, then more acks
+are needed before the transmission can be started again.
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 7 ++++++-
+ 1 file changed, 6 insertions(+), 1 deletion(-)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index f6ccb639744a2a..0fdcafca3aa02b 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -817,10 +817,15 @@ static void batadv_tp_recv_ack(struct batadv_priv *bat_priv,
+ static bool batadv_tp_avail(struct batadv_tp_vars *tp_vars,
+ size_t payload_len)
+ {
++ u32 last_sent = READ_ONCE(tp_vars->last_sent);
+ u32 win_left, win_limit;
+
+ win_limit = atomic_read(&tp_vars->last_acked) + tp_vars->cwnd;
+- win_left = win_limit - tp_vars->last_sent;
++
++ if (batadv_seq_before(last_sent, win_limit))
++ win_left = win_limit - last_sent;
++ else
++ win_left = 0;
+
+ return win_left >= payload_len;
+ }
+--
+2.53.0
+
--- /dev/null
+From dd6c5523b0be5cdaceda49f5f13c067fa275fde6 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:04 +0200
+Subject: batman-adv: tp_meter: fix fast recovery precondition
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 2b0d08f08ed3b2174f05c43089ec65f3543a025b upstream.
+
+The fast recovery precondition checks if the recover (initialized to
+BATADV_TP_FIRST_SEQ) is bigger than the received ack. But since recover is
+only updated when this check is successful, it will never enter the fast
+recovery mode.
+
+According to RFC6582 Section 3.2 step 2, the check should actually be
+different:
+
+> When the third duplicate ACK is received, the TCP sender first
+> checks the value of recover to see if the Cumulative
+> Acknowledgment field covers more than recover
+
+The precondition must therefore check if recover is smaller than the
+received ack - basically swapping the operands of the current check.
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index 4ff80e4214ff0a..c79352cfddc4ae 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -733,7 +733,7 @@ static void batadv_tp_recv_ack(struct batadv_priv *bat_priv,
+ if (atomic_read(&tp_vars->dup_acks) != 3)
+ goto out;
+
+- if (recv_ack >= tp_vars->recover)
++ if (tp_vars->recover >= recv_ack)
+ goto out;
+
+ /* if this is the third duplicate ACK do Fast Retransmit */
+--
+2.53.0
+
--- /dev/null
+From ba1e89c0adeb1b5a17ae3c71e1bf020738400886 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:18 +0200
+Subject: batman-adv: tp_meter: handle overlapping packets
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit cbde75c38b21f022891525078622587ad557b7c1 upstream.
+
+If the size of the packets would change during the transmission, it could
+happen that some retries of packets are overlapping. In this case, precise
+comparisons of sequence numbers by the receiver would be wrong. It is then
+necessary to check if the start sequence number to the end sequence number
+("seqno + length") would contain a new range.
+
+If this is the case then this is enough to accept this packet. In all other
+cases, the packet still has to be dropped (and not acked).
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+[ Switch to pre-splitted tp_vars structure names ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 25 +++++++++++--------------
+ 1 file changed, 11 insertions(+), 14 deletions(-)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index f745bc09a04963..50b83e0438a07b 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -1284,7 +1284,8 @@ static int batadv_tp_send_ack(struct batadv_priv *bat_priv, const u8 *dst,
+ /**
+ * batadv_tp_handle_out_of_order() - store an out of order packet
+ * @tp_vars: the private data of the current TP meter session
+- * @skb: the buffer containing the received packet
++ * @seqno: sequence number of new received packet
++ * @payload_len: length of the received packet
+ *
+ * Store the out of order packet in the unacked list for late processing. This
+ * packets are kept in this list so that they can be ACKed at once as soon as
+@@ -1293,22 +1294,17 @@ static int batadv_tp_send_ack(struct batadv_priv *bat_priv, const u8 *dst,
+ * Return: true if the packed has been successfully processed, false otherwise
+ */
+ static bool batadv_tp_handle_out_of_order(struct batadv_tp_vars *tp_vars,
+- const struct sk_buff *skb)
++ u32 seqno, u32 payload_len)
+ __must_hold(&tp_vars->unacked_lock)
+ {
+- const struct batadv_icmp_tp_packet *icmp;
+ struct batadv_tp_unacked *un, *new;
+- u32 payload_len;
+ bool added = false;
+
+ new = kmalloc(sizeof(*new), GFP_ATOMIC);
+ if (unlikely(!new))
+ return false;
+
+- icmp = (struct batadv_icmp_tp_packet *)skb->data;
+-
+- new->seqno = ntohl(icmp->seqno);
+- payload_len = skb->len - sizeof(struct batadv_unicast_packet);
++ new->seqno = seqno;
+ new->len = payload_len;
+
+ /* if the list is empty immediately attach this new object */
+@@ -1476,7 +1472,7 @@ static void batadv_tp_recv_msg(struct batadv_priv *bat_priv,
+ {
+ const struct batadv_icmp_tp_packet *icmp;
+ struct batadv_tp_vars *tp_vars;
+- size_t packet_size;
++ u32 payload_len;
+ u32 to_ack;
+ u32 seqno;
+
+@@ -1511,15 +1507,17 @@ static void batadv_tp_recv_msg(struct batadv_priv *bat_priv,
+ /* if the packet is a duplicate, it may be the case that an ACK has been
+ * lost. Resend the ACK
+ */
+- if (batadv_seq_before(seqno, tp_vars->last_recv))
++ payload_len = skb->len - sizeof(struct batadv_unicast_packet);
++ to_ack = seqno + payload_len;
++ if (batadv_seq_before(to_ack, tp_vars->last_recv))
+ goto send_ack;
+
+ /* if the packet is out of order enqueue it */
+- if (ntohl(icmp->seqno) != tp_vars->last_recv) {
++ if (batadv_seq_before(tp_vars->last_recv, seqno)) {
+ /* exit immediately (and do not send any ACK) if the packet has
+ * not been enqueued correctly
+ */
+- if (!batadv_tp_handle_out_of_order(tp_vars, skb)) {
++ if (!batadv_tp_handle_out_of_order(tp_vars, seqno, payload_len)) {
+ spin_unlock_bh(&tp_vars->unacked_lock);
+ goto out;
+ }
+@@ -1529,8 +1527,7 @@ static void batadv_tp_recv_msg(struct batadv_priv *bat_priv,
+ }
+
+ /* if everything was fine count the ACKed bytes */
+- packet_size = skb->len - sizeof(struct batadv_unicast_packet);
+- tp_vars->last_recv += packet_size;
++ tp_vars->last_recv = to_ack;
+
+ /* check if this ordered message filled a gap.... */
+ batadv_tp_ack_unordered(tp_vars);
+--
+2.53.0
+
--- /dev/null
+From 38fc401a35eb38b318195cb72df11b66a029ba45 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:05 +0200
+Subject: batman-adv: tp_meter: handle seqno wrap-around for fast recovery
+ detection
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit f54c85ed42a1b27a516cf2a4728f5a612b799e07 upstream.
+
+The recover variable and the last_sent sequence number are initialized on
+purpose as a really high value which will wrap-around after the first 2000
+bytes. The fast recovery precondition must therefore not use simple integer
+comparisons but use helpers which are aware of the sequence number
+wrap-arounds.
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index c79352cfddc4ae..089ac7cc9fbebc 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -733,7 +733,7 @@ static void batadv_tp_recv_ack(struct batadv_priv *bat_priv,
+ if (atomic_read(&tp_vars->dup_acks) != 3)
+ goto out;
+
+- if (tp_vars->recover >= recv_ack)
++ if (!batadv_seq_before(tp_vars->recover, recv_ack))
+ goto out;
+
+ /* if this is the third duplicate ACK do Fast Retransmit */
+--
+2.53.0
+
--- /dev/null
+From e873fb927f6012a780059788943b6b6a2ca0509f Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:01 +0200
+Subject: batman-adv: tp_meter: initialize dec_cwnd explicitly
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit febfb1b86224489535312296ecfa3d4bf467f339 upstream.
+
+When batadv_tp_update_cwnd() is called, dec_cwnd is increased. But dec_cwnd
+is only initialixed (to 0) when a duplicate Ack was received or when cwnd
+is below the ss_threshold.
+
+Just initialize the cwnd during the initialization to avoid any potential
+access of uninitialized data.
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index 750e5e9d3dc9d6..f6ccb639744a2a 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -1055,6 +1055,8 @@ void batadv_tp_start(struct batadv_priv *bat_priv, const u8 *dst,
+ * soft_interface, hence its MTU
+ */
+ tp_vars->cwnd = BATADV_TP_PLEN * 3;
++ tp_vars->dec_cwnd = 0;
++
+ /* at the beginning initialise the SS threshold to the biggest possible
+ * window size, hence the AWND size
+ */
+--
+2.53.0
+
--- /dev/null
+From 32e6439ebb546a129578459ed5168feddaed52d7 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:00 +0200
+Subject: batman-adv: tp_meter: initialize dup_acks explicitly
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit b2b68b32a715e0328662801576974aa37b942b00 upstream.
+
+When an ack with a sequence number equal to the last_acked is received, the
+dup_acks counter is increased to decide whether fast retransmit should be
+performed. Only when the sequence numbers are not equal, the dup_acks is
+set to the initial value (0).
+
+But if the initial packet would have the sequence number
+BATADV_TP_FIRST_SEQ, dup_acks would not be initialized and atomic_inc would
+operate on an undefined starting value. It is therefore required to have it
+explicitly initialized during the start of the sender session.
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index f350a60e6c76b9..750e5e9d3dc9d6 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -1045,6 +1045,7 @@ void batadv_tp_start(struct batadv_priv *bat_priv, const u8 *dst,
+ tp_vars->icmp_uid = icmp_uid;
+
+ tp_vars->last_sent = BATADV_TP_FIRST_SEQ;
++ atomic_set(&tp_vars->dup_acks, 0);
+ atomic_set(&tp_vars->last_acked, BATADV_TP_FIRST_SEQ);
+ tp_vars->fast_recovery = false;
+ tp_vars->recover = BATADV_TP_FIRST_SEQ;
+--
+2.53.0
+
--- /dev/null
+From 44d8f1adc3b995e0aab69fd66ca19f4eebe6cf47 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:09 +0200
+Subject: batman-adv: tp_meter: initialize last_recv_time during init
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 811cb00fa8cdc3f0a7f6eefc000a6888367c8c8f upstream.
+
+The last_recv_time is the most important indicator for a receiver session
+to figure out whether a session timed out or not. But this information was
+only initialized after the session was added to the tp_receiver_list and
+after the timer was started.
+
+In the worst case, the timer (function) could have tried to access this
+information before the actual initialization was reached. Like rest of the
+variables of the tp_meter receiver session, this field has to be filled out
+before any other (parallel running) context has the chance to access it.
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+[ Context ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 10 +++++++---
+ 1 file changed, 7 insertions(+), 3 deletions(-)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index 2bba53fc6da5c0..133eed2fa9507f 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -1403,8 +1403,10 @@ batadv_tp_init_recv(struct batadv_priv *bat_priv,
+
+ tp_vars = batadv_tp_list_find_session(bat_priv, icmp->orig,
+ icmp->session, BATADV_TP_RECEIVER);
+- if (tp_vars)
++ if (tp_vars) {
++ tp_vars->last_recv_time = jiffies;
+ goto out_unlock;
++ }
+
+ if (!atomic_add_unless(&bat_priv->tp_num, 1, BATADV_TP_MAX_NUM)) {
+ batadv_dbg(BATADV_DBG_TP_METER, bat_priv,
+@@ -1432,6 +1434,8 @@ batadv_tp_init_recv(struct batadv_priv *bat_priv,
+ kref_get(&tp_vars->refcount);
+ timer_setup(&tp_vars->timer, batadv_tp_receiver_shutdown, 0);
+
++ tp_vars->last_recv_time = jiffies;
++
+ kref_get(&tp_vars->refcount);
+ hlist_add_head_rcu(&tp_vars->list, &bat_priv->tp_list);
+
+@@ -1480,9 +1484,9 @@ static void batadv_tp_recv_msg(struct batadv_priv *bat_priv,
+ icmp->orig);
+ goto out;
+ }
+- }
+
+- tp_vars->last_recv_time = jiffies;
++ tp_vars->last_recv_time = jiffies;
++ }
+
+ /* if the packet is a duplicate, it may be the case that an ACK has been
+ * lost. Resend the ACK
+--
+2.53.0
+
--- /dev/null
+From cc1388bbbf10cbfce38716bebd2fec199c425152 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:10:59 +0200
+Subject: batman-adv: tp_meter: keep unacked list in ascending ordered
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 5aa8651527ea0b610e7a09fb3b8204c1398b9525 upstream.
+
+When batadv_tp_handle_out_of_order inserts a new entry in the list of
+unacked (out of order) packets, it searches from the entry with the newest
+sequence number towards oldest sequence number. If an entry is found which
+is older than the newly entry, the new entry has to be added after the
+found one to keep the ascending order.
+
+But for this operation list_add_tail() was used. But this function adds an
+entry _before_ another one. As result, the list would contain a lot of
+swapped sequence numbers. The consumer of this list
+(batadv_tp_ack_unordered()) would then fail to correctly ack packets.
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index dfc3374549921f..f350a60e6c76b9 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -1325,7 +1325,7 @@ static bool batadv_tp_handle_out_of_order(struct batadv_tp_vars *tp_vars,
+ * one is attached _after_ it. In this way the list is kept in
+ * ascending order
+ */
+- list_add_tail(&new->list, &un->list);
++ list_add(&new->list, &un->list);
+ added = true;
+ break;
+ }
+--
+2.53.0
+
--- /dev/null
+From e5656e4d6f83c88da8e02e42e93e0538668507f7 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:17 +0200
+Subject: batman-adv: tp_meter: prevent parallel modifications of last_recv
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 6dde0cfcb36e4d5b3de35b75696937478441eed4 upstream.
+
+When last_recv is updated to store the last receive sequence number, it is
+assuming that nothing is modifying in parallel while:
+
+* check for outdated packets is done
+* out of order check is performed (and packets are stored in out-of-order
+ queue)
+* the out-of-order queue was searched for closed gaps
+* sequence number for next ack is calculated
+
+Nothing of that was actually protected. It could therefore happen that the
+last_recv was updated multiple times in parallel and the final sequence
+number was calculated with deltas which had no connection to the sequence
+number they were added to.
+
+Lock this whole region with the same lock which was already used to protect
+the unacked (out-of-order) list.
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+[ Switch to pre-splitted tp_vars structure names ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 22 +++++++++++++---------
+ net/batman-adv/types.h | 2 +-
+ 2 files changed, 14 insertions(+), 10 deletions(-)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index b882919a868ca0..f745bc09a04963 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -1294,6 +1294,7 @@ static int batadv_tp_send_ack(struct batadv_priv *bat_priv, const u8 *dst,
+ */
+ static bool batadv_tp_handle_out_of_order(struct batadv_tp_vars *tp_vars,
+ const struct sk_buff *skb)
++ __must_hold(&tp_vars->unacked_lock)
+ {
+ const struct batadv_icmp_tp_packet *icmp;
+ struct batadv_tp_unacked *un, *new;
+@@ -1310,12 +1311,11 @@ static bool batadv_tp_handle_out_of_order(struct batadv_tp_vars *tp_vars,
+ payload_len = skb->len - sizeof(struct batadv_unicast_packet);
+ new->len = payload_len;
+
+- spin_lock_bh(&tp_vars->unacked_lock);
+ /* if the list is empty immediately attach this new object */
+ if (list_empty(&tp_vars->unacked_list)) {
+ list_add(&new->list, &tp_vars->unacked_list);
+ tp_vars->unacked_count++;
+- goto out;
++ return true;
+ }
+
+ /* otherwise loop over the list and either drop the packet because this
+@@ -1364,9 +1364,6 @@ static bool batadv_tp_handle_out_of_order(struct batadv_tp_vars *tp_vars,
+ tp_vars->unacked_count--;
+ }
+
+-out:
+- spin_unlock_bh(&tp_vars->unacked_lock);
+-
+ return true;
+ }
+
+@@ -1376,6 +1373,7 @@ static bool batadv_tp_handle_out_of_order(struct batadv_tp_vars *tp_vars,
+ * @tp_vars: the private data of the current TP meter session
+ */
+ static void batadv_tp_ack_unordered(struct batadv_tp_vars *tp_vars)
++ __must_hold(&tp_vars->unacked_lock)
+ {
+ struct batadv_tp_unacked *un, *safe;
+ u32 to_ack;
+@@ -1383,7 +1381,6 @@ static void batadv_tp_ack_unordered(struct batadv_tp_vars *tp_vars)
+ /* go through the unacked packet list and possibly ACK them as
+ * well
+ */
+- spin_lock_bh(&tp_vars->unacked_lock);
+ list_for_each_entry_safe(un, safe, &tp_vars->unacked_list, list) {
+ /* the list is ordered, therefore it is possible to stop as soon
+ * there is a gap between the last acked seqno and the seqno of
+@@ -1401,7 +1398,6 @@ static void batadv_tp_ack_unordered(struct batadv_tp_vars *tp_vars)
+ kfree(un);
+ tp_vars->unacked_count--;
+ }
+- spin_unlock_bh(&tp_vars->unacked_lock);
+ }
+
+ /**
+@@ -1481,6 +1477,7 @@ static void batadv_tp_recv_msg(struct batadv_priv *bat_priv,
+ const struct batadv_icmp_tp_packet *icmp;
+ struct batadv_tp_vars *tp_vars;
+ size_t packet_size;
++ u32 to_ack;
+ u32 seqno;
+
+ icmp = (struct batadv_icmp_tp_packet *)skb->data;
+@@ -1509,6 +1506,8 @@ static void batadv_tp_recv_msg(struct batadv_priv *bat_priv,
+ WRITE_ONCE(tp_vars->last_recv_time, jiffies);
+ }
+
++ spin_lock_bh(&tp_vars->unacked_lock);
++
+ /* if the packet is a duplicate, it may be the case that an ACK has been
+ * lost. Resend the ACK
+ */
+@@ -1520,8 +1519,10 @@ static void batadv_tp_recv_msg(struct batadv_priv *bat_priv,
+ /* exit immediately (and do not send any ACK) if the packet has
+ * not been enqueued correctly
+ */
+- if (!batadv_tp_handle_out_of_order(tp_vars, skb))
++ if (!batadv_tp_handle_out_of_order(tp_vars, skb)) {
++ spin_unlock_bh(&tp_vars->unacked_lock);
+ goto out;
++ }
+
+ /* send a duplicate ACK */
+ goto send_ack;
+@@ -1535,11 +1536,14 @@ static void batadv_tp_recv_msg(struct batadv_priv *bat_priv,
+ batadv_tp_ack_unordered(tp_vars);
+
+ send_ack:
++ to_ack = tp_vars->last_recv;
++ spin_unlock_bh(&tp_vars->unacked_lock);
++
+ /* send the ACK. If the received packet was out of order, the ACK that
+ * is going to be sent is a duplicate (the sender will count them and
+ * possibly enter Fast Retransmit as soon as it has reached 3)
+ */
+- batadv_tp_send_ack(bat_priv, icmp->orig, tp_vars->last_recv,
++ batadv_tp_send_ack(bat_priv, icmp->orig, to_ack,
+ icmp->timestamp, icmp->session, icmp->uid);
+ out:
+ batadv_tp_vars_put(tp_vars);
+diff --git a/net/batman-adv/types.h b/net/batman-adv/types.h
+index d66726d0ea8112..0a6d0303a616c5 100644
+--- a/net/batman-adv/types.h
++++ b/net/batman-adv/types.h
+@@ -1491,7 +1491,7 @@ struct batadv_tp_vars {
+ /** @unacked_list: list of unacked packets (meta-info only) */
+ struct list_head unacked_list;
+
+- /** @unacked_lock: protect unacked_list */
++ /** @unacked_lock: protect unacked_list + &batadv_tp_receiver.last_recv */
+ spinlock_t unacked_lock;
+
+ /** @unacked_count: number of unacked entries */
+--
+2.53.0
+
--- /dev/null
+From ab5601dbe335fdfb45f756d336418edb2f780630 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:15 +0200
+Subject: batman-adv: tp_meter: restrict number of unacked list entries
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit e7c775110e1858e5a7471a23a9c9658c0af9df89 upstream.
+
+When the unacked_list is unbound, an attacker could send messages with
+small lengths and appropriated seqno + gaps to force the receiver to
+allocate more and more unacked_list entries. And the end either causing an
+out-of-memory situation or increase the management overhead for the (large)
+list that significant portions of CPU cycles are wasted in searching
+through the list.
+
+When limiting the list to a specific number, it is important to still
+correctly add a new entry to the list. But if the list became larger than
+the limit, the last entry of the list (with the highest seqno) must be
+dropped to still allow the earlier seqnos to finish and therefore to
+continue the process. Otherwise, the process might get stuck with too high
+seqnos which are not handled by batadv_tp_ack_unordered().
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+[ Switch to pre-splitted tp_vars structure names ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 23 ++++++++++++++++++++++-
+ net/batman-adv/types.h | 3 +++
+ 2 files changed, 25 insertions(+), 1 deletion(-)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index 133eed2fa9507f..638dd438a6c052 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -87,6 +87,11 @@
+ #define BATADV_TP_PLEN (BATADV_TP_PACKET_LEN - ETH_HLEN - \
+ sizeof(struct batadv_unicast_packet))
+
++/**
++ * BATADV_TP_MAX_UNACKED - maximum number of packets a receiver didn't yet ack
++ */
++#define BATADV_TP_MAX_UNACKED 100
++
+ static u8 batadv_tp_prerandom[4096] __read_mostly;
+
+ /**
+@@ -1195,6 +1200,7 @@ static void batadv_tp_receiver_shutdown(struct timer_list *t)
+ list_for_each_entry_safe(un, safe, &tp_vars->unacked_list, list) {
+ list_del(&un->list);
+ kfree(un);
++ tp_vars->unacked_count--;
+ }
+ spin_unlock_bh(&tp_vars->unacked_lock);
+
+@@ -1308,6 +1314,7 @@ static bool batadv_tp_handle_out_of_order(struct batadv_tp_vars *tp_vars,
+ /* if the list is empty immediately attach this new object */
+ if (list_empty(&tp_vars->unacked_list)) {
+ list_add(&new->list, &tp_vars->unacked_list);
++ tp_vars->unacked_count++;
+ goto out;
+ }
+
+@@ -1338,12 +1345,24 @@ static bool batadv_tp_handle_out_of_order(struct batadv_tp_vars *tp_vars,
+ */
+ list_add(&new->list, &un->list);
+ added = true;
++ tp_vars->unacked_count++;
+ break;
+ }
+
+ /* received packet with smallest seqno out of order; add it to front */
+- if (!added)
++ if (!added) {
+ list_add(&new->list, &tp_vars->unacked_list);
++ tp_vars->unacked_count++;
++ }
++
++ /* remove the last (biggest) unacked seqno when list is too large */
++ if (tp_vars->unacked_count > BATADV_TP_MAX_UNACKED) {
++ un = list_last_entry(&tp_vars->unacked_list,
++ struct batadv_tp_unacked, list);
++ list_del(&un->list);
++ kfree(un);
++ tp_vars->unacked_count--;
++ }
+
+ out:
+ spin_unlock_bh(&tp_vars->unacked_lock);
+@@ -1380,6 +1399,7 @@ static void batadv_tp_ack_unordered(struct batadv_tp_vars *tp_vars)
+
+ list_del(&un->list);
+ kfree(un);
++ tp_vars->unacked_count--;
+ }
+ spin_unlock_bh(&tp_vars->unacked_lock);
+ }
+@@ -1430,6 +1450,7 @@ batadv_tp_init_recv(struct batadv_priv *bat_priv,
+
+ spin_lock_init(&tp_vars->unacked_lock);
+ INIT_LIST_HEAD(&tp_vars->unacked_list);
++ tp_vars->unacked_count = 0;
+
+ kref_get(&tp_vars->refcount);
+ timer_setup(&tp_vars->timer, batadv_tp_receiver_shutdown, 0);
+diff --git a/net/batman-adv/types.h b/net/batman-adv/types.h
+index a61e319e4db0e9..d66726d0ea8112 100644
+--- a/net/batman-adv/types.h
++++ b/net/batman-adv/types.h
+@@ -1494,6 +1494,9 @@ struct batadv_tp_vars {
+ /** @unacked_lock: protect unacked_list */
+ spinlock_t unacked_lock;
+
++ /** @unacked_count: number of unacked entries */
++ size_t unacked_count;
++
+ /** @last_recv_time: time (jiffies) a msg was received */
+ unsigned long last_recv_time;
+
+--
+2.53.0
+
--- /dev/null
+From 22a90a94c2949165086caa3a6ad1dff0285b06ba Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:19 +0200
+Subject: batman-adv: tt: don't merge change entries with different VIDs
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit f08e06c2d5c3e2434e7c773f2213f4a7dce6bc1e upstream.
+
+batadv_tt_local_event() merges/cancels events for the same client which
+would conflict or be duplicates. The matching of the queued events only
+compares the MAC address - the VLAN ID stored in each event is ignored.
+
+If a MAC would now appear on multiple VID, the two ADD change events (for
+VID 1 and VID 2) would be merged to a single vid event. The remote can
+therefore not calculate the correct TT table and desync. A full translation
+table exchange is required to recover from this state.
+
+A check of VID is therefore necessary to avoid such wrong merges/cancels.
+
+Cc: stable@kernel.org
+Fixes: c018ad3de61a ("batman-adv: add the VLAN ID attribute to the TT entry")
+[ Context ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/translation-table.c | 3 +++
+ 1 file changed, 3 insertions(+)
+
+diff --git a/net/batman-adv/translation-table.c b/net/batman-adv/translation-table.c
+index 53d2aadcafa154..c3d62df9e2b87d 100644
+--- a/net/batman-adv/translation-table.c
++++ b/net/batman-adv/translation-table.c
+@@ -485,6 +485,9 @@ static void batadv_tt_local_event(struct batadv_priv *bat_priv,
+ if (!batadv_compare_eth(entry->change.addr, common->addr))
+ continue;
+
++ if (entry->change.vid != tt_change_node->change.vid)
++ continue;
++
+ /* DEL+ADD in the same orig interval have no effect and can be
+ * removed to avoid silly behaviour on the receiver side. The
+ * other way around (ADD+DEL) can happen in case of roaming of
+--
+2.53.0
+
--- /dev/null
+From f2e2e77009d59b556ec40f518a2b48e021027b17 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:20 +0200
+Subject: batman-adv: tt: track roam count per VID
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 12407d5f61c2653a64f2ff4b22f3c267f8420ef1 upstream.
+
+batadv_tt_check_roam_count() is supposed to track roaming of a TT entry.
+But TT entries are for a MAC + VID. The VID was completely missed and thus
+leads to incorrect detection of ROAM counts when a client MAC exists in
+multiple VLANs.
+
+Cc: stable@kernel.org
+Fixes: c018ad3de61a ("batman-adv: add the VLAN ID attribute to the TT entry")
+[ Context ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/translation-table.c | 9 +++++++--
+ net/batman-adv/types.h | 3 +++
+ 2 files changed, 10 insertions(+), 2 deletions(-)
+
+diff --git a/net/batman-adv/translation-table.c b/net/batman-adv/translation-table.c
+index c3d62df9e2b87d..5161ddf1c788e0 100644
+--- a/net/batman-adv/translation-table.c
++++ b/net/batman-adv/translation-table.c
+@@ -3517,6 +3517,7 @@ static void batadv_tt_roam_purge(struct batadv_priv *bat_priv)
+ * batadv_tt_check_roam_count() - check if a client has roamed too frequently
+ * @bat_priv: the bat priv with all the soft interface information
+ * @client: mac address of the roaming client
++ * @vid: VLAN identifier
+ *
+ * This function checks whether the client already reached the
+ * maximum number of possible roaming phases. In this case the ROAMING_ADV
+@@ -3524,7 +3525,7 @@ static void batadv_tt_roam_purge(struct batadv_priv *bat_priv)
+ *
+ * Return: true if the ROAMING_ADV can be sent, false otherwise
+ */
+-static bool batadv_tt_check_roam_count(struct batadv_priv *bat_priv, u8 *client)
++static bool batadv_tt_check_roam_count(struct batadv_priv *bat_priv, u8 *client, u16 vid)
+ {
+ struct batadv_tt_roam_node *tt_roam_node;
+ bool ret = false;
+@@ -3537,6 +3538,9 @@ static bool batadv_tt_check_roam_count(struct batadv_priv *bat_priv, u8 *client)
+ if (!batadv_compare_eth(tt_roam_node->addr, client))
+ continue;
+
++ if (tt_roam_node->vid != vid)
++ continue;
++
+ if (batadv_has_timed_out(tt_roam_node->first_time,
+ BATADV_ROAMING_MAX_TIME))
+ continue;
+@@ -3558,6 +3562,7 @@ static bool batadv_tt_check_roam_count(struct batadv_priv *bat_priv, u8 *client)
+ atomic_set(&tt_roam_node->counter,
+ BATADV_ROAMING_MAX_COUNT - 1);
+ ether_addr_copy(tt_roam_node->addr, client);
++ tt_roam_node->vid = vid;
+
+ list_add(&tt_roam_node->list, &bat_priv->tt.roam_list);
+ ret = true;
+@@ -3594,7 +3599,7 @@ static void batadv_send_roam_adv(struct batadv_priv *bat_priv, u8 *client,
+ /* before going on we have to check whether the client has
+ * already roamed to us too many times
+ */
+- if (!batadv_tt_check_roam_count(bat_priv, client))
++ if (!batadv_tt_check_roam_count(bat_priv, client, vid))
+ goto out;
+
+ batadv_dbg(BATADV_DBG_TT, bat_priv,
+diff --git a/net/batman-adv/types.h b/net/batman-adv/types.h
+index 0a6d0303a616c5..673b04dd71b043 100644
+--- a/net/batman-adv/types.h
++++ b/net/batman-adv/types.h
+@@ -1995,6 +1995,9 @@ struct batadv_tt_roam_node {
+ /** @addr: mac address of the client in the roaming phase */
+ u8 addr[ETH_ALEN];
+
++ /** @vid: VLAN identifier */
++ u16 vid;
++
+ /**
+ * @counter: number of allowed roaming events per client within a single
+ * OGM interval (changes are committed with each OGM)
+--
+2.53.0
+
--- /dev/null
+From c052a0b7183276a112af8373678e639cf36a50fb Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:23 +0200
+Subject: batman-adv: tvlv: avoid race of cifsnotfound handler state
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit edb557b2ba38fea2c5eb710cf366c797e187218c upstream.
+
+TVLV handlers can have the flag BATADV_TVLV_HANDLER_OGM_CIFNOTFND set to
+signal that the OGM handler should be called (with NULL for data) when the
+specific TVLV container was not found in the OGM. This is used by:
+
+* DAT
+* GW
+* Multicast (OGM + Tracker)
+
+The state whether the handler was executed was stored in the struct
+batadv_tvlv_handler. But the TVLV processing is started without any lock.
+Multiple parallel contexts processing TVLVs would therefore overwrite each
+others BATADV_TVLV_HANDLER_OGM_CALLED flag in the shared
+batadv_tvlv_handler.
+
+Drop the shared BATADV_TVLV_HANDLER_OGM_CALLED flag and instead determine,
+per TVLV buffer, whether a matching container was present by scanning the
+packet's buffer.
+
+Cc: stable@kernel.org
+Fixes: ef26157747d4 ("batman-adv: tvlv - basic infrastructure")
+[ Context ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tvlv.c | 63 ++++++++++++++++++++++++++++++++++++++----
+ net/batman-adv/types.h | 7 -----
+ 2 files changed, 57 insertions(+), 13 deletions(-)
+
+diff --git a/net/batman-adv/tvlv.c b/net/batman-adv/tvlv.c
+index e25430da5afb1b..61a1e5e5f0d913 100644
+--- a/net/batman-adv/tvlv.c
++++ b/net/batman-adv/tvlv.c
+@@ -394,7 +394,6 @@ static int batadv_tvlv_call_handler(struct batadv_priv *bat_priv,
+ tvlv_handler->ogm_handler(bat_priv, orig_node,
+ BATADV_NO_FLAGS,
+ tvlv_value, tvlv_value_len);
+- tvlv_handler->flags |= BATADV_TVLV_HANDLER_OGM_CALLED;
+ } else {
+ if (!src)
+ return NET_RX_SUCCESS;
+@@ -413,6 +412,48 @@ static int batadv_tvlv_call_handler(struct batadv_priv *bat_priv,
+ return NET_RX_SUCCESS;
+ }
+
++/**
++ * batadv_tvlv_containers_contain() - check if a tvlv buffer holds a container
++ * @tvlv_value: tvlv content
++ * @tvlv_value_len: tvlv content length
++ * @type: tvlv container type to look for
++ * @version: tvlv container version to look for
++ *
++ * Return: true if a container of the given type and version is present in the
++ * tvlv buffer, false otherwise.
++ */
++static bool batadv_tvlv_containers_contain(void *tvlv_value,
++ u16 tvlv_value_len, u8 type,
++ u8 version)
++{
++ struct batadv_tvlv_hdr *tvlv_hdr;
++ u16 tvlv_value_cont_len;
++
++ while (tvlv_value_len >= sizeof(*tvlv_hdr)) {
++ tvlv_hdr = tvlv_value;
++ tvlv_value_cont_len = ntohs(tvlv_hdr->len);
++ tvlv_value = tvlv_hdr + 1;
++ tvlv_value_len -= sizeof(*tvlv_hdr);
++
++ if (tvlv_value_cont_len > tvlv_value_len)
++ break;
++
++ /* the next tvlv header is accessed assuming (at least) 2-byte
++ * alignment, so it must start at an even offset.
++ */
++ if (tvlv_value_cont_len & 1)
++ break;
++
++ if (tvlv_hdr->type == type && tvlv_hdr->version == version)
++ return true;
++
++ tvlv_value = (u8 *)tvlv_value + tvlv_value_cont_len;
++ tvlv_value_len -= tvlv_value_cont_len;
++ }
++
++ return false;
++}
++
+ /**
+ * batadv_tvlv_containers_process() - parse the given tvlv buffer to call the
+ * appropriate handlers
+@@ -433,7 +474,9 @@ int batadv_tvlv_containers_process(struct batadv_priv *bat_priv,
+ u8 *src, u8 *dst,
+ void *tvlv_value, u16 tvlv_value_len)
+ {
++ u16 tvlv_value_start_len = tvlv_value_len;
+ struct batadv_tvlv_handler *tvlv_handler;
++ void *tvlv_value_start = tvlv_value;
+ struct batadv_tvlv_hdr *tvlv_hdr;
+ u16 tvlv_value_cont_len;
+ u8 cifnotfound = BATADV_TVLV_HANDLER_OGM_CIFNOTFND;
+@@ -473,12 +516,20 @@ int batadv_tvlv_containers_process(struct batadv_priv *bat_priv,
+ rcu_read_lock();
+ hlist_for_each_entry_rcu(tvlv_handler,
+ &bat_priv->tvlv.handler_list, list) {
+- if ((tvlv_handler->flags & BATADV_TVLV_HANDLER_OGM_CIFNOTFND) &&
+- !(tvlv_handler->flags & BATADV_TVLV_HANDLER_OGM_CALLED))
+- tvlv_handler->ogm_handler(bat_priv, orig_node,
+- cifnotfound, NULL, 0);
++ if (!(tvlv_handler->flags & BATADV_TVLV_HANDLER_OGM_CIFNOTFND))
++ continue;
+
+- tvlv_handler->flags &= ~BATADV_TVLV_HANDLER_OGM_CALLED;
++ /* if the corresponding container was present then the handler
++ * was already called from the loop above
++ */
++ if (batadv_tvlv_containers_contain(tvlv_value_start,
++ tvlv_value_start_len,
++ tvlv_handler->type,
++ tvlv_handler->version))
++ continue;
++
++ tvlv_handler->ogm_handler(bat_priv, orig_node,
++ cifnotfound, NULL, 0);
+ }
+ rcu_read_unlock();
+
+diff --git a/net/batman-adv/types.h b/net/batman-adv/types.h
+index 673b04dd71b043..f009b2a381a468 100644
+--- a/net/batman-adv/types.h
++++ b/net/batman-adv/types.h
+@@ -2408,13 +2408,6 @@ enum batadv_tvlv_handler_flags {
+ * will call this handler even if its type was not found (with no data)
+ */
+ BATADV_TVLV_HANDLER_OGM_CIFNOTFND = BIT(1),
+-
+- /**
+- * @BATADV_TVLV_HANDLER_OGM_CALLED: interval tvlv handling flag - the
+- * API marks a handler as being called, so it won't be called if the
+- * BATADV_TVLV_HANDLER_OGM_CIFNOTFND flag was set
+- */
+- BATADV_TVLV_HANDLER_OGM_CALLED = BIT(2),
+ };
+
+ #endif /* _NET_BATMAN_ADV_TYPES_H_ */
+--
+2.53.0
+
--- /dev/null
+From 70e7b2220fad294bafebc8640a223ac08d825f01 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:22 +0200
+Subject: batman-adv: tvlv: enforce 2-byte alignment
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 32a6799255525d6ea4da0f7e9e0e521ad9560a46 upstream.
+
+The fields of an aggregated OGM(v2) are accessed assuming (at least) 2-byte
+alignment, so a following OGM must start at an even offset. As the header
+length is even, an odd tvlv_len would misalign it and trigger unaligned
+accesses on strict-alignment architectures.
+
+Such a misaligned TVLV/OGM/OGMv2 is not created by a normal participant in
+the mesh. Therefore, reject such malformed packets.
+
+Cc: stable@kernel.org
+Fixes: ef26157747d4 ("batman-adv: tvlv - basic infrastructure")
+[ Drop change for non-existing mcast handling ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/bat_iv_ogm.c | 11 ++++++++++-
+ net/batman-adv/bat_v_ogm.c | 11 ++++++++++-
+ net/batman-adv/tvlv.c | 6 ++++++
+ 3 files changed, 26 insertions(+), 2 deletions(-)
+
+diff --git a/net/batman-adv/bat_iv_ogm.c b/net/batman-adv/bat_iv_ogm.c
+index c52e3b82889868..7fd692b642e392 100644
+--- a/net/batman-adv/bat_iv_ogm.c
++++ b/net/batman-adv/bat_iv_ogm.c
+@@ -311,14 +311,23 @@ batadv_iv_ogm_aggr_packet(int buff_pos, int packet_len,
+ const struct batadv_ogm_packet *ogm_packet)
+ {
+ int next_buff_pos = 0;
++ u16 tvlv_len;
+
+ /* check if there is enough space for the header */
+ next_buff_pos += buff_pos + sizeof(*ogm_packet);
+ if (next_buff_pos > packet_len)
+ return false;
+
++ tvlv_len = ntohs(ogm_packet->tvlv_len);
++
++ /* the fields of an aggregated OGM are accessed assuming (at least)
++ * 2-byte alignment, so a following OGM must start at an even offset.
++ */
++ if (tvlv_len & 1)
++ return false;
++
+ /* check if there is enough space for the optional TVLV */
+- next_buff_pos += ntohs(ogm_packet->tvlv_len);
++ next_buff_pos += tvlv_len;
+
+ return next_buff_pos <= packet_len;
+ }
+diff --git a/net/batman-adv/bat_v_ogm.c b/net/batman-adv/bat_v_ogm.c
+index f87c2b80e4291e..e8ad4817d20018 100644
+--- a/net/batman-adv/bat_v_ogm.c
++++ b/net/batman-adv/bat_v_ogm.c
+@@ -854,14 +854,23 @@ batadv_v_ogm_aggr_packet(int buff_pos, int packet_len,
+ const struct batadv_ogm2_packet *ogm2_packet)
+ {
+ int next_buff_pos = 0;
++ u16 tvlv_len;
+
+ /* check if there is enough space for the header */
+ next_buff_pos += buff_pos + sizeof(*ogm2_packet);
+ if (next_buff_pos > packet_len)
+ return false;
+
++ tvlv_len = ntohs(ogm2_packet->tvlv_len);
++
++ /* the fields of an aggregated OGMv2 are accessed assuming (at least)
++ * 2-byte alignment, so a following OGMv2 must start at an even offset.
++ */
++ if (tvlv_len & 1)
++ return false;
++
+ /* check if there is enough space for the optional TVLV */
+- next_buff_pos += ntohs(ogm2_packet->tvlv_len);
++ next_buff_pos += tvlv_len;
+
+ return next_buff_pos <= packet_len;
+ }
+diff --git a/net/batman-adv/tvlv.c b/net/batman-adv/tvlv.c
+index 99e5e8518dcc9a..e25430da5afb1b 100644
+--- a/net/batman-adv/tvlv.c
++++ b/net/batman-adv/tvlv.c
+@@ -448,6 +448,12 @@ int batadv_tvlv_containers_process(struct batadv_priv *bat_priv,
+ if (tvlv_value_cont_len > tvlv_value_len)
+ break;
+
++ /* the next tvlv header is accessed assuming (at least) 2-byte
++ * alignment, so it must start at an even offset.
++ */
++ if (tvlv_value_cont_len & 1)
++ break;
++
+ tvlv_handler = batadv_tvlv_handler_get(bat_priv,
+ tvlv_hdr->type,
+ tvlv_hdr->version);
+--
+2.53.0
+
--- /dev/null
+From f349fccf6e64eb281a526e38664a966944e11974 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:14 +0200
+Subject: batman-adv: v: prevent OGM aggregation on disabled hardif
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit d11c00b95b2a3b3934007fc003dccc6fdcc061ad upstream.
+
+When an interface gets disabled, the worker is correctly disabled by
+batadv_hardif_disable_interface() -> ... -> batadv_v_ogm_iface_disable().
+In this process, the skb aggr_list is also freed.
+
+But batadv_v_ogm_send_meshif() can still queue new skbs (via
+batadv_v_ogm_queue_on_if()) to the aggr_list. This will only stop after all
+cores can no longer find the RCU protected list of hard interfaces. These
+queued skbs will never be freed or consumed by batadv_v_ogm_aggr_work.
+
+The batadv_v_ogm_iface_disable() function must block
+batadv_v_ogm_queue_on_if() to avoid leak of skbs.
+
+Cc: stable@kernel.org
+Fixes: f89255a02f1d ("batman-adv: BATMAN_V: introduce per hard-iface OGMv2 queues")
+[ Context ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/bat_v.c | 1 +
+ net/batman-adv/bat_v_ogm.c | 12 ++++++++++++
+ net/batman-adv/types.h | 6 ++++++
+ 3 files changed, 19 insertions(+)
+
+diff --git a/net/batman-adv/bat_v.c b/net/batman-adv/bat_v.c
+index 651e01b86141e3..34874942ae8d03 100644
+--- a/net/batman-adv/bat_v.c
++++ b/net/batman-adv/bat_v.c
+@@ -838,6 +838,7 @@ void batadv_v_hardif_init(struct batadv_hard_iface *hard_iface)
+
+ hard_iface->bat_v.aggr_len = 0;
+ skb_queue_head_init(&hard_iface->bat_v.aggr_list);
++ hard_iface->bat_v.aggr_list_enabled = false;
+ INIT_DELAYED_WORK(&hard_iface->bat_v.aggr_wq,
+ batadv_v_ogm_aggr_work);
+ }
+diff --git a/net/batman-adv/bat_v_ogm.c b/net/batman-adv/bat_v_ogm.c
+index 57d0c6862f449c..f87c2b80e4291e 100644
+--- a/net/batman-adv/bat_v_ogm.c
++++ b/net/batman-adv/bat_v_ogm.c
+@@ -255,11 +255,18 @@ static void batadv_v_ogm_queue_on_if(struct batadv_priv *bat_priv,
+ }
+
+ spin_lock_bh(&hard_iface->bat_v.aggr_list.lock);
++ if (!hard_iface->bat_v.aggr_list_enabled) {
++ kfree_skb(skb);
++ goto unlock;
++ }
++
+ if (!batadv_v_ogm_queue_left(skb, hard_iface))
+ batadv_v_ogm_aggr_send(bat_priv, hard_iface);
+
+ hard_iface->bat_v.aggr_len += batadv_v_ogm_len(skb);
+ __skb_queue_tail(&hard_iface->bat_v.aggr_list, skb);
++
++unlock:
+ spin_unlock_bh(&hard_iface->bat_v.aggr_list.lock);
+ }
+
+@@ -422,6 +429,10 @@ int batadv_v_ogm_iface_enable(struct batadv_hard_iface *hard_iface)
+ {
+ struct batadv_priv *bat_priv = netdev_priv(hard_iface->soft_iface);
+
++ spin_lock_bh(&hard_iface->bat_v.aggr_list.lock);
++ hard_iface->bat_v.aggr_list_enabled = true;
++ spin_unlock_bh(&hard_iface->bat_v.aggr_list.lock);
++
+ batadv_v_ogm_start_queue_timer(hard_iface);
+ batadv_v_ogm_start_timer(bat_priv);
+
+@@ -437,6 +448,7 @@ void batadv_v_ogm_iface_disable(struct batadv_hard_iface *hard_iface)
+ cancel_delayed_work_sync(&hard_iface->bat_v.aggr_wq);
+
+ spin_lock_bh(&hard_iface->bat_v.aggr_list.lock);
++ hard_iface->bat_v.aggr_list_enabled = false;
+ batadv_v_ogm_aggr_list_free(hard_iface);
+ spin_unlock_bh(&hard_iface->bat_v.aggr_list.lock);
+ }
+diff --git a/net/batman-adv/types.h b/net/batman-adv/types.h
+index ca1c258faa095f..a61e319e4db0e9 100644
+--- a/net/batman-adv/types.h
++++ b/net/batman-adv/types.h
+@@ -130,6 +130,12 @@ struct batadv_hard_iface_bat_v {
+ /** @aggr_list: queue for to be aggregated OGM packets */
+ struct sk_buff_head aggr_list;
+
++ /**
++ * @aggr_list_enabled: aggr_list is active and new skbs can be
++ * enqueued. Protected by aggr_list.lock after initialization
++ */
++ bool aggr_list_enabled:1;
++
+ /** @aggr_len: size of the OGM aggregate (excluding ethernet header) */
+ unsigned int aggr_len;
+
+--
+2.53.0
+
kvm-x86-mmu-ensure-hugepage-is-in-by-slot-before-che.patch
revert-ptp-add-testptp-mask-test.patch
mm-mglru-skip-special-vmas-in-lru_gen_look_around.patch
+batman-adv-tp_meter-keep-unacked-list-in-ascending-o.patch
+batman-adv-tp_meter-initialize-dup_acks-explicitly.patch
+batman-adv-tp_meter-initialize-dec_cwnd-explicitly.patch
+batman-adv-tp_meter-avoid-window-underflow.patch
+batman-adv-tp_meter-avoid-divide-by-zero-for-dec_cwn.patch
+batman-adv-tp_meter-fix-fast-recovery-precondition.patch
+batman-adv-tp_meter-handle-seqno-wrap-around-for-fas.patch
+batman-adv-tp_meter-add-only-finished-tp_vars-to-lis.patch
+batman-adv-bla-annotate-lasttime-access-with-read-wr.patch
+batman-adv-prevent-elp-transmission-interval-underfl.patch
+batman-adv-tp_meter-initialize-last_recv_time-during.patch
+batman-adv-ensure-bcast-is-writable-before-modifying.patch
+batman-adv-fix-m-b-cast-csum-after-decrementing-ttl.patch
+batman-adv-frag-ensure-fragment-is-writable-before-m.patch
+batman-adv-frag-avoid-underflow-of-ttl.patch
+batman-adv-v-prevent-ogm-aggregation-on-disabled-har.patch
+batman-adv-tp_meter-restrict-number-of-unacked-list-.patch
+batman-adv-tp_meter-annotate-last_recv_time-access-w.patch
+batman-adv-tp_meter-prevent-parallel-modifications-o.patch
+batman-adv-tp_meter-handle-overlapping-packets.patch
+batman-adv-tt-don-t-merge-change-entries-with-differ.patch
+batman-adv-tt-track-roam-count-per-vid.patch
+batman-adv-dat-prevent-false-sharing-between-vlans.patch
+batman-adv-tvlv-enforce-2-byte-alignment.patch
+batman-adv-tvlv-avoid-race-of-cifsnotfound-handler-s.patch
--- /dev/null
+From 8f7ad6fa32ea7f5f6fa95ab7c016d02973aba88d Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:38 +0200
+Subject: batman-adv: bla: annotate lasttime access with READ/WRITE_ONCE
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 98b0fb191c878a64cbaebfe231d96d57576acf8c upstream.
+
+The lasttime field for claim, backbone_gw, and loopdetect tracks the
+jiffies value of the most recent activity and is used to detect timeouts.
+These accesses are not consistently protected by a lock, so
+READ_ONCE/WRITE_ONCE must be used to prevent data races caused by compiler
+optimizations.
+
+Cc: stable@kernel.org
+Fixes: 23721387c409 ("batman-adv: add basic bridge loop avoidance code")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/bridge_loop_avoidance.c | 28 +++++++++++++-------------
+ 1 file changed, 14 insertions(+), 14 deletions(-)
+
+diff --git a/net/batman-adv/bridge_loop_avoidance.c b/net/batman-adv/bridge_loop_avoidance.c
+index 15aeb07285e61f..cc6c14cac06282 100644
+--- a/net/batman-adv/bridge_loop_avoidance.c
++++ b/net/batman-adv/bridge_loop_avoidance.c
+@@ -512,7 +512,7 @@ batadv_bla_get_backbone_gw(struct batadv_priv *bat_priv, const u8 *orig,
+ return NULL;
+
+ entry->vid = vid;
+- entry->lasttime = jiffies;
++ WRITE_ONCE(entry->lasttime, jiffies);
+ entry->crc = BATADV_BLA_CRC_INIT;
+ entry->bat_priv = bat_priv;
+ spin_lock_init(&entry->crc_lock);
+@@ -580,7 +580,7 @@ batadv_bla_update_own_backbone_gw(struct batadv_priv *bat_priv,
+ if (unlikely(!backbone_gw))
+ return;
+
+- backbone_gw->lasttime = jiffies;
++ WRITE_ONCE(backbone_gw->lasttime, jiffies);
+ batadv_backbone_gw_put(backbone_gw);
+ }
+
+@@ -714,7 +714,7 @@ static void batadv_bla_add_claim(struct batadv_priv *bat_priv,
+ ether_addr_copy(claim->addr, mac);
+ spin_lock_init(&claim->backbone_lock);
+ claim->vid = vid;
+- claim->lasttime = jiffies;
++ WRITE_ONCE(claim->lasttime, jiffies);
+ kref_get(&backbone_gw->refcount);
+ claim->backbone_gw = backbone_gw;
+ kref_init(&claim->refcount);
+@@ -736,7 +736,7 @@ static void batadv_bla_add_claim(struct batadv_priv *bat_priv,
+ return;
+ }
+ } else {
+- claim->lasttime = jiffies;
++ WRITE_ONCE(claim->lasttime, jiffies);
+ if (claim->backbone_gw == backbone_gw)
+ /* no need to register a new backbone */
+ goto claim_free_ref;
+@@ -769,7 +769,7 @@ static void batadv_bla_add_claim(struct batadv_priv *bat_priv,
+ spin_lock_bh(&backbone_gw->crc_lock);
+ backbone_gw->crc ^= crc16(0, claim->addr, ETH_ALEN);
+ spin_unlock_bh(&backbone_gw->crc_lock);
+- backbone_gw->lasttime = jiffies;
++ WRITE_ONCE(backbone_gw->lasttime, jiffies);
+
+ claim_free_ref:
+ batadv_claim_put(claim);
+@@ -858,7 +858,7 @@ static bool batadv_handle_announce(struct batadv_priv *bat_priv, u8 *an_addr,
+ return true;
+
+ /* handle as ANNOUNCE frame */
+- backbone_gw->lasttime = jiffies;
++ WRITE_ONCE(backbone_gw->lasttime, jiffies);
+ crc = ntohs(*((__force __be16 *)(&an_addr[4])));
+
+ batadv_dbg(BATADV_DBG_BLA, bat_priv,
+@@ -1253,7 +1253,7 @@ static void batadv_bla_purge_backbone_gw(struct batadv_priv *bat_priv, int now)
+ head, hash_entry) {
+ if (now)
+ goto purge_now;
+- if (!batadv_has_timed_out(backbone_gw->lasttime,
++ if (!batadv_has_timed_out(READ_ONCE(backbone_gw->lasttime),
+ BATADV_BLA_BACKBONE_TIMEOUT))
+ continue;
+
+@@ -1334,7 +1334,7 @@ static void batadv_bla_purge_claims(struct batadv_priv *bat_priv,
+ primary_if->net_dev->dev_addr))
+ goto skip;
+
+- if (!batadv_has_timed_out(claim->lasttime,
++ if (!batadv_has_timed_out(READ_ONCE(claim->lasttime),
+ BATADV_BLA_CLAIM_TIMEOUT))
+ goto skip;
+
+@@ -1494,7 +1494,7 @@ static void batadv_bla_periodic_work(struct work_struct *work)
+ eth_random_addr(bat_priv->bla.loopdetect_addr);
+ bat_priv->bla.loopdetect_addr[0] = 0xba;
+ bat_priv->bla.loopdetect_addr[1] = 0xbe;
+- bat_priv->bla.loopdetect_lasttime = jiffies;
++ WRITE_ONCE(bat_priv->bla.loopdetect_lasttime, jiffies);
+ atomic_set(&bat_priv->bla.loopdetect_next,
+ BATADV_BLA_LOOPDETECT_PERIODS);
+
+@@ -1515,7 +1515,7 @@ static void batadv_bla_periodic_work(struct work_struct *work)
+ primary_if->net_dev->dev_addr))
+ continue;
+
+- backbone_gw->lasttime = jiffies;
++ WRITE_ONCE(backbone_gw->lasttime, jiffies);
+
+ batadv_bla_send_announce(bat_priv, backbone_gw);
+ if (send_loopdetect)
+@@ -1900,7 +1900,7 @@ batadv_bla_loopdetect_check(struct batadv_priv *bat_priv, struct sk_buff *skb,
+ /* If the packet came too late, don't forward it on the mesh
+ * but don't consider that as loop. It might be a coincidence.
+ */
+- if (batadv_has_timed_out(bat_priv->bla.loopdetect_lasttime,
++ if (batadv_has_timed_out(READ_ONCE(bat_priv->bla.loopdetect_lasttime),
+ BATADV_BLA_LOOPDETECT_TIMEOUT))
+ return true;
+
+@@ -2016,7 +2016,7 @@ bool batadv_bla_rx(struct batadv_priv *bat_priv, struct sk_buff *skb,
+
+ if (own_claim) {
+ /* ... allow it in any case */
+- claim->lasttime = jiffies;
++ WRITE_ONCE(claim->lasttime, jiffies);
+ goto allow;
+ }
+
+@@ -2118,7 +2118,7 @@ bool batadv_bla_tx(struct batadv_priv *bat_priv, struct sk_buff *skb,
+ /* if yes, the client has roamed and we have
+ * to unclaim it.
+ */
+- if (batadv_has_timed_out(claim->lasttime, 100)) {
++ if (batadv_has_timed_out(READ_ONCE(claim->lasttime), 100)) {
+ /* only unclaim if the last claim entry is
+ * older than 100 ms to make sure we really
+ * have a roaming client here.
+@@ -2372,7 +2372,7 @@ batadv_bla_backbone_dump_entry(struct sk_buff *msg, u32 portid,
+ backbone_crc = backbone_gw->crc;
+ spin_unlock_bh(&backbone_gw->crc_lock);
+
+- msecs = jiffies_to_msecs(jiffies - backbone_gw->lasttime);
++ msecs = jiffies_to_msecs(jiffies - READ_ONCE(backbone_gw->lasttime));
+
+ if (is_own)
+ if (nla_put_flag(msg, BATADV_ATTR_BLA_OWN)) {
+--
+2.53.0
+
--- /dev/null
+From ccbf0785c5e840d366ff4c4978fea86733a0ae66 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:52 +0200
+Subject: batman-adv: dat: prevent false sharing between VLANs
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 20d7658b74169f86d4ac01b9185b3eadddf71f28 upstream.
+
+The local hash of DAT entries is supposed to be VLAN (VID) aware. But
+the adding to the hash and the search in the hash were not checking the VID
+information of the hash entries. The entries would therefore only be
+correctly separated when batadv_hash_dat() didn't select the same buckets
+for different VIDs.
+
+Cc: stable@kernel.org
+Fixes: be1db4f6615b ("batman-adv: make the Distributed ARP Table vlan aware")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/distributed-arp-table.c | 12 +++++++++---
+ 1 file changed, 9 insertions(+), 3 deletions(-)
+
+diff --git a/net/batman-adv/distributed-arp-table.c b/net/batman-adv/distributed-arp-table.c
+index 4d53e7c3ea5463..bd5477263ecd7c 100644
+--- a/net/batman-adv/distributed-arp-table.c
++++ b/net/batman-adv/distributed-arp-table.c
+@@ -215,10 +215,13 @@ static void batadv_dat_purge(struct work_struct *work)
+ */
+ static bool batadv_compare_dat(const struct hlist_node *node, const void *data2)
+ {
+- const void *data1 = container_of(node, struct batadv_dat_entry,
+- hash_entry);
++ const struct batadv_dat_entry *entry1;
++ const struct batadv_dat_entry *entry2;
+
+- return memcmp(data1, data2, sizeof(__be32)) == 0;
++ entry1 = container_of(node, struct batadv_dat_entry, hash_entry);
++ entry2 = data2;
++
++ return entry1->ip == entry2->ip && entry1->vid == entry2->vid;
+ }
+
+ /**
+@@ -345,6 +348,9 @@ batadv_dat_entry_hash_find(struct batadv_priv *bat_priv, __be32 ip,
+ if (dat_entry->ip != ip)
+ continue;
+
++ if (dat_entry->vid != vid)
++ continue;
++
+ if (!kref_get_unless_zero(&dat_entry->refcount))
+ continue;
+
+--
+2.53.0
+
--- /dev/null
+From 9459d407a92bb0848d0407197ecd0a5361c558c1 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:41 +0200
+Subject: batman-adv: ensure bcast is writable before modifying TTL
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 4cd6d3a4b96a8576f1fed8f9f9f17c2dc2978e0c upstream.
+
+Before batman-adv is allowed to write to an skb, it either has to have its
+own copy of the skb or used skb_cow() to ensure that the data part is not
+shared.
+
+The old implementation used a shared queue and created copies before
+attempting to write to it. But with the new implementation, the broadcast
+packet is already modified when it gets received. Potentially writing to
+shared buffers in this process.
+
+Adding a skb_cow() right before this operation avoids this and can at the
+same time prepare it for the modifications required to rebroadcast the
+packet.
+
+Cc: stable@kernel.org
+Fixes: 3f69339068f9 ("batman-adv: bcast: queue per interface, if needed")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/routing.c | 6 ++++++
+ 1 file changed, 6 insertions(+)
+
+diff --git a/net/batman-adv/routing.c b/net/batman-adv/routing.c
+index f1061985149fc5..32a40c1c115c6a 100644
+--- a/net/batman-adv/routing.c
++++ b/net/batman-adv/routing.c
+@@ -1198,6 +1198,12 @@ int batadv_recv_bcast_packet(struct sk_buff *skb,
+ if (batadv_is_my_mac(bat_priv, bcast_packet->orig))
+ goto free_skb;
+
++ /* create a copy of the skb, if needed, to modify it. */
++ if (skb_cow(skb, ETH_HLEN) < 0)
++ goto free_skb;
++
++ bcast_packet = (struct batadv_bcast_packet *)skb->data;
++
+ if (bcast_packet->ttl-- < 2)
+ goto free_skb;
+
+--
+2.53.0
+
--- /dev/null
+From 7a4ead9dab9c3e66c277a9f85fdcdedb553e4517 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:42 +0200
+Subject: batman-adv: fix (m|b)cast csum after decrementing TTL
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit e728bbdf32660c8f32b8f5e8d09427a2c131ad60 upstream.
+
+The broadcast and multicast packets can be received at the same time by the
+local system and forwarded to other nodes. Both are simply decrementing the
+TTL at the beginning of the receive path - independent of chosen paths
+(receive/forward). But such a modification of the data conflicts with the
+hw csum. This is not a problem when the packet is directly forwarded but
+can cause errors in the local receive path.
+
+Such a problem can then trigger a "hw csum failure". The receiver path must
+therefore ensure that the csum is fixed for each modification of the
+payload before batadv_interface_rx() is reached.
+
+Since all batman-adv packet types with a ttl have it as u8 at offset 2, a
+helper can be used for all of them. But it is only used at the moment for
+batadv_bcast_packet and batadv_mcast_packet because they are the only ones
+which deliver the packet locally but unconditionally modify the TTL.
+
+Cc: stable@kernel.org
+Fixes: 3f69339068f9 ("batman-adv: bcast: queue per interface, if needed")
+Fixes: 07afe1ba288c ("batman-adv: mcast: implement multicast packet reception and forwarding")
+[ Context ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/routing.c | 58 ++++++++++++++++++++++++++++++++++++++--
+ 1 file changed, 56 insertions(+), 2 deletions(-)
+
+diff --git a/net/batman-adv/routing.c b/net/batman-adv/routing.c
+index 32a40c1c115c6a..85bc2d284a7798 100644
+--- a/net/batman-adv/routing.c
++++ b/net/batman-adv/routing.c
+@@ -8,6 +8,7 @@
+ #include "main.h"
+
+ #include <linux/atomic.h>
++#include <linux/build_bug.h>
+ #include <linux/byteorder/generic.h>
+ #include <linux/compiler.h>
+ #include <linux/errno.h>
+@@ -205,6 +206,59 @@ bool batadv_check_management_packet(struct sk_buff *skb,
+ return true;
+ }
+
++/**
++ * batadv_skb_decrement_ttl() - decrement ttl in a batman-adv header, csum-safe
++ * @skb: the received packet with @skb->data pointing to the batman-adv header
++ *
++ * Supports the following packet types, all of which carry the TTL at offset 2:
++ *
++ * - batadv_ogm_packet
++ * - batadv_ogm2_packet
++ * - batadv_icmp_header
++ * - batadv_icmp_packet
++ * - batadv_icmp_tp_packet
++ * - batadv_icmp_packet_rr
++ * - batadv_unicast_packet
++ * - batadv_frag_packet
++ * - batadv_bcast_packet
++ * - batadv_mcast_packet
++ * - batadv_coded_packet
++ * - batadv_unicast_tvlv_packet
++ *
++ * Return: true if the packet may be forwarded (ttl decremented),
++ * false if it must be dropped (ttl would expire)
++ */
++static bool batadv_skb_decrement_ttl(struct sk_buff *skb)
++{
++ static const size_t ttl_offset = 2;
++ u8 *ttl_pos;
++
++ BUILD_BUG_ON(offsetof(struct batadv_ogm_packet, ttl) != ttl_offset);
++ BUILD_BUG_ON(offsetof(struct batadv_ogm2_packet, ttl) != ttl_offset);
++ BUILD_BUG_ON(offsetof(struct batadv_icmp_header, ttl) != ttl_offset);
++ BUILD_BUG_ON(offsetof(struct batadv_icmp_packet, ttl) != ttl_offset);
++ BUILD_BUG_ON(offsetof(struct batadv_icmp_tp_packet, ttl) != ttl_offset);
++ BUILD_BUG_ON(offsetof(struct batadv_icmp_packet_rr, ttl) != ttl_offset);
++ BUILD_BUG_ON(offsetof(struct batadv_unicast_packet, ttl) != ttl_offset);
++ BUILD_BUG_ON(offsetof(struct batadv_frag_packet, ttl) != ttl_offset);
++ BUILD_BUG_ON(offsetof(struct batadv_bcast_packet, ttl) != ttl_offset);
++ BUILD_BUG_ON(offsetof(struct batadv_mcast_packet, ttl) != ttl_offset);
++ BUILD_BUG_ON(offsetof(struct batadv_coded_packet, ttl) != ttl_offset);
++ BUILD_BUG_ON(offsetof(struct batadv_unicast_tvlv_packet, ttl) != ttl_offset);
++
++ ttl_pos = skb->data + ttl_offset;
++
++ /* would expire on this hop -> drop, leave header + csum untouched */
++ if (*ttl_pos < 2)
++ return false;
++
++ skb_postpull_rcsum(skb, ttl_pos, 1);
++ (*ttl_pos)--;
++ skb_postpush_rcsum(skb, ttl_pos, 1);
++
++ return true;
++}
++
+ /**
+ * batadv_recv_my_icmp_packet() - receive an icmp packet locally
+ * @bat_priv: the bat priv with all the soft interface information
+@@ -1204,7 +1258,7 @@ int batadv_recv_bcast_packet(struct sk_buff *skb,
+
+ bcast_packet = (struct batadv_bcast_packet *)skb->data;
+
+- if (bcast_packet->ttl-- < 2)
++ if (!batadv_skb_decrement_ttl(skb))
+ goto free_skb;
+
+ orig_node = batadv_orig_hash_find(bat_priv, bcast_packet->orig);
+@@ -1311,7 +1365,7 @@ int batadv_recv_mcast_packet(struct sk_buff *skb,
+ goto free_skb;
+
+ mcast_packet = (struct batadv_mcast_packet *)skb->data;
+- if (mcast_packet->ttl-- < 2)
++ if (!batadv_skb_decrement_ttl(skb))
+ goto free_skb;
+
+ tvlv_buff = (unsigned char *)(skb->data + hdr_size);
+--
+2.53.0
+
--- /dev/null
+From a621528e2c42e781f48ebba1caedf6528469e82e Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:44 +0200
+Subject: batman-adv: frag: avoid underflow of TTL
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 493d9d2528e1a09b090e4b37f0f553def7bd5ce9 upstream.
+
+Packets with a TTL are using it to limit the amount of time this packet can
+be forwarded. But for batadv_frag_packet, the TTL was always only reduced
+but it was never evaluated. It could even underflow without any effect.
+
+Check the TTL in batadv_frag_skb_fwd() before attempting to prepare it for
+forwarding. This keeps it in sync with the not fragmented unicast packet.
+
+Cc: stable@kernel.org
+Fixes: 610bfc6bc99b ("batman-adv: Receive fragmented packets and merge")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/fragmentation.c | 7 +++++++
+ 1 file changed, 7 insertions(+)
+
+diff --git a/net/batman-adv/fragmentation.c b/net/batman-adv/fragmentation.c
+index f6714405b0f948..375c9499121346 100644
+--- a/net/batman-adv/fragmentation.c
++++ b/net/batman-adv/fragmentation.c
+@@ -415,6 +415,13 @@ bool batadv_frag_skb_fwd(struct sk_buff *skb,
+ */
+ total_size = ntohs(packet->total_size);
+ if (total_size > neigh_node->if_incoming->net_dev->mtu) {
++ if (packet->ttl < 2) {
++ kfree_skb(skb);
++ *rx_result = NET_RX_DROP;
++ ret = true;
++ goto out;
++ }
++
+ if (skb_cow(skb, ETH_HLEN) < 0) {
+ kfree_skb(skb);
+ *rx_result = NET_RX_DROP;
+--
+2.53.0
+
--- /dev/null
+From 17b0bd7b43267127ce0725a2cd18fb7e51cac111 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:43 +0200
+Subject: batman-adv: frag: ensure fragment is writable before modifying TTL
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit b7293c6e8c15b2db77809b25cf8389e35331b27a upstream.
+
+Before batman-adv is allowed to write to an skb, it either has to have its
+own copy of the skb or use skb_cow() to ensure that the data part is not
+shared. But batadv_frag_skb_fwd() modifies the TTL even when it is shared.
+
+Adding a skb_cow() right before this operation avoids this and can at the
+same time prepare it for the modifications required to forward the
+fragment.
+
+Cc: stable@kernel.org
+Fixes: 610bfc6bc99b ("batman-adv: Receive fragmented packets and merge")
+[ Context ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/fragmentation.c | 15 ++++++++++++++-
+ net/batman-adv/fragmentation.h | 3 ++-
+ net/batman-adv/routing.c | 3 +--
+ 3 files changed, 17 insertions(+), 4 deletions(-)
+
+diff --git a/net/batman-adv/fragmentation.c b/net/batman-adv/fragmentation.c
+index fd7cb789ae9a6a..f6714405b0f948 100644
+--- a/net/batman-adv/fragmentation.c
++++ b/net/batman-adv/fragmentation.c
+@@ -384,6 +384,8 @@ bool batadv_frag_skb_buffer(struct sk_buff **skb,
+ * @skb: skb to forward
+ * @recv_if: interface that the skb is received on
+ * @orig_node_src: originator that the skb is received from
++ * @rx_result: set to NET_RX_SUCCESS when the fragment was forwarded and
++ * NET_RX_DROP when it was dropped; only valid when true is returned
+ *
+ * Look up the next-hop of the fragments payload and check if the merged packet
+ * will exceed the MTU towards the next-hop. If so, the fragment is forwarded
+@@ -393,7 +395,8 @@ bool batadv_frag_skb_buffer(struct sk_buff **skb,
+ */
+ bool batadv_frag_skb_fwd(struct sk_buff *skb,
+ struct batadv_hard_iface *recv_if,
+- struct batadv_orig_node *orig_node_src)
++ struct batadv_orig_node *orig_node_src,
++ int *rx_result)
+ {
+ struct batadv_priv *bat_priv = netdev_priv(recv_if->soft_iface);
+ struct batadv_neigh_node *neigh_node = NULL;
+@@ -412,12 +415,22 @@ bool batadv_frag_skb_fwd(struct sk_buff *skb,
+ */
+ total_size = ntohs(packet->total_size);
+ if (total_size > neigh_node->if_incoming->net_dev->mtu) {
++ if (skb_cow(skb, ETH_HLEN) < 0) {
++ kfree_skb(skb);
++ *rx_result = NET_RX_DROP;
++ ret = true;
++ goto out;
++ }
++
++ packet = (struct batadv_frag_packet *)skb->data;
++
+ batadv_inc_counter(bat_priv, BATADV_CNT_FRAG_FWD);
+ batadv_add_counter(bat_priv, BATADV_CNT_FRAG_FWD_BYTES,
+ skb->len + ETH_HLEN);
+
+ packet->ttl--;
+ batadv_send_unicast_skb(skb, neigh_node);
++ *rx_result = NET_RX_SUCCESS;
+ ret = true;
+ }
+
+diff --git a/net/batman-adv/fragmentation.h b/net/batman-adv/fragmentation.h
+index dbf0871f870303..51e281027ab630 100644
+--- a/net/batman-adv/fragmentation.h
++++ b/net/batman-adv/fragmentation.h
+@@ -19,7 +19,8 @@ void batadv_frag_purge_orig(struct batadv_orig_node *orig,
+ bool (*check_cb)(struct batadv_frag_table_entry *));
+ bool batadv_frag_skb_fwd(struct sk_buff *skb,
+ struct batadv_hard_iface *recv_if,
+- struct batadv_orig_node *orig_node_src);
++ struct batadv_orig_node *orig_node_src,
++ int *rx_result);
+ bool batadv_frag_skb_buffer(struct sk_buff **skb,
+ struct batadv_orig_node *orig_node);
+ int batadv_frag_send_packet(struct sk_buff *skb,
+diff --git a/net/batman-adv/routing.c b/net/batman-adv/routing.c
+index 85bc2d284a7798..6fd18b69e5f6be 100644
+--- a/net/batman-adv/routing.c
++++ b/net/batman-adv/routing.c
+@@ -1175,10 +1175,9 @@ int batadv_recv_frag_packet(struct sk_buff *skb,
+
+ /* Route the fragment if it is not for us and too big to be merged. */
+ if (!batadv_is_my_mac(bat_priv, frag_packet->dest) &&
+- batadv_frag_skb_fwd(skb, recv_if, orig_node_src)) {
++ batadv_frag_skb_fwd(skb, recv_if, orig_node_src, &ret)) {
+ /* skb was consumed */
+ skb = NULL;
+- ret = NET_RX_SUCCESS;
+ goto put_orig_node;
+ }
+
+--
+2.53.0
+
--- /dev/null
+From ea963a22dabe874b6f0347ab10ad82c46b268c99 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:39 +0200
+Subject: batman-adv: prevent ELP transmission interval underflow
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 5e50d4b8ae3ea622122d3c6a38d7f6fe68dfddca upstream.
+
+batadv_v_elp_start_timer() enqeues a delayed work. The time when it starts
+is randomly chosen between (elp_interval - BATADV_JITTER) and
+(elp_interval + BATADV_JITTER). The configured elp_interval must therefore
+be larger or equal to BATADV_JITTER to avoid that it causes an underflow of
+the unsigned integer. If this would happen, then a "fast" ELP interval
+would turn into a "day long" delay.
+
+At the same time, it must not be larger than the maximum value the variable
+can store.
+
+Cc: stable@kernel.org
+Fixes: a10800829040 ("batman-adv: Add elp_interval hardif genl configuration")
+[ Context ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/netlink.c | 8 +++++++-
+ 1 file changed, 7 insertions(+), 1 deletion(-)
+
+diff --git a/net/batman-adv/netlink.c b/net/batman-adv/netlink.c
+index 9362cd9d6f3d33..a7522015224a5b 100644
+--- a/net/batman-adv/netlink.c
++++ b/net/batman-adv/netlink.c
+@@ -936,9 +936,15 @@ static int batadv_netlink_set_hardif(struct sk_buff *skb,
+ #ifdef CONFIG_BATMAN_ADV_BATMAN_V
+
+ if (info->attrs[BATADV_ATTR_ELP_INTERVAL]) {
++ u32 elp_interval;
++
+ attr = info->attrs[BATADV_ATTR_ELP_INTERVAL];
++ elp_interval = nla_get_u32(attr);
++
++ elp_interval = min_t(u32, elp_interval, INT_MAX);
++ elp_interval = max_t(u32, elp_interval, BATADV_JITTER);
+
+- atomic_set(&hard_iface->bat_v.elp_interval, nla_get_u32(attr));
++ atomic_set(&hard_iface->bat_v.elp_interval, elp_interval);
+ }
+
+ if (info->attrs[BATADV_ATTR_THROUGHPUT_OVERRIDE]) {
+--
+2.53.0
+
--- /dev/null
+From 9f57d0cb63c0c1c960650d4a1538c34f8a3fc452 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:37 +0200
+Subject: batman-adv: tp_meter: add only finished tp_vars to lists
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 15ccbf685222274f5add1387af58c2a41a95f81e upstream.
+
+When the receiver variables (aka "session") are initialized, then they are
+added to the list of sessions before the timer is set up. A RCU protected
+reader could therefore find the entry and run mod_setup before
+batadv_tp_init_recv() finished the timer initialization.
+
+The same is true for batadv_tp_start(), which must first initialize the
+finish_work and the test_length to avoid a similar problem.
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 18 +++++++++---------
+ 1 file changed, 9 insertions(+), 9 deletions(-)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index 089ac7cc9fbebc..2bba53fc6da5c0 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -1096,21 +1096,21 @@ void batadv_tp_start(struct batadv_priv *bat_priv, const u8 *dst,
+ tp_vars->prerandom_offset = 0;
+ spin_lock_init(&tp_vars->prerandom_lock);
+
+- kref_get(&tp_vars->refcount);
+- hlist_add_head_rcu(&tp_vars->list, &bat_priv->tp_list);
+- spin_unlock_bh(&bat_priv->tp_list_lock);
+-
+ tp_vars->test_length = test_length;
+ if (!tp_vars->test_length)
+ tp_vars->test_length = BATADV_TP_DEF_TEST_LENGTH;
+
++ /* init work item for finished tp tests */
++ INIT_DELAYED_WORK(&tp_vars->finish_work, batadv_tp_sender_finish);
++
++ kref_get(&tp_vars->refcount);
++ hlist_add_head_rcu(&tp_vars->list, &bat_priv->tp_list);
++ spin_unlock_bh(&bat_priv->tp_list_lock);
++
+ batadv_dbg(BATADV_DBG_TP_METER, bat_priv,
+ "Meter: starting throughput meter towards %pM (length=%ums)\n",
+ dst, test_length);
+
+- /* init work item for finished tp tests */
+- INIT_DELAYED_WORK(&tp_vars->finish_work, batadv_tp_sender_finish);
+-
+ /* start tp kthread. This way the write() call issued from userspace can
+ * happily return and avoid to block
+ */
+@@ -1430,10 +1430,10 @@ batadv_tp_init_recv(struct batadv_priv *bat_priv,
+ INIT_LIST_HEAD(&tp_vars->unacked_list);
+
+ kref_get(&tp_vars->refcount);
+- hlist_add_head_rcu(&tp_vars->list, &bat_priv->tp_list);
++ timer_setup(&tp_vars->timer, batadv_tp_receiver_shutdown, 0);
+
+ kref_get(&tp_vars->refcount);
+- timer_setup(&tp_vars->timer, batadv_tp_receiver_shutdown, 0);
++ hlist_add_head_rcu(&tp_vars->list, &bat_priv->tp_list);
+
+ batadv_tp_reset_receiver_timer(tp_vars);
+
+--
+2.53.0
+
--- /dev/null
+From ed1e61b7338d93653bfc166aeabde99ef7d2fe7d Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:47 +0200
+Subject: batman-adv: tp_meter: annotate last_recv_time access with
+ READ/WRITE_ONCE
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit d67c728f07fca2ee6ffdc6dd4421cf2e8691f4d1 upstream.
+
+The last_recv_time field for batadv_tp_receiver tracks the jiffies value of
+the most recent activity and is used to detect timeouts. These accesses are
+not consistently protected by a lock, so READ_ONCE/WRITE_ONCE must be used
+to prevent data races caused by compiler optimizations.
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 8 ++++----
+ 1 file changed, 4 insertions(+), 4 deletions(-)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index 638dd438a6c052..b882919a868ca0 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -1183,7 +1183,7 @@ static void batadv_tp_receiver_shutdown(struct timer_list *t)
+ bat_priv = tp_vars->bat_priv;
+
+ /* if there is recent activity rearm the timer */
+- if (!batadv_has_timed_out(tp_vars->last_recv_time,
++ if (!batadv_has_timed_out(READ_ONCE(tp_vars->last_recv_time),
+ BATADV_TP_RECV_TIMEOUT)) {
+ /* reset the receiver shutdown timer */
+ batadv_tp_reset_receiver_timer(tp_vars);
+@@ -1424,7 +1424,7 @@ batadv_tp_init_recv(struct batadv_priv *bat_priv,
+ tp_vars = batadv_tp_list_find_session(bat_priv, icmp->orig,
+ icmp->session, BATADV_TP_RECEIVER);
+ if (tp_vars) {
+- tp_vars->last_recv_time = jiffies;
++ WRITE_ONCE(tp_vars->last_recv_time, jiffies);
+ goto out_unlock;
+ }
+
+@@ -1455,7 +1455,7 @@ batadv_tp_init_recv(struct batadv_priv *bat_priv,
+ kref_get(&tp_vars->refcount);
+ timer_setup(&tp_vars->timer, batadv_tp_receiver_shutdown, 0);
+
+- tp_vars->last_recv_time = jiffies;
++ WRITE_ONCE(tp_vars->last_recv_time, jiffies);
+
+ kref_get(&tp_vars->refcount);
+ hlist_add_head_rcu(&tp_vars->list, &bat_priv->tp_list);
+@@ -1506,7 +1506,7 @@ static void batadv_tp_recv_msg(struct batadv_priv *bat_priv,
+ goto out;
+ }
+
+- tp_vars->last_recv_time = jiffies;
++ WRITE_ONCE(tp_vars->last_recv_time, jiffies);
+ }
+
+ /* if the packet is a duplicate, it may be the case that an ACK has been
+--
+2.53.0
+
--- /dev/null
+From bb014b18d3d2438fd59d930951ce55b123498369 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:34 +0200
+Subject: batman-adv: tp_meter: avoid divide-by-zero for dec_cwnd
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 33ccd52f3cc9ed46ce395199f89aa3234dc83314 upstream.
+
+The cwnd is always MSS <= cwnd <= 0x20000000. But the calculation in
+batadv_tp_update_cwnd() assumes unsigned 32 bit arithmetics.
+
+ ((mss * 8) ** 2) / (cwnd * 8)
+
+In case cwnd is actually 0x20000000, it will be shifted by 3 bit to the
+left end up at 0x100000000 or U32_MAX + 1. It will therefore wrap around
+and be 0 - resulting in:
+
+ ((mss * 8) ** 2) / 0
+
+This is of course invalid and cannot be calculated. The calculation should
+must be simplified to avoid this overflow:
+
+ (mss ** 2) * 8 / cwnd
+
+It will keep the precision enhancement from the scaling (by 8) but avoid
+the overflow in the divisor.
+
+In theory, there could still be an overflow in the dividend. It is at the
+moment fixed to BATADV_TP_PLEN in batadv_tp_recv_ack() - so it is not an
+imminent problem. But allowing it to use the whole u32 bit range, would
+mean that it can still use up to 67 bits. To keep this calculation safe for
+32 bit arithmetic, mss must never use more than floor((32 - 3) / 2) bits -
+or in other words: must never be larger than 16383.
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 5 ++++-
+ 1 file changed, 4 insertions(+), 1 deletion(-)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index 0fdcafca3aa02b..4ff80e4214ff0a 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -154,9 +154,12 @@ static void batadv_tp_update_cwnd(struct batadv_tp_vars *tp_vars, u32 mss)
+ return;
+ }
+
++ /* prevent overflow in (mss * mss) << 3 */
++ mss = min_t(u32, mss, (1U << 14) - 1);
++
+ /* increment CWND at least of 1 (section 3.1 of RFC5681) */
+ tp_vars->dec_cwnd += max_t(u32, 1U << 3,
+- ((mss * mss) << 6) / (tp_vars->cwnd << 3));
++ ((mss * mss) << 3) / tp_vars->cwnd);
+ if (tp_vars->dec_cwnd < (mss << 3)) {
+ spin_unlock_bh(&tp_vars->cwnd_lock);
+ return;
+--
+2.53.0
+
--- /dev/null
+From 995911928b4f6cd2b8f7d8c67f8cba8857aaa9c4 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:33 +0200
+Subject: batman-adv: tp_meter: avoid window underflow
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 765947b81fb54b6ebb0bc1cfe55c0fa399e002b8 upstream.
+
+In batadv_tp_avail(), win_left is calculated with 32-bit unsigned
+arithmetic: win_left = win_limit - tp_vars->last_sent;
+
+During Fast Recovery, cwnd is inflated and last_sent advances rapidly. When
+Fast Recovery ends, cwnd drops abruptly back to ss_threshold. If the newly
+shrunk win_limit is less than last_sent, the unsigned subtraction will
+underflow, wrapping to a massive positive value. Instead of returning that
+the window is full (unavailable), it returns that the sender can continue
+sending.
+
+To handle this situation, it must be checked whether the windows end
+sequence number (win_limit) has to be compared with the last sent sequence
+number. If it would be before the last sent sequence number, then more acks
+are needed before the transmission can be started again.
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 7 ++++++-
+ 1 file changed, 6 insertions(+), 1 deletion(-)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index f6ccb639744a2a..0fdcafca3aa02b 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -817,10 +817,15 @@ static void batadv_tp_recv_ack(struct batadv_priv *bat_priv,
+ static bool batadv_tp_avail(struct batadv_tp_vars *tp_vars,
+ size_t payload_len)
+ {
++ u32 last_sent = READ_ONCE(tp_vars->last_sent);
+ u32 win_left, win_limit;
+
+ win_limit = atomic_read(&tp_vars->last_acked) + tp_vars->cwnd;
+- win_left = win_limit - tp_vars->last_sent;
++
++ if (batadv_seq_before(last_sent, win_limit))
++ win_left = win_limit - last_sent;
++ else
++ win_left = 0;
+
+ return win_left >= payload_len;
+ }
+--
+2.53.0
+
--- /dev/null
+From 8bfc320374cf39bb95d3791382e1a563ffe41994 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:35 +0200
+Subject: batman-adv: tp_meter: fix fast recovery precondition
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 2b0d08f08ed3b2174f05c43089ec65f3543a025b upstream.
+
+The fast recovery precondition checks if the recover (initialized to
+BATADV_TP_FIRST_SEQ) is bigger than the received ack. But since recover is
+only updated when this check is successful, it will never enter the fast
+recovery mode.
+
+According to RFC6582 Section 3.2 step 2, the check should actually be
+different:
+
+> When the third duplicate ACK is received, the TCP sender first
+> checks the value of recover to see if the Cumulative
+> Acknowledgment field covers more than recover
+
+The precondition must therefore check if recover is smaller than the
+received ack - basically swapping the operands of the current check.
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index 4ff80e4214ff0a..c79352cfddc4ae 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -733,7 +733,7 @@ static void batadv_tp_recv_ack(struct batadv_priv *bat_priv,
+ if (atomic_read(&tp_vars->dup_acks) != 3)
+ goto out;
+
+- if (recv_ack >= tp_vars->recover)
++ if (tp_vars->recover >= recv_ack)
+ goto out;
+
+ /* if this is the third duplicate ACK do Fast Retransmit */
+--
+2.53.0
+
--- /dev/null
+From 7fb49577e3a6185990d92896981361aafe9a8093 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:49 +0200
+Subject: batman-adv: tp_meter: handle overlapping packets
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit cbde75c38b21f022891525078622587ad557b7c1 upstream.
+
+If the size of the packets would change during the transmission, it could
+happen that some retries of packets are overlapping. In this case, precise
+comparisons of sequence numbers by the receiver would be wrong. It is then
+necessary to check if the start sequence number to the end sequence number
+("seqno + length") would contain a new range.
+
+If this is the case then this is enough to accept this packet. In all other
+cases, the packet still has to be dropped (and not acked).
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+[ Switch to pre-splitted tp_vars structure names ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 25 +++++++++++--------------
+ 1 file changed, 11 insertions(+), 14 deletions(-)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index f745bc09a04963..50b83e0438a07b 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -1284,7 +1284,8 @@ static int batadv_tp_send_ack(struct batadv_priv *bat_priv, const u8 *dst,
+ /**
+ * batadv_tp_handle_out_of_order() - store an out of order packet
+ * @tp_vars: the private data of the current TP meter session
+- * @skb: the buffer containing the received packet
++ * @seqno: sequence number of new received packet
++ * @payload_len: length of the received packet
+ *
+ * Store the out of order packet in the unacked list for late processing. This
+ * packets are kept in this list so that they can be ACKed at once as soon as
+@@ -1293,22 +1294,17 @@ static int batadv_tp_send_ack(struct batadv_priv *bat_priv, const u8 *dst,
+ * Return: true if the packed has been successfully processed, false otherwise
+ */
+ static bool batadv_tp_handle_out_of_order(struct batadv_tp_vars *tp_vars,
+- const struct sk_buff *skb)
++ u32 seqno, u32 payload_len)
+ __must_hold(&tp_vars->unacked_lock)
+ {
+- const struct batadv_icmp_tp_packet *icmp;
+ struct batadv_tp_unacked *un, *new;
+- u32 payload_len;
+ bool added = false;
+
+ new = kmalloc(sizeof(*new), GFP_ATOMIC);
+ if (unlikely(!new))
+ return false;
+
+- icmp = (struct batadv_icmp_tp_packet *)skb->data;
+-
+- new->seqno = ntohl(icmp->seqno);
+- payload_len = skb->len - sizeof(struct batadv_unicast_packet);
++ new->seqno = seqno;
+ new->len = payload_len;
+
+ /* if the list is empty immediately attach this new object */
+@@ -1476,7 +1472,7 @@ static void batadv_tp_recv_msg(struct batadv_priv *bat_priv,
+ {
+ const struct batadv_icmp_tp_packet *icmp;
+ struct batadv_tp_vars *tp_vars;
+- size_t packet_size;
++ u32 payload_len;
+ u32 to_ack;
+ u32 seqno;
+
+@@ -1511,15 +1507,17 @@ static void batadv_tp_recv_msg(struct batadv_priv *bat_priv,
+ /* if the packet is a duplicate, it may be the case that an ACK has been
+ * lost. Resend the ACK
+ */
+- if (batadv_seq_before(seqno, tp_vars->last_recv))
++ payload_len = skb->len - sizeof(struct batadv_unicast_packet);
++ to_ack = seqno + payload_len;
++ if (batadv_seq_before(to_ack, tp_vars->last_recv))
+ goto send_ack;
+
+ /* if the packet is out of order enqueue it */
+- if (ntohl(icmp->seqno) != tp_vars->last_recv) {
++ if (batadv_seq_before(tp_vars->last_recv, seqno)) {
+ /* exit immediately (and do not send any ACK) if the packet has
+ * not been enqueued correctly
+ */
+- if (!batadv_tp_handle_out_of_order(tp_vars, skb)) {
++ if (!batadv_tp_handle_out_of_order(tp_vars, seqno, payload_len)) {
+ spin_unlock_bh(&tp_vars->unacked_lock);
+ goto out;
+ }
+@@ -1529,8 +1527,7 @@ static void batadv_tp_recv_msg(struct batadv_priv *bat_priv,
+ }
+
+ /* if everything was fine count the ACKed bytes */
+- packet_size = skb->len - sizeof(struct batadv_unicast_packet);
+- tp_vars->last_recv += packet_size;
++ tp_vars->last_recv = to_ack;
+
+ /* check if this ordered message filled a gap.... */
+ batadv_tp_ack_unordered(tp_vars);
+--
+2.53.0
+
--- /dev/null
+From d8d12e03670e6d4f0faebea9b5e67a5b0f156489 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:36 +0200
+Subject: batman-adv: tp_meter: handle seqno wrap-around for fast recovery
+ detection
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit f54c85ed42a1b27a516cf2a4728f5a612b799e07 upstream.
+
+The recover variable and the last_sent sequence number are initialized on
+purpose as a really high value which will wrap-around after the first 2000
+bytes. The fast recovery precondition must therefore not use simple integer
+comparisons but use helpers which are aware of the sequence number
+wrap-arounds.
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index c79352cfddc4ae..089ac7cc9fbebc 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -733,7 +733,7 @@ static void batadv_tp_recv_ack(struct batadv_priv *bat_priv,
+ if (atomic_read(&tp_vars->dup_acks) != 3)
+ goto out;
+
+- if (tp_vars->recover >= recv_ack)
++ if (!batadv_seq_before(tp_vars->recover, recv_ack))
+ goto out;
+
+ /* if this is the third duplicate ACK do Fast Retransmit */
+--
+2.53.0
+
--- /dev/null
+From 0c673441894cc4d6eb4e8abefd5f3d8e967fe8f1 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:32 +0200
+Subject: batman-adv: tp_meter: initialize dec_cwnd explicitly
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit febfb1b86224489535312296ecfa3d4bf467f339 upstream.
+
+When batadv_tp_update_cwnd() is called, dec_cwnd is increased. But dec_cwnd
+is only initialixed (to 0) when a duplicate Ack was received or when cwnd
+is below the ss_threshold.
+
+Just initialize the cwnd during the initialization to avoid any potential
+access of uninitialized data.
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index 750e5e9d3dc9d6..f6ccb639744a2a 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -1055,6 +1055,8 @@ void batadv_tp_start(struct batadv_priv *bat_priv, const u8 *dst,
+ * soft_interface, hence its MTU
+ */
+ tp_vars->cwnd = BATADV_TP_PLEN * 3;
++ tp_vars->dec_cwnd = 0;
++
+ /* at the beginning initialise the SS threshold to the biggest possible
+ * window size, hence the AWND size
+ */
+--
+2.53.0
+
--- /dev/null
+From 81f357d34f1f1b6bfa9fc4890a9e6810c16253d6 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:31 +0200
+Subject: batman-adv: tp_meter: initialize dup_acks explicitly
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit b2b68b32a715e0328662801576974aa37b942b00 upstream.
+
+When an ack with a sequence number equal to the last_acked is received, the
+dup_acks counter is increased to decide whether fast retransmit should be
+performed. Only when the sequence numbers are not equal, the dup_acks is
+set to the initial value (0).
+
+But if the initial packet would have the sequence number
+BATADV_TP_FIRST_SEQ, dup_acks would not be initialized and atomic_inc would
+operate on an undefined starting value. It is therefore required to have it
+explicitly initialized during the start of the sender session.
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index f350a60e6c76b9..750e5e9d3dc9d6 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -1045,6 +1045,7 @@ void batadv_tp_start(struct batadv_priv *bat_priv, const u8 *dst,
+ tp_vars->icmp_uid = icmp_uid;
+
+ tp_vars->last_sent = BATADV_TP_FIRST_SEQ;
++ atomic_set(&tp_vars->dup_acks, 0);
+ atomic_set(&tp_vars->last_acked, BATADV_TP_FIRST_SEQ);
+ tp_vars->fast_recovery = false;
+ tp_vars->recover = BATADV_TP_FIRST_SEQ;
+--
+2.53.0
+
--- /dev/null
+From 90d4994e77eb12756cf63db7a0e1f36a55b6b4b8 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:40 +0200
+Subject: batman-adv: tp_meter: initialize last_recv_time during init
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 811cb00fa8cdc3f0a7f6eefc000a6888367c8c8f upstream.
+
+The last_recv_time is the most important indicator for a receiver session
+to figure out whether a session timed out or not. But this information was
+only initialized after the session was added to the tp_receiver_list and
+after the timer was started.
+
+In the worst case, the timer (function) could have tried to access this
+information before the actual initialization was reached. Like rest of the
+variables of the tp_meter receiver session, this field has to be filled out
+before any other (parallel running) context has the chance to access it.
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+[ Context ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 10 +++++++---
+ 1 file changed, 7 insertions(+), 3 deletions(-)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index 2bba53fc6da5c0..133eed2fa9507f 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -1403,8 +1403,10 @@ batadv_tp_init_recv(struct batadv_priv *bat_priv,
+
+ tp_vars = batadv_tp_list_find_session(bat_priv, icmp->orig,
+ icmp->session, BATADV_TP_RECEIVER);
+- if (tp_vars)
++ if (tp_vars) {
++ tp_vars->last_recv_time = jiffies;
+ goto out_unlock;
++ }
+
+ if (!atomic_add_unless(&bat_priv->tp_num, 1, BATADV_TP_MAX_NUM)) {
+ batadv_dbg(BATADV_DBG_TP_METER, bat_priv,
+@@ -1432,6 +1434,8 @@ batadv_tp_init_recv(struct batadv_priv *bat_priv,
+ kref_get(&tp_vars->refcount);
+ timer_setup(&tp_vars->timer, batadv_tp_receiver_shutdown, 0);
+
++ tp_vars->last_recv_time = jiffies;
++
+ kref_get(&tp_vars->refcount);
+ hlist_add_head_rcu(&tp_vars->list, &bat_priv->tp_list);
+
+@@ -1480,9 +1484,9 @@ static void batadv_tp_recv_msg(struct batadv_priv *bat_priv,
+ icmp->orig);
+ goto out;
+ }
+- }
+
+- tp_vars->last_recv_time = jiffies;
++ tp_vars->last_recv_time = jiffies;
++ }
+
+ /* if the packet is a duplicate, it may be the case that an ACK has been
+ * lost. Resend the ACK
+--
+2.53.0
+
--- /dev/null
+From edebcce561dfc2f9b4a9612e33aad29c5357c0c0 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:30 +0200
+Subject: batman-adv: tp_meter: keep unacked list in ascending ordered
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 5aa8651527ea0b610e7a09fb3b8204c1398b9525 upstream.
+
+When batadv_tp_handle_out_of_order inserts a new entry in the list of
+unacked (out of order) packets, it searches from the entry with the newest
+sequence number towards oldest sequence number. If an entry is found which
+is older than the newly entry, the new entry has to be added after the
+found one to keep the ascending order.
+
+But for this operation list_add_tail() was used. But this function adds an
+entry _before_ another one. As result, the list would contain a lot of
+swapped sequence numbers. The consumer of this list
+(batadv_tp_ack_unordered()) would then fail to correctly ack packets.
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index dfc3374549921f..f350a60e6c76b9 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -1325,7 +1325,7 @@ static bool batadv_tp_handle_out_of_order(struct batadv_tp_vars *tp_vars,
+ * one is attached _after_ it. In this way the list is kept in
+ * ascending order
+ */
+- list_add_tail(&new->list, &un->list);
++ list_add(&new->list, &un->list);
+ added = true;
+ break;
+ }
+--
+2.53.0
+
--- /dev/null
+From 3c8832b3656c8a73dce73dfab0e0efca46919922 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:48 +0200
+Subject: batman-adv: tp_meter: prevent parallel modifications of last_recv
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 6dde0cfcb36e4d5b3de35b75696937478441eed4 upstream.
+
+When last_recv is updated to store the last receive sequence number, it is
+assuming that nothing is modifying in parallel while:
+
+* check for outdated packets is done
+* out of order check is performed (and packets are stored in out-of-order
+ queue)
+* the out-of-order queue was searched for closed gaps
+* sequence number for next ack is calculated
+
+Nothing of that was actually protected. It could therefore happen that the
+last_recv was updated multiple times in parallel and the final sequence
+number was calculated with deltas which had no connection to the sequence
+number they were added to.
+
+Lock this whole region with the same lock which was already used to protect
+the unacked (out-of-order) list.
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+[ Switch to pre-splitted tp_vars structure names ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 22 +++++++++++++---------
+ net/batman-adv/types.h | 2 +-
+ 2 files changed, 14 insertions(+), 10 deletions(-)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index b882919a868ca0..f745bc09a04963 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -1294,6 +1294,7 @@ static int batadv_tp_send_ack(struct batadv_priv *bat_priv, const u8 *dst,
+ */
+ static bool batadv_tp_handle_out_of_order(struct batadv_tp_vars *tp_vars,
+ const struct sk_buff *skb)
++ __must_hold(&tp_vars->unacked_lock)
+ {
+ const struct batadv_icmp_tp_packet *icmp;
+ struct batadv_tp_unacked *un, *new;
+@@ -1310,12 +1311,11 @@ static bool batadv_tp_handle_out_of_order(struct batadv_tp_vars *tp_vars,
+ payload_len = skb->len - sizeof(struct batadv_unicast_packet);
+ new->len = payload_len;
+
+- spin_lock_bh(&tp_vars->unacked_lock);
+ /* if the list is empty immediately attach this new object */
+ if (list_empty(&tp_vars->unacked_list)) {
+ list_add(&new->list, &tp_vars->unacked_list);
+ tp_vars->unacked_count++;
+- goto out;
++ return true;
+ }
+
+ /* otherwise loop over the list and either drop the packet because this
+@@ -1364,9 +1364,6 @@ static bool batadv_tp_handle_out_of_order(struct batadv_tp_vars *tp_vars,
+ tp_vars->unacked_count--;
+ }
+
+-out:
+- spin_unlock_bh(&tp_vars->unacked_lock);
+-
+ return true;
+ }
+
+@@ -1376,6 +1373,7 @@ static bool batadv_tp_handle_out_of_order(struct batadv_tp_vars *tp_vars,
+ * @tp_vars: the private data of the current TP meter session
+ */
+ static void batadv_tp_ack_unordered(struct batadv_tp_vars *tp_vars)
++ __must_hold(&tp_vars->unacked_lock)
+ {
+ struct batadv_tp_unacked *un, *safe;
+ u32 to_ack;
+@@ -1383,7 +1381,6 @@ static void batadv_tp_ack_unordered(struct batadv_tp_vars *tp_vars)
+ /* go through the unacked packet list and possibly ACK them as
+ * well
+ */
+- spin_lock_bh(&tp_vars->unacked_lock);
+ list_for_each_entry_safe(un, safe, &tp_vars->unacked_list, list) {
+ /* the list is ordered, therefore it is possible to stop as soon
+ * there is a gap between the last acked seqno and the seqno of
+@@ -1401,7 +1398,6 @@ static void batadv_tp_ack_unordered(struct batadv_tp_vars *tp_vars)
+ kfree(un);
+ tp_vars->unacked_count--;
+ }
+- spin_unlock_bh(&tp_vars->unacked_lock);
+ }
+
+ /**
+@@ -1481,6 +1477,7 @@ static void batadv_tp_recv_msg(struct batadv_priv *bat_priv,
+ const struct batadv_icmp_tp_packet *icmp;
+ struct batadv_tp_vars *tp_vars;
+ size_t packet_size;
++ u32 to_ack;
+ u32 seqno;
+
+ icmp = (struct batadv_icmp_tp_packet *)skb->data;
+@@ -1509,6 +1506,8 @@ static void batadv_tp_recv_msg(struct batadv_priv *bat_priv,
+ WRITE_ONCE(tp_vars->last_recv_time, jiffies);
+ }
+
++ spin_lock_bh(&tp_vars->unacked_lock);
++
+ /* if the packet is a duplicate, it may be the case that an ACK has been
+ * lost. Resend the ACK
+ */
+@@ -1520,8 +1519,10 @@ static void batadv_tp_recv_msg(struct batadv_priv *bat_priv,
+ /* exit immediately (and do not send any ACK) if the packet has
+ * not been enqueued correctly
+ */
+- if (!batadv_tp_handle_out_of_order(tp_vars, skb))
++ if (!batadv_tp_handle_out_of_order(tp_vars, skb)) {
++ spin_unlock_bh(&tp_vars->unacked_lock);
+ goto out;
++ }
+
+ /* send a duplicate ACK */
+ goto send_ack;
+@@ -1535,11 +1536,14 @@ static void batadv_tp_recv_msg(struct batadv_priv *bat_priv,
+ batadv_tp_ack_unordered(tp_vars);
+
+ send_ack:
++ to_ack = tp_vars->last_recv;
++ spin_unlock_bh(&tp_vars->unacked_lock);
++
+ /* send the ACK. If the received packet was out of order, the ACK that
+ * is going to be sent is a duplicate (the sender will count them and
+ * possibly enter Fast Retransmit as soon as it has reached 3)
+ */
+- batadv_tp_send_ack(bat_priv, icmp->orig, tp_vars->last_recv,
++ batadv_tp_send_ack(bat_priv, icmp->orig, to_ack,
+ icmp->timestamp, icmp->session, icmp->uid);
+ out:
+ batadv_tp_vars_put(tp_vars);
+diff --git a/net/batman-adv/types.h b/net/batman-adv/types.h
+index e8679a4cd0413b..01c8fef027b419 100644
+--- a/net/batman-adv/types.h
++++ b/net/batman-adv/types.h
+@@ -1561,7 +1561,7 @@ struct batadv_tp_vars {
+ /** @unacked_list: list of unacked packets (meta-info only) */
+ struct list_head unacked_list;
+
+- /** @unacked_lock: protect unacked_list */
++ /** @unacked_lock: protect unacked_list + &batadv_tp_receiver.last_recv */
+ spinlock_t unacked_lock;
+
+ /** @unacked_count: number of unacked entries */
+--
+2.53.0
+
--- /dev/null
+From 6e797b12224f160f8c7c12efdbd3496da814d51a Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:46 +0200
+Subject: batman-adv: tp_meter: restrict number of unacked list entries
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit e7c775110e1858e5a7471a23a9c9658c0af9df89 upstream.
+
+When the unacked_list is unbound, an attacker could send messages with
+small lengths and appropriated seqno + gaps to force the receiver to
+allocate more and more unacked_list entries. And the end either causing an
+out-of-memory situation or increase the management overhead for the (large)
+list that significant portions of CPU cycles are wasted in searching
+through the list.
+
+When limiting the list to a specific number, it is important to still
+correctly add a new entry to the list. But if the list became larger than
+the limit, the last entry of the list (with the highest seqno) must be
+dropped to still allow the earlier seqnos to finish and therefore to
+continue the process. Otherwise, the process might get stuck with too high
+seqnos which are not handled by batadv_tp_ack_unordered().
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+[ Switch to pre-splitted tp_vars structure names ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 23 ++++++++++++++++++++++-
+ net/batman-adv/types.h | 3 +++
+ 2 files changed, 25 insertions(+), 1 deletion(-)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index 133eed2fa9507f..638dd438a6c052 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -87,6 +87,11 @@
+ #define BATADV_TP_PLEN (BATADV_TP_PACKET_LEN - ETH_HLEN - \
+ sizeof(struct batadv_unicast_packet))
+
++/**
++ * BATADV_TP_MAX_UNACKED - maximum number of packets a receiver didn't yet ack
++ */
++#define BATADV_TP_MAX_UNACKED 100
++
+ static u8 batadv_tp_prerandom[4096] __read_mostly;
+
+ /**
+@@ -1195,6 +1200,7 @@ static void batadv_tp_receiver_shutdown(struct timer_list *t)
+ list_for_each_entry_safe(un, safe, &tp_vars->unacked_list, list) {
+ list_del(&un->list);
+ kfree(un);
++ tp_vars->unacked_count--;
+ }
+ spin_unlock_bh(&tp_vars->unacked_lock);
+
+@@ -1308,6 +1314,7 @@ static bool batadv_tp_handle_out_of_order(struct batadv_tp_vars *tp_vars,
+ /* if the list is empty immediately attach this new object */
+ if (list_empty(&tp_vars->unacked_list)) {
+ list_add(&new->list, &tp_vars->unacked_list);
++ tp_vars->unacked_count++;
+ goto out;
+ }
+
+@@ -1338,12 +1345,24 @@ static bool batadv_tp_handle_out_of_order(struct batadv_tp_vars *tp_vars,
+ */
+ list_add(&new->list, &un->list);
+ added = true;
++ tp_vars->unacked_count++;
+ break;
+ }
+
+ /* received packet with smallest seqno out of order; add it to front */
+- if (!added)
++ if (!added) {
+ list_add(&new->list, &tp_vars->unacked_list);
++ tp_vars->unacked_count++;
++ }
++
++ /* remove the last (biggest) unacked seqno when list is too large */
++ if (tp_vars->unacked_count > BATADV_TP_MAX_UNACKED) {
++ un = list_last_entry(&tp_vars->unacked_list,
++ struct batadv_tp_unacked, list);
++ list_del(&un->list);
++ kfree(un);
++ tp_vars->unacked_count--;
++ }
+
+ out:
+ spin_unlock_bh(&tp_vars->unacked_lock);
+@@ -1380,6 +1399,7 @@ static void batadv_tp_ack_unordered(struct batadv_tp_vars *tp_vars)
+
+ list_del(&un->list);
+ kfree(un);
++ tp_vars->unacked_count--;
+ }
+ spin_unlock_bh(&tp_vars->unacked_lock);
+ }
+@@ -1430,6 +1450,7 @@ batadv_tp_init_recv(struct batadv_priv *bat_priv,
+
+ spin_lock_init(&tp_vars->unacked_lock);
+ INIT_LIST_HEAD(&tp_vars->unacked_list);
++ tp_vars->unacked_count = 0;
+
+ kref_get(&tp_vars->refcount);
+ timer_setup(&tp_vars->timer, batadv_tp_receiver_shutdown, 0);
+diff --git a/net/batman-adv/types.h b/net/batman-adv/types.h
+index 3b2a170b1cf18b..e8679a4cd0413b 100644
+--- a/net/batman-adv/types.h
++++ b/net/batman-adv/types.h
+@@ -1564,6 +1564,9 @@ struct batadv_tp_vars {
+ /** @unacked_lock: protect unacked_list */
+ spinlock_t unacked_lock;
+
++ /** @unacked_count: number of unacked entries */
++ size_t unacked_count;
++
+ /** @last_recv_time: time (jiffies) a msg was received */
+ unsigned long last_recv_time;
+
+--
+2.53.0
+
--- /dev/null
+From 2519bd1f1bfc3eaabb26731f985da62aa288fbd6 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:50 +0200
+Subject: batman-adv: tt: don't merge change entries with different VIDs
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit f08e06c2d5c3e2434e7c773f2213f4a7dce6bc1e upstream.
+
+batadv_tt_local_event() merges/cancels events for the same client which
+would conflict or be duplicates. The matching of the queued events only
+compares the MAC address - the VLAN ID stored in each event is ignored.
+
+If a MAC would now appear on multiple VID, the two ADD change events (for
+VID 1 and VID 2) would be merged to a single vid event. The remote can
+therefore not calculate the correct TT table and desync. A full translation
+table exchange is required to recover from this state.
+
+A check of VID is therefore necessary to avoid such wrong merges/cancels.
+
+Cc: stable@kernel.org
+Fixes: c018ad3de61a ("batman-adv: add the VLAN ID attribute to the TT entry")
+[ Context ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/translation-table.c | 3 +++
+ 1 file changed, 3 insertions(+)
+
+diff --git a/net/batman-adv/translation-table.c b/net/batman-adv/translation-table.c
+index 7041cd69e20070..69a42bc3fa02e6 100644
+--- a/net/batman-adv/translation-table.c
++++ b/net/batman-adv/translation-table.c
+@@ -485,6 +485,9 @@ static void batadv_tt_local_event(struct batadv_priv *bat_priv,
+ if (!batadv_compare_eth(entry->change.addr, common->addr))
+ continue;
+
++ if (entry->change.vid != tt_change_node->change.vid)
++ continue;
++
+ /* DEL+ADD in the same orig interval have no effect and can be
+ * removed to avoid silly behaviour on the receiver side. The
+ * other way around (ADD+DEL) can happen in case of roaming of
+--
+2.53.0
+
--- /dev/null
+From 7035db422b280d1700bcc15770570cfab1299765 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:51 +0200
+Subject: batman-adv: tt: track roam count per VID
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 12407d5f61c2653a64f2ff4b22f3c267f8420ef1 upstream.
+
+batadv_tt_check_roam_count() is supposed to track roaming of a TT entry.
+But TT entries are for a MAC + VID. The VID was completely missed and thus
+leads to incorrect detection of ROAM counts when a client MAC exists in
+multiple VLANs.
+
+Cc: stable@kernel.org
+Fixes: c018ad3de61a ("batman-adv: add the VLAN ID attribute to the TT entry")
+[ Context ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/translation-table.c | 9 +++++++--
+ net/batman-adv/types.h | 3 +++
+ 2 files changed, 10 insertions(+), 2 deletions(-)
+
+diff --git a/net/batman-adv/translation-table.c b/net/batman-adv/translation-table.c
+index 69a42bc3fa02e6..2a7caab5c197ea 100644
+--- a/net/batman-adv/translation-table.c
++++ b/net/batman-adv/translation-table.c
+@@ -3517,6 +3517,7 @@ static void batadv_tt_roam_purge(struct batadv_priv *bat_priv)
+ * batadv_tt_check_roam_count() - check if a client has roamed too frequently
+ * @bat_priv: the bat priv with all the soft interface information
+ * @client: mac address of the roaming client
++ * @vid: VLAN identifier
+ *
+ * This function checks whether the client already reached the
+ * maximum number of possible roaming phases. In this case the ROAMING_ADV
+@@ -3524,7 +3525,7 @@ static void batadv_tt_roam_purge(struct batadv_priv *bat_priv)
+ *
+ * Return: true if the ROAMING_ADV can be sent, false otherwise
+ */
+-static bool batadv_tt_check_roam_count(struct batadv_priv *bat_priv, u8 *client)
++static bool batadv_tt_check_roam_count(struct batadv_priv *bat_priv, u8 *client, u16 vid)
+ {
+ struct batadv_tt_roam_node *tt_roam_node;
+ bool ret = false;
+@@ -3537,6 +3538,9 @@ static bool batadv_tt_check_roam_count(struct batadv_priv *bat_priv, u8 *client)
+ if (!batadv_compare_eth(tt_roam_node->addr, client))
+ continue;
+
++ if (tt_roam_node->vid != vid)
++ continue;
++
+ if (batadv_has_timed_out(tt_roam_node->first_time,
+ BATADV_ROAMING_MAX_TIME))
+ continue;
+@@ -3558,6 +3562,7 @@ static bool batadv_tt_check_roam_count(struct batadv_priv *bat_priv, u8 *client)
+ atomic_set(&tt_roam_node->counter,
+ BATADV_ROAMING_MAX_COUNT - 1);
+ ether_addr_copy(tt_roam_node->addr, client);
++ tt_roam_node->vid = vid;
+
+ list_add(&tt_roam_node->list, &bat_priv->tt.roam_list);
+ ret = true;
+@@ -3594,7 +3599,7 @@ static void batadv_send_roam_adv(struct batadv_priv *bat_priv, u8 *client,
+ /* before going on we have to check whether the client has
+ * already roamed to us too many times
+ */
+- if (!batadv_tt_check_roam_count(bat_priv, client))
++ if (!batadv_tt_check_roam_count(bat_priv, client, vid))
+ goto out;
+
+ batadv_dbg(BATADV_DBG_TT, bat_priv,
+diff --git a/net/batman-adv/types.h b/net/batman-adv/types.h
+index 01c8fef027b419..9bde0469e748ce 100644
+--- a/net/batman-adv/types.h
++++ b/net/batman-adv/types.h
+@@ -2065,6 +2065,9 @@ struct batadv_tt_roam_node {
+ /** @addr: mac address of the client in the roaming phase */
+ u8 addr[ETH_ALEN];
+
++ /** @vid: VLAN identifier */
++ u16 vid;
++
+ /**
+ * @counter: number of allowed roaming events per client within a single
+ * OGM interval (changes are committed with each OGM)
+--
+2.53.0
+
--- /dev/null
+From 4c23e693dd8c390dad2284158f8665baf46e51a1 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:54 +0200
+Subject: batman-adv: tvlv: avoid race of cifsnotfound handler state
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit edb557b2ba38fea2c5eb710cf366c797e187218c upstream.
+
+TVLV handlers can have the flag BATADV_TVLV_HANDLER_OGM_CIFNOTFND set to
+signal that the OGM handler should be called (with NULL for data) when the
+specific TVLV container was not found in the OGM. This is used by:
+
+* DAT
+* GW
+* Multicast (OGM + Tracker)
+
+The state whether the handler was executed was stored in the struct
+batadv_tvlv_handler. But the TVLV processing is started without any lock.
+Multiple parallel contexts processing TVLVs would therefore overwrite each
+others BATADV_TVLV_HANDLER_OGM_CALLED flag in the shared
+batadv_tvlv_handler.
+
+Drop the shared BATADV_TVLV_HANDLER_OGM_CALLED flag and instead determine,
+per TVLV buffer, whether a matching container was present by scanning the
+packet's buffer.
+
+Cc: stable@kernel.org
+Fixes: ef26157747d4 ("batman-adv: tvlv - basic infrastructure")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tvlv.c | 63 ++++++++++++++++++++++++++++++++++++++----
+ net/batman-adv/types.h | 7 -----
+ 2 files changed, 57 insertions(+), 13 deletions(-)
+
+diff --git a/net/batman-adv/tvlv.c b/net/batman-adv/tvlv.c
+index e1cd27b99bd119..cc66e231ccb8a0 100644
+--- a/net/batman-adv/tvlv.c
++++ b/net/batman-adv/tvlv.c
+@@ -398,7 +398,6 @@ static int batadv_tvlv_call_handler(struct batadv_priv *bat_priv,
+ tvlv_handler->ogm_handler(bat_priv, orig_node,
+ BATADV_NO_FLAGS,
+ tvlv_value, tvlv_value_len);
+- tvlv_handler->flags |= BATADV_TVLV_HANDLER_OGM_CALLED;
+ break;
+ case BATADV_UNICAST_TVLV:
+ if (!skb)
+@@ -430,6 +429,48 @@ static int batadv_tvlv_call_handler(struct batadv_priv *bat_priv,
+ return NET_RX_SUCCESS;
+ }
+
++/**
++ * batadv_tvlv_containers_contain() - check if a tvlv buffer holds a container
++ * @tvlv_value: tvlv content
++ * @tvlv_value_len: tvlv content length
++ * @type: tvlv container type to look for
++ * @version: tvlv container version to look for
++ *
++ * Return: true if a container of the given type and version is present in the
++ * tvlv buffer, false otherwise.
++ */
++static bool batadv_tvlv_containers_contain(void *tvlv_value,
++ u16 tvlv_value_len, u8 type,
++ u8 version)
++{
++ struct batadv_tvlv_hdr *tvlv_hdr;
++ u16 tvlv_value_cont_len;
++
++ while (tvlv_value_len >= sizeof(*tvlv_hdr)) {
++ tvlv_hdr = tvlv_value;
++ tvlv_value_cont_len = ntohs(tvlv_hdr->len);
++ tvlv_value = tvlv_hdr + 1;
++ tvlv_value_len -= sizeof(*tvlv_hdr);
++
++ if (tvlv_value_cont_len > tvlv_value_len)
++ break;
++
++ /* the next tvlv header is accessed assuming (at least) 2-byte
++ * alignment, so it must start at an even offset.
++ */
++ if (tvlv_value_cont_len & 1)
++ break;
++
++ if (tvlv_hdr->type == type && tvlv_hdr->version == version)
++ return true;
++
++ tvlv_value = (u8 *)tvlv_value + tvlv_value_cont_len;
++ tvlv_value_len -= tvlv_value_cont_len;
++ }
++
++ return false;
++}
++
+ /**
+ * batadv_tvlv_containers_process() - parse the given tvlv buffer to call the
+ * appropriate handlers
+@@ -449,7 +490,9 @@ int batadv_tvlv_containers_process(struct batadv_priv *bat_priv,
+ struct sk_buff *skb, void *tvlv_value,
+ u16 tvlv_value_len)
+ {
++ u16 tvlv_value_start_len = tvlv_value_len;
+ struct batadv_tvlv_handler *tvlv_handler;
++ void *tvlv_value_start = tvlv_value;
+ struct batadv_tvlv_hdr *tvlv_hdr;
+ u16 tvlv_value_cont_len;
+ u8 cifnotfound = BATADV_TVLV_HANDLER_OGM_CIFNOTFND;
+@@ -493,12 +536,20 @@ int batadv_tvlv_containers_process(struct batadv_priv *bat_priv,
+ if (!tvlv_handler->ogm_handler)
+ continue;
+
+- if ((tvlv_handler->flags & BATADV_TVLV_HANDLER_OGM_CIFNOTFND) &&
+- !(tvlv_handler->flags & BATADV_TVLV_HANDLER_OGM_CALLED))
+- tvlv_handler->ogm_handler(bat_priv, orig_node,
+- cifnotfound, NULL, 0);
++ if (!(tvlv_handler->flags & BATADV_TVLV_HANDLER_OGM_CIFNOTFND))
++ continue;
+
+- tvlv_handler->flags &= ~BATADV_TVLV_HANDLER_OGM_CALLED;
++ /* if the corresponding container was present then the handler
++ * was already called from the loop above
++ */
++ if (batadv_tvlv_containers_contain(tvlv_value_start,
++ tvlv_value_start_len,
++ tvlv_handler->type,
++ tvlv_handler->version))
++ continue;
++
++ tvlv_handler->ogm_handler(bat_priv, orig_node,
++ cifnotfound, NULL, 0);
+ }
+ rcu_read_unlock();
+
+diff --git a/net/batman-adv/types.h b/net/batman-adv/types.h
+index 9bde0469e748ce..0022bee14574df 100644
+--- a/net/batman-adv/types.h
++++ b/net/batman-adv/types.h
+@@ -2483,13 +2483,6 @@ enum batadv_tvlv_handler_flags {
+ * will call this handler even if its type was not found (with no data)
+ */
+ BATADV_TVLV_HANDLER_OGM_CIFNOTFND = BIT(1),
+-
+- /**
+- * @BATADV_TVLV_HANDLER_OGM_CALLED: interval tvlv handling flag - the
+- * API marks a handler as being called, so it won't be called if the
+- * BATADV_TVLV_HANDLER_OGM_CIFNOTFND flag was set
+- */
+- BATADV_TVLV_HANDLER_OGM_CALLED = BIT(2),
+ };
+
+ #endif /* _NET_BATMAN_ADV_TYPES_H_ */
+--
+2.53.0
+
--- /dev/null
+From 9ec598250ee85db73f5438bf7996e4a66a21f2ee Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:53 +0200
+Subject: batman-adv: tvlv: enforce 2-byte alignment
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 32a6799255525d6ea4da0f7e9e0e521ad9560a46 upstream.
+
+The fields of an aggregated OGM(v2) are accessed assuming (at least) 2-byte
+alignment, so a following OGM must start at an even offset. As the header
+length is even, an odd tvlv_len would misalign it and trigger unaligned
+accesses on strict-alignment architectures.
+
+Such a misaligned TVLV/OGM/OGMv2 is not created by a normal participant in
+the mesh. Therefore, reject such malformed packets.
+
+Cc: stable@kernel.org
+Fixes: ef26157747d4 ("batman-adv: tvlv - basic infrastructure")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/bat_iv_ogm.c | 11 ++++++++++-
+ net/batman-adv/bat_v_ogm.c | 11 ++++++++++-
+ net/batman-adv/routing.c | 6 ++++++
+ net/batman-adv/tvlv.c | 6 ++++++
+ 4 files changed, 32 insertions(+), 2 deletions(-)
+
+diff --git a/net/batman-adv/bat_iv_ogm.c b/net/batman-adv/bat_iv_ogm.c
+index b37c9fb178ae50..5dd3e1f281bab2 100644
+--- a/net/batman-adv/bat_iv_ogm.c
++++ b/net/batman-adv/bat_iv_ogm.c
+@@ -310,14 +310,23 @@ batadv_iv_ogm_aggr_packet(int buff_pos, int packet_len,
+ const struct batadv_ogm_packet *ogm_packet)
+ {
+ int next_buff_pos = 0;
++ u16 tvlv_len;
+
+ /* check if there is enough space for the header */
+ next_buff_pos += buff_pos + sizeof(*ogm_packet);
+ if (next_buff_pos > packet_len)
+ return false;
+
++ tvlv_len = ntohs(ogm_packet->tvlv_len);
++
++ /* the fields of an aggregated OGM are accessed assuming (at least)
++ * 2-byte alignment, so a following OGM must start at an even offset.
++ */
++ if (tvlv_len & 1)
++ return false;
++
+ /* check if there is enough space for the optional TVLV */
+- next_buff_pos += ntohs(ogm_packet->tvlv_len);
++ next_buff_pos += tvlv_len;
+
+ return next_buff_pos <= packet_len;
+ }
+diff --git a/net/batman-adv/bat_v_ogm.c b/net/batman-adv/bat_v_ogm.c
+index 48a67705eba85c..c5c4d33cb19838 100644
+--- a/net/batman-adv/bat_v_ogm.c
++++ b/net/batman-adv/bat_v_ogm.c
+@@ -853,14 +853,23 @@ batadv_v_ogm_aggr_packet(int buff_pos, int packet_len,
+ const struct batadv_ogm2_packet *ogm2_packet)
+ {
+ int next_buff_pos = 0;
++ u16 tvlv_len;
+
+ /* check if there is enough space for the header */
+ next_buff_pos += buff_pos + sizeof(*ogm2_packet);
+ if (next_buff_pos > packet_len)
+ return false;
+
++ tvlv_len = ntohs(ogm2_packet->tvlv_len);
++
++ /* the fields of an aggregated OGMv2 are accessed assuming (at least)
++ * 2-byte alignment, so a following OGMv2 must start at an even offset.
++ */
++ if (tvlv_len & 1)
++ return false;
++
+ /* check if there is enough space for the optional TVLV */
+- next_buff_pos += ntohs(ogm2_packet->tvlv_len);
++ next_buff_pos += tvlv_len;
+
+ return next_buff_pos <= packet_len;
+ }
+diff --git a/net/batman-adv/routing.c b/net/batman-adv/routing.c
+index 6fd18b69e5f6be..f828d8143f7526 100644
+--- a/net/batman-adv/routing.c
++++ b/net/batman-adv/routing.c
+@@ -1373,6 +1373,12 @@ int batadv_recv_mcast_packet(struct sk_buff *skb,
+ if (tvlv_buff_len > skb->len - hdr_size)
+ goto free_skb;
+
++ /* the fields of an multicast payload are accessed assuming (at least)
++ * 2-byte alignment, so a following packet must start at an even offset.
++ */
++ if (tvlv_buff_len & 1)
++ goto free_skb;
++
+ ret = batadv_tvlv_containers_process(bat_priv, BATADV_MCAST, NULL, skb,
+ tvlv_buff, tvlv_buff_len);
+ if (ret >= 0) {
+diff --git a/net/batman-adv/tvlv.c b/net/batman-adv/tvlv.c
+index 8d6b017c433cc9..e1cd27b99bd119 100644
+--- a/net/batman-adv/tvlv.c
++++ b/net/batman-adv/tvlv.c
+@@ -464,6 +464,12 @@ int batadv_tvlv_containers_process(struct batadv_priv *bat_priv,
+ if (tvlv_value_cont_len > tvlv_value_len)
+ break;
+
++ /* the next tvlv header is accessed assuming (at least) 2-byte
++ * alignment, so it must start at an even offset.
++ */
++ if (tvlv_value_cont_len & 1)
++ break;
++
+ tvlv_handler = batadv_tvlv_handler_get(bat_priv,
+ tvlv_hdr->type,
+ tvlv_hdr->version);
+--
+2.53.0
+
--- /dev/null
+From 372ba31c4696617a1a6084e4468b7ef253996048 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:45 +0200
+Subject: batman-adv: v: prevent OGM aggregation on disabled hardif
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit d11c00b95b2a3b3934007fc003dccc6fdcc061ad upstream.
+
+When an interface gets disabled, the worker is correctly disabled by
+batadv_hardif_disable_interface() -> ... -> batadv_v_ogm_iface_disable().
+In this process, the skb aggr_list is also freed.
+
+But batadv_v_ogm_send_meshif() can still queue new skbs (via
+batadv_v_ogm_queue_on_if()) to the aggr_list. This will only stop after all
+cores can no longer find the RCU protected list of hard interfaces. These
+queued skbs will never be freed or consumed by batadv_v_ogm_aggr_work.
+
+The batadv_v_ogm_iface_disable() function must block
+batadv_v_ogm_queue_on_if() to avoid leak of skbs.
+
+Cc: stable@kernel.org
+Fixes: f89255a02f1d ("batman-adv: BATMAN_V: introduce per hard-iface OGMv2 queues")
+[ Context ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/bat_v.c | 1 +
+ net/batman-adv/bat_v_ogm.c | 12 ++++++++++++
+ net/batman-adv/types.h | 6 ++++++
+ 3 files changed, 19 insertions(+)
+
+diff --git a/net/batman-adv/bat_v.c b/net/batman-adv/bat_v.c
+index d35479c465e2c4..e13b39121f2db8 100644
+--- a/net/batman-adv/bat_v.c
++++ b/net/batman-adv/bat_v.c
+@@ -819,6 +819,7 @@ void batadv_v_hardif_init(struct batadv_hard_iface *hard_iface)
+
+ hard_iface->bat_v.aggr_len = 0;
+ skb_queue_head_init(&hard_iface->bat_v.aggr_list);
++ hard_iface->bat_v.aggr_list_enabled = false;
+ INIT_DELAYED_WORK(&hard_iface->bat_v.aggr_wq,
+ batadv_v_ogm_aggr_work);
+ }
+diff --git a/net/batman-adv/bat_v_ogm.c b/net/batman-adv/bat_v_ogm.c
+index 8cfc3944dcfd52..48a67705eba85c 100644
+--- a/net/batman-adv/bat_v_ogm.c
++++ b/net/batman-adv/bat_v_ogm.c
+@@ -254,11 +254,18 @@ static void batadv_v_ogm_queue_on_if(struct batadv_priv *bat_priv,
+ }
+
+ spin_lock_bh(&hard_iface->bat_v.aggr_list.lock);
++ if (!hard_iface->bat_v.aggr_list_enabled) {
++ kfree_skb(skb);
++ goto unlock;
++ }
++
+ if (!batadv_v_ogm_queue_left(skb, hard_iface))
+ batadv_v_ogm_aggr_send(bat_priv, hard_iface);
+
+ hard_iface->bat_v.aggr_len += batadv_v_ogm_len(skb);
+ __skb_queue_tail(&hard_iface->bat_v.aggr_list, skb);
++
++unlock:
+ spin_unlock_bh(&hard_iface->bat_v.aggr_list.lock);
+ }
+
+@@ -421,6 +428,10 @@ int batadv_v_ogm_iface_enable(struct batadv_hard_iface *hard_iface)
+ {
+ struct batadv_priv *bat_priv = netdev_priv(hard_iface->soft_iface);
+
++ spin_lock_bh(&hard_iface->bat_v.aggr_list.lock);
++ hard_iface->bat_v.aggr_list_enabled = true;
++ spin_unlock_bh(&hard_iface->bat_v.aggr_list.lock);
++
+ batadv_v_ogm_start_queue_timer(hard_iface);
+ batadv_v_ogm_start_timer(bat_priv);
+
+@@ -436,6 +447,7 @@ void batadv_v_ogm_iface_disable(struct batadv_hard_iface *hard_iface)
+ cancel_delayed_work_sync(&hard_iface->bat_v.aggr_wq);
+
+ spin_lock_bh(&hard_iface->bat_v.aggr_list.lock);
++ hard_iface->bat_v.aggr_list_enabled = false;
+ batadv_v_ogm_aggr_list_free(hard_iface);
+ spin_unlock_bh(&hard_iface->bat_v.aggr_list.lock);
+ }
+diff --git a/net/batman-adv/types.h b/net/batman-adv/types.h
+index f703d266780d74..3b2a170b1cf18b 100644
+--- a/net/batman-adv/types.h
++++ b/net/batman-adv/types.h
+@@ -130,6 +130,12 @@ struct batadv_hard_iface_bat_v {
+ /** @aggr_list: queue for to be aggregated OGM packets */
+ struct sk_buff_head aggr_list;
+
++ /**
++ * @aggr_list_enabled: aggr_list is active and new skbs can be
++ * enqueued. Protected by aggr_list.lock after initialization
++ */
++ bool aggr_list_enabled:1;
++
+ /** @aggr_len: size of the OGM aggregate (excluding ethernet header) */
+ unsigned int aggr_len;
+
+--
+2.53.0
+
kvm-sev-ignore-mmio-requests-of-length-0.patch
kvm-sev-reject-mmio-requests-larger-than-8-bytes-wit.patch
kvm-sev-ignore-port-i-o-requests-of-length-0.patch
+batman-adv-tp_meter-keep-unacked-list-in-ascending-o.patch
+batman-adv-tp_meter-initialize-dup_acks-explicitly.patch
+batman-adv-tp_meter-initialize-dec_cwnd-explicitly.patch
+batman-adv-tp_meter-avoid-window-underflow.patch
+batman-adv-tp_meter-avoid-divide-by-zero-for-dec_cwn.patch
+batman-adv-tp_meter-fix-fast-recovery-precondition.patch
+batman-adv-tp_meter-handle-seqno-wrap-around-for-fas.patch
+batman-adv-tp_meter-add-only-finished-tp_vars-to-lis.patch
+batman-adv-bla-annotate-lasttime-access-with-read-wr.patch
+batman-adv-prevent-elp-transmission-interval-underfl.patch
+batman-adv-tp_meter-initialize-last_recv_time-during.patch
+batman-adv-ensure-bcast-is-writable-before-modifying.patch
+batman-adv-fix-m-b-cast-csum-after-decrementing-ttl.patch
+batman-adv-frag-ensure-fragment-is-writable-before-m.patch
+batman-adv-frag-avoid-underflow-of-ttl.patch
+batman-adv-v-prevent-ogm-aggregation-on-disabled-har.patch
+batman-adv-tp_meter-restrict-number-of-unacked-list-.patch
+batman-adv-tp_meter-annotate-last_recv_time-access-w.patch
+batman-adv-tp_meter-prevent-parallel-modifications-o.patch
+batman-adv-tp_meter-handle-overlapping-packets.patch
+batman-adv-tt-don-t-merge-change-entries-with-differ.patch
+batman-adv-tt-track-roam-count-per-vid.patch
+batman-adv-dat-prevent-false-sharing-between-vlans.patch
+batman-adv-tvlv-enforce-2-byte-alignment.patch
+batman-adv-tvlv-avoid-race-of-cifsnotfound-handler-s.patch
--- /dev/null
+From 202878d4663336141898937177592bfccab146f2 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:53 +0200
+Subject: batman-adv: bla: annotate lasttime access with READ/WRITE_ONCE
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 98b0fb191c878a64cbaebfe231d96d57576acf8c upstream.
+
+The lasttime field for claim, backbone_gw, and loopdetect tracks the
+jiffies value of the most recent activity and is used to detect timeouts.
+These accesses are not consistently protected by a lock, so
+READ_ONCE/WRITE_ONCE must be used to prevent data races caused by compiler
+optimizations.
+
+Cc: stable@kernel.org
+Fixes: 23721387c409 ("batman-adv: add basic bridge loop avoidance code")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/bridge_loop_avoidance.c | 28 +++++++++++++-------------
+ 1 file changed, 14 insertions(+), 14 deletions(-)
+
+diff --git a/net/batman-adv/bridge_loop_avoidance.c b/net/batman-adv/bridge_loop_avoidance.c
+index 3072f94275ac60..2c6e2b0d1ded48 100644
+--- a/net/batman-adv/bridge_loop_avoidance.c
++++ b/net/batman-adv/bridge_loop_avoidance.c
+@@ -513,7 +513,7 @@ batadv_bla_get_backbone_gw(struct batadv_priv *bat_priv, const u8 *orig,
+ return NULL;
+
+ entry->vid = vid;
+- entry->lasttime = jiffies;
++ WRITE_ONCE(entry->lasttime, jiffies);
+ entry->crc = BATADV_BLA_CRC_INIT;
+ entry->bat_priv = bat_priv;
+ spin_lock_init(&entry->crc_lock);
+@@ -581,7 +581,7 @@ batadv_bla_update_own_backbone_gw(struct batadv_priv *bat_priv,
+ if (unlikely(!backbone_gw))
+ return;
+
+- backbone_gw->lasttime = jiffies;
++ WRITE_ONCE(backbone_gw->lasttime, jiffies);
+ batadv_backbone_gw_put(backbone_gw);
+ }
+
+@@ -715,7 +715,7 @@ static void batadv_bla_add_claim(struct batadv_priv *bat_priv,
+ ether_addr_copy(claim->addr, mac);
+ spin_lock_init(&claim->backbone_lock);
+ claim->vid = vid;
+- claim->lasttime = jiffies;
++ WRITE_ONCE(claim->lasttime, jiffies);
+ kref_get(&backbone_gw->refcount);
+ claim->backbone_gw = backbone_gw;
+ kref_init(&claim->refcount);
+@@ -737,7 +737,7 @@ static void batadv_bla_add_claim(struct batadv_priv *bat_priv,
+ return;
+ }
+ } else {
+- claim->lasttime = jiffies;
++ WRITE_ONCE(claim->lasttime, jiffies);
+ if (claim->backbone_gw == backbone_gw)
+ /* no need to register a new backbone */
+ goto claim_free_ref;
+@@ -770,7 +770,7 @@ static void batadv_bla_add_claim(struct batadv_priv *bat_priv,
+ spin_lock_bh(&backbone_gw->crc_lock);
+ backbone_gw->crc ^= crc16(0, claim->addr, ETH_ALEN);
+ spin_unlock_bh(&backbone_gw->crc_lock);
+- backbone_gw->lasttime = jiffies;
++ WRITE_ONCE(backbone_gw->lasttime, jiffies);
+
+ claim_free_ref:
+ batadv_claim_put(claim);
+@@ -859,7 +859,7 @@ static bool batadv_handle_announce(struct batadv_priv *bat_priv, u8 *an_addr,
+ return true;
+
+ /* handle as ANNOUNCE frame */
+- backbone_gw->lasttime = jiffies;
++ WRITE_ONCE(backbone_gw->lasttime, jiffies);
+ crc = ntohs(*((__force __be16 *)(&an_addr[4])));
+
+ batadv_dbg(BATADV_DBG_BLA, bat_priv,
+@@ -1254,7 +1254,7 @@ static void batadv_bla_purge_backbone_gw(struct batadv_priv *bat_priv, int now)
+ head, hash_entry) {
+ if (now)
+ goto purge_now;
+- if (!batadv_has_timed_out(backbone_gw->lasttime,
++ if (!batadv_has_timed_out(READ_ONCE(backbone_gw->lasttime),
+ BATADV_BLA_BACKBONE_TIMEOUT))
+ continue;
+
+@@ -1335,7 +1335,7 @@ static void batadv_bla_purge_claims(struct batadv_priv *bat_priv,
+ primary_if->net_dev->dev_addr))
+ goto skip;
+
+- if (!batadv_has_timed_out(claim->lasttime,
++ if (!batadv_has_timed_out(READ_ONCE(claim->lasttime),
+ BATADV_BLA_CLAIM_TIMEOUT))
+ goto skip;
+
+@@ -1495,7 +1495,7 @@ static void batadv_bla_periodic_work(struct work_struct *work)
+ eth_random_addr(bat_priv->bla.loopdetect_addr);
+ bat_priv->bla.loopdetect_addr[0] = 0xba;
+ bat_priv->bla.loopdetect_addr[1] = 0xbe;
+- bat_priv->bla.loopdetect_lasttime = jiffies;
++ WRITE_ONCE(bat_priv->bla.loopdetect_lasttime, jiffies);
+ atomic_set(&bat_priv->bla.loopdetect_next,
+ BATADV_BLA_LOOPDETECT_PERIODS);
+
+@@ -1516,7 +1516,7 @@ static void batadv_bla_periodic_work(struct work_struct *work)
+ primary_if->net_dev->dev_addr))
+ continue;
+
+- backbone_gw->lasttime = jiffies;
++ WRITE_ONCE(backbone_gw->lasttime, jiffies);
+
+ batadv_bla_send_announce(bat_priv, backbone_gw);
+ if (send_loopdetect)
+@@ -1934,7 +1934,7 @@ batadv_bla_loopdetect_check(struct batadv_priv *bat_priv, struct sk_buff *skb,
+ /* If the packet came too late, don't forward it on the mesh
+ * but don't consider that as loop. It might be a coincidence.
+ */
+- if (batadv_has_timed_out(bat_priv->bla.loopdetect_lasttime,
++ if (batadv_has_timed_out(READ_ONCE(bat_priv->bla.loopdetect_lasttime),
+ BATADV_BLA_LOOPDETECT_TIMEOUT))
+ return true;
+
+@@ -2049,7 +2049,7 @@ bool batadv_bla_rx(struct batadv_priv *bat_priv, struct sk_buff *skb,
+
+ if (own_claim) {
+ /* ... allow it in any case */
+- claim->lasttime = jiffies;
++ WRITE_ONCE(claim->lasttime, jiffies);
+ goto allow;
+ }
+
+@@ -2151,7 +2151,7 @@ bool batadv_bla_tx(struct batadv_priv *bat_priv, struct sk_buff *skb,
+ /* if yes, the client has roamed and we have
+ * to unclaim it.
+ */
+- if (batadv_has_timed_out(claim->lasttime, 100)) {
++ if (batadv_has_timed_out(READ_ONCE(claim->lasttime), 100)) {
+ /* only unclaim if the last claim entry is
+ * older than 100 ms to make sure we really
+ * have a roaming client here.
+@@ -2396,7 +2396,7 @@ batadv_bla_backbone_dump_entry(struct sk_buff *msg, u32 portid,
+ backbone_crc = backbone_gw->crc;
+ spin_unlock_bh(&backbone_gw->crc_lock);
+
+- msecs = jiffies_to_msecs(jiffies - backbone_gw->lasttime);
++ msecs = jiffies_to_msecs(jiffies - READ_ONCE(backbone_gw->lasttime));
+
+ if (is_own)
+ if (nla_put_flag(msg, BATADV_ATTR_BLA_OWN)) {
+--
+2.53.0
+
--- /dev/null
+From bca97555cfa96c3eb4e1f9c40fb57634257a8b88 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:12:08 +0200
+Subject: batman-adv: dat: prevent false sharing between VLANs
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 20d7658b74169f86d4ac01b9185b3eadddf71f28 upstream.
+
+The local hash of DAT entries is supposed to be VLAN (VID) aware. But
+the adding to the hash and the search in the hash were not checking the VID
+information of the hash entries. The entries would therefore only be
+correctly separated when batadv_hash_dat() didn't select the same buckets
+for different VIDs.
+
+Cc: stable@kernel.org
+Fixes: be1db4f6615b ("batman-adv: make the Distributed ARP Table vlan aware")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/distributed-arp-table.c | 12 +++++++++---
+ 1 file changed, 9 insertions(+), 3 deletions(-)
+
+diff --git a/net/batman-adv/distributed-arp-table.c b/net/batman-adv/distributed-arp-table.c
+index 031c295fff1b1d..860db505e8697f 100644
+--- a/net/batman-adv/distributed-arp-table.c
++++ b/net/batman-adv/distributed-arp-table.c
+@@ -214,10 +214,13 @@ static void batadv_dat_purge(struct work_struct *work)
+ */
+ static bool batadv_compare_dat(const struct hlist_node *node, const void *data2)
+ {
+- const void *data1 = container_of(node, struct batadv_dat_entry,
+- hash_entry);
++ const struct batadv_dat_entry *entry1;
++ const struct batadv_dat_entry *entry2;
+
+- return memcmp(data1, data2, sizeof(__be32)) == 0;
++ entry1 = container_of(node, struct batadv_dat_entry, hash_entry);
++ entry2 = data2;
++
++ return entry1->ip == entry2->ip && entry1->vid == entry2->vid;
+ }
+
+ /**
+@@ -344,6 +347,9 @@ batadv_dat_entry_hash_find(struct batadv_priv *bat_priv, __be32 ip,
+ if (dat_entry->ip != ip)
+ continue;
+
++ if (dat_entry->vid != vid)
++ continue;
++
+ if (!kref_get_unless_zero(&dat_entry->refcount))
+ continue;
+
+--
+2.53.0
+
--- /dev/null
+From 7f2f2b859d335d0d7c7a07956096ef652b86a5e4 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:57 +0200
+Subject: batman-adv: ensure bcast is writable before modifying TTL
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 4cd6d3a4b96a8576f1fed8f9f9f17c2dc2978e0c upstream.
+
+Before batman-adv is allowed to write to an skb, it either has to have its
+own copy of the skb or used skb_cow() to ensure that the data part is not
+shared.
+
+The old implementation used a shared queue and created copies before
+attempting to write to it. But with the new implementation, the broadcast
+packet is already modified when it gets received. Potentially writing to
+shared buffers in this process.
+
+Adding a skb_cow() right before this operation avoids this and can at the
+same time prepare it for the modifications required to rebroadcast the
+packet.
+
+Cc: stable@kernel.org
+Fixes: 3f69339068f9 ("batman-adv: bcast: queue per interface, if needed")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/routing.c | 6 ++++++
+ 1 file changed, 6 insertions(+)
+
+diff --git a/net/batman-adv/routing.c b/net/batman-adv/routing.c
+index 12c16f81cc51df..0672dc30bed3b1 100644
+--- a/net/batman-adv/routing.c
++++ b/net/batman-adv/routing.c
+@@ -1191,6 +1191,12 @@ int batadv_recv_bcast_packet(struct sk_buff *skb,
+ if (batadv_is_my_mac(bat_priv, bcast_packet->orig))
+ goto free_skb;
+
++ /* create a copy of the skb, if needed, to modify it. */
++ if (skb_cow(skb, ETH_HLEN) < 0)
++ goto free_skb;
++
++ bcast_packet = (struct batadv_bcast_packet *)skb->data;
++
+ if (bcast_packet->ttl-- < 2)
+ goto free_skb;
+
+--
+2.53.0
+
--- /dev/null
+From e9436a3c43d56b7427bcaa3b323e71fabd4587ca Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:58 +0200
+Subject: batman-adv: fix (m|b)cast csum after decrementing TTL
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit e728bbdf32660c8f32b8f5e8d09427a2c131ad60 upstream.
+
+The broadcast and multicast packets can be received at the same time by the
+local system and forwarded to other nodes. Both are simply decrementing the
+TTL at the beginning of the receive path - independent of chosen paths
+(receive/forward). But such a modification of the data conflicts with the
+hw csum. This is not a problem when the packet is directly forwarded but
+can cause errors in the local receive path.
+
+Such a problem can then trigger a "hw csum failure". The receiver path must
+therefore ensure that the csum is fixed for each modification of the
+payload before batadv_interface_rx() is reached.
+
+Since all batman-adv packet types with a ttl have it as u8 at offset 2, a
+helper can be used for all of them. But it is only used at the moment for
+batadv_bcast_packet and batadv_mcast_packet because they are the only ones
+which deliver the packet locally but unconditionally modify the TTL.
+
+Cc: stable@kernel.org
+Fixes: 3f69339068f9 ("batman-adv: bcast: queue per interface, if needed")
+Fixes: 07afe1ba288c ("batman-adv: mcast: implement multicast packet reception and forwarding")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/routing.c | 58 ++++++++++++++++++++++++++++++++++++++--
+ 1 file changed, 56 insertions(+), 2 deletions(-)
+
+diff --git a/net/batman-adv/routing.c b/net/batman-adv/routing.c
+index 0672dc30bed3b1..cdcea90db61235 100644
+--- a/net/batman-adv/routing.c
++++ b/net/batman-adv/routing.c
+@@ -8,6 +8,7 @@
+ #include "main.h"
+
+ #include <linux/atomic.h>
++#include <linux/build_bug.h>
+ #include <linux/byteorder/generic.h>
+ #include <linux/compiler.h>
+ #include <linux/errno.h>
+@@ -204,6 +205,59 @@ bool batadv_check_management_packet(struct sk_buff *skb,
+ return true;
+ }
+
++/**
++ * batadv_skb_decrement_ttl() - decrement ttl in a batman-adv header, csum-safe
++ * @skb: the received packet with @skb->data pointing to the batman-adv header
++ *
++ * Supports the following packet types, all of which carry the TTL at offset 2:
++ *
++ * - batadv_ogm_packet
++ * - batadv_ogm2_packet
++ * - batadv_icmp_header
++ * - batadv_icmp_packet
++ * - batadv_icmp_tp_packet
++ * - batadv_icmp_packet_rr
++ * - batadv_unicast_packet
++ * - batadv_frag_packet
++ * - batadv_bcast_packet
++ * - batadv_mcast_packet
++ * - batadv_coded_packet
++ * - batadv_unicast_tvlv_packet
++ *
++ * Return: true if the packet may be forwarded (ttl decremented),
++ * false if it must be dropped (ttl would expire)
++ */
++static bool batadv_skb_decrement_ttl(struct sk_buff *skb)
++{
++ static const size_t ttl_offset = 2;
++ u8 *ttl_pos;
++
++ BUILD_BUG_ON(offsetof(struct batadv_ogm_packet, ttl) != ttl_offset);
++ BUILD_BUG_ON(offsetof(struct batadv_ogm2_packet, ttl) != ttl_offset);
++ BUILD_BUG_ON(offsetof(struct batadv_icmp_header, ttl) != ttl_offset);
++ BUILD_BUG_ON(offsetof(struct batadv_icmp_packet, ttl) != ttl_offset);
++ BUILD_BUG_ON(offsetof(struct batadv_icmp_tp_packet, ttl) != ttl_offset);
++ BUILD_BUG_ON(offsetof(struct batadv_icmp_packet_rr, ttl) != ttl_offset);
++ BUILD_BUG_ON(offsetof(struct batadv_unicast_packet, ttl) != ttl_offset);
++ BUILD_BUG_ON(offsetof(struct batadv_frag_packet, ttl) != ttl_offset);
++ BUILD_BUG_ON(offsetof(struct batadv_bcast_packet, ttl) != ttl_offset);
++ BUILD_BUG_ON(offsetof(struct batadv_mcast_packet, ttl) != ttl_offset);
++ BUILD_BUG_ON(offsetof(struct batadv_coded_packet, ttl) != ttl_offset);
++ BUILD_BUG_ON(offsetof(struct batadv_unicast_tvlv_packet, ttl) != ttl_offset);
++
++ ttl_pos = skb->data + ttl_offset;
++
++ /* would expire on this hop -> drop, leave header + csum untouched */
++ if (*ttl_pos < 2)
++ return false;
++
++ skb_postpull_rcsum(skb, ttl_pos, 1);
++ (*ttl_pos)--;
++ skb_postpush_rcsum(skb, ttl_pos, 1);
++
++ return true;
++}
++
+ /**
+ * batadv_recv_my_icmp_packet() - receive an icmp packet locally
+ * @bat_priv: the bat priv with all the mesh interface information
+@@ -1197,7 +1251,7 @@ int batadv_recv_bcast_packet(struct sk_buff *skb,
+
+ bcast_packet = (struct batadv_bcast_packet *)skb->data;
+
+- if (bcast_packet->ttl-- < 2)
++ if (!batadv_skb_decrement_ttl(skb))
+ goto free_skb;
+
+ orig_node = batadv_orig_hash_find(bat_priv, bcast_packet->orig);
+@@ -1304,7 +1358,7 @@ int batadv_recv_mcast_packet(struct sk_buff *skb,
+ goto free_skb;
+
+ mcast_packet = (struct batadv_mcast_packet *)skb->data;
+- if (mcast_packet->ttl-- < 2)
++ if (!batadv_skb_decrement_ttl(skb))
+ goto free_skb;
+
+ tvlv_buff = (unsigned char *)(skb->data + hdr_size);
+--
+2.53.0
+
--- /dev/null
+From 357b073fd79be41a341e5a0e9195e0339daa8812 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:12:00 +0200
+Subject: batman-adv: frag: avoid underflow of TTL
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 493d9d2528e1a09b090e4b37f0f553def7bd5ce9 upstream.
+
+Packets with a TTL are using it to limit the amount of time this packet can
+be forwarded. But for batadv_frag_packet, the TTL was always only reduced
+but it was never evaluated. It could even underflow without any effect.
+
+Check the TTL in batadv_frag_skb_fwd() before attempting to prepare it for
+forwarding. This keeps it in sync with the not fragmented unicast packet.
+
+Cc: stable@kernel.org
+Fixes: 610bfc6bc99b ("batman-adv: Receive fragmented packets and merge")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/fragmentation.c | 7 +++++++
+ 1 file changed, 7 insertions(+)
+
+diff --git a/net/batman-adv/fragmentation.c b/net/batman-adv/fragmentation.c
+index ffa5d4b0740959..4779741e7273ea 100644
+--- a/net/batman-adv/fragmentation.c
++++ b/net/batman-adv/fragmentation.c
+@@ -415,6 +415,13 @@ bool batadv_frag_skb_fwd(struct sk_buff *skb,
+ */
+ total_size = ntohs(packet->total_size);
+ if (total_size > neigh_node->if_incoming->net_dev->mtu) {
++ if (packet->ttl < 2) {
++ kfree_skb(skb);
++ *rx_result = NET_RX_DROP;
++ ret = true;
++ goto out;
++ }
++
+ if (skb_cow(skb, ETH_HLEN) < 0) {
+ kfree_skb(skb);
+ *rx_result = NET_RX_DROP;
+--
+2.53.0
+
--- /dev/null
+From 948a175efc11b0e227f9b94a96d3759a83ab3699 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:59 +0200
+Subject: batman-adv: frag: ensure fragment is writable before modifying TTL
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit b7293c6e8c15b2db77809b25cf8389e35331b27a upstream.
+
+Before batman-adv is allowed to write to an skb, it either has to have its
+own copy of the skb or use skb_cow() to ensure that the data part is not
+shared. But batadv_frag_skb_fwd() modifies the TTL even when it is shared.
+
+Adding a skb_cow() right before this operation avoids this and can at the
+same time prepare it for the modifications required to forward the
+fragment.
+
+Cc: stable@kernel.org
+Fixes: 610bfc6bc99b ("batman-adv: Receive fragmented packets and merge")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/fragmentation.c | 15 ++++++++++++++-
+ net/batman-adv/fragmentation.h | 3 ++-
+ net/batman-adv/routing.c | 3 +--
+ 3 files changed, 17 insertions(+), 4 deletions(-)
+
+diff --git a/net/batman-adv/fragmentation.c b/net/batman-adv/fragmentation.c
+index 31395281692cb5..ffa5d4b0740959 100644
+--- a/net/batman-adv/fragmentation.c
++++ b/net/batman-adv/fragmentation.c
+@@ -384,6 +384,8 @@ bool batadv_frag_skb_buffer(struct sk_buff **skb,
+ * @skb: skb to forward
+ * @recv_if: interface that the skb is received on
+ * @orig_node_src: originator that the skb is received from
++ * @rx_result: set to NET_RX_SUCCESS when the fragment was forwarded and
++ * NET_RX_DROP when it was dropped; only valid when true is returned
+ *
+ * Look up the next-hop of the fragments payload and check if the merged packet
+ * will exceed the MTU towards the next-hop. If so, the fragment is forwarded
+@@ -393,7 +395,8 @@ bool batadv_frag_skb_buffer(struct sk_buff **skb,
+ */
+ bool batadv_frag_skb_fwd(struct sk_buff *skb,
+ struct batadv_hard_iface *recv_if,
+- struct batadv_orig_node *orig_node_src)
++ struct batadv_orig_node *orig_node_src,
++ int *rx_result)
+ {
+ struct batadv_priv *bat_priv = netdev_priv(recv_if->mesh_iface);
+ struct batadv_neigh_node *neigh_node = NULL;
+@@ -412,12 +415,22 @@ bool batadv_frag_skb_fwd(struct sk_buff *skb,
+ */
+ total_size = ntohs(packet->total_size);
+ if (total_size > neigh_node->if_incoming->net_dev->mtu) {
++ if (skb_cow(skb, ETH_HLEN) < 0) {
++ kfree_skb(skb);
++ *rx_result = NET_RX_DROP;
++ ret = true;
++ goto out;
++ }
++
++ packet = (struct batadv_frag_packet *)skb->data;
++
+ batadv_inc_counter(bat_priv, BATADV_CNT_FRAG_FWD);
+ batadv_add_counter(bat_priv, BATADV_CNT_FRAG_FWD_BYTES,
+ skb->len + ETH_HLEN);
+
+ packet->ttl--;
+ batadv_send_unicast_skb(skb, neigh_node);
++ *rx_result = NET_RX_SUCCESS;
+ ret = true;
+ }
+
+diff --git a/net/batman-adv/fragmentation.h b/net/batman-adv/fragmentation.h
+index dbf0871f870303..51e281027ab630 100644
+--- a/net/batman-adv/fragmentation.h
++++ b/net/batman-adv/fragmentation.h
+@@ -19,7 +19,8 @@ void batadv_frag_purge_orig(struct batadv_orig_node *orig,
+ bool (*check_cb)(struct batadv_frag_table_entry *));
+ bool batadv_frag_skb_fwd(struct sk_buff *skb,
+ struct batadv_hard_iface *recv_if,
+- struct batadv_orig_node *orig_node_src);
++ struct batadv_orig_node *orig_node_src,
++ int *rx_result);
+ bool batadv_frag_skb_buffer(struct sk_buff **skb,
+ struct batadv_orig_node *orig_node);
+ int batadv_frag_send_packet(struct sk_buff *skb,
+diff --git a/net/batman-adv/routing.c b/net/batman-adv/routing.c
+index cdcea90db61235..4483f8d9c75831 100644
+--- a/net/batman-adv/routing.c
++++ b/net/batman-adv/routing.c
+@@ -1168,10 +1168,9 @@ int batadv_recv_frag_packet(struct sk_buff *skb,
+
+ /* Route the fragment if it is not for us and too big to be merged. */
+ if (!batadv_is_my_mac(bat_priv, frag_packet->dest) &&
+- batadv_frag_skb_fwd(skb, recv_if, orig_node_src)) {
++ batadv_frag_skb_fwd(skb, recv_if, orig_node_src, &ret)) {
+ /* skb was consumed */
+ skb = NULL;
+- ret = NET_RX_SUCCESS;
+ goto put_orig_node;
+ }
+
+--
+2.53.0
+
--- /dev/null
+From 292e9ada11423a5949113ba88c6c117ba8949d9f Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:56 +0200
+Subject: batman-adv: gw: don't deselect gateway with active hardif
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit df97a7107b16375a10a36d7a63e9b4291a8ac680 upstream.
+
+The batadv_hardif_cnt() was previously checking if there is an
+batadv_hard_iface->mesh_iface which is has the same mesh_iface. And since
+batadv_hardif_disable_interface() was resetting the
+batadv_hard_iface->mesh_iface after this check, it had to verify whether
+*1* interface was still part of the mesh_iface before it started the
+gateway deselection.
+
+But after batadv_hardif_cnt() is now checking the lower interfaces of
+mesh_iface and batadv_hardif_disable_interface() already removed the
+interface via netdev_upper_dev_unlink() earlier in this function, the check
+must now make sure that *0* interfaces can be found by batadv_hardif_cnt()
+before selected gateway must be deselected. Otherwise the deselection would
+already happen one batadv_hard_iface too early.
+
+Because a 0 hardif count from batadv_hardif_cnt() is equal to an empty
+list, it is possible to replace the counting with a simple list_empty().
+
+Cc: stable@kernel.org
+Fixes: 7dc284702bcd ("batman-adv: store hard_iface as iflink private data")
+Reviewed-by: Nora Schiffer <neocturne@universe-factory.net>
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/hard-interface.c | 28 ++--------------------------
+ 1 file changed, 2 insertions(+), 26 deletions(-)
+
+diff --git a/net/batman-adv/hard-interface.c b/net/batman-adv/hard-interface.c
+index 1c488049d55463..39b1ed813497d2 100644
+--- a/net/batman-adv/hard-interface.c
++++ b/net/batman-adv/hard-interface.c
+@@ -786,30 +786,6 @@ int batadv_hardif_enable_interface(struct batadv_hard_iface *hard_iface,
+ return ret;
+ }
+
+-/**
+- * batadv_hardif_cnt() - get number of interfaces enslaved to mesh interface
+- * @mesh_iface: mesh interface to check
+- *
+- * This function is only using RCU for locking - the result can therefore be
+- * off when another function is modifying the list at the same time. The
+- * caller can use the rtnl_lock to make sure that the count is accurate.
+- *
+- * Return: number of connected/enslaved hard interfaces
+- */
+-static size_t batadv_hardif_cnt(struct net_device *mesh_iface)
+-{
+- struct batadv_hard_iface *hard_iface;
+- struct list_head *iter;
+- size_t count = 0;
+-
+- rcu_read_lock();
+- netdev_for_each_lower_private_rcu(mesh_iface, hard_iface, iter)
+- count++;
+- rcu_read_unlock();
+-
+- return count;
+-}
+-
+ /**
+ * batadv_hardif_disable_interface() - Remove hard interface from mesh interface
+ * @hard_iface: hard interface to be removed
+@@ -850,8 +826,8 @@ void batadv_hardif_disable_interface(struct batadv_hard_iface *hard_iface)
+ netdev_upper_dev_unlink(hard_iface->net_dev, hard_iface->mesh_iface);
+ batadv_hardif_recalc_extra_skbroom(hard_iface->mesh_iface);
+
+- /* nobody uses this interface anymore */
+- if (batadv_hardif_cnt(hard_iface->mesh_iface) <= 1)
++ /* nobody uses this mesh interface anymore */
++ if (list_empty(&hard_iface->mesh_iface->adj_list.lower))
+ batadv_gw_check_client_stop(bat_priv);
+
+ hard_iface->mesh_iface = NULL;
+--
+2.53.0
+
--- /dev/null
+From ee52bb15fff196bf2d65dcfcf7f5f58fe70217ad Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:54 +0200
+Subject: batman-adv: prevent ELP transmission interval underflow
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 5e50d4b8ae3ea622122d3c6a38d7f6fe68dfddca upstream.
+
+batadv_v_elp_start_timer() enqeues a delayed work. The time when it starts
+is randomly chosen between (elp_interval - BATADV_JITTER) and
+(elp_interval + BATADV_JITTER). The configured elp_interval must therefore
+be larger or equal to BATADV_JITTER to avoid that it causes an underflow of
+the unsigned integer. If this would happen, then a "fast" ELP interval
+would turn into a "day long" delay.
+
+At the same time, it must not be larger than the maximum value the variable
+can store.
+
+Cc: stable@kernel.org
+Fixes: a10800829040 ("batman-adv: Add elp_interval hardif genl configuration")
+[ Context ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/netlink.c | 8 +++++++-
+ 1 file changed, 7 insertions(+), 1 deletion(-)
+
+diff --git a/net/batman-adv/netlink.c b/net/batman-adv/netlink.c
+index 78c651f634cd39..1d144d8cc0928e 100644
+--- a/net/batman-adv/netlink.c
++++ b/net/batman-adv/netlink.c
+@@ -917,9 +917,15 @@ static int batadv_netlink_set_hardif(struct sk_buff *skb,
+ #ifdef CONFIG_BATMAN_ADV_BATMAN_V
+
+ if (info->attrs[BATADV_ATTR_ELP_INTERVAL]) {
++ u32 elp_interval;
++
+ attr = info->attrs[BATADV_ATTR_ELP_INTERVAL];
++ elp_interval = nla_get_u32(attr);
++
++ elp_interval = min_t(u32, elp_interval, INT_MAX);
++ elp_interval = max_t(u32, elp_interval, BATADV_JITTER);
+
+- atomic_set(&hard_iface->bat_v.elp_interval, nla_get_u32(attr));
++ atomic_set(&hard_iface->bat_v.elp_interval, elp_interval);
+ }
+
+ if (info->attrs[BATADV_ATTR_THROUGHPUT_OVERRIDE]) {
+--
+2.53.0
+
--- /dev/null
+From 437963dd64218a45c7608812098b45ef81dd4b32 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:52 +0200
+Subject: batman-adv: tp_meter: add only finished tp_vars to lists
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 15ccbf685222274f5add1387af58c2a41a95f81e upstream.
+
+When the receiver variables (aka "session") are initialized, then they are
+added to the list of sessions before the timer is set up. A RCU protected
+reader could therefore find the entry and run mod_setup before
+batadv_tp_init_recv() finished the timer initialization.
+
+The same is true for batadv_tp_start(), which must first initialize the
+finish_work and the test_length to avoid a similar problem.
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 18 +++++++++---------
+ 1 file changed, 9 insertions(+), 9 deletions(-)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index 0ebfc4462b8f17..0444aa46b95c29 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -1096,21 +1096,21 @@ void batadv_tp_start(struct batadv_priv *bat_priv, const u8 *dst,
+ tp_vars->prerandom_offset = 0;
+ spin_lock_init(&tp_vars->prerandom_lock);
+
+- kref_get(&tp_vars->refcount);
+- hlist_add_head_rcu(&tp_vars->list, &bat_priv->tp_list);
+- spin_unlock_bh(&bat_priv->tp_list_lock);
+-
+ tp_vars->test_length = test_length;
+ if (!tp_vars->test_length)
+ tp_vars->test_length = BATADV_TP_DEF_TEST_LENGTH;
+
++ /* init work item for finished tp tests */
++ INIT_DELAYED_WORK(&tp_vars->finish_work, batadv_tp_sender_finish);
++
++ kref_get(&tp_vars->refcount);
++ hlist_add_head_rcu(&tp_vars->list, &bat_priv->tp_list);
++ spin_unlock_bh(&bat_priv->tp_list_lock);
++
+ batadv_dbg(BATADV_DBG_TP_METER, bat_priv,
+ "Meter: starting throughput meter towards %pM (length=%ums)\n",
+ dst, test_length);
+
+- /* init work item for finished tp tests */
+- INIT_DELAYED_WORK(&tp_vars->finish_work, batadv_tp_sender_finish);
+-
+ /* start tp kthread. This way the write() call issued from userspace can
+ * happily return and avoid to block
+ */
+@@ -1430,10 +1430,10 @@ batadv_tp_init_recv(struct batadv_priv *bat_priv,
+ INIT_LIST_HEAD(&tp_vars->unacked_list);
+
+ kref_get(&tp_vars->refcount);
+- hlist_add_head_rcu(&tp_vars->list, &bat_priv->tp_list);
++ timer_setup(&tp_vars->timer, batadv_tp_receiver_shutdown, 0);
+
+ kref_get(&tp_vars->refcount);
+- timer_setup(&tp_vars->timer, batadv_tp_receiver_shutdown, 0);
++ hlist_add_head_rcu(&tp_vars->list, &bat_priv->tp_list);
+
+ batadv_tp_reset_receiver_timer(tp_vars);
+
+--
+2.53.0
+
--- /dev/null
+From 4c40ba3c684901303ac8081252f572173bc24043 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:12:03 +0200
+Subject: batman-adv: tp_meter: annotate last_recv_time access with
+ READ/WRITE_ONCE
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit d67c728f07fca2ee6ffdc6dd4421cf2e8691f4d1 upstream.
+
+The last_recv_time field for batadv_tp_receiver tracks the jiffies value of
+the most recent activity and is used to detect timeouts. These accesses are
+not consistently protected by a lock, so READ_ONCE/WRITE_ONCE must be used
+to prevent data races caused by compiler optimizations.
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 8 ++++----
+ 1 file changed, 4 insertions(+), 4 deletions(-)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index e5387e8f33244a..e69bf10e66ac33 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -1183,7 +1183,7 @@ static void batadv_tp_receiver_shutdown(struct timer_list *t)
+ bat_priv = tp_vars->bat_priv;
+
+ /* if there is recent activity rearm the timer */
+- if (!batadv_has_timed_out(tp_vars->last_recv_time,
++ if (!batadv_has_timed_out(READ_ONCE(tp_vars->last_recv_time),
+ BATADV_TP_RECV_TIMEOUT)) {
+ /* reset the receiver shutdown timer */
+ batadv_tp_reset_receiver_timer(tp_vars);
+@@ -1424,7 +1424,7 @@ batadv_tp_init_recv(struct batadv_priv *bat_priv,
+ tp_vars = batadv_tp_list_find_session(bat_priv, icmp->orig,
+ icmp->session, BATADV_TP_RECEIVER);
+ if (tp_vars) {
+- tp_vars->last_recv_time = jiffies;
++ WRITE_ONCE(tp_vars->last_recv_time, jiffies);
+ goto out_unlock;
+ }
+
+@@ -1455,7 +1455,7 @@ batadv_tp_init_recv(struct batadv_priv *bat_priv,
+ kref_get(&tp_vars->refcount);
+ timer_setup(&tp_vars->timer, batadv_tp_receiver_shutdown, 0);
+
+- tp_vars->last_recv_time = jiffies;
++ WRITE_ONCE(tp_vars->last_recv_time, jiffies);
+
+ kref_get(&tp_vars->refcount);
+ hlist_add_head_rcu(&tp_vars->list, &bat_priv->tp_list);
+@@ -1506,7 +1506,7 @@ static void batadv_tp_recv_msg(struct batadv_priv *bat_priv,
+ goto out;
+ }
+
+- tp_vars->last_recv_time = jiffies;
++ WRITE_ONCE(tp_vars->last_recv_time, jiffies);
+ }
+
+ /* if the packet is a duplicate, it may be the case that an ACK has been
+--
+2.53.0
+
--- /dev/null
+From 9a7182a7e003167b4f09f93a62e903a1c9ed45e3 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:49 +0200
+Subject: batman-adv: tp_meter: avoid divide-by-zero for dec_cwnd
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 33ccd52f3cc9ed46ce395199f89aa3234dc83314 upstream.
+
+The cwnd is always MSS <= cwnd <= 0x20000000. But the calculation in
+batadv_tp_update_cwnd() assumes unsigned 32 bit arithmetics.
+
+ ((mss * 8) ** 2) / (cwnd * 8)
+
+In case cwnd is actually 0x20000000, it will be shifted by 3 bit to the
+left end up at 0x100000000 or U32_MAX + 1. It will therefore wrap around
+and be 0 - resulting in:
+
+ ((mss * 8) ** 2) / 0
+
+This is of course invalid and cannot be calculated. The calculation should
+must be simplified to avoid this overflow:
+
+ (mss ** 2) * 8 / cwnd
+
+It will keep the precision enhancement from the scaling (by 8) but avoid
+the overflow in the divisor.
+
+In theory, there could still be an overflow in the dividend. It is at the
+moment fixed to BATADV_TP_PLEN in batadv_tp_recv_ack() - so it is not an
+imminent problem. But allowing it to use the whole u32 bit range, would
+mean that it can still use up to 67 bits. To keep this calculation safe for
+32 bit arithmetic, mss must never use more than floor((32 - 3) / 2) bits -
+or in other words: must never be larger than 16383.
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 5 ++++-
+ 1 file changed, 4 insertions(+), 1 deletion(-)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index 71a4352cd78c77..00d8bb01611f99 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -154,9 +154,12 @@ static void batadv_tp_update_cwnd(struct batadv_tp_vars *tp_vars, u32 mss)
+ return;
+ }
+
++ /* prevent overflow in (mss * mss) << 3 */
++ mss = min_t(u32, mss, (1U << 14) - 1);
++
+ /* increment CWND at least of 1 (section 3.1 of RFC5681) */
+ tp_vars->dec_cwnd += max_t(u32, 1U << 3,
+- ((mss * mss) << 6) / (tp_vars->cwnd << 3));
++ ((mss * mss) << 3) / tp_vars->cwnd);
+ if (tp_vars->dec_cwnd < (mss << 3)) {
+ spin_unlock_bh(&tp_vars->cwnd_lock);
+ return;
+--
+2.53.0
+
--- /dev/null
+From c141c9d1393829739398c5eeca89c56f4bb621eb Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:48 +0200
+Subject: batman-adv: tp_meter: avoid window underflow
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 765947b81fb54b6ebb0bc1cfe55c0fa399e002b8 upstream.
+
+In batadv_tp_avail(), win_left is calculated with 32-bit unsigned
+arithmetic: win_left = win_limit - tp_vars->last_sent;
+
+During Fast Recovery, cwnd is inflated and last_sent advances rapidly. When
+Fast Recovery ends, cwnd drops abruptly back to ss_threshold. If the newly
+shrunk win_limit is less than last_sent, the unsigned subtraction will
+underflow, wrapping to a massive positive value. Instead of returning that
+the window is full (unavailable), it returns that the sender can continue
+sending.
+
+To handle this situation, it must be checked whether the windows end
+sequence number (win_limit) has to be compared with the last sent sequence
+number. If it would be before the last sent sequence number, then more acks
+are needed before the transmission can be started again.
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 7 ++++++-
+ 1 file changed, 6 insertions(+), 1 deletion(-)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index 473641d32dc683..71a4352cd78c77 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -817,10 +817,15 @@ static void batadv_tp_recv_ack(struct batadv_priv *bat_priv,
+ static bool batadv_tp_avail(struct batadv_tp_vars *tp_vars,
+ size_t payload_len)
+ {
++ u32 last_sent = READ_ONCE(tp_vars->last_sent);
+ u32 win_left, win_limit;
+
+ win_limit = atomic_read(&tp_vars->last_acked) + tp_vars->cwnd;
+- win_left = win_limit - tp_vars->last_sent;
++
++ if (batadv_seq_before(last_sent, win_limit))
++ win_left = win_limit - last_sent;
++ else
++ win_left = 0;
+
+ return win_left >= payload_len;
+ }
+--
+2.53.0
+
--- /dev/null
+From c2da02b138d748873fd5d24a5f764545b33a60d7 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:50 +0200
+Subject: batman-adv: tp_meter: fix fast recovery precondition
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 2b0d08f08ed3b2174f05c43089ec65f3543a025b upstream.
+
+The fast recovery precondition checks if the recover (initialized to
+BATADV_TP_FIRST_SEQ) is bigger than the received ack. But since recover is
+only updated when this check is successful, it will never enter the fast
+recovery mode.
+
+According to RFC6582 Section 3.2 step 2, the check should actually be
+different:
+
+> When the third duplicate ACK is received, the TCP sender first
+> checks the value of recover to see if the Cumulative
+> Acknowledgment field covers more than recover
+
+The precondition must therefore check if recover is smaller than the
+received ack - basically swapping the operands of the current check.
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index 00d8bb01611f99..a85622267ba655 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -733,7 +733,7 @@ static void batadv_tp_recv_ack(struct batadv_priv *bat_priv,
+ if (atomic_read(&tp_vars->dup_acks) != 3)
+ goto out;
+
+- if (recv_ack >= tp_vars->recover)
++ if (tp_vars->recover >= recv_ack)
+ goto out;
+
+ /* if this is the third duplicate ACK do Fast Retransmit */
+--
+2.53.0
+
--- /dev/null
+From b909cd0adc450ea331944be0afd87f94abf29fa1 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:12:05 +0200
+Subject: batman-adv: tp_meter: handle overlapping packets
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit cbde75c38b21f022891525078622587ad557b7c1 upstream.
+
+If the size of the packets would change during the transmission, it could
+happen that some retries of packets are overlapping. In this case, precise
+comparisons of sequence numbers by the receiver would be wrong. It is then
+necessary to check if the start sequence number to the end sequence number
+("seqno + length") would contain a new range.
+
+If this is the case then this is enough to accept this packet. In all other
+cases, the packet still has to be dropped (and not acked).
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+[ Switch to pre-splitted tp_vars structure names ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 25 +++++++++++--------------
+ 1 file changed, 11 insertions(+), 14 deletions(-)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index 629831ea9a58e5..02af19aaaff291 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -1284,7 +1284,8 @@ static int batadv_tp_send_ack(struct batadv_priv *bat_priv, const u8 *dst,
+ /**
+ * batadv_tp_handle_out_of_order() - store an out of order packet
+ * @tp_vars: the private data of the current TP meter session
+- * @skb: the buffer containing the received packet
++ * @seqno: sequence number of new received packet
++ * @payload_len: length of the received packet
+ *
+ * Store the out of order packet in the unacked list for late processing. This
+ * packets are kept in this list so that they can be ACKed at once as soon as
+@@ -1293,22 +1294,17 @@ static int batadv_tp_send_ack(struct batadv_priv *bat_priv, const u8 *dst,
+ * Return: true if the packed has been successfully processed, false otherwise
+ */
+ static bool batadv_tp_handle_out_of_order(struct batadv_tp_vars *tp_vars,
+- const struct sk_buff *skb)
++ u32 seqno, u32 payload_len)
+ __must_hold(&tp_vars->unacked_lock)
+ {
+- const struct batadv_icmp_tp_packet *icmp;
+ struct batadv_tp_unacked *un, *new;
+- u32 payload_len;
+ bool added = false;
+
+ new = kmalloc(sizeof(*new), GFP_ATOMIC);
+ if (unlikely(!new))
+ return false;
+
+- icmp = (struct batadv_icmp_tp_packet *)skb->data;
+-
+- new->seqno = ntohl(icmp->seqno);
+- payload_len = skb->len - sizeof(struct batadv_unicast_packet);
++ new->seqno = seqno;
+ new->len = payload_len;
+
+ /* if the list is empty immediately attach this new object */
+@@ -1476,7 +1472,7 @@ static void batadv_tp_recv_msg(struct batadv_priv *bat_priv,
+ {
+ const struct batadv_icmp_tp_packet *icmp;
+ struct batadv_tp_vars *tp_vars;
+- size_t packet_size;
++ u32 payload_len;
+ u32 to_ack;
+ u32 seqno;
+
+@@ -1511,15 +1507,17 @@ static void batadv_tp_recv_msg(struct batadv_priv *bat_priv,
+ /* if the packet is a duplicate, it may be the case that an ACK has been
+ * lost. Resend the ACK
+ */
+- if (batadv_seq_before(seqno, tp_vars->last_recv))
++ payload_len = skb->len - sizeof(struct batadv_unicast_packet);
++ to_ack = seqno + payload_len;
++ if (batadv_seq_before(to_ack, tp_vars->last_recv))
+ goto send_ack;
+
+ /* if the packet is out of order enqueue it */
+- if (ntohl(icmp->seqno) != tp_vars->last_recv) {
++ if (batadv_seq_before(tp_vars->last_recv, seqno)) {
+ /* exit immediately (and do not send any ACK) if the packet has
+ * not been enqueued correctly
+ */
+- if (!batadv_tp_handle_out_of_order(tp_vars, skb)) {
++ if (!batadv_tp_handle_out_of_order(tp_vars, seqno, payload_len)) {
+ spin_unlock_bh(&tp_vars->unacked_lock);
+ goto out;
+ }
+@@ -1529,8 +1527,7 @@ static void batadv_tp_recv_msg(struct batadv_priv *bat_priv,
+ }
+
+ /* if everything was fine count the ACKed bytes */
+- packet_size = skb->len - sizeof(struct batadv_unicast_packet);
+- tp_vars->last_recv += packet_size;
++ tp_vars->last_recv = to_ack;
+
+ /* check if this ordered message filled a gap.... */
+ batadv_tp_ack_unordered(tp_vars);
+--
+2.53.0
+
--- /dev/null
+From 1bb42504094a679da4bcfa7936ae212e44c159cd Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:51 +0200
+Subject: batman-adv: tp_meter: handle seqno wrap-around for fast recovery
+ detection
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit f54c85ed42a1b27a516cf2a4728f5a612b799e07 upstream.
+
+The recover variable and the last_sent sequence number are initialized on
+purpose as a really high value which will wrap-around after the first 2000
+bytes. The fast recovery precondition must therefore not use simple integer
+comparisons but use helpers which are aware of the sequence number
+wrap-arounds.
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index a85622267ba655..0ebfc4462b8f17 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -733,7 +733,7 @@ static void batadv_tp_recv_ack(struct batadv_priv *bat_priv,
+ if (atomic_read(&tp_vars->dup_acks) != 3)
+ goto out;
+
+- if (tp_vars->recover >= recv_ack)
++ if (!batadv_seq_before(tp_vars->recover, recv_ack))
+ goto out;
+
+ /* if this is the third duplicate ACK do Fast Retransmit */
+--
+2.53.0
+
--- /dev/null
+From bc6dfc14d9d5b3521dbdf89915c970897a7015ba Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:47 +0200
+Subject: batman-adv: tp_meter: initialize dec_cwnd explicitly
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit febfb1b86224489535312296ecfa3d4bf467f339 upstream.
+
+When batadv_tp_update_cwnd() is called, dec_cwnd is increased. But dec_cwnd
+is only initialixed (to 0) when a duplicate Ack was received or when cwnd
+is below the ss_threshold.
+
+Just initialize the cwnd during the initialization to avoid any potential
+access of uninitialized data.
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index fe9a447643074a..473641d32dc683 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -1055,6 +1055,8 @@ void batadv_tp_start(struct batadv_priv *bat_priv, const u8 *dst,
+ * mesh_interface, hence its MTU
+ */
+ tp_vars->cwnd = BATADV_TP_PLEN * 3;
++ tp_vars->dec_cwnd = 0;
++
+ /* at the beginning initialise the SS threshold to the biggest possible
+ * window size, hence the AWND size
+ */
+--
+2.53.0
+
--- /dev/null
+From b6fe66028fae037882ea6e13809d1299ce87a4ce Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:46 +0200
+Subject: batman-adv: tp_meter: initialize dup_acks explicitly
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit b2b68b32a715e0328662801576974aa37b942b00 upstream.
+
+When an ack with a sequence number equal to the last_acked is received, the
+dup_acks counter is increased to decide whether fast retransmit should be
+performed. Only when the sequence numbers are not equal, the dup_acks is
+set to the initial value (0).
+
+But if the initial packet would have the sequence number
+BATADV_TP_FIRST_SEQ, dup_acks would not be initialized and atomic_inc would
+operate on an undefined starting value. It is therefore required to have it
+explicitly initialized during the start of the sender session.
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index f222c5093b647e..fe9a447643074a 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -1045,6 +1045,7 @@ void batadv_tp_start(struct batadv_priv *bat_priv, const u8 *dst,
+ tp_vars->icmp_uid = icmp_uid;
+
+ tp_vars->last_sent = BATADV_TP_FIRST_SEQ;
++ atomic_set(&tp_vars->dup_acks, 0);
+ atomic_set(&tp_vars->last_acked, BATADV_TP_FIRST_SEQ);
+ tp_vars->fast_recovery = false;
+ tp_vars->recover = BATADV_TP_FIRST_SEQ;
+--
+2.53.0
+
--- /dev/null
+From 057e1f7d30c1fa98382c0be169cd55bca7481a0c Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:55 +0200
+Subject: batman-adv: tp_meter: initialize last_recv_time during init
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 811cb00fa8cdc3f0a7f6eefc000a6888367c8c8f upstream.
+
+The last_recv_time is the most important indicator for a receiver session
+to figure out whether a session timed out or not. But this information was
+only initialized after the session was added to the tp_receiver_list and
+after the timer was started.
+
+In the worst case, the timer (function) could have tried to access this
+information before the actual initialization was reached. Like rest of the
+variables of the tp_meter receiver session, this field has to be filled out
+before any other (parallel running) context has the chance to access it.
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+[ Context ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 10 +++++++---
+ 1 file changed, 7 insertions(+), 3 deletions(-)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index 0444aa46b95c29..10b8daca3a6154 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -1403,8 +1403,10 @@ batadv_tp_init_recv(struct batadv_priv *bat_priv,
+
+ tp_vars = batadv_tp_list_find_session(bat_priv, icmp->orig,
+ icmp->session, BATADV_TP_RECEIVER);
+- if (tp_vars)
++ if (tp_vars) {
++ tp_vars->last_recv_time = jiffies;
+ goto out_unlock;
++ }
+
+ if (!atomic_add_unless(&bat_priv->tp_num, 1, BATADV_TP_MAX_NUM)) {
+ batadv_dbg(BATADV_DBG_TP_METER, bat_priv,
+@@ -1432,6 +1434,8 @@ batadv_tp_init_recv(struct batadv_priv *bat_priv,
+ kref_get(&tp_vars->refcount);
+ timer_setup(&tp_vars->timer, batadv_tp_receiver_shutdown, 0);
+
++ tp_vars->last_recv_time = jiffies;
++
+ kref_get(&tp_vars->refcount);
+ hlist_add_head_rcu(&tp_vars->list, &bat_priv->tp_list);
+
+@@ -1480,9 +1484,9 @@ static void batadv_tp_recv_msg(struct batadv_priv *bat_priv,
+ icmp->orig);
+ goto out;
+ }
+- }
+
+- tp_vars->last_recv_time = jiffies;
++ tp_vars->last_recv_time = jiffies;
++ }
+
+ /* if the packet is a duplicate, it may be the case that an ACK has been
+ * lost. Resend the ACK
+--
+2.53.0
+
--- /dev/null
+From e8c4b955ee14124851fc8b809868476971e75eef Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:45 +0200
+Subject: batman-adv: tp_meter: keep unacked list in ascending ordered
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 5aa8651527ea0b610e7a09fb3b8204c1398b9525 upstream.
+
+When batadv_tp_handle_out_of_order inserts a new entry in the list of
+unacked (out of order) packets, it searches from the entry with the newest
+sequence number towards oldest sequence number. If an entry is found which
+is older than the newly entry, the new entry has to be added after the
+found one to keep the ascending order.
+
+But for this operation list_add_tail() was used. But this function adds an
+entry _before_ another one. As result, the list would contain a lot of
+swapped sequence numbers. The consumer of this list
+(batadv_tp_ack_unordered()) would then fail to correctly ack packets.
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index b1629e0ac82683..f222c5093b647e 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -1325,7 +1325,7 @@ static bool batadv_tp_handle_out_of_order(struct batadv_tp_vars *tp_vars,
+ * one is attached _after_ it. In this way the list is kept in
+ * ascending order
+ */
+- list_add_tail(&new->list, &un->list);
++ list_add(&new->list, &un->list);
+ added = true;
+ break;
+ }
+--
+2.53.0
+
--- /dev/null
+From beae79a5118a251f694c211d72e334a05ac7f8df Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:12:04 +0200
+Subject: batman-adv: tp_meter: prevent parallel modifications of last_recv
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 6dde0cfcb36e4d5b3de35b75696937478441eed4 upstream.
+
+When last_recv is updated to store the last receive sequence number, it is
+assuming that nothing is modifying in parallel while:
+
+* check for outdated packets is done
+* out of order check is performed (and packets are stored in out-of-order
+ queue)
+* the out-of-order queue was searched for closed gaps
+* sequence number for next ack is calculated
+
+Nothing of that was actually protected. It could therefore happen that the
+last_recv was updated multiple times in parallel and the final sequence
+number was calculated with deltas which had no connection to the sequence
+number they were added to.
+
+Lock this whole region with the same lock which was already used to protect
+the unacked (out-of-order) list.
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+[ Switch to pre-splitted tp_vars structure names ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 22 +++++++++++++---------
+ net/batman-adv/types.h | 2 +-
+ 2 files changed, 14 insertions(+), 10 deletions(-)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index e69bf10e66ac33..629831ea9a58e5 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -1294,6 +1294,7 @@ static int batadv_tp_send_ack(struct batadv_priv *bat_priv, const u8 *dst,
+ */
+ static bool batadv_tp_handle_out_of_order(struct batadv_tp_vars *tp_vars,
+ const struct sk_buff *skb)
++ __must_hold(&tp_vars->unacked_lock)
+ {
+ const struct batadv_icmp_tp_packet *icmp;
+ struct batadv_tp_unacked *un, *new;
+@@ -1310,12 +1311,11 @@ static bool batadv_tp_handle_out_of_order(struct batadv_tp_vars *tp_vars,
+ payload_len = skb->len - sizeof(struct batadv_unicast_packet);
+ new->len = payload_len;
+
+- spin_lock_bh(&tp_vars->unacked_lock);
+ /* if the list is empty immediately attach this new object */
+ if (list_empty(&tp_vars->unacked_list)) {
+ list_add(&new->list, &tp_vars->unacked_list);
+ tp_vars->unacked_count++;
+- goto out;
++ return true;
+ }
+
+ /* otherwise loop over the list and either drop the packet because this
+@@ -1364,9 +1364,6 @@ static bool batadv_tp_handle_out_of_order(struct batadv_tp_vars *tp_vars,
+ tp_vars->unacked_count--;
+ }
+
+-out:
+- spin_unlock_bh(&tp_vars->unacked_lock);
+-
+ return true;
+ }
+
+@@ -1376,6 +1373,7 @@ static bool batadv_tp_handle_out_of_order(struct batadv_tp_vars *tp_vars,
+ * @tp_vars: the private data of the current TP meter session
+ */
+ static void batadv_tp_ack_unordered(struct batadv_tp_vars *tp_vars)
++ __must_hold(&tp_vars->unacked_lock)
+ {
+ struct batadv_tp_unacked *un, *safe;
+ u32 to_ack;
+@@ -1383,7 +1381,6 @@ static void batadv_tp_ack_unordered(struct batadv_tp_vars *tp_vars)
+ /* go through the unacked packet list and possibly ACK them as
+ * well
+ */
+- spin_lock_bh(&tp_vars->unacked_lock);
+ list_for_each_entry_safe(un, safe, &tp_vars->unacked_list, list) {
+ /* the list is ordered, therefore it is possible to stop as soon
+ * there is a gap between the last acked seqno and the seqno of
+@@ -1401,7 +1398,6 @@ static void batadv_tp_ack_unordered(struct batadv_tp_vars *tp_vars)
+ kfree(un);
+ tp_vars->unacked_count--;
+ }
+- spin_unlock_bh(&tp_vars->unacked_lock);
+ }
+
+ /**
+@@ -1481,6 +1477,7 @@ static void batadv_tp_recv_msg(struct batadv_priv *bat_priv,
+ const struct batadv_icmp_tp_packet *icmp;
+ struct batadv_tp_vars *tp_vars;
+ size_t packet_size;
++ u32 to_ack;
+ u32 seqno;
+
+ icmp = (struct batadv_icmp_tp_packet *)skb->data;
+@@ -1509,6 +1506,8 @@ static void batadv_tp_recv_msg(struct batadv_priv *bat_priv,
+ WRITE_ONCE(tp_vars->last_recv_time, jiffies);
+ }
+
++ spin_lock_bh(&tp_vars->unacked_lock);
++
+ /* if the packet is a duplicate, it may be the case that an ACK has been
+ * lost. Resend the ACK
+ */
+@@ -1520,8 +1519,10 @@ static void batadv_tp_recv_msg(struct batadv_priv *bat_priv,
+ /* exit immediately (and do not send any ACK) if the packet has
+ * not been enqueued correctly
+ */
+- if (!batadv_tp_handle_out_of_order(tp_vars, skb))
++ if (!batadv_tp_handle_out_of_order(tp_vars, skb)) {
++ spin_unlock_bh(&tp_vars->unacked_lock);
+ goto out;
++ }
+
+ /* send a duplicate ACK */
+ goto send_ack;
+@@ -1535,11 +1536,14 @@ static void batadv_tp_recv_msg(struct batadv_priv *bat_priv,
+ batadv_tp_ack_unordered(tp_vars);
+
+ send_ack:
++ to_ack = tp_vars->last_recv;
++ spin_unlock_bh(&tp_vars->unacked_lock);
++
+ /* send the ACK. If the received packet was out of order, the ACK that
+ * is going to be sent is a duplicate (the sender will count them and
+ * possibly enter Fast Retransmit as soon as it has reached 3)
+ */
+- batadv_tp_send_ack(bat_priv, icmp->orig, tp_vars->last_recv,
++ batadv_tp_send_ack(bat_priv, icmp->orig, to_ack,
+ icmp->timestamp, icmp->session, icmp->uid);
+ out:
+ batadv_tp_vars_put(tp_vars);
+diff --git a/net/batman-adv/types.h b/net/batman-adv/types.h
+index 8b180d8245b2a6..84de2570eac3ed 100644
+--- a/net/batman-adv/types.h
++++ b/net/batman-adv/types.h
+@@ -1423,7 +1423,7 @@ struct batadv_tp_vars {
+ /** @unacked_list: list of unacked packets (meta-info only) */
+ struct list_head unacked_list;
+
+- /** @unacked_lock: protect unacked_list */
++ /** @unacked_lock: protect unacked_list + &batadv_tp_receiver.last_recv */
+ spinlock_t unacked_lock;
+
+ /** @unacked_count: number of unacked entries */
+--
+2.53.0
+
--- /dev/null
+From 072ff1e2dee7e78ab8b308f299947774c2b38653 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:12:02 +0200
+Subject: batman-adv: tp_meter: restrict number of unacked list entries
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit e7c775110e1858e5a7471a23a9c9658c0af9df89 upstream.
+
+When the unacked_list is unbound, an attacker could send messages with
+small lengths and appropriated seqno + gaps to force the receiver to
+allocate more and more unacked_list entries. And the end either causing an
+out-of-memory situation or increase the management overhead for the (large)
+list that significant portions of CPU cycles are wasted in searching
+through the list.
+
+When limiting the list to a specific number, it is important to still
+correctly add a new entry to the list. But if the list became larger than
+the limit, the last entry of the list (with the highest seqno) must be
+dropped to still allow the earlier seqnos to finish and therefore to
+continue the process. Otherwise, the process might get stuck with too high
+seqnos which are not handled by batadv_tp_ack_unordered().
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+[ Switch to pre-splitted tp_vars structure names ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 23 ++++++++++++++++++++++-
+ net/batman-adv/types.h | 3 +++
+ 2 files changed, 25 insertions(+), 1 deletion(-)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index 10b8daca3a6154..e5387e8f33244a 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -87,6 +87,11 @@
+ #define BATADV_TP_PLEN (BATADV_TP_PACKET_LEN - ETH_HLEN - \
+ sizeof(struct batadv_unicast_packet))
+
++/**
++ * BATADV_TP_MAX_UNACKED - maximum number of packets a receiver didn't yet ack
++ */
++#define BATADV_TP_MAX_UNACKED 100
++
+ static u8 batadv_tp_prerandom[4096] __read_mostly;
+
+ /**
+@@ -1195,6 +1200,7 @@ static void batadv_tp_receiver_shutdown(struct timer_list *t)
+ list_for_each_entry_safe(un, safe, &tp_vars->unacked_list, list) {
+ list_del(&un->list);
+ kfree(un);
++ tp_vars->unacked_count--;
+ }
+ spin_unlock_bh(&tp_vars->unacked_lock);
+
+@@ -1308,6 +1314,7 @@ static bool batadv_tp_handle_out_of_order(struct batadv_tp_vars *tp_vars,
+ /* if the list is empty immediately attach this new object */
+ if (list_empty(&tp_vars->unacked_list)) {
+ list_add(&new->list, &tp_vars->unacked_list);
++ tp_vars->unacked_count++;
+ goto out;
+ }
+
+@@ -1338,12 +1345,24 @@ static bool batadv_tp_handle_out_of_order(struct batadv_tp_vars *tp_vars,
+ */
+ list_add(&new->list, &un->list);
+ added = true;
++ tp_vars->unacked_count++;
+ break;
+ }
+
+ /* received packet with smallest seqno out of order; add it to front */
+- if (!added)
++ if (!added) {
+ list_add(&new->list, &tp_vars->unacked_list);
++ tp_vars->unacked_count++;
++ }
++
++ /* remove the last (biggest) unacked seqno when list is too large */
++ if (tp_vars->unacked_count > BATADV_TP_MAX_UNACKED) {
++ un = list_last_entry(&tp_vars->unacked_list,
++ struct batadv_tp_unacked, list);
++ list_del(&un->list);
++ kfree(un);
++ tp_vars->unacked_count--;
++ }
+
+ out:
+ spin_unlock_bh(&tp_vars->unacked_lock);
+@@ -1380,6 +1399,7 @@ static void batadv_tp_ack_unordered(struct batadv_tp_vars *tp_vars)
+
+ list_del(&un->list);
+ kfree(un);
++ tp_vars->unacked_count--;
+ }
+ spin_unlock_bh(&tp_vars->unacked_lock);
+ }
+@@ -1430,6 +1450,7 @@ batadv_tp_init_recv(struct batadv_priv *bat_priv,
+
+ spin_lock_init(&tp_vars->unacked_lock);
+ INIT_LIST_HEAD(&tp_vars->unacked_list);
++ tp_vars->unacked_count = 0;
+
+ kref_get(&tp_vars->refcount);
+ timer_setup(&tp_vars->timer, batadv_tp_receiver_shutdown, 0);
+diff --git a/net/batman-adv/types.h b/net/batman-adv/types.h
+index 417d653021c7af..8b180d8245b2a6 100644
+--- a/net/batman-adv/types.h
++++ b/net/batman-adv/types.h
+@@ -1426,6 +1426,9 @@ struct batadv_tp_vars {
+ /** @unacked_lock: protect unacked_list */
+ spinlock_t unacked_lock;
+
++ /** @unacked_count: number of unacked entries */
++ size_t unacked_count;
++
+ /** @last_recv_time: time (jiffies) a msg was received */
+ unsigned long last_recv_time;
+
+--
+2.53.0
+
--- /dev/null
+From fde94418383277688ef60900ba712901678eefbe Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:12:06 +0200
+Subject: batman-adv: tt: don't merge change entries with different VIDs
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit f08e06c2d5c3e2434e7c773f2213f4a7dce6bc1e upstream.
+
+batadv_tt_local_event() merges/cancels events for the same client which
+would conflict or be duplicates. The matching of the queued events only
+compares the MAC address - the VLAN ID stored in each event is ignored.
+
+If a MAC would now appear on multiple VID, the two ADD change events (for
+VID 1 and VID 2) would be merged to a single vid event. The remote can
+therefore not calculate the correct TT table and desync. A full translation
+table exchange is required to recover from this state.
+
+A check of VID is therefore necessary to avoid such wrong merges/cancels.
+
+Cc: stable@kernel.org
+Fixes: c018ad3de61a ("batman-adv: add the VLAN ID attribute to the TT entry")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/translation-table.c | 3 +++
+ 1 file changed, 3 insertions(+)
+
+diff --git a/net/batman-adv/translation-table.c b/net/batman-adv/translation-table.c
+index 9f6e67771ffa80..acd8af4446671f 100644
+--- a/net/batman-adv/translation-table.c
++++ b/net/batman-adv/translation-table.c
+@@ -446,6 +446,9 @@ static void batadv_tt_local_event(struct batadv_priv *bat_priv,
+ if (!batadv_compare_eth(entry->change.addr, common->addr))
+ continue;
+
++ if (entry->change.vid != tt_change_node->change.vid)
++ continue;
++
+ del_op_entry = entry->change.flags & BATADV_TT_CLIENT_DEL;
+ if (del_op_requested != del_op_entry) {
+ /* DEL+ADD in the same orig interval have no effect and
+--
+2.53.0
+
--- /dev/null
+From 6f20158d28ae2ff9127aed59c0d5211b4455b63a Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:12:07 +0200
+Subject: batman-adv: tt: track roam count per VID
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 12407d5f61c2653a64f2ff4b22f3c267f8420ef1 upstream.
+
+batadv_tt_check_roam_count() is supposed to track roaming of a TT entry.
+But TT entries are for a MAC + VID. The VID was completely missed and thus
+leads to incorrect detection of ROAM counts when a client MAC exists in
+multiple VLANs.
+
+Cc: stable@kernel.org
+Fixes: c018ad3de61a ("batman-adv: add the VLAN ID attribute to the TT entry")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/translation-table.c | 9 +++++++--
+ net/batman-adv/types.h | 3 +++
+ 2 files changed, 10 insertions(+), 2 deletions(-)
+
+diff --git a/net/batman-adv/translation-table.c b/net/batman-adv/translation-table.c
+index acd8af4446671f..83dfd804a143ab 100644
+--- a/net/batman-adv/translation-table.c
++++ b/net/batman-adv/translation-table.c
+@@ -3442,6 +3442,7 @@ static void batadv_tt_roam_purge(struct batadv_priv *bat_priv)
+ * batadv_tt_check_roam_count() - check if a client has roamed too frequently
+ * @bat_priv: the bat priv with all the mesh interface information
+ * @client: mac address of the roaming client
++ * @vid: VLAN identifier
+ *
+ * This function checks whether the client already reached the
+ * maximum number of possible roaming phases. In this case the ROAMING_ADV
+@@ -3449,7 +3450,7 @@ static void batadv_tt_roam_purge(struct batadv_priv *bat_priv)
+ *
+ * Return: true if the ROAMING_ADV can be sent, false otherwise
+ */
+-static bool batadv_tt_check_roam_count(struct batadv_priv *bat_priv, u8 *client)
++static bool batadv_tt_check_roam_count(struct batadv_priv *bat_priv, u8 *client, u16 vid)
+ {
+ struct batadv_tt_roam_node *tt_roam_node;
+ bool ret = false;
+@@ -3462,6 +3463,9 @@ static bool batadv_tt_check_roam_count(struct batadv_priv *bat_priv, u8 *client)
+ if (!batadv_compare_eth(tt_roam_node->addr, client))
+ continue;
+
++ if (tt_roam_node->vid != vid)
++ continue;
++
+ if (batadv_has_timed_out(tt_roam_node->first_time,
+ BATADV_ROAMING_MAX_TIME))
+ continue;
+@@ -3483,6 +3487,7 @@ static bool batadv_tt_check_roam_count(struct batadv_priv *bat_priv, u8 *client)
+ atomic_set(&tt_roam_node->counter,
+ BATADV_ROAMING_MAX_COUNT - 1);
+ ether_addr_copy(tt_roam_node->addr, client);
++ tt_roam_node->vid = vid;
+
+ list_add(&tt_roam_node->list, &bat_priv->tt.roam_list);
+ ret = true;
+@@ -3519,7 +3524,7 @@ static void batadv_send_roam_adv(struct batadv_priv *bat_priv, u8 *client,
+ /* before going on we have to check whether the client has
+ * already roamed to us too many times
+ */
+- if (!batadv_tt_check_roam_count(bat_priv, client))
++ if (!batadv_tt_check_roam_count(bat_priv, client, vid))
+ goto out;
+
+ batadv_dbg(BATADV_DBG_TT, bat_priv,
+diff --git a/net/batman-adv/types.h b/net/batman-adv/types.h
+index 84de2570eac3ed..ef712ba4fff2cb 100644
+--- a/net/batman-adv/types.h
++++ b/net/batman-adv/types.h
+@@ -1912,6 +1912,9 @@ struct batadv_tt_roam_node {
+ /** @addr: mac address of the client in the roaming phase */
+ u8 addr[ETH_ALEN];
+
++ /** @vid: VLAN identifier */
++ u16 vid;
++
+ /**
+ * @counter: number of allowed roaming events per client within a single
+ * OGM interval (changes are committed with each OGM)
+--
+2.53.0
+
--- /dev/null
+From 79d578f38da153522da846e36f128a4a786c7524 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:12:10 +0200
+Subject: batman-adv: tvlv: avoid race of cifsnotfound handler state
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit edb557b2ba38fea2c5eb710cf366c797e187218c upstream.
+
+TVLV handlers can have the flag BATADV_TVLV_HANDLER_OGM_CIFNOTFND set to
+signal that the OGM handler should be called (with NULL for data) when the
+specific TVLV container was not found in the OGM. This is used by:
+
+* DAT
+* GW
+* Multicast (OGM + Tracker)
+
+The state whether the handler was executed was stored in the struct
+batadv_tvlv_handler. But the TVLV processing is started without any lock.
+Multiple parallel contexts processing TVLVs would therefore overwrite each
+others BATADV_TVLV_HANDLER_OGM_CALLED flag in the shared
+batadv_tvlv_handler.
+
+Drop the shared BATADV_TVLV_HANDLER_OGM_CALLED flag and instead determine,
+per TVLV buffer, whether a matching container was present by scanning the
+packet's buffer.
+
+Cc: stable@kernel.org
+Fixes: ef26157747d4 ("batman-adv: tvlv - basic infrastructure")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tvlv.c | 63 ++++++++++++++++++++++++++++++++++++++----
+ net/batman-adv/types.h | 7 -----
+ 2 files changed, 57 insertions(+), 13 deletions(-)
+
+diff --git a/net/batman-adv/tvlv.c b/net/batman-adv/tvlv.c
+index 63fb54024d1543..a91f1891747c0a 100644
+--- a/net/batman-adv/tvlv.c
++++ b/net/batman-adv/tvlv.c
+@@ -398,7 +398,6 @@ static int batadv_tvlv_call_handler(struct batadv_priv *bat_priv,
+ tvlv_handler->ogm_handler(bat_priv, orig_node,
+ BATADV_NO_FLAGS,
+ tvlv_value, tvlv_value_len);
+- tvlv_handler->flags |= BATADV_TVLV_HANDLER_OGM_CALLED;
+ break;
+ case BATADV_UNICAST_TVLV:
+ if (!skb)
+@@ -430,6 +429,48 @@ static int batadv_tvlv_call_handler(struct batadv_priv *bat_priv,
+ return NET_RX_SUCCESS;
+ }
+
++/**
++ * batadv_tvlv_containers_contain() - check if a tvlv buffer holds a container
++ * @tvlv_value: tvlv content
++ * @tvlv_value_len: tvlv content length
++ * @type: tvlv container type to look for
++ * @version: tvlv container version to look for
++ *
++ * Return: true if a container of the given type and version is present in the
++ * tvlv buffer, false otherwise.
++ */
++static bool batadv_tvlv_containers_contain(void *tvlv_value,
++ u16 tvlv_value_len, u8 type,
++ u8 version)
++{
++ struct batadv_tvlv_hdr *tvlv_hdr;
++ u16 tvlv_value_cont_len;
++
++ while (tvlv_value_len >= sizeof(*tvlv_hdr)) {
++ tvlv_hdr = tvlv_value;
++ tvlv_value_cont_len = ntohs(tvlv_hdr->len);
++ tvlv_value = tvlv_hdr + 1;
++ tvlv_value_len -= sizeof(*tvlv_hdr);
++
++ if (tvlv_value_cont_len > tvlv_value_len)
++ break;
++
++ /* the next tvlv header is accessed assuming (at least) 2-byte
++ * alignment, so it must start at an even offset.
++ */
++ if (tvlv_value_cont_len & 1)
++ break;
++
++ if (tvlv_hdr->type == type && tvlv_hdr->version == version)
++ return true;
++
++ tvlv_value = (u8 *)tvlv_value + tvlv_value_cont_len;
++ tvlv_value_len -= tvlv_value_cont_len;
++ }
++
++ return false;
++}
++
+ /**
+ * batadv_tvlv_containers_process() - parse the given tvlv buffer to call the
+ * appropriate handlers
+@@ -449,7 +490,9 @@ int batadv_tvlv_containers_process(struct batadv_priv *bat_priv,
+ struct sk_buff *skb, void *tvlv_value,
+ u16 tvlv_value_len)
+ {
++ u16 tvlv_value_start_len = tvlv_value_len;
+ struct batadv_tvlv_handler *tvlv_handler;
++ void *tvlv_value_start = tvlv_value;
+ struct batadv_tvlv_hdr *tvlv_hdr;
+ u16 tvlv_value_cont_len;
+ u8 cifnotfound = BATADV_TVLV_HANDLER_OGM_CIFNOTFND;
+@@ -493,12 +536,20 @@ int batadv_tvlv_containers_process(struct batadv_priv *bat_priv,
+ if (!tvlv_handler->ogm_handler)
+ continue;
+
+- if ((tvlv_handler->flags & BATADV_TVLV_HANDLER_OGM_CIFNOTFND) &&
+- !(tvlv_handler->flags & BATADV_TVLV_HANDLER_OGM_CALLED))
+- tvlv_handler->ogm_handler(bat_priv, orig_node,
+- cifnotfound, NULL, 0);
++ if (!(tvlv_handler->flags & BATADV_TVLV_HANDLER_OGM_CIFNOTFND))
++ continue;
+
+- tvlv_handler->flags &= ~BATADV_TVLV_HANDLER_OGM_CALLED;
++ /* if the corresponding container was present then the handler
++ * was already called from the loop above
++ */
++ if (batadv_tvlv_containers_contain(tvlv_value_start,
++ tvlv_value_start_len,
++ tvlv_handler->type,
++ tvlv_handler->version))
++ continue;
++
++ tvlv_handler->ogm_handler(bat_priv, orig_node,
++ cifnotfound, NULL, 0);
+ }
+ rcu_read_unlock();
+
+diff --git a/net/batman-adv/types.h b/net/batman-adv/types.h
+index ef712ba4fff2cb..ac4494f1b8e2a8 100644
+--- a/net/batman-adv/types.h
++++ b/net/batman-adv/types.h
+@@ -2245,13 +2245,6 @@ enum batadv_tvlv_handler_flags {
+ * will call this handler even if its type was not found (with no data)
+ */
+ BATADV_TVLV_HANDLER_OGM_CIFNOTFND = BIT(1),
+-
+- /**
+- * @BATADV_TVLV_HANDLER_OGM_CALLED: interval tvlv handling flag - the
+- * API marks a handler as being called, so it won't be called if the
+- * BATADV_TVLV_HANDLER_OGM_CIFNOTFND flag was set
+- */
+- BATADV_TVLV_HANDLER_OGM_CALLED = BIT(2),
+ };
+
+ #endif /* _NET_BATMAN_ADV_TYPES_H_ */
+--
+2.53.0
+
--- /dev/null
+From f2d6ca20d75da2c0d50989e4163b2522aac39a3e Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:12:09 +0200
+Subject: batman-adv: tvlv: enforce 2-byte alignment
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 32a6799255525d6ea4da0f7e9e0e521ad9560a46 upstream.
+
+The fields of an aggregated OGM(v2) are accessed assuming (at least) 2-byte
+alignment, so a following OGM must start at an even offset. As the header
+length is even, an odd tvlv_len would misalign it and trigger unaligned
+accesses on strict-alignment architectures.
+
+Such a misaligned TVLV/OGM/OGMv2 is not created by a normal participant in
+the mesh. Therefore, reject such malformed packets.
+
+Cc: stable@kernel.org
+Fixes: ef26157747d4 ("batman-adv: tvlv - basic infrastructure")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/bat_iv_ogm.c | 11 ++++++++++-
+ net/batman-adv/bat_v_ogm.c | 11 ++++++++++-
+ net/batman-adv/routing.c | 6 ++++++
+ net/batman-adv/tvlv.c | 6 ++++++
+ 4 files changed, 32 insertions(+), 2 deletions(-)
+
+diff --git a/net/batman-adv/bat_iv_ogm.c b/net/batman-adv/bat_iv_ogm.c
+index b8b1b997960a96..6e79f69c2fedec 100644
+--- a/net/batman-adv/bat_iv_ogm.c
++++ b/net/batman-adv/bat_iv_ogm.c
+@@ -311,14 +311,23 @@ batadv_iv_ogm_aggr_packet(int buff_pos, int packet_len,
+ const struct batadv_ogm_packet *ogm_packet)
+ {
+ int next_buff_pos = 0;
++ u16 tvlv_len;
+
+ /* check if there is enough space for the header */
+ next_buff_pos += buff_pos + sizeof(*ogm_packet);
+ if (next_buff_pos > packet_len)
+ return false;
+
++ tvlv_len = ntohs(ogm_packet->tvlv_len);
++
++ /* the fields of an aggregated OGM are accessed assuming (at least)
++ * 2-byte alignment, so a following OGM must start at an even offset.
++ */
++ if (tvlv_len & 1)
++ return false;
++
+ /* check if there is enough space for the optional TVLV */
+- next_buff_pos += ntohs(ogm_packet->tvlv_len);
++ next_buff_pos += tvlv_len;
+
+ return next_buff_pos <= packet_len;
+ }
+diff --git a/net/batman-adv/bat_v_ogm.c b/net/batman-adv/bat_v_ogm.c
+index 6852bf5da8c558..1f9b2d2b4831ce 100644
+--- a/net/batman-adv/bat_v_ogm.c
++++ b/net/batman-adv/bat_v_ogm.c
+@@ -849,14 +849,23 @@ batadv_v_ogm_aggr_packet(int buff_pos, int packet_len,
+ const struct batadv_ogm2_packet *ogm2_packet)
+ {
+ int next_buff_pos = 0;
++ u16 tvlv_len;
+
+ /* check if there is enough space for the header */
+ next_buff_pos += buff_pos + sizeof(*ogm2_packet);
+ if (next_buff_pos > packet_len)
+ return false;
+
++ tvlv_len = ntohs(ogm2_packet->tvlv_len);
++
++ /* the fields of an aggregated OGMv2 are accessed assuming (at least)
++ * 2-byte alignment, so a following OGMv2 must start at an even offset.
++ */
++ if (tvlv_len & 1)
++ return false;
++
+ /* check if there is enough space for the optional TVLV */
+- next_buff_pos += ntohs(ogm2_packet->tvlv_len);
++ next_buff_pos += tvlv_len;
+
+ return next_buff_pos <= packet_len;
+ }
+diff --git a/net/batman-adv/routing.c b/net/batman-adv/routing.c
+index 4483f8d9c75831..41951c7a1c50b2 100644
+--- a/net/batman-adv/routing.c
++++ b/net/batman-adv/routing.c
+@@ -1366,6 +1366,12 @@ int batadv_recv_mcast_packet(struct sk_buff *skb,
+ if (tvlv_buff_len > skb->len - hdr_size)
+ goto free_skb;
+
++ /* the fields of an multicast payload are accessed assuming (at least)
++ * 2-byte alignment, so a following packet must start at an even offset.
++ */
++ if (tvlv_buff_len & 1)
++ goto free_skb;
++
+ ret = batadv_tvlv_containers_process(bat_priv, BATADV_MCAST, NULL, skb,
+ tvlv_buff, tvlv_buff_len);
+ if (ret >= 0) {
+diff --git a/net/batman-adv/tvlv.c b/net/batman-adv/tvlv.c
+index cde798c82dcf13..63fb54024d1543 100644
+--- a/net/batman-adv/tvlv.c
++++ b/net/batman-adv/tvlv.c
+@@ -464,6 +464,12 @@ int batadv_tvlv_containers_process(struct batadv_priv *bat_priv,
+ if (tvlv_value_cont_len > tvlv_value_len)
+ break;
+
++ /* the next tvlv header is accessed assuming (at least) 2-byte
++ * alignment, so it must start at an even offset.
++ */
++ if (tvlv_value_cont_len & 1)
++ break;
++
+ tvlv_handler = batadv_tvlv_handler_get(bat_priv,
+ tvlv_hdr->type,
+ tvlv_hdr->version);
+--
+2.53.0
+
--- /dev/null
+From edc061f6f6c8712aa9256d46d7c0555f58b96e89 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:12:01 +0200
+Subject: batman-adv: v: prevent OGM aggregation on disabled hardif
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit d11c00b95b2a3b3934007fc003dccc6fdcc061ad upstream.
+
+When an interface gets disabled, the worker is correctly disabled by
+batadv_hardif_disable_interface() -> ... -> batadv_v_ogm_iface_disable().
+In this process, the skb aggr_list is also freed.
+
+But batadv_v_ogm_send_meshif() can still queue new skbs (via
+batadv_v_ogm_queue_on_if()) to the aggr_list. This will only stop after all
+cores can no longer find the RCU protected list of hard interfaces. These
+queued skbs will never be freed or consumed by batadv_v_ogm_aggr_work.
+
+The batadv_v_ogm_iface_disable() function must block
+batadv_v_ogm_queue_on_if() to avoid leak of skbs.
+
+Cc: stable@kernel.org
+Fixes: f89255a02f1d ("batman-adv: BATMAN_V: introduce per hard-iface OGMv2 queues")
+[ Context ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/bat_v.c | 1 +
+ net/batman-adv/bat_v_ogm.c | 12 ++++++++++++
+ net/batman-adv/types.h | 6 ++++++
+ 3 files changed, 19 insertions(+)
+
+diff --git a/net/batman-adv/bat_v.c b/net/batman-adv/bat_v.c
+index de94447142642e..17d2a1ccdce67a 100644
+--- a/net/batman-adv/bat_v.c
++++ b/net/batman-adv/bat_v.c
+@@ -817,6 +817,7 @@ void batadv_v_hardif_init(struct batadv_hard_iface *hard_iface)
+
+ hard_iface->bat_v.aggr_len = 0;
+ skb_queue_head_init(&hard_iface->bat_v.aggr_list);
++ hard_iface->bat_v.aggr_list_enabled = false;
+ INIT_DELAYED_WORK(&hard_iface->bat_v.aggr_wq,
+ batadv_v_ogm_aggr_work);
+ }
+diff --git a/net/batman-adv/bat_v_ogm.c b/net/batman-adv/bat_v_ogm.c
+index d66ca77b1aaa3c..6852bf5da8c558 100644
+--- a/net/batman-adv/bat_v_ogm.c
++++ b/net/batman-adv/bat_v_ogm.c
+@@ -252,11 +252,18 @@ static void batadv_v_ogm_queue_on_if(struct batadv_priv *bat_priv,
+ }
+
+ spin_lock_bh(&hard_iface->bat_v.aggr_list.lock);
++ if (!hard_iface->bat_v.aggr_list_enabled) {
++ kfree_skb(skb);
++ goto unlock;
++ }
++
+ if (!batadv_v_ogm_queue_left(skb, hard_iface))
+ batadv_v_ogm_aggr_send(bat_priv, hard_iface);
+
+ hard_iface->bat_v.aggr_len += batadv_v_ogm_len(skb);
+ __skb_queue_tail(&hard_iface->bat_v.aggr_list, skb);
++
++unlock:
+ spin_unlock_bh(&hard_iface->bat_v.aggr_list.lock);
+ }
+
+@@ -417,6 +424,10 @@ int batadv_v_ogm_iface_enable(struct batadv_hard_iface *hard_iface)
+ {
+ struct batadv_priv *bat_priv = netdev_priv(hard_iface->mesh_iface);
+
++ spin_lock_bh(&hard_iface->bat_v.aggr_list.lock);
++ hard_iface->bat_v.aggr_list_enabled = true;
++ spin_unlock_bh(&hard_iface->bat_v.aggr_list.lock);
++
+ batadv_v_ogm_start_queue_timer(hard_iface);
+ batadv_v_ogm_start_timer(bat_priv);
+
+@@ -432,6 +443,7 @@ void batadv_v_ogm_iface_disable(struct batadv_hard_iface *hard_iface)
+ cancel_delayed_work_sync(&hard_iface->bat_v.aggr_wq);
+
+ spin_lock_bh(&hard_iface->bat_v.aggr_list.lock);
++ hard_iface->bat_v.aggr_list_enabled = false;
+ batadv_v_ogm_aggr_list_free(hard_iface);
+ spin_unlock_bh(&hard_iface->bat_v.aggr_list.lock);
+ }
+diff --git a/net/batman-adv/types.h b/net/batman-adv/types.h
+index c9bd49d23547ef..417d653021c7af 100644
+--- a/net/batman-adv/types.h
++++ b/net/batman-adv/types.h
+@@ -130,6 +130,12 @@ struct batadv_hard_iface_bat_v {
+ /** @aggr_list: queue for to be aggregated OGM packets */
+ struct sk_buff_head aggr_list;
+
++ /**
++ * @aggr_list_enabled: aggr_list is active and new skbs can be
++ * enqueued. Protected by aggr_list.lock after initialization
++ */
++ bool aggr_list_enabled:1;
++
+ /** @aggr_len: size of the OGM aggregate (excluding ethernet header) */
+ unsigned int aggr_len;
+
+--
+2.53.0
+
selinux-fix-overlayfs-mmap-and-mprotect-access-check.patch
revert-pci-qcom-advertise-hotplug-slot-capability-wi.patch
lockd-fix-test-handling-when-not-all-permissions-are.patch
+batman-adv-tp_meter-keep-unacked-list-in-ascending-o.patch
+batman-adv-tp_meter-initialize-dup_acks-explicitly.patch
+batman-adv-tp_meter-initialize-dec_cwnd-explicitly.patch
+batman-adv-tp_meter-avoid-window-underflow.patch
+batman-adv-tp_meter-avoid-divide-by-zero-for-dec_cwn.patch
+batman-adv-tp_meter-fix-fast-recovery-precondition.patch
+batman-adv-tp_meter-handle-seqno-wrap-around-for-fas.patch
+batman-adv-tp_meter-add-only-finished-tp_vars-to-lis.patch
+batman-adv-bla-annotate-lasttime-access-with-read-wr.patch
+batman-adv-prevent-elp-transmission-interval-underfl.patch
+batman-adv-tp_meter-initialize-last_recv_time-during.patch
+batman-adv-gw-don-t-deselect-gateway-with-active-har.patch
+batman-adv-ensure-bcast-is-writable-before-modifying.patch
+batman-adv-fix-m-b-cast-csum-after-decrementing-ttl.patch
+batman-adv-frag-ensure-fragment-is-writable-before-m.patch
+batman-adv-frag-avoid-underflow-of-ttl.patch
+batman-adv-v-prevent-ogm-aggregation-on-disabled-har.patch
+batman-adv-tp_meter-restrict-number-of-unacked-list-.patch
+batman-adv-tp_meter-annotate-last_recv_time-access-w.patch
+batman-adv-tp_meter-prevent-parallel-modifications-o.patch
+batman-adv-tp_meter-handle-overlapping-packets.patch
+batman-adv-tt-don-t-merge-change-entries-with-differ.patch
+batman-adv-tt-track-roam-count-per-vid.patch
+batman-adv-dat-prevent-false-sharing-between-vlans.patch
+batman-adv-tvlv-enforce-2-byte-alignment.patch
+batman-adv-tvlv-avoid-race-of-cifsnotfound-handler-s.patch
--- /dev/null
+From cb5246a7ab87db30dafc9e71218e1f3315d6f916 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:23 +0200
+Subject: batman-adv: bla: annotate lasttime access with READ/WRITE_ONCE
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 98b0fb191c878a64cbaebfe231d96d57576acf8c upstream.
+
+The lasttime field for claim, backbone_gw, and loopdetect tracks the
+jiffies value of the most recent activity and is used to detect timeouts.
+These accesses are not consistently protected by a lock, so
+READ_ONCE/WRITE_ONCE must be used to prevent data races caused by compiler
+optimizations.
+
+Cc: stable@kernel.org
+Fixes: 23721387c409 ("batman-adv: add basic bridge loop avoidance code")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/bridge_loop_avoidance.c | 28 +++++++++++++-------------
+ 1 file changed, 14 insertions(+), 14 deletions(-)
+
+diff --git a/net/batman-adv/bridge_loop_avoidance.c b/net/batman-adv/bridge_loop_avoidance.c
+index cfb1eb25c6ac4d..ac12c06f11b89e 100644
+--- a/net/batman-adv/bridge_loop_avoidance.c
++++ b/net/batman-adv/bridge_loop_avoidance.c
+@@ -512,7 +512,7 @@ batadv_bla_get_backbone_gw(struct batadv_priv *bat_priv, const u8 *orig,
+ return NULL;
+
+ entry->vid = vid;
+- entry->lasttime = jiffies;
++ WRITE_ONCE(entry->lasttime, jiffies);
+ entry->crc = BATADV_BLA_CRC_INIT;
+ entry->bat_priv = bat_priv;
+ spin_lock_init(&entry->crc_lock);
+@@ -580,7 +580,7 @@ batadv_bla_update_own_backbone_gw(struct batadv_priv *bat_priv,
+ if (unlikely(!backbone_gw))
+ return;
+
+- backbone_gw->lasttime = jiffies;
++ WRITE_ONCE(backbone_gw->lasttime, jiffies);
+ batadv_backbone_gw_put(backbone_gw);
+ }
+
+@@ -714,7 +714,7 @@ static void batadv_bla_add_claim(struct batadv_priv *bat_priv,
+ ether_addr_copy(claim->addr, mac);
+ spin_lock_init(&claim->backbone_lock);
+ claim->vid = vid;
+- claim->lasttime = jiffies;
++ WRITE_ONCE(claim->lasttime, jiffies);
+ kref_get(&backbone_gw->refcount);
+ claim->backbone_gw = backbone_gw;
+ kref_init(&claim->refcount);
+@@ -736,7 +736,7 @@ static void batadv_bla_add_claim(struct batadv_priv *bat_priv,
+ return;
+ }
+ } else {
+- claim->lasttime = jiffies;
++ WRITE_ONCE(claim->lasttime, jiffies);
+ if (claim->backbone_gw == backbone_gw)
+ /* no need to register a new backbone */
+ goto claim_free_ref;
+@@ -769,7 +769,7 @@ static void batadv_bla_add_claim(struct batadv_priv *bat_priv,
+ spin_lock_bh(&backbone_gw->crc_lock);
+ backbone_gw->crc ^= crc16(0, claim->addr, ETH_ALEN);
+ spin_unlock_bh(&backbone_gw->crc_lock);
+- backbone_gw->lasttime = jiffies;
++ WRITE_ONCE(backbone_gw->lasttime, jiffies);
+
+ claim_free_ref:
+ batadv_claim_put(claim);
+@@ -858,7 +858,7 @@ static bool batadv_handle_announce(struct batadv_priv *bat_priv, u8 *an_addr,
+ return true;
+
+ /* handle as ANNOUNCE frame */
+- backbone_gw->lasttime = jiffies;
++ WRITE_ONCE(backbone_gw->lasttime, jiffies);
+ crc = ntohs(*((__force __be16 *)(&an_addr[4])));
+
+ batadv_dbg(BATADV_DBG_BLA, bat_priv,
+@@ -1253,7 +1253,7 @@ static void batadv_bla_purge_backbone_gw(struct batadv_priv *bat_priv, int now)
+ head, hash_entry) {
+ if (now)
+ goto purge_now;
+- if (!batadv_has_timed_out(backbone_gw->lasttime,
++ if (!batadv_has_timed_out(READ_ONCE(backbone_gw->lasttime),
+ BATADV_BLA_BACKBONE_TIMEOUT))
+ continue;
+
+@@ -1334,7 +1334,7 @@ static void batadv_bla_purge_claims(struct batadv_priv *bat_priv,
+ primary_if->net_dev->dev_addr))
+ goto skip;
+
+- if (!batadv_has_timed_out(claim->lasttime,
++ if (!batadv_has_timed_out(READ_ONCE(claim->lasttime),
+ BATADV_BLA_CLAIM_TIMEOUT))
+ goto skip;
+
+@@ -1494,7 +1494,7 @@ static void batadv_bla_periodic_work(struct work_struct *work)
+ eth_random_addr(bat_priv->bla.loopdetect_addr);
+ bat_priv->bla.loopdetect_addr[0] = 0xba;
+ bat_priv->bla.loopdetect_addr[1] = 0xbe;
+- bat_priv->bla.loopdetect_lasttime = jiffies;
++ WRITE_ONCE(bat_priv->bla.loopdetect_lasttime, jiffies);
+ atomic_set(&bat_priv->bla.loopdetect_next,
+ BATADV_BLA_LOOPDETECT_PERIODS);
+
+@@ -1515,7 +1515,7 @@ static void batadv_bla_periodic_work(struct work_struct *work)
+ primary_if->net_dev->dev_addr))
+ continue;
+
+- backbone_gw->lasttime = jiffies;
++ WRITE_ONCE(backbone_gw->lasttime, jiffies);
+
+ batadv_bla_send_announce(bat_priv, backbone_gw);
+ if (send_loopdetect)
+@@ -1900,7 +1900,7 @@ batadv_bla_loopdetect_check(struct batadv_priv *bat_priv, struct sk_buff *skb,
+ /* If the packet came too late, don't forward it on the mesh
+ * but don't consider that as loop. It might be a coincidence.
+ */
+- if (batadv_has_timed_out(bat_priv->bla.loopdetect_lasttime,
++ if (batadv_has_timed_out(READ_ONCE(bat_priv->bla.loopdetect_lasttime),
+ BATADV_BLA_LOOPDETECT_TIMEOUT))
+ return true;
+
+@@ -2016,7 +2016,7 @@ bool batadv_bla_rx(struct batadv_priv *bat_priv, struct sk_buff *skb,
+
+ if (own_claim) {
+ /* ... allow it in any case */
+- claim->lasttime = jiffies;
++ WRITE_ONCE(claim->lasttime, jiffies);
+ goto allow;
+ }
+
+@@ -2118,7 +2118,7 @@ bool batadv_bla_tx(struct batadv_priv *bat_priv, struct sk_buff *skb,
+ /* if yes, the client has roamed and we have
+ * to unclaim it.
+ */
+- if (batadv_has_timed_out(claim->lasttime, 100)) {
++ if (batadv_has_timed_out(READ_ONCE(claim->lasttime), 100)) {
+ /* only unclaim if the last claim entry is
+ * older than 100 ms to make sure we really
+ * have a roaming client here.
+@@ -2372,7 +2372,7 @@ batadv_bla_backbone_dump_entry(struct sk_buff *msg, u32 portid,
+ backbone_crc = backbone_gw->crc;
+ spin_unlock_bh(&backbone_gw->crc_lock);
+
+- msecs = jiffies_to_msecs(jiffies - backbone_gw->lasttime);
++ msecs = jiffies_to_msecs(jiffies - READ_ONCE(backbone_gw->lasttime));
+
+ if (is_own)
+ if (nla_put_flag(msg, BATADV_ATTR_BLA_OWN)) {
+--
+2.53.0
+
--- /dev/null
+From 576b43c844616c3e0a9ec5ae51ea4237bd071bb9 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:37 +0200
+Subject: batman-adv: dat: prevent false sharing between VLANs
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 20d7658b74169f86d4ac01b9185b3eadddf71f28 upstream.
+
+The local hash of DAT entries is supposed to be VLAN (VID) aware. But
+the adding to the hash and the search in the hash were not checking the VID
+information of the hash entries. The entries would therefore only be
+correctly separated when batadv_hash_dat() didn't select the same buckets
+for different VIDs.
+
+Cc: stable@kernel.org
+Fixes: be1db4f6615b ("batman-adv: make the Distributed ARP Table vlan aware")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/distributed-arp-table.c | 12 +++++++++---
+ 1 file changed, 9 insertions(+), 3 deletions(-)
+
+diff --git a/net/batman-adv/distributed-arp-table.c b/net/batman-adv/distributed-arp-table.c
+index c76bddbf816ada..92d90e352d1f41 100644
+--- a/net/batman-adv/distributed-arp-table.c
++++ b/net/batman-adv/distributed-arp-table.c
+@@ -215,10 +215,13 @@ static void batadv_dat_purge(struct work_struct *work)
+ */
+ static bool batadv_compare_dat(const struct hlist_node *node, const void *data2)
+ {
+- const void *data1 = container_of(node, struct batadv_dat_entry,
+- hash_entry);
++ const struct batadv_dat_entry *entry1;
++ const struct batadv_dat_entry *entry2;
+
+- return memcmp(data1, data2, sizeof(__be32)) == 0;
++ entry1 = container_of(node, struct batadv_dat_entry, hash_entry);
++ entry2 = data2;
++
++ return entry1->ip == entry2->ip && entry1->vid == entry2->vid;
+ }
+
+ /**
+@@ -345,6 +348,9 @@ batadv_dat_entry_hash_find(struct batadv_priv *bat_priv, __be32 ip,
+ if (dat_entry->ip != ip)
+ continue;
+
++ if (dat_entry->vid != vid)
++ continue;
++
+ if (!kref_get_unless_zero(&dat_entry->refcount))
+ continue;
+
+--
+2.53.0
+
--- /dev/null
+From 22d7710b0c5f12f47914a4a5df86a87adb7ed249 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:26 +0200
+Subject: batman-adv: ensure bcast is writable before modifying TTL
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 4cd6d3a4b96a8576f1fed8f9f9f17c2dc2978e0c upstream.
+
+Before batman-adv is allowed to write to an skb, it either has to have its
+own copy of the skb or used skb_cow() to ensure that the data part is not
+shared.
+
+The old implementation used a shared queue and created copies before
+attempting to write to it. But with the new implementation, the broadcast
+packet is already modified when it gets received. Potentially writing to
+shared buffers in this process.
+
+Adding a skb_cow() right before this operation avoids this and can at the
+same time prepare it for the modifications required to rebroadcast the
+packet.
+
+Cc: stable@kernel.org
+Fixes: 3f69339068f9 ("batman-adv: bcast: queue per interface, if needed")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/routing.c | 6 ++++++
+ 1 file changed, 6 insertions(+)
+
+diff --git a/net/batman-adv/routing.c b/net/batman-adv/routing.c
+index 163cd43c4821b6..747c3ff1c253b0 100644
+--- a/net/batman-adv/routing.c
++++ b/net/batman-adv/routing.c
+@@ -1198,6 +1198,12 @@ int batadv_recv_bcast_packet(struct sk_buff *skb,
+ if (batadv_is_my_mac(bat_priv, bcast_packet->orig))
+ goto free_skb;
+
++ /* create a copy of the skb, if needed, to modify it. */
++ if (skb_cow(skb, ETH_HLEN) < 0)
++ goto free_skb;
++
++ bcast_packet = (struct batadv_bcast_packet *)skb->data;
++
+ if (bcast_packet->ttl-- < 2)
+ goto free_skb;
+
+--
+2.53.0
+
--- /dev/null
+From 90abf691e4f1bb5b3ac05a61baaf6e3f35b945c2 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:27 +0200
+Subject: batman-adv: fix (m|b)cast csum after decrementing TTL
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit e728bbdf32660c8f32b8f5e8d09427a2c131ad60 upstream.
+
+The broadcast and multicast packets can be received at the same time by the
+local system and forwarded to other nodes. Both are simply decrementing the
+TTL at the beginning of the receive path - independent of chosen paths
+(receive/forward). But such a modification of the data conflicts with the
+hw csum. This is not a problem when the packet is directly forwarded but
+can cause errors in the local receive path.
+
+Such a problem can then trigger a "hw csum failure". The receiver path must
+therefore ensure that the csum is fixed for each modification of the
+payload before batadv_interface_rx() is reached.
+
+Since all batman-adv packet types with a ttl have it as u8 at offset 2, a
+helper can be used for all of them. But it is only used at the moment for
+batadv_bcast_packet and batadv_mcast_packet because they are the only ones
+which deliver the packet locally but unconditionally modify the TTL.
+
+Cc: stable@kernel.org
+Fixes: 3f69339068f9 ("batman-adv: bcast: queue per interface, if needed")
+Fixes: 07afe1ba288c ("batman-adv: mcast: implement multicast packet reception and forwarding")
+[ Context, Drop change for non-existing mcast handling ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/routing.c | 55 +++++++++++++++++++++++++++++++++++++++-
+ 1 file changed, 54 insertions(+), 1 deletion(-)
+
+diff --git a/net/batman-adv/routing.c b/net/batman-adv/routing.c
+index 747c3ff1c253b0..36e9df0cca0b2f 100644
+--- a/net/batman-adv/routing.c
++++ b/net/batman-adv/routing.c
+@@ -8,6 +8,7 @@
+ #include "main.h"
+
+ #include <linux/atomic.h>
++#include <linux/build_bug.h>
+ #include <linux/byteorder/generic.h>
+ #include <linux/compiler.h>
+ #include <linux/errno.h>
+@@ -205,6 +206,58 @@ bool batadv_check_management_packet(struct sk_buff *skb,
+ return true;
+ }
+
++/**
++ * batadv_skb_decrement_ttl() - decrement ttl in a batman-adv header, csum-safe
++ * @skb: the received packet with @skb->data pointing to the batman-adv header
++ *
++ * Supports the following packet types, all of which carry the TTL at offset 2:
++ *
++ * - batadv_ogm_packet
++ * - batadv_ogm2_packet
++ * - batadv_icmp_header
++ * - batadv_icmp_packet
++ * - batadv_icmp_tp_packet
++ * - batadv_icmp_packet_rr
++ * - batadv_unicast_packet
++ * - batadv_frag_packet
++ * - batadv_bcast_packet
++ * - batadv_mcast_packet
++ * - batadv_coded_packet
++ * - batadv_unicast_tvlv_packet
++ *
++ * Return: true if the packet may be forwarded (ttl decremented),
++ * false if it must be dropped (ttl would expire)
++ */
++static bool batadv_skb_decrement_ttl(struct sk_buff *skb)
++{
++ static const size_t ttl_offset = 2;
++ u8 *ttl_pos;
++
++ BUILD_BUG_ON(offsetof(struct batadv_ogm_packet, ttl) != ttl_offset);
++ BUILD_BUG_ON(offsetof(struct batadv_ogm2_packet, ttl) != ttl_offset);
++ BUILD_BUG_ON(offsetof(struct batadv_icmp_header, ttl) != ttl_offset);
++ BUILD_BUG_ON(offsetof(struct batadv_icmp_packet, ttl) != ttl_offset);
++ BUILD_BUG_ON(offsetof(struct batadv_icmp_tp_packet, ttl) != ttl_offset);
++ BUILD_BUG_ON(offsetof(struct batadv_icmp_packet_rr, ttl) != ttl_offset);
++ BUILD_BUG_ON(offsetof(struct batadv_unicast_packet, ttl) != ttl_offset);
++ BUILD_BUG_ON(offsetof(struct batadv_frag_packet, ttl) != ttl_offset);
++ BUILD_BUG_ON(offsetof(struct batadv_bcast_packet, ttl) != ttl_offset);
++ BUILD_BUG_ON(offsetof(struct batadv_coded_packet, ttl) != ttl_offset);
++ BUILD_BUG_ON(offsetof(struct batadv_unicast_tvlv_packet, ttl) != ttl_offset);
++
++ ttl_pos = skb->data + ttl_offset;
++
++ /* would expire on this hop -> drop, leave header + csum untouched */
++ if (*ttl_pos < 2)
++ return false;
++
++ skb_postpull_rcsum(skb, ttl_pos, 1);
++ (*ttl_pos)--;
++ skb_postpush_rcsum(skb, ttl_pos, 1);
++
++ return true;
++}
++
+ /**
+ * batadv_recv_my_icmp_packet() - receive an icmp packet locally
+ * @bat_priv: the bat priv with all the soft interface information
+@@ -1204,7 +1257,7 @@ int batadv_recv_bcast_packet(struct sk_buff *skb,
+
+ bcast_packet = (struct batadv_bcast_packet *)skb->data;
+
+- if (bcast_packet->ttl-- < 2)
++ if (!batadv_skb_decrement_ttl(skb))
+ goto free_skb;
+
+ orig_node = batadv_orig_hash_find(bat_priv, bcast_packet->orig);
+--
+2.53.0
+
--- /dev/null
+From 99ddece5a90d685eedd734c45dfeaa6c8d1d4b99 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:29 +0200
+Subject: batman-adv: frag: avoid underflow of TTL
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 493d9d2528e1a09b090e4b37f0f553def7bd5ce9 upstream.
+
+Packets with a TTL are using it to limit the amount of time this packet can
+be forwarded. But for batadv_frag_packet, the TTL was always only reduced
+but it was never evaluated. It could even underflow without any effect.
+
+Check the TTL in batadv_frag_skb_fwd() before attempting to prepare it for
+forwarding. This keeps it in sync with the not fragmented unicast packet.
+
+Cc: stable@kernel.org
+Fixes: 610bfc6bc99b ("batman-adv: Receive fragmented packets and merge")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/fragmentation.c | 7 +++++++
+ 1 file changed, 7 insertions(+)
+
+diff --git a/net/batman-adv/fragmentation.c b/net/batman-adv/fragmentation.c
+index fbf030c57ac04d..0138b0953e10b5 100644
+--- a/net/batman-adv/fragmentation.c
++++ b/net/batman-adv/fragmentation.c
+@@ -420,6 +420,13 @@ bool batadv_frag_skb_fwd(struct sk_buff *skb,
+ */
+ total_size = ntohs(packet->total_size);
+ if (total_size > neigh_node->if_incoming->net_dev->mtu) {
++ if (packet->ttl < 2) {
++ kfree_skb(skb);
++ *rx_result = NET_RX_DROP;
++ ret = true;
++ goto out;
++ }
++
+ if (skb_cow(skb, ETH_HLEN) < 0) {
+ kfree_skb(skb);
+ *rx_result = NET_RX_DROP;
+--
+2.53.0
+
--- /dev/null
+From d88f6965e30135818c4dc92c84718761628e9a1a Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:28 +0200
+Subject: batman-adv: frag: ensure fragment is writable before modifying TTL
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit b7293c6e8c15b2db77809b25cf8389e35331b27a upstream.
+
+Before batman-adv is allowed to write to an skb, it either has to have its
+own copy of the skb or use skb_cow() to ensure that the data part is not
+shared. But batadv_frag_skb_fwd() modifies the TTL even when it is shared.
+
+Adding a skb_cow() right before this operation avoids this and can at the
+same time prepare it for the modifications required to forward the
+fragment.
+
+Cc: stable@kernel.org
+Fixes: 610bfc6bc99b ("batman-adv: Receive fragmented packets and merge")
+[ Context ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/fragmentation.c | 15 ++++++++++++++-
+ net/batman-adv/fragmentation.h | 3 ++-
+ net/batman-adv/routing.c | 3 +--
+ 3 files changed, 17 insertions(+), 4 deletions(-)
+
+diff --git a/net/batman-adv/fragmentation.c b/net/batman-adv/fragmentation.c
+index 4c193194034155..fbf030c57ac04d 100644
+--- a/net/batman-adv/fragmentation.c
++++ b/net/batman-adv/fragmentation.c
+@@ -385,6 +385,8 @@ bool batadv_frag_skb_buffer(struct sk_buff **skb,
+ * @skb: skb to forward
+ * @recv_if: interface that the skb is received on
+ * @orig_node_src: originator that the skb is received from
++ * @rx_result: set to NET_RX_SUCCESS when the fragment was forwarded and
++ * NET_RX_DROP when it was dropped; only valid when true is returned
+ *
+ * Look up the next-hop of the fragments payload and check if the merged packet
+ * will exceed the MTU towards the next-hop. If so, the fragment is forwarded
+@@ -394,7 +396,8 @@ bool batadv_frag_skb_buffer(struct sk_buff **skb,
+ */
+ bool batadv_frag_skb_fwd(struct sk_buff *skb,
+ struct batadv_hard_iface *recv_if,
+- struct batadv_orig_node *orig_node_src)
++ struct batadv_orig_node *orig_node_src,
++ int *rx_result)
+ {
+ struct batadv_priv *bat_priv = netdev_priv(recv_if->soft_iface);
+ struct batadv_orig_node *orig_node_dst;
+@@ -417,12 +420,22 @@ bool batadv_frag_skb_fwd(struct sk_buff *skb,
+ */
+ total_size = ntohs(packet->total_size);
+ if (total_size > neigh_node->if_incoming->net_dev->mtu) {
++ if (skb_cow(skb, ETH_HLEN) < 0) {
++ kfree_skb(skb);
++ *rx_result = NET_RX_DROP;
++ ret = true;
++ goto out;
++ }
++
++ packet = (struct batadv_frag_packet *)skb->data;
++
+ batadv_inc_counter(bat_priv, BATADV_CNT_FRAG_FWD);
+ batadv_add_counter(bat_priv, BATADV_CNT_FRAG_FWD_BYTES,
+ skb->len + ETH_HLEN);
+
+ packet->ttl--;
+ batadv_send_unicast_skb(skb, neigh_node);
++ *rx_result = NET_RX_SUCCESS;
+ ret = true;
+ }
+
+diff --git a/net/batman-adv/fragmentation.h b/net/batman-adv/fragmentation.h
+index dbf0871f870303..51e281027ab630 100644
+--- a/net/batman-adv/fragmentation.h
++++ b/net/batman-adv/fragmentation.h
+@@ -19,7 +19,8 @@ void batadv_frag_purge_orig(struct batadv_orig_node *orig,
+ bool (*check_cb)(struct batadv_frag_table_entry *));
+ bool batadv_frag_skb_fwd(struct sk_buff *skb,
+ struct batadv_hard_iface *recv_if,
+- struct batadv_orig_node *orig_node_src);
++ struct batadv_orig_node *orig_node_src,
++ int *rx_result);
+ bool batadv_frag_skb_buffer(struct sk_buff **skb,
+ struct batadv_orig_node *orig_node);
+ int batadv_frag_send_packet(struct sk_buff *skb,
+diff --git a/net/batman-adv/routing.c b/net/batman-adv/routing.c
+index 36e9df0cca0b2f..ec278f73805e95 100644
+--- a/net/batman-adv/routing.c
++++ b/net/batman-adv/routing.c
+@@ -1174,10 +1174,9 @@ int batadv_recv_frag_packet(struct sk_buff *skb,
+
+ /* Route the fragment if it is not for us and too big to be merged. */
+ if (!batadv_is_my_mac(bat_priv, frag_packet->dest) &&
+- batadv_frag_skb_fwd(skb, recv_if, orig_node_src)) {
++ batadv_frag_skb_fwd(skb, recv_if, orig_node_src, &ret)) {
+ /* skb was consumed */
+ skb = NULL;
+- ret = NET_RX_SUCCESS;
+ goto put_orig_node;
+ }
+
+--
+2.53.0
+
--- /dev/null
+From 3b0f53c4fa6464b2ddd81bf83f61e7d974ab161b Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:24 +0200
+Subject: batman-adv: prevent ELP transmission interval underflow
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 5e50d4b8ae3ea622122d3c6a38d7f6fe68dfddca upstream.
+
+batadv_v_elp_start_timer() enqeues a delayed work. The time when it starts
+is randomly chosen between (elp_interval - BATADV_JITTER) and
+(elp_interval + BATADV_JITTER). The configured elp_interval must therefore
+be larger or equal to BATADV_JITTER to avoid that it causes an underflow of
+the unsigned integer. If this would happen, then a "fast" ELP interval
+would turn into a "day long" delay.
+
+At the same time, it must not be larger than the maximum value the variable
+can store.
+
+Cc: stable@kernel.org
+Fixes: a10800829040 ("batman-adv: Add elp_interval hardif genl configuration")
+[ Context ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/netlink.c | 8 +++++++-
+ 1 file changed, 7 insertions(+), 1 deletion(-)
+
+diff --git a/net/batman-adv/netlink.c b/net/batman-adv/netlink.c
+index 0c64d81a776174..03ba6282af91a3 100644
+--- a/net/batman-adv/netlink.c
++++ b/net/batman-adv/netlink.c
+@@ -938,9 +938,15 @@ static int batadv_netlink_set_hardif(struct sk_buff *skb,
+ #ifdef CONFIG_BATMAN_ADV_BATMAN_V
+
+ if (info->attrs[BATADV_ATTR_ELP_INTERVAL]) {
++ u32 elp_interval;
++
+ attr = info->attrs[BATADV_ATTR_ELP_INTERVAL];
++ elp_interval = nla_get_u32(attr);
++
++ elp_interval = min_t(u32, elp_interval, INT_MAX);
++ elp_interval = max_t(u32, elp_interval, BATADV_JITTER);
+
+- atomic_set(&hard_iface->bat_v.elp_interval, nla_get_u32(attr));
++ atomic_set(&hard_iface->bat_v.elp_interval, elp_interval);
+ }
+
+ if (info->attrs[BATADV_ATTR_THROUGHPUT_OVERRIDE]) {
+--
+2.53.0
+
--- /dev/null
+From ce99a933cd31ed38056bbb10c07429de3097d142 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:22 +0200
+Subject: batman-adv: tp_meter: add only finished tp_vars to lists
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 15ccbf685222274f5add1387af58c2a41a95f81e upstream.
+
+When the receiver variables (aka "session") are initialized, then they are
+added to the list of sessions before the timer is set up. A RCU protected
+reader could therefore find the entry and run mod_setup before
+batadv_tp_init_recv() finished the timer initialization.
+
+The same is true for batadv_tp_start(), which must first initialize the
+finish_work and the test_length to avoid a similar problem.
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 18 +++++++++---------
+ 1 file changed, 9 insertions(+), 9 deletions(-)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index 089ac7cc9fbebc..2bba53fc6da5c0 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -1096,21 +1096,21 @@ void batadv_tp_start(struct batadv_priv *bat_priv, const u8 *dst,
+ tp_vars->prerandom_offset = 0;
+ spin_lock_init(&tp_vars->prerandom_lock);
+
+- kref_get(&tp_vars->refcount);
+- hlist_add_head_rcu(&tp_vars->list, &bat_priv->tp_list);
+- spin_unlock_bh(&bat_priv->tp_list_lock);
+-
+ tp_vars->test_length = test_length;
+ if (!tp_vars->test_length)
+ tp_vars->test_length = BATADV_TP_DEF_TEST_LENGTH;
+
++ /* init work item for finished tp tests */
++ INIT_DELAYED_WORK(&tp_vars->finish_work, batadv_tp_sender_finish);
++
++ kref_get(&tp_vars->refcount);
++ hlist_add_head_rcu(&tp_vars->list, &bat_priv->tp_list);
++ spin_unlock_bh(&bat_priv->tp_list_lock);
++
+ batadv_dbg(BATADV_DBG_TP_METER, bat_priv,
+ "Meter: starting throughput meter towards %pM (length=%ums)\n",
+ dst, test_length);
+
+- /* init work item for finished tp tests */
+- INIT_DELAYED_WORK(&tp_vars->finish_work, batadv_tp_sender_finish);
+-
+ /* start tp kthread. This way the write() call issued from userspace can
+ * happily return and avoid to block
+ */
+@@ -1430,10 +1430,10 @@ batadv_tp_init_recv(struct batadv_priv *bat_priv,
+ INIT_LIST_HEAD(&tp_vars->unacked_list);
+
+ kref_get(&tp_vars->refcount);
+- hlist_add_head_rcu(&tp_vars->list, &bat_priv->tp_list);
++ timer_setup(&tp_vars->timer, batadv_tp_receiver_shutdown, 0);
+
+ kref_get(&tp_vars->refcount);
+- timer_setup(&tp_vars->timer, batadv_tp_receiver_shutdown, 0);
++ hlist_add_head_rcu(&tp_vars->list, &bat_priv->tp_list);
+
+ batadv_tp_reset_receiver_timer(tp_vars);
+
+--
+2.53.0
+
--- /dev/null
+From 9df041c16713653867e3f5fd61f2c8343fbbbec7 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:32 +0200
+Subject: batman-adv: tp_meter: annotate last_recv_time access with
+ READ/WRITE_ONCE
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit d67c728f07fca2ee6ffdc6dd4421cf2e8691f4d1 upstream.
+
+The last_recv_time field for batadv_tp_receiver tracks the jiffies value of
+the most recent activity and is used to detect timeouts. These accesses are
+not consistently protected by a lock, so READ_ONCE/WRITE_ONCE must be used
+to prevent data races caused by compiler optimizations.
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 8 ++++----
+ 1 file changed, 4 insertions(+), 4 deletions(-)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index 638dd438a6c052..b882919a868ca0 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -1183,7 +1183,7 @@ static void batadv_tp_receiver_shutdown(struct timer_list *t)
+ bat_priv = tp_vars->bat_priv;
+
+ /* if there is recent activity rearm the timer */
+- if (!batadv_has_timed_out(tp_vars->last_recv_time,
++ if (!batadv_has_timed_out(READ_ONCE(tp_vars->last_recv_time),
+ BATADV_TP_RECV_TIMEOUT)) {
+ /* reset the receiver shutdown timer */
+ batadv_tp_reset_receiver_timer(tp_vars);
+@@ -1424,7 +1424,7 @@ batadv_tp_init_recv(struct batadv_priv *bat_priv,
+ tp_vars = batadv_tp_list_find_session(bat_priv, icmp->orig,
+ icmp->session, BATADV_TP_RECEIVER);
+ if (tp_vars) {
+- tp_vars->last_recv_time = jiffies;
++ WRITE_ONCE(tp_vars->last_recv_time, jiffies);
+ goto out_unlock;
+ }
+
+@@ -1455,7 +1455,7 @@ batadv_tp_init_recv(struct batadv_priv *bat_priv,
+ kref_get(&tp_vars->refcount);
+ timer_setup(&tp_vars->timer, batadv_tp_receiver_shutdown, 0);
+
+- tp_vars->last_recv_time = jiffies;
++ WRITE_ONCE(tp_vars->last_recv_time, jiffies);
+
+ kref_get(&tp_vars->refcount);
+ hlist_add_head_rcu(&tp_vars->list, &bat_priv->tp_list);
+@@ -1506,7 +1506,7 @@ static void batadv_tp_recv_msg(struct batadv_priv *bat_priv,
+ goto out;
+ }
+
+- tp_vars->last_recv_time = jiffies;
++ WRITE_ONCE(tp_vars->last_recv_time, jiffies);
+ }
+
+ /* if the packet is a duplicate, it may be the case that an ACK has been
+--
+2.53.0
+
--- /dev/null
+From dc86aea78d0950a0672e883560e39a401039ba94 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:19 +0200
+Subject: batman-adv: tp_meter: avoid divide-by-zero for dec_cwnd
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 33ccd52f3cc9ed46ce395199f89aa3234dc83314 upstream.
+
+The cwnd is always MSS <= cwnd <= 0x20000000. But the calculation in
+batadv_tp_update_cwnd() assumes unsigned 32 bit arithmetics.
+
+ ((mss * 8) ** 2) / (cwnd * 8)
+
+In case cwnd is actually 0x20000000, it will be shifted by 3 bit to the
+left end up at 0x100000000 or U32_MAX + 1. It will therefore wrap around
+and be 0 - resulting in:
+
+ ((mss * 8) ** 2) / 0
+
+This is of course invalid and cannot be calculated. The calculation should
+must be simplified to avoid this overflow:
+
+ (mss ** 2) * 8 / cwnd
+
+It will keep the precision enhancement from the scaling (by 8) but avoid
+the overflow in the divisor.
+
+In theory, there could still be an overflow in the dividend. It is at the
+moment fixed to BATADV_TP_PLEN in batadv_tp_recv_ack() - so it is not an
+imminent problem. But allowing it to use the whole u32 bit range, would
+mean that it can still use up to 67 bits. To keep this calculation safe for
+32 bit arithmetic, mss must never use more than floor((32 - 3) / 2) bits -
+or in other words: must never be larger than 16383.
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 5 ++++-
+ 1 file changed, 4 insertions(+), 1 deletion(-)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index 0fdcafca3aa02b..4ff80e4214ff0a 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -154,9 +154,12 @@ static void batadv_tp_update_cwnd(struct batadv_tp_vars *tp_vars, u32 mss)
+ return;
+ }
+
++ /* prevent overflow in (mss * mss) << 3 */
++ mss = min_t(u32, mss, (1U << 14) - 1);
++
+ /* increment CWND at least of 1 (section 3.1 of RFC5681) */
+ tp_vars->dec_cwnd += max_t(u32, 1U << 3,
+- ((mss * mss) << 6) / (tp_vars->cwnd << 3));
++ ((mss * mss) << 3) / tp_vars->cwnd);
+ if (tp_vars->dec_cwnd < (mss << 3)) {
+ spin_unlock_bh(&tp_vars->cwnd_lock);
+ return;
+--
+2.53.0
+
--- /dev/null
+From 2da25fca733480ae48254e672697397abd827735 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:18 +0200
+Subject: batman-adv: tp_meter: avoid window underflow
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 765947b81fb54b6ebb0bc1cfe55c0fa399e002b8 upstream.
+
+In batadv_tp_avail(), win_left is calculated with 32-bit unsigned
+arithmetic: win_left = win_limit - tp_vars->last_sent;
+
+During Fast Recovery, cwnd is inflated and last_sent advances rapidly. When
+Fast Recovery ends, cwnd drops abruptly back to ss_threshold. If the newly
+shrunk win_limit is less than last_sent, the unsigned subtraction will
+underflow, wrapping to a massive positive value. Instead of returning that
+the window is full (unavailable), it returns that the sender can continue
+sending.
+
+To handle this situation, it must be checked whether the windows end
+sequence number (win_limit) has to be compared with the last sent sequence
+number. If it would be before the last sent sequence number, then more acks
+are needed before the transmission can be started again.
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 7 ++++++-
+ 1 file changed, 6 insertions(+), 1 deletion(-)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index f6ccb639744a2a..0fdcafca3aa02b 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -817,10 +817,15 @@ static void batadv_tp_recv_ack(struct batadv_priv *bat_priv,
+ static bool batadv_tp_avail(struct batadv_tp_vars *tp_vars,
+ size_t payload_len)
+ {
++ u32 last_sent = READ_ONCE(tp_vars->last_sent);
+ u32 win_left, win_limit;
+
+ win_limit = atomic_read(&tp_vars->last_acked) + tp_vars->cwnd;
+- win_left = win_limit - tp_vars->last_sent;
++
++ if (batadv_seq_before(last_sent, win_limit))
++ win_left = win_limit - last_sent;
++ else
++ win_left = 0;
+
+ return win_left >= payload_len;
+ }
+--
+2.53.0
+
--- /dev/null
+From 6509380ba93875ee13c52d53add68c9d4ee8d87d Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:20 +0200
+Subject: batman-adv: tp_meter: fix fast recovery precondition
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 2b0d08f08ed3b2174f05c43089ec65f3543a025b upstream.
+
+The fast recovery precondition checks if the recover (initialized to
+BATADV_TP_FIRST_SEQ) is bigger than the received ack. But since recover is
+only updated when this check is successful, it will never enter the fast
+recovery mode.
+
+According to RFC6582 Section 3.2 step 2, the check should actually be
+different:
+
+> When the third duplicate ACK is received, the TCP sender first
+> checks the value of recover to see if the Cumulative
+> Acknowledgment field covers more than recover
+
+The precondition must therefore check if recover is smaller than the
+received ack - basically swapping the operands of the current check.
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index 4ff80e4214ff0a..c79352cfddc4ae 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -733,7 +733,7 @@ static void batadv_tp_recv_ack(struct batadv_priv *bat_priv,
+ if (atomic_read(&tp_vars->dup_acks) != 3)
+ goto out;
+
+- if (recv_ack >= tp_vars->recover)
++ if (tp_vars->recover >= recv_ack)
+ goto out;
+
+ /* if this is the third duplicate ACK do Fast Retransmit */
+--
+2.53.0
+
--- /dev/null
+From 8d52b3bc303833fc0c728d4bc76a15608b93babd Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:34 +0200
+Subject: batman-adv: tp_meter: handle overlapping packets
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit cbde75c38b21f022891525078622587ad557b7c1 upstream.
+
+If the size of the packets would change during the transmission, it could
+happen that some retries of packets are overlapping. In this case, precise
+comparisons of sequence numbers by the receiver would be wrong. It is then
+necessary to check if the start sequence number to the end sequence number
+("seqno + length") would contain a new range.
+
+If this is the case then this is enough to accept this packet. In all other
+cases, the packet still has to be dropped (and not acked).
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+[ Switch to pre-splitted tp_vars structure names ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 25 +++++++++++--------------
+ 1 file changed, 11 insertions(+), 14 deletions(-)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index f745bc09a04963..50b83e0438a07b 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -1284,7 +1284,8 @@ static int batadv_tp_send_ack(struct batadv_priv *bat_priv, const u8 *dst,
+ /**
+ * batadv_tp_handle_out_of_order() - store an out of order packet
+ * @tp_vars: the private data of the current TP meter session
+- * @skb: the buffer containing the received packet
++ * @seqno: sequence number of new received packet
++ * @payload_len: length of the received packet
+ *
+ * Store the out of order packet in the unacked list for late processing. This
+ * packets are kept in this list so that they can be ACKed at once as soon as
+@@ -1293,22 +1294,17 @@ static int batadv_tp_send_ack(struct batadv_priv *bat_priv, const u8 *dst,
+ * Return: true if the packed has been successfully processed, false otherwise
+ */
+ static bool batadv_tp_handle_out_of_order(struct batadv_tp_vars *tp_vars,
+- const struct sk_buff *skb)
++ u32 seqno, u32 payload_len)
+ __must_hold(&tp_vars->unacked_lock)
+ {
+- const struct batadv_icmp_tp_packet *icmp;
+ struct batadv_tp_unacked *un, *new;
+- u32 payload_len;
+ bool added = false;
+
+ new = kmalloc(sizeof(*new), GFP_ATOMIC);
+ if (unlikely(!new))
+ return false;
+
+- icmp = (struct batadv_icmp_tp_packet *)skb->data;
+-
+- new->seqno = ntohl(icmp->seqno);
+- payload_len = skb->len - sizeof(struct batadv_unicast_packet);
++ new->seqno = seqno;
+ new->len = payload_len;
+
+ /* if the list is empty immediately attach this new object */
+@@ -1476,7 +1472,7 @@ static void batadv_tp_recv_msg(struct batadv_priv *bat_priv,
+ {
+ const struct batadv_icmp_tp_packet *icmp;
+ struct batadv_tp_vars *tp_vars;
+- size_t packet_size;
++ u32 payload_len;
+ u32 to_ack;
+ u32 seqno;
+
+@@ -1511,15 +1507,17 @@ static void batadv_tp_recv_msg(struct batadv_priv *bat_priv,
+ /* if the packet is a duplicate, it may be the case that an ACK has been
+ * lost. Resend the ACK
+ */
+- if (batadv_seq_before(seqno, tp_vars->last_recv))
++ payload_len = skb->len - sizeof(struct batadv_unicast_packet);
++ to_ack = seqno + payload_len;
++ if (batadv_seq_before(to_ack, tp_vars->last_recv))
+ goto send_ack;
+
+ /* if the packet is out of order enqueue it */
+- if (ntohl(icmp->seqno) != tp_vars->last_recv) {
++ if (batadv_seq_before(tp_vars->last_recv, seqno)) {
+ /* exit immediately (and do not send any ACK) if the packet has
+ * not been enqueued correctly
+ */
+- if (!batadv_tp_handle_out_of_order(tp_vars, skb)) {
++ if (!batadv_tp_handle_out_of_order(tp_vars, seqno, payload_len)) {
+ spin_unlock_bh(&tp_vars->unacked_lock);
+ goto out;
+ }
+@@ -1529,8 +1527,7 @@ static void batadv_tp_recv_msg(struct batadv_priv *bat_priv,
+ }
+
+ /* if everything was fine count the ACKed bytes */
+- packet_size = skb->len - sizeof(struct batadv_unicast_packet);
+- tp_vars->last_recv += packet_size;
++ tp_vars->last_recv = to_ack;
+
+ /* check if this ordered message filled a gap.... */
+ batadv_tp_ack_unordered(tp_vars);
+--
+2.53.0
+
--- /dev/null
+From e9b6e7ba8f3dbd6c4e1d47d35900a8eead4cabe2 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:21 +0200
+Subject: batman-adv: tp_meter: handle seqno wrap-around for fast recovery
+ detection
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit f54c85ed42a1b27a516cf2a4728f5a612b799e07 upstream.
+
+The recover variable and the last_sent sequence number are initialized on
+purpose as a really high value which will wrap-around after the first 2000
+bytes. The fast recovery precondition must therefore not use simple integer
+comparisons but use helpers which are aware of the sequence number
+wrap-arounds.
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index c79352cfddc4ae..089ac7cc9fbebc 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -733,7 +733,7 @@ static void batadv_tp_recv_ack(struct batadv_priv *bat_priv,
+ if (atomic_read(&tp_vars->dup_acks) != 3)
+ goto out;
+
+- if (tp_vars->recover >= recv_ack)
++ if (!batadv_seq_before(tp_vars->recover, recv_ack))
+ goto out;
+
+ /* if this is the third duplicate ACK do Fast Retransmit */
+--
+2.53.0
+
--- /dev/null
+From 6145606fe38b320c24ea6660a8a00b929abfe0c2 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:17 +0200
+Subject: batman-adv: tp_meter: initialize dec_cwnd explicitly
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit febfb1b86224489535312296ecfa3d4bf467f339 upstream.
+
+When batadv_tp_update_cwnd() is called, dec_cwnd is increased. But dec_cwnd
+is only initialixed (to 0) when a duplicate Ack was received or when cwnd
+is below the ss_threshold.
+
+Just initialize the cwnd during the initialization to avoid any potential
+access of uninitialized data.
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index 750e5e9d3dc9d6..f6ccb639744a2a 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -1055,6 +1055,8 @@ void batadv_tp_start(struct batadv_priv *bat_priv, const u8 *dst,
+ * soft_interface, hence its MTU
+ */
+ tp_vars->cwnd = BATADV_TP_PLEN * 3;
++ tp_vars->dec_cwnd = 0;
++
+ /* at the beginning initialise the SS threshold to the biggest possible
+ * window size, hence the AWND size
+ */
+--
+2.53.0
+
--- /dev/null
+From 3be63c0302467f403a076aaa413d5835c2333034 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:16 +0200
+Subject: batman-adv: tp_meter: initialize dup_acks explicitly
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit b2b68b32a715e0328662801576974aa37b942b00 upstream.
+
+When an ack with a sequence number equal to the last_acked is received, the
+dup_acks counter is increased to decide whether fast retransmit should be
+performed. Only when the sequence numbers are not equal, the dup_acks is
+set to the initial value (0).
+
+But if the initial packet would have the sequence number
+BATADV_TP_FIRST_SEQ, dup_acks would not be initialized and atomic_inc would
+operate on an undefined starting value. It is therefore required to have it
+explicitly initialized during the start of the sender session.
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index f350a60e6c76b9..750e5e9d3dc9d6 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -1045,6 +1045,7 @@ void batadv_tp_start(struct batadv_priv *bat_priv, const u8 *dst,
+ tp_vars->icmp_uid = icmp_uid;
+
+ tp_vars->last_sent = BATADV_TP_FIRST_SEQ;
++ atomic_set(&tp_vars->dup_acks, 0);
+ atomic_set(&tp_vars->last_acked, BATADV_TP_FIRST_SEQ);
+ tp_vars->fast_recovery = false;
+ tp_vars->recover = BATADV_TP_FIRST_SEQ;
+--
+2.53.0
+
--- /dev/null
+From 1a12b4e88d4defb3b9b531da9d319d123ffc9c46 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:25 +0200
+Subject: batman-adv: tp_meter: initialize last_recv_time during init
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 811cb00fa8cdc3f0a7f6eefc000a6888367c8c8f upstream.
+
+The last_recv_time is the most important indicator for a receiver session
+to figure out whether a session timed out or not. But this information was
+only initialized after the session was added to the tp_receiver_list and
+after the timer was started.
+
+In the worst case, the timer (function) could have tried to access this
+information before the actual initialization was reached. Like rest of the
+variables of the tp_meter receiver session, this field has to be filled out
+before any other (parallel running) context has the chance to access it.
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+[ Context ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 10 +++++++---
+ 1 file changed, 7 insertions(+), 3 deletions(-)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index 2bba53fc6da5c0..133eed2fa9507f 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -1403,8 +1403,10 @@ batadv_tp_init_recv(struct batadv_priv *bat_priv,
+
+ tp_vars = batadv_tp_list_find_session(bat_priv, icmp->orig,
+ icmp->session, BATADV_TP_RECEIVER);
+- if (tp_vars)
++ if (tp_vars) {
++ tp_vars->last_recv_time = jiffies;
+ goto out_unlock;
++ }
+
+ if (!atomic_add_unless(&bat_priv->tp_num, 1, BATADV_TP_MAX_NUM)) {
+ batadv_dbg(BATADV_DBG_TP_METER, bat_priv,
+@@ -1432,6 +1434,8 @@ batadv_tp_init_recv(struct batadv_priv *bat_priv,
+ kref_get(&tp_vars->refcount);
+ timer_setup(&tp_vars->timer, batadv_tp_receiver_shutdown, 0);
+
++ tp_vars->last_recv_time = jiffies;
++
+ kref_get(&tp_vars->refcount);
+ hlist_add_head_rcu(&tp_vars->list, &bat_priv->tp_list);
+
+@@ -1480,9 +1484,9 @@ static void batadv_tp_recv_msg(struct batadv_priv *bat_priv,
+ icmp->orig);
+ goto out;
+ }
+- }
+
+- tp_vars->last_recv_time = jiffies;
++ tp_vars->last_recv_time = jiffies;
++ }
+
+ /* if the packet is a duplicate, it may be the case that an ACK has been
+ * lost. Resend the ACK
+--
+2.53.0
+
--- /dev/null
+From 8ecfc8c90700f52bb6d5c91a50e24cd6be319a55 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:15 +0200
+Subject: batman-adv: tp_meter: keep unacked list in ascending ordered
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 5aa8651527ea0b610e7a09fb3b8204c1398b9525 upstream.
+
+When batadv_tp_handle_out_of_order inserts a new entry in the list of
+unacked (out of order) packets, it searches from the entry with the newest
+sequence number towards oldest sequence number. If an entry is found which
+is older than the newly entry, the new entry has to be added after the
+found one to keep the ascending order.
+
+But for this operation list_add_tail() was used. But this function adds an
+entry _before_ another one. As result, the list would contain a lot of
+swapped sequence numbers. The consumer of this list
+(batadv_tp_ack_unordered()) would then fail to correctly ack packets.
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index dfc3374549921f..f350a60e6c76b9 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -1325,7 +1325,7 @@ static bool batadv_tp_handle_out_of_order(struct batadv_tp_vars *tp_vars,
+ * one is attached _after_ it. In this way the list is kept in
+ * ascending order
+ */
+- list_add_tail(&new->list, &un->list);
++ list_add(&new->list, &un->list);
+ added = true;
+ break;
+ }
+--
+2.53.0
+
--- /dev/null
+From b1d63ded013db25261513ec8b576d7d72b490f3f Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:33 +0200
+Subject: batman-adv: tp_meter: prevent parallel modifications of last_recv
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 6dde0cfcb36e4d5b3de35b75696937478441eed4 upstream.
+
+When last_recv is updated to store the last receive sequence number, it is
+assuming that nothing is modifying in parallel while:
+
+* check for outdated packets is done
+* out of order check is performed (and packets are stored in out-of-order
+ queue)
+* the out-of-order queue was searched for closed gaps
+* sequence number for next ack is calculated
+
+Nothing of that was actually protected. It could therefore happen that the
+last_recv was updated multiple times in parallel and the final sequence
+number was calculated with deltas which had no connection to the sequence
+number they were added to.
+
+Lock this whole region with the same lock which was already used to protect
+the unacked (out-of-order) list.
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+[ Switch to pre-splitted tp_vars structure names ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 22 +++++++++++++---------
+ net/batman-adv/types.h | 2 +-
+ 2 files changed, 14 insertions(+), 10 deletions(-)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index b882919a868ca0..f745bc09a04963 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -1294,6 +1294,7 @@ static int batadv_tp_send_ack(struct batadv_priv *bat_priv, const u8 *dst,
+ */
+ static bool batadv_tp_handle_out_of_order(struct batadv_tp_vars *tp_vars,
+ const struct sk_buff *skb)
++ __must_hold(&tp_vars->unacked_lock)
+ {
+ const struct batadv_icmp_tp_packet *icmp;
+ struct batadv_tp_unacked *un, *new;
+@@ -1310,12 +1311,11 @@ static bool batadv_tp_handle_out_of_order(struct batadv_tp_vars *tp_vars,
+ payload_len = skb->len - sizeof(struct batadv_unicast_packet);
+ new->len = payload_len;
+
+- spin_lock_bh(&tp_vars->unacked_lock);
+ /* if the list is empty immediately attach this new object */
+ if (list_empty(&tp_vars->unacked_list)) {
+ list_add(&new->list, &tp_vars->unacked_list);
+ tp_vars->unacked_count++;
+- goto out;
++ return true;
+ }
+
+ /* otherwise loop over the list and either drop the packet because this
+@@ -1364,9 +1364,6 @@ static bool batadv_tp_handle_out_of_order(struct batadv_tp_vars *tp_vars,
+ tp_vars->unacked_count--;
+ }
+
+-out:
+- spin_unlock_bh(&tp_vars->unacked_lock);
+-
+ return true;
+ }
+
+@@ -1376,6 +1373,7 @@ static bool batadv_tp_handle_out_of_order(struct batadv_tp_vars *tp_vars,
+ * @tp_vars: the private data of the current TP meter session
+ */
+ static void batadv_tp_ack_unordered(struct batadv_tp_vars *tp_vars)
++ __must_hold(&tp_vars->unacked_lock)
+ {
+ struct batadv_tp_unacked *un, *safe;
+ u32 to_ack;
+@@ -1383,7 +1381,6 @@ static void batadv_tp_ack_unordered(struct batadv_tp_vars *tp_vars)
+ /* go through the unacked packet list and possibly ACK them as
+ * well
+ */
+- spin_lock_bh(&tp_vars->unacked_lock);
+ list_for_each_entry_safe(un, safe, &tp_vars->unacked_list, list) {
+ /* the list is ordered, therefore it is possible to stop as soon
+ * there is a gap between the last acked seqno and the seqno of
+@@ -1401,7 +1398,6 @@ static void batadv_tp_ack_unordered(struct batadv_tp_vars *tp_vars)
+ kfree(un);
+ tp_vars->unacked_count--;
+ }
+- spin_unlock_bh(&tp_vars->unacked_lock);
+ }
+
+ /**
+@@ -1481,6 +1477,7 @@ static void batadv_tp_recv_msg(struct batadv_priv *bat_priv,
+ const struct batadv_icmp_tp_packet *icmp;
+ struct batadv_tp_vars *tp_vars;
+ size_t packet_size;
++ u32 to_ack;
+ u32 seqno;
+
+ icmp = (struct batadv_icmp_tp_packet *)skb->data;
+@@ -1509,6 +1506,8 @@ static void batadv_tp_recv_msg(struct batadv_priv *bat_priv,
+ WRITE_ONCE(tp_vars->last_recv_time, jiffies);
+ }
+
++ spin_lock_bh(&tp_vars->unacked_lock);
++
+ /* if the packet is a duplicate, it may be the case that an ACK has been
+ * lost. Resend the ACK
+ */
+@@ -1520,8 +1519,10 @@ static void batadv_tp_recv_msg(struct batadv_priv *bat_priv,
+ /* exit immediately (and do not send any ACK) if the packet has
+ * not been enqueued correctly
+ */
+- if (!batadv_tp_handle_out_of_order(tp_vars, skb))
++ if (!batadv_tp_handle_out_of_order(tp_vars, skb)) {
++ spin_unlock_bh(&tp_vars->unacked_lock);
+ goto out;
++ }
+
+ /* send a duplicate ACK */
+ goto send_ack;
+@@ -1535,11 +1536,14 @@ static void batadv_tp_recv_msg(struct batadv_priv *bat_priv,
+ batadv_tp_ack_unordered(tp_vars);
+
+ send_ack:
++ to_ack = tp_vars->last_recv;
++ spin_unlock_bh(&tp_vars->unacked_lock);
++
+ /* send the ACK. If the received packet was out of order, the ACK that
+ * is going to be sent is a duplicate (the sender will count them and
+ * possibly enter Fast Retransmit as soon as it has reached 3)
+ */
+- batadv_tp_send_ack(bat_priv, icmp->orig, tp_vars->last_recv,
++ batadv_tp_send_ack(bat_priv, icmp->orig, to_ack,
+ icmp->timestamp, icmp->session, icmp->uid);
+ out:
+ batadv_tp_vars_put(tp_vars);
+diff --git a/net/batman-adv/types.h b/net/batman-adv/types.h
+index 7c32947414cd80..883da8acae7381 100644
+--- a/net/batman-adv/types.h
++++ b/net/batman-adv/types.h
+@@ -1491,7 +1491,7 @@ struct batadv_tp_vars {
+ /** @unacked_list: list of unacked packets (meta-info only) */
+ struct list_head unacked_list;
+
+- /** @unacked_lock: protect unacked_list */
++ /** @unacked_lock: protect unacked_list + &batadv_tp_receiver.last_recv */
+ spinlock_t unacked_lock;
+
+ /** @unacked_count: number of unacked entries */
+--
+2.53.0
+
--- /dev/null
+From 642ada629b7c53545d4af2a4bc52a2e1fd84d0c0 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:31 +0200
+Subject: batman-adv: tp_meter: restrict number of unacked list entries
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit e7c775110e1858e5a7471a23a9c9658c0af9df89 upstream.
+
+When the unacked_list is unbound, an attacker could send messages with
+small lengths and appropriated seqno + gaps to force the receiver to
+allocate more and more unacked_list entries. And the end either causing an
+out-of-memory situation or increase the management overhead for the (large)
+list that significant portions of CPU cycles are wasted in searching
+through the list.
+
+When limiting the list to a specific number, it is important to still
+correctly add a new entry to the list. But if the list became larger than
+the limit, the last entry of the list (with the highest seqno) must be
+dropped to still allow the earlier seqnos to finish and therefore to
+continue the process. Otherwise, the process might get stuck with too high
+seqnos which are not handled by batadv_tp_ack_unordered().
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+[ Switch to pre-splitted tp_vars structure names ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 23 ++++++++++++++++++++++-
+ net/batman-adv/types.h | 3 +++
+ 2 files changed, 25 insertions(+), 1 deletion(-)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index 133eed2fa9507f..638dd438a6c052 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -87,6 +87,11 @@
+ #define BATADV_TP_PLEN (BATADV_TP_PACKET_LEN - ETH_HLEN - \
+ sizeof(struct batadv_unicast_packet))
+
++/**
++ * BATADV_TP_MAX_UNACKED - maximum number of packets a receiver didn't yet ack
++ */
++#define BATADV_TP_MAX_UNACKED 100
++
+ static u8 batadv_tp_prerandom[4096] __read_mostly;
+
+ /**
+@@ -1195,6 +1200,7 @@ static void batadv_tp_receiver_shutdown(struct timer_list *t)
+ list_for_each_entry_safe(un, safe, &tp_vars->unacked_list, list) {
+ list_del(&un->list);
+ kfree(un);
++ tp_vars->unacked_count--;
+ }
+ spin_unlock_bh(&tp_vars->unacked_lock);
+
+@@ -1308,6 +1314,7 @@ static bool batadv_tp_handle_out_of_order(struct batadv_tp_vars *tp_vars,
+ /* if the list is empty immediately attach this new object */
+ if (list_empty(&tp_vars->unacked_list)) {
+ list_add(&new->list, &tp_vars->unacked_list);
++ tp_vars->unacked_count++;
+ goto out;
+ }
+
+@@ -1338,12 +1345,24 @@ static bool batadv_tp_handle_out_of_order(struct batadv_tp_vars *tp_vars,
+ */
+ list_add(&new->list, &un->list);
+ added = true;
++ tp_vars->unacked_count++;
+ break;
+ }
+
+ /* received packet with smallest seqno out of order; add it to front */
+- if (!added)
++ if (!added) {
+ list_add(&new->list, &tp_vars->unacked_list);
++ tp_vars->unacked_count++;
++ }
++
++ /* remove the last (biggest) unacked seqno when list is too large */
++ if (tp_vars->unacked_count > BATADV_TP_MAX_UNACKED) {
++ un = list_last_entry(&tp_vars->unacked_list,
++ struct batadv_tp_unacked, list);
++ list_del(&un->list);
++ kfree(un);
++ tp_vars->unacked_count--;
++ }
+
+ out:
+ spin_unlock_bh(&tp_vars->unacked_lock);
+@@ -1380,6 +1399,7 @@ static void batadv_tp_ack_unordered(struct batadv_tp_vars *tp_vars)
+
+ list_del(&un->list);
+ kfree(un);
++ tp_vars->unacked_count--;
+ }
+ spin_unlock_bh(&tp_vars->unacked_lock);
+ }
+@@ -1430,6 +1450,7 @@ batadv_tp_init_recv(struct batadv_priv *bat_priv,
+
+ spin_lock_init(&tp_vars->unacked_lock);
+ INIT_LIST_HEAD(&tp_vars->unacked_list);
++ tp_vars->unacked_count = 0;
+
+ kref_get(&tp_vars->refcount);
+ timer_setup(&tp_vars->timer, batadv_tp_receiver_shutdown, 0);
+diff --git a/net/batman-adv/types.h b/net/batman-adv/types.h
+index d56886951177d9..7c32947414cd80 100644
+--- a/net/batman-adv/types.h
++++ b/net/batman-adv/types.h
+@@ -1494,6 +1494,9 @@ struct batadv_tp_vars {
+ /** @unacked_lock: protect unacked_list */
+ spinlock_t unacked_lock;
+
++ /** @unacked_count: number of unacked entries */
++ size_t unacked_count;
++
+ /** @last_recv_time: time (jiffies) a msg was received */
+ unsigned long last_recv_time;
+
+--
+2.53.0
+
--- /dev/null
+From 36b4b364317e4ff265b360a55140487feff9e64f Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:35 +0200
+Subject: batman-adv: tt: don't merge change entries with different VIDs
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit f08e06c2d5c3e2434e7c773f2213f4a7dce6bc1e upstream.
+
+batadv_tt_local_event() merges/cancels events for the same client which
+would conflict or be duplicates. The matching of the queued events only
+compares the MAC address - the VLAN ID stored in each event is ignored.
+
+If a MAC would now appear on multiple VID, the two ADD change events (for
+VID 1 and VID 2) would be merged to a single vid event. The remote can
+therefore not calculate the correct TT table and desync. A full translation
+table exchange is required to recover from this state.
+
+A check of VID is therefore necessary to avoid such wrong merges/cancels.
+
+Cc: stable@kernel.org
+Fixes: c018ad3de61a ("batman-adv: add the VLAN ID attribute to the TT entry")
+[ Context ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/translation-table.c | 3 +++
+ 1 file changed, 3 insertions(+)
+
+diff --git a/net/batman-adv/translation-table.c b/net/batman-adv/translation-table.c
+index 7041cd69e20070..69a42bc3fa02e6 100644
+--- a/net/batman-adv/translation-table.c
++++ b/net/batman-adv/translation-table.c
+@@ -485,6 +485,9 @@ static void batadv_tt_local_event(struct batadv_priv *bat_priv,
+ if (!batadv_compare_eth(entry->change.addr, common->addr))
+ continue;
+
++ if (entry->change.vid != tt_change_node->change.vid)
++ continue;
++
+ /* DEL+ADD in the same orig interval have no effect and can be
+ * removed to avoid silly behaviour on the receiver side. The
+ * other way around (ADD+DEL) can happen in case of roaming of
+--
+2.53.0
+
--- /dev/null
+From 1684d7c52136f57f14fae426b995cf59286509ca Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:36 +0200
+Subject: batman-adv: tt: track roam count per VID
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 12407d5f61c2653a64f2ff4b22f3c267f8420ef1 upstream.
+
+batadv_tt_check_roam_count() is supposed to track roaming of a TT entry.
+But TT entries are for a MAC + VID. The VID was completely missed and thus
+leads to incorrect detection of ROAM counts when a client MAC exists in
+multiple VLANs.
+
+Cc: stable@kernel.org
+Fixes: c018ad3de61a ("batman-adv: add the VLAN ID attribute to the TT entry")
+[ Context ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/translation-table.c | 9 +++++++--
+ net/batman-adv/types.h | 3 +++
+ 2 files changed, 10 insertions(+), 2 deletions(-)
+
+diff --git a/net/batman-adv/translation-table.c b/net/batman-adv/translation-table.c
+index 69a42bc3fa02e6..2a7caab5c197ea 100644
+--- a/net/batman-adv/translation-table.c
++++ b/net/batman-adv/translation-table.c
+@@ -3517,6 +3517,7 @@ static void batadv_tt_roam_purge(struct batadv_priv *bat_priv)
+ * batadv_tt_check_roam_count() - check if a client has roamed too frequently
+ * @bat_priv: the bat priv with all the soft interface information
+ * @client: mac address of the roaming client
++ * @vid: VLAN identifier
+ *
+ * This function checks whether the client already reached the
+ * maximum number of possible roaming phases. In this case the ROAMING_ADV
+@@ -3524,7 +3525,7 @@ static void batadv_tt_roam_purge(struct batadv_priv *bat_priv)
+ *
+ * Return: true if the ROAMING_ADV can be sent, false otherwise
+ */
+-static bool batadv_tt_check_roam_count(struct batadv_priv *bat_priv, u8 *client)
++static bool batadv_tt_check_roam_count(struct batadv_priv *bat_priv, u8 *client, u16 vid)
+ {
+ struct batadv_tt_roam_node *tt_roam_node;
+ bool ret = false;
+@@ -3537,6 +3538,9 @@ static bool batadv_tt_check_roam_count(struct batadv_priv *bat_priv, u8 *client)
+ if (!batadv_compare_eth(tt_roam_node->addr, client))
+ continue;
+
++ if (tt_roam_node->vid != vid)
++ continue;
++
+ if (batadv_has_timed_out(tt_roam_node->first_time,
+ BATADV_ROAMING_MAX_TIME))
+ continue;
+@@ -3558,6 +3562,7 @@ static bool batadv_tt_check_roam_count(struct batadv_priv *bat_priv, u8 *client)
+ atomic_set(&tt_roam_node->counter,
+ BATADV_ROAMING_MAX_COUNT - 1);
+ ether_addr_copy(tt_roam_node->addr, client);
++ tt_roam_node->vid = vid;
+
+ list_add(&tt_roam_node->list, &bat_priv->tt.roam_list);
+ ret = true;
+@@ -3594,7 +3599,7 @@ static void batadv_send_roam_adv(struct batadv_priv *bat_priv, u8 *client,
+ /* before going on we have to check whether the client has
+ * already roamed to us too many times
+ */
+- if (!batadv_tt_check_roam_count(bat_priv, client))
++ if (!batadv_tt_check_roam_count(bat_priv, client, vid))
+ goto out;
+
+ batadv_dbg(BATADV_DBG_TT, bat_priv,
+diff --git a/net/batman-adv/types.h b/net/batman-adv/types.h
+index 883da8acae7381..0368cb93865f39 100644
+--- a/net/batman-adv/types.h
++++ b/net/batman-adv/types.h
+@@ -1995,6 +1995,9 @@ struct batadv_tt_roam_node {
+ /** @addr: mac address of the client in the roaming phase */
+ u8 addr[ETH_ALEN];
+
++ /** @vid: VLAN identifier */
++ u16 vid;
++
+ /**
+ * @counter: number of allowed roaming events per client within a single
+ * OGM interval (changes are committed with each OGM)
+--
+2.53.0
+
--- /dev/null
+From c4bfecb070ea143bda1e2db4896c482dcf803d35 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:39 +0200
+Subject: batman-adv: tvlv: avoid race of cifsnotfound handler state
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit edb557b2ba38fea2c5eb710cf366c797e187218c upstream.
+
+TVLV handlers can have the flag BATADV_TVLV_HANDLER_OGM_CIFNOTFND set to
+signal that the OGM handler should be called (with NULL for data) when the
+specific TVLV container was not found in the OGM. This is used by:
+
+* DAT
+* GW
+* Multicast (OGM + Tracker)
+
+The state whether the handler was executed was stored in the struct
+batadv_tvlv_handler. But the TVLV processing is started without any lock.
+Multiple parallel contexts processing TVLVs would therefore overwrite each
+others BATADV_TVLV_HANDLER_OGM_CALLED flag in the shared
+batadv_tvlv_handler.
+
+Drop the shared BATADV_TVLV_HANDLER_OGM_CALLED flag and instead determine,
+per TVLV buffer, whether a matching container was present by scanning the
+packet's buffer.
+
+Cc: stable@kernel.org
+Fixes: ef26157747d4 ("batman-adv: tvlv - basic infrastructure")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tvlv.c | 63 ++++++++++++++++++++++++++++++++++++++----
+ net/batman-adv/types.h | 7 -----
+ 2 files changed, 57 insertions(+), 13 deletions(-)
+
+diff --git a/net/batman-adv/tvlv.c b/net/batman-adv/tvlv.c
+index e1cd27b99bd119..cc66e231ccb8a0 100644
+--- a/net/batman-adv/tvlv.c
++++ b/net/batman-adv/tvlv.c
+@@ -398,7 +398,6 @@ static int batadv_tvlv_call_handler(struct batadv_priv *bat_priv,
+ tvlv_handler->ogm_handler(bat_priv, orig_node,
+ BATADV_NO_FLAGS,
+ tvlv_value, tvlv_value_len);
+- tvlv_handler->flags |= BATADV_TVLV_HANDLER_OGM_CALLED;
+ break;
+ case BATADV_UNICAST_TVLV:
+ if (!skb)
+@@ -430,6 +429,48 @@ static int batadv_tvlv_call_handler(struct batadv_priv *bat_priv,
+ return NET_RX_SUCCESS;
+ }
+
++/**
++ * batadv_tvlv_containers_contain() - check if a tvlv buffer holds a container
++ * @tvlv_value: tvlv content
++ * @tvlv_value_len: tvlv content length
++ * @type: tvlv container type to look for
++ * @version: tvlv container version to look for
++ *
++ * Return: true if a container of the given type and version is present in the
++ * tvlv buffer, false otherwise.
++ */
++static bool batadv_tvlv_containers_contain(void *tvlv_value,
++ u16 tvlv_value_len, u8 type,
++ u8 version)
++{
++ struct batadv_tvlv_hdr *tvlv_hdr;
++ u16 tvlv_value_cont_len;
++
++ while (tvlv_value_len >= sizeof(*tvlv_hdr)) {
++ tvlv_hdr = tvlv_value;
++ tvlv_value_cont_len = ntohs(tvlv_hdr->len);
++ tvlv_value = tvlv_hdr + 1;
++ tvlv_value_len -= sizeof(*tvlv_hdr);
++
++ if (tvlv_value_cont_len > tvlv_value_len)
++ break;
++
++ /* the next tvlv header is accessed assuming (at least) 2-byte
++ * alignment, so it must start at an even offset.
++ */
++ if (tvlv_value_cont_len & 1)
++ break;
++
++ if (tvlv_hdr->type == type && tvlv_hdr->version == version)
++ return true;
++
++ tvlv_value = (u8 *)tvlv_value + tvlv_value_cont_len;
++ tvlv_value_len -= tvlv_value_cont_len;
++ }
++
++ return false;
++}
++
+ /**
+ * batadv_tvlv_containers_process() - parse the given tvlv buffer to call the
+ * appropriate handlers
+@@ -449,7 +490,9 @@ int batadv_tvlv_containers_process(struct batadv_priv *bat_priv,
+ struct sk_buff *skb, void *tvlv_value,
+ u16 tvlv_value_len)
+ {
++ u16 tvlv_value_start_len = tvlv_value_len;
+ struct batadv_tvlv_handler *tvlv_handler;
++ void *tvlv_value_start = tvlv_value;
+ struct batadv_tvlv_hdr *tvlv_hdr;
+ u16 tvlv_value_cont_len;
+ u8 cifnotfound = BATADV_TVLV_HANDLER_OGM_CIFNOTFND;
+@@ -493,12 +536,20 @@ int batadv_tvlv_containers_process(struct batadv_priv *bat_priv,
+ if (!tvlv_handler->ogm_handler)
+ continue;
+
+- if ((tvlv_handler->flags & BATADV_TVLV_HANDLER_OGM_CIFNOTFND) &&
+- !(tvlv_handler->flags & BATADV_TVLV_HANDLER_OGM_CALLED))
+- tvlv_handler->ogm_handler(bat_priv, orig_node,
+- cifnotfound, NULL, 0);
++ if (!(tvlv_handler->flags & BATADV_TVLV_HANDLER_OGM_CIFNOTFND))
++ continue;
+
+- tvlv_handler->flags &= ~BATADV_TVLV_HANDLER_OGM_CALLED;
++ /* if the corresponding container was present then the handler
++ * was already called from the loop above
++ */
++ if (batadv_tvlv_containers_contain(tvlv_value_start,
++ tvlv_value_start_len,
++ tvlv_handler->type,
++ tvlv_handler->version))
++ continue;
++
++ tvlv_handler->ogm_handler(bat_priv, orig_node,
++ cifnotfound, NULL, 0);
+ }
+ rcu_read_unlock();
+
+diff --git a/net/batman-adv/types.h b/net/batman-adv/types.h
+index 0368cb93865f39..4856cc98fc3c51 100644
+--- a/net/batman-adv/types.h
++++ b/net/batman-adv/types.h
+@@ -2413,13 +2413,6 @@ enum batadv_tvlv_handler_flags {
+ * will call this handler even if its type was not found (with no data)
+ */
+ BATADV_TVLV_HANDLER_OGM_CIFNOTFND = BIT(1),
+-
+- /**
+- * @BATADV_TVLV_HANDLER_OGM_CALLED: interval tvlv handling flag - the
+- * API marks a handler as being called, so it won't be called if the
+- * BATADV_TVLV_HANDLER_OGM_CIFNOTFND flag was set
+- */
+- BATADV_TVLV_HANDLER_OGM_CALLED = BIT(2),
+ };
+
+ #endif /* _NET_BATMAN_ADV_TYPES_H_ */
+--
+2.53.0
+
--- /dev/null
+From b9884b623fa18c2def868eac1a8509453235f6c8 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:38 +0200
+Subject: batman-adv: tvlv: enforce 2-byte alignment
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 32a6799255525d6ea4da0f7e9e0e521ad9560a46 upstream.
+
+The fields of an aggregated OGM(v2) are accessed assuming (at least) 2-byte
+alignment, so a following OGM must start at an even offset. As the header
+length is even, an odd tvlv_len would misalign it and trigger unaligned
+accesses on strict-alignment architectures.
+
+Such a misaligned TVLV/OGM/OGMv2 is not created by a normal participant in
+the mesh. Therefore, reject such malformed packets.
+
+Cc: stable@kernel.org
+Fixes: ef26157747d4 ("batman-adv: tvlv - basic infrastructure")
+[ Drop change for non-existing mcast handling ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/bat_iv_ogm.c | 11 ++++++++++-
+ net/batman-adv/bat_v_ogm.c | 11 ++++++++++-
+ net/batman-adv/tvlv.c | 6 ++++++
+ 3 files changed, 26 insertions(+), 2 deletions(-)
+
+diff --git a/net/batman-adv/bat_iv_ogm.c b/net/batman-adv/bat_iv_ogm.c
+index b37c9fb178ae50..5dd3e1f281bab2 100644
+--- a/net/batman-adv/bat_iv_ogm.c
++++ b/net/batman-adv/bat_iv_ogm.c
+@@ -310,14 +310,23 @@ batadv_iv_ogm_aggr_packet(int buff_pos, int packet_len,
+ const struct batadv_ogm_packet *ogm_packet)
+ {
+ int next_buff_pos = 0;
++ u16 tvlv_len;
+
+ /* check if there is enough space for the header */
+ next_buff_pos += buff_pos + sizeof(*ogm_packet);
+ if (next_buff_pos > packet_len)
+ return false;
+
++ tvlv_len = ntohs(ogm_packet->tvlv_len);
++
++ /* the fields of an aggregated OGM are accessed assuming (at least)
++ * 2-byte alignment, so a following OGM must start at an even offset.
++ */
++ if (tvlv_len & 1)
++ return false;
++
+ /* check if there is enough space for the optional TVLV */
+- next_buff_pos += ntohs(ogm_packet->tvlv_len);
++ next_buff_pos += tvlv_len;
+
+ return next_buff_pos <= packet_len;
+ }
+diff --git a/net/batman-adv/bat_v_ogm.c b/net/batman-adv/bat_v_ogm.c
+index 48a67705eba85c..c5c4d33cb19838 100644
+--- a/net/batman-adv/bat_v_ogm.c
++++ b/net/batman-adv/bat_v_ogm.c
+@@ -853,14 +853,23 @@ batadv_v_ogm_aggr_packet(int buff_pos, int packet_len,
+ const struct batadv_ogm2_packet *ogm2_packet)
+ {
+ int next_buff_pos = 0;
++ u16 tvlv_len;
+
+ /* check if there is enough space for the header */
+ next_buff_pos += buff_pos + sizeof(*ogm2_packet);
+ if (next_buff_pos > packet_len)
+ return false;
+
++ tvlv_len = ntohs(ogm2_packet->tvlv_len);
++
++ /* the fields of an aggregated OGMv2 are accessed assuming (at least)
++ * 2-byte alignment, so a following OGMv2 must start at an even offset.
++ */
++ if (tvlv_len & 1)
++ return false;
++
+ /* check if there is enough space for the optional TVLV */
+- next_buff_pos += ntohs(ogm2_packet->tvlv_len);
++ next_buff_pos += tvlv_len;
+
+ return next_buff_pos <= packet_len;
+ }
+diff --git a/net/batman-adv/tvlv.c b/net/batman-adv/tvlv.c
+index 8d6b017c433cc9..e1cd27b99bd119 100644
+--- a/net/batman-adv/tvlv.c
++++ b/net/batman-adv/tvlv.c
+@@ -464,6 +464,12 @@ int batadv_tvlv_containers_process(struct batadv_priv *bat_priv,
+ if (tvlv_value_cont_len > tvlv_value_len)
+ break;
+
++ /* the next tvlv header is accessed assuming (at least) 2-byte
++ * alignment, so it must start at an even offset.
++ */
++ if (tvlv_value_cont_len & 1)
++ break;
++
+ tvlv_handler = batadv_tvlv_handler_get(bat_priv,
+ tvlv_hdr->type,
+ tvlv_hdr->version);
+--
+2.53.0
+
--- /dev/null
+From 55980242fb7e3c6e754d68b05c6794e5b5b8c3d8 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:11:30 +0200
+Subject: batman-adv: v: prevent OGM aggregation on disabled hardif
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit d11c00b95b2a3b3934007fc003dccc6fdcc061ad upstream.
+
+When an interface gets disabled, the worker is correctly disabled by
+batadv_hardif_disable_interface() -> ... -> batadv_v_ogm_iface_disable().
+In this process, the skb aggr_list is also freed.
+
+But batadv_v_ogm_send_meshif() can still queue new skbs (via
+batadv_v_ogm_queue_on_if()) to the aggr_list. This will only stop after all
+cores can no longer find the RCU protected list of hard interfaces. These
+queued skbs will never be freed or consumed by batadv_v_ogm_aggr_work.
+
+The batadv_v_ogm_iface_disable() function must block
+batadv_v_ogm_queue_on_if() to avoid leak of skbs.
+
+Cc: stable@kernel.org
+Fixes: f89255a02f1d ("batman-adv: BATMAN_V: introduce per hard-iface OGMv2 queues")
+[ Context ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/bat_v.c | 1 +
+ net/batman-adv/bat_v_ogm.c | 12 ++++++++++++
+ net/batman-adv/types.h | 6 ++++++
+ 3 files changed, 19 insertions(+)
+
+diff --git a/net/batman-adv/bat_v.c b/net/batman-adv/bat_v.c
+index d35479c465e2c4..e13b39121f2db8 100644
+--- a/net/batman-adv/bat_v.c
++++ b/net/batman-adv/bat_v.c
+@@ -819,6 +819,7 @@ void batadv_v_hardif_init(struct batadv_hard_iface *hard_iface)
+
+ hard_iface->bat_v.aggr_len = 0;
+ skb_queue_head_init(&hard_iface->bat_v.aggr_list);
++ hard_iface->bat_v.aggr_list_enabled = false;
+ INIT_DELAYED_WORK(&hard_iface->bat_v.aggr_wq,
+ batadv_v_ogm_aggr_work);
+ }
+diff --git a/net/batman-adv/bat_v_ogm.c b/net/batman-adv/bat_v_ogm.c
+index 8cfc3944dcfd52..48a67705eba85c 100644
+--- a/net/batman-adv/bat_v_ogm.c
++++ b/net/batman-adv/bat_v_ogm.c
+@@ -254,11 +254,18 @@ static void batadv_v_ogm_queue_on_if(struct batadv_priv *bat_priv,
+ }
+
+ spin_lock_bh(&hard_iface->bat_v.aggr_list.lock);
++ if (!hard_iface->bat_v.aggr_list_enabled) {
++ kfree_skb(skb);
++ goto unlock;
++ }
++
+ if (!batadv_v_ogm_queue_left(skb, hard_iface))
+ batadv_v_ogm_aggr_send(bat_priv, hard_iface);
+
+ hard_iface->bat_v.aggr_len += batadv_v_ogm_len(skb);
+ __skb_queue_tail(&hard_iface->bat_v.aggr_list, skb);
++
++unlock:
+ spin_unlock_bh(&hard_iface->bat_v.aggr_list.lock);
+ }
+
+@@ -421,6 +428,10 @@ int batadv_v_ogm_iface_enable(struct batadv_hard_iface *hard_iface)
+ {
+ struct batadv_priv *bat_priv = netdev_priv(hard_iface->soft_iface);
+
++ spin_lock_bh(&hard_iface->bat_v.aggr_list.lock);
++ hard_iface->bat_v.aggr_list_enabled = true;
++ spin_unlock_bh(&hard_iface->bat_v.aggr_list.lock);
++
+ batadv_v_ogm_start_queue_timer(hard_iface);
+ batadv_v_ogm_start_timer(bat_priv);
+
+@@ -436,6 +447,7 @@ void batadv_v_ogm_iface_disable(struct batadv_hard_iface *hard_iface)
+ cancel_delayed_work_sync(&hard_iface->bat_v.aggr_wq);
+
+ spin_lock_bh(&hard_iface->bat_v.aggr_list.lock);
++ hard_iface->bat_v.aggr_list_enabled = false;
+ batadv_v_ogm_aggr_list_free(hard_iface);
+ spin_unlock_bh(&hard_iface->bat_v.aggr_list.lock);
+ }
+diff --git a/net/batman-adv/types.h b/net/batman-adv/types.h
+index 2dfdc78d4ded99..d56886951177d9 100644
+--- a/net/batman-adv/types.h
++++ b/net/batman-adv/types.h
+@@ -130,6 +130,12 @@ struct batadv_hard_iface_bat_v {
+ /** @aggr_list: queue for to be aggregated OGM packets */
+ struct sk_buff_head aggr_list;
+
++ /**
++ * @aggr_list_enabled: aggr_list is active and new skbs can be
++ * enqueued. Protected by aggr_list.lock after initialization
++ */
++ bool aggr_list_enabled:1;
++
+ /** @aggr_len: size of the OGM aggregate (excluding ethernet header) */
+ unsigned int aggr_len;
+
+--
+2.53.0
+
--- /dev/null
+From aa14e051d0d097c168a672517c3cf44173b3a8cd Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Sat, 27 Jun 2026 14:57:18 +0800
+Subject: fs: prepare for adding LSM blob to backing_file
+
+From: Amir Goldstein <amir73il@gmail.com>
+
+[ Upstream commit 880bd496ec72a6dcb00cb70c430ef752ba242ae7 ]
+
+In preparation to adding LSM blob to backing_file struct, factor out
+helpers init_backing_file() and backing_file_free().
+
+Cc: stable@vger.kernel.org
+Cc: linux-fsdevel@vger.kernel.org
+Cc: linux-unionfs@vger.kernel.org
+Cc: linux-erofs@lists.ozlabs.org
+Signed-off-by: Amir Goldstein <amir73il@gmail.com>
+Reviewed-by: Serge Hallyn <serge@hallyn.com>
+[PM: use the term "LSM blob", fix comment style to match file]
+Signed-off-by: Paul Moore <paul@paul-moore.com>
+[1. The commit def3ae83da02
+("fs: store real path instead of fake path in backing file f_path")
+is not merged, The 6.6 LTS version accordingly operates on
+&ff->real_path instead of &ff->user_path.
+
+2. Mainline's file_free() does both the backing_file cleanup and the
+kmem_cache_free() synchronously. Linux 6.6.y defers the actual kfree()
+to file_free_rcu() via call_rcu(), so only path_put() is done
+synchronously in file_free().]
+Signed-off-by: Cai Xinchen <caixinchen1@huawei.com>
+
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ fs/file_table.c | 20 +++++++++++++++++++-
+ 1 file changed, 19 insertions(+), 1 deletion(-)
+
+diff --git a/fs/file_table.c b/fs/file_table.c
+index 234284ef72a9a5..b4c208a771539d 100644
+--- a/fs/file_table.c
++++ b/fs/file_table.c
+@@ -72,11 +72,16 @@ static void file_free_rcu(struct rcu_head *head)
+ kmem_cache_free(filp_cachep, f);
+ }
+
++static inline void backing_file_free(struct backing_file *ff)
++{
++ path_put(&ff->real_path);
++}
++
+ static inline void file_free(struct file *f)
+ {
+ security_file_free(f);
+ if (unlikely(f->f_mode & FMODE_BACKING))
+- path_put(backing_file_real_path(f));
++ backing_file_free(backing_file(f));
+ if (likely(!(f->f_mode & FMODE_NOACCOUNT)))
+ percpu_counter_dec(&nr_files);
+ call_rcu(&f->f_rcuhead, file_free_rcu);
+@@ -252,6 +257,12 @@ struct file *alloc_empty_file_noaccount(int flags, const struct cred *cred)
+ return f;
+ }
+
++static int init_backing_file(struct backing_file *ff)
++{
++ memset(&ff->real_path, 0, sizeof(ff->real_path));
++ return 0;
++}
++
+ /*
+ * Variant of alloc_empty_file() that allocates a backing_file container
+ * and doesn't check and modify nr_files.
+@@ -274,7 +285,14 @@ struct file *alloc_empty_backing_file(int flags, const struct cred *cred)
+ return ERR_PTR(error);
+ }
+
++ /* The f_mode flags must be set before fput(). */
+ ff->file.f_mode |= FMODE_BACKING | FMODE_NOACCOUNT;
++ error = init_backing_file(ff);
++ if (unlikely(error)) {
++ fput(&ff->file);
++ return ERR_PTR(error);
++ }
++
+ return &ff->file;
+ }
+
+--
+2.53.0
+
--- /dev/null
+From f8d61919aeb3768608afa3cc2158c74dda3ee5a8 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Sat, 27 Jun 2026 14:57:19 +0800
+Subject: lsm: add backing_file LSM hooks
+
+From: Paul Moore <paul@paul-moore.com>
+
+[ Upstream commit 6af36aeb147a06dea47c49859cd6ca5659aeb987 ]
+
+Stacked filesystems such as overlayfs do not currently provide the
+necessary mechanisms for LSMs to properly enforce access controls on the
+mmap() and mprotect() operations. In order to resolve this gap, a LSM
+security blob is being added to the backing_file struct and the following
+new LSM hooks are being created:
+
+ security_backing_file_alloc()
+ security_backing_file_free()
+ security_mmap_backing_file()
+
+The first two hooks are to manage the lifecycle of the LSM security blob
+in the backing_file struct, while the third provides a new mmap() access
+control point for the underlying backing file. It is also expected that
+LSMs will likely want to update their security_file_mprotect() callback
+to address issues with their mprotect() controls, but that does not
+require a change to the security_file_mprotect() LSM hook.
+
+There are a three other small changes to support these new LSM hooks:
+* Pass the user file associated with a backing file down to
+alloc_empty_backing_file() so it can be included in the
+security_backing_file_alloc() hook.
+* Add getter and setter functions for the backing_file struct LSM blob
+as the backing_file struct remains private to fs/file_table.c.
+* Constify the file struct field in the LSM common_audit_data struct to
+better support LSMs that need to pass a const file struct pointer into
+the common LSM audit code.
+
+Thanks to Arnd Bergmann for identifying the missing EXPORT_SYMBOL_GPL()
+and supplying a fixup.
+
+Cc: stable@vger.kernel.org
+Cc: linux-fsdevel@vger.kernel.org
+Cc: linux-unionfs@vger.kernel.org
+Cc: linux-erofs@lists.ozlabs.org
+Reviewed-by: Amir Goldstein <amir73il@gmail.com>
+Reviewed-by: Serge Hallyn <serge@hallyn.com>
+Reviewed-by: Christian Brauner <brauner@kernel.org>
+Signed-off-by: Paul Moore <paul@paul-moore.com>
+[1. Mainline uses call_int_hook(FUNC, ...) with the default IRC baked
+into the macro. Linux 6.6.y uses call_int_hook(FUNC, IRC, ...) requiring
+an explicit default return value.
+
+2. fs/backing-file.c does not exist in LTS
+Linux 6.6.y places backing_file_open() in fs/open.c and lacks a
+dedicated fs/backing-file.c. The backing_file_mmap() function and
+scoped_with_creds() do not exist in 6.6.y. Therefore the LTS patch calls
+security_mmap_backing_file() directly in ovl_mmap() in
+fs/overlayfs/file.c rather than modifying backing_file_mmap().
+
+3. Missing filesystems/modules
+Linux 6.6.y does not have backing_tmpfile_open(), fs/fuse/passthrough.c,
+or the erofs ishare mmap path that the mainline patch touches. These hunks
+are dropped in the 6.6 LTS backport.
+
+4. Use macro backing_file to replace inline function to eliminate the
+const warning.]
+Signed-off-by: Cai Xinchen <caixinchen1@huawei.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ fs/file_table.c | 32 +++++++---
+ fs/internal.h | 3 +-
+ fs/open.c | 7 ++-
+ fs/overlayfs/file.c | 8 ++-
+ include/linux/fs.h | 15 ++++-
+ include/linux/lsm_audit.h | 2 +-
+ include/linux/lsm_hook_defs.h | 5 ++
+ include/linux/lsm_hooks.h | 1 +
+ include/linux/security.h | 22 +++++++
+ security/security.c | 110 ++++++++++++++++++++++++++++++++++
+ 10 files changed, 190 insertions(+), 15 deletions(-)
+
+diff --git a/fs/file_table.c b/fs/file_table.c
+index b4c208a771539d..fa4d4c54f790c8 100644
+--- a/fs/file_table.c
++++ b/fs/file_table.c
+@@ -48,12 +48,12 @@ static struct percpu_counter nr_files __cacheline_aligned_in_smp;
+ struct backing_file {
+ struct file file;
+ struct path real_path;
++#ifdef CONFIG_SECURITY
++ void *security;
++#endif
+ };
+
+-static inline struct backing_file *backing_file(struct file *f)
+-{
+- return container_of(f, struct backing_file, file);
+-}
++#define backing_file(f) container_of(f, struct backing_file, file)
+
+ struct path *backing_file_real_path(struct file *f)
+ {
+@@ -72,8 +72,21 @@ static void file_free_rcu(struct rcu_head *head)
+ kmem_cache_free(filp_cachep, f);
+ }
+
++#ifdef CONFIG_SECURITY
++void *backing_file_security(const struct file *f)
++{
++ return backing_file(f)->security;
++}
++
++void backing_file_set_security(struct file *f, void *security)
++{
++ backing_file(f)->security = security;
++}
++#endif /* CONFIG_SECURITY */
++
+ static inline void backing_file_free(struct backing_file *ff)
+ {
++ security_backing_file_free(&ff->file);
+ path_put(&ff->real_path);
+ }
+
+@@ -257,10 +270,12 @@ struct file *alloc_empty_file_noaccount(int flags, const struct cred *cred)
+ return f;
+ }
+
+-static int init_backing_file(struct backing_file *ff)
++static int init_backing_file(struct backing_file *ff,
++ const struct file *user_file)
+ {
+ memset(&ff->real_path, 0, sizeof(ff->real_path));
+- return 0;
++ backing_file_set_security(&ff->file, NULL);
++ return security_backing_file_alloc(&ff->file, user_file);
+ }
+
+ /*
+@@ -270,7 +285,8 @@ static int init_backing_file(struct backing_file *ff)
+ * This is only for kernel internal use, and the allocate file must not be
+ * installed into file tables or such.
+ */
+-struct file *alloc_empty_backing_file(int flags, const struct cred *cred)
++struct file *alloc_empty_backing_file(int flags, const struct cred *cred,
++ const struct file *user_file)
+ {
+ struct backing_file *ff;
+ int error;
+@@ -287,7 +303,7 @@ struct file *alloc_empty_backing_file(int flags, const struct cred *cred)
+
+ /* The f_mode flags must be set before fput(). */
+ ff->file.f_mode |= FMODE_BACKING | FMODE_NOACCOUNT;
+- error = init_backing_file(ff);
++ error = init_backing_file(ff, user_file);
+ if (unlikely(error)) {
+ fput(&ff->file);
+ return ERR_PTR(error);
+diff --git a/fs/internal.h b/fs/internal.h
+index d64ae03998cce9..576026e1ce3aab 100644
+--- a/fs/internal.h
++++ b/fs/internal.h
+@@ -93,7 +93,8 @@ extern void chroot_fs_refs(const struct path *, const struct path *);
+ */
+ struct file *alloc_empty_file(int flags, const struct cred *cred);
+ struct file *alloc_empty_file_noaccount(int flags, const struct cred *cred);
+-struct file *alloc_empty_backing_file(int flags, const struct cred *cred);
++struct file *alloc_empty_backing_file(int flags, const struct cred *cred,
++ const struct file *user_file);
+
+ static inline void put_file_access(struct file *file)
+ {
+diff --git a/fs/open.c b/fs/open.c
+index b5ea1dcbfb2242..5b164481d80b34 100644
+--- a/fs/open.c
++++ b/fs/open.c
+@@ -1175,18 +1175,19 @@ EXPORT_SYMBOL_GPL(kernel_file_open);
+ * the backing inode on the underlying filesystem, which can be
+ * retrieved using backing_file_real_path().
+ */
+-struct file *backing_file_open(const struct path *path, int flags,
++struct file *backing_file_open(const struct file *user_file, int flags,
+ const struct path *real_path,
+ const struct cred *cred)
+ {
++ const struct path *user_path = &user_file->f_path;
+ struct file *f;
+ int error;
+
+- f = alloc_empty_backing_file(flags, cred);
++ f = alloc_empty_backing_file(flags, cred, user_file);
+ if (IS_ERR(f))
+ return f;
+
+- f->f_path = *path;
++ f->f_path = *user_path;
+ path_get(real_path);
+ *backing_file_real_path(f) = *real_path;
+ error = do_dentry_open(f, d_inode(real_path->dentry), NULL);
+diff --git a/fs/overlayfs/file.c b/fs/overlayfs/file.c
+index 8be4dc050d1ed2..07f00cf91977ee 100644
+--- a/fs/overlayfs/file.c
++++ b/fs/overlayfs/file.c
+@@ -60,7 +60,7 @@ static struct file *ovl_open_realfile(const struct file *file,
+ if (!inode_owner_or_capable(real_idmap, realinode))
+ flags &= ~O_NOATIME;
+
+- realfile = backing_file_open(&file->f_path, flags, realpath,
++ realfile = backing_file_open(file, flags, realpath,
+ current_cred());
+ }
+ revert_creds(old_cred);
+@@ -527,6 +527,7 @@ static int ovl_fsync(struct file *file, loff_t start, loff_t end, int datasync)
+
+ static int ovl_mmap(struct file *file, struct vm_area_struct *vma)
+ {
++ struct file *user_file = vma->vm_file;
+ struct file *realfile = file->private_data;
+ const struct cred *old_cred;
+ int ret;
+@@ -540,6 +541,11 @@ static int ovl_mmap(struct file *file, struct vm_area_struct *vma)
+ vma_set_file(vma, realfile);
+
+ old_cred = ovl_override_creds(file_inode(file)->i_sb);
++ ret = security_mmap_backing_file(vma, realfile, user_file);
++ if (ret) {
++ revert_creds(old_cred);
++ return ret;
++ }
+ ret = call_mmap(vma->vm_file, vma);
+ revert_creds(old_cred);
+ ovl_file_accessed(file);
+diff --git a/include/linux/fs.h b/include/linux/fs.h
+index 4cdeeaedaa404f..5a897ee50f29cf 100644
+--- a/include/linux/fs.h
++++ b/include/linux/fs.h
+@@ -2515,11 +2515,24 @@ struct file *dentry_open(const struct path *path, int flags,
+ const struct cred *creds);
+ struct file *dentry_create(const struct path *path, int flags, umode_t mode,
+ const struct cred *cred);
+-struct file *backing_file_open(const struct path *path, int flags,
++struct file *backing_file_open(const struct file *user_file, int flags,
+ const struct path *real_path,
+ const struct cred *cred);
+ struct path *backing_file_real_path(struct file *f);
+
++#ifdef CONFIG_SECURITY
++void *backing_file_security(const struct file *f);
++void backing_file_set_security(struct file *f, void *security);
++#else
++static inline void *backing_file_security(const struct file *f)
++{
++ return NULL;
++}
++static inline void backing_file_set_security(struct file *f, void *security)
++{
++}
++#endif /* CONFIG_SECURITY */
++
+ /*
+ * file_real_path - get the path corresponding to f_inode
+ *
+diff --git a/include/linux/lsm_audit.h b/include/linux/lsm_audit.h
+index 97a8b21eb03339..c0a2839253fa1f 100644
+--- a/include/linux/lsm_audit.h
++++ b/include/linux/lsm_audit.h
+@@ -93,7 +93,7 @@ struct common_audit_data {
+ #endif
+ char *kmod_name;
+ struct lsm_ioctlop_audit *op;
+- struct file *file;
++ const struct file *file;
+ struct lsm_ibpkey_audit *ibpkey;
+ struct lsm_ibendport_audit *ibendport;
+ int reason;
+diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h
+index 2923754c13bce6..567d7f16609c8b 100644
+--- a/include/linux/lsm_hook_defs.h
++++ b/include/linux/lsm_hook_defs.h
+@@ -169,6 +169,9 @@ LSM_HOOK(int, 0, kernfs_init_security, struct kernfs_node *kn_dir,
+ LSM_HOOK(int, 0, file_permission, struct file *file, int mask)
+ LSM_HOOK(int, 0, file_alloc_security, struct file *file)
+ LSM_HOOK(void, LSM_RET_VOID, file_free_security, struct file *file)
++LSM_HOOK(int, 0, backing_file_alloc, struct file *backing_file,
++ const struct file *user_file)
++LSM_HOOK(void, LSM_RET_VOID, backing_file_free, struct file *backing_file)
+ LSM_HOOK(int, 0, file_ioctl, struct file *file, unsigned int cmd,
+ unsigned long arg)
+ LSM_HOOK(int, 0, file_ioctl_compat, struct file *file, unsigned int cmd,
+@@ -176,6 +179,8 @@ LSM_HOOK(int, 0, file_ioctl_compat, struct file *file, unsigned int cmd,
+ LSM_HOOK(int, 0, mmap_addr, unsigned long addr)
+ LSM_HOOK(int, 0, mmap_file, struct file *file, unsigned long reqprot,
+ unsigned long prot, unsigned long flags)
++LSM_HOOK(int, 0, mmap_backing_file, struct vm_area_struct *vma,
++ struct file *backing_file, struct file *user_file)
+ LSM_HOOK(int, 0, file_mprotect, struct vm_area_struct *vma,
+ unsigned long reqprot, unsigned long prot)
+ LSM_HOOK(int, 0, file_lock, struct file *file, unsigned int cmd)
+diff --git a/include/linux/lsm_hooks.h b/include/linux/lsm_hooks.h
+index dcb5e5b5eb1352..3b56b60195ceb9 100644
+--- a/include/linux/lsm_hooks.h
++++ b/include/linux/lsm_hooks.h
+@@ -59,6 +59,7 @@ struct security_hook_list {
+ struct lsm_blob_sizes {
+ int lbs_cred;
+ int lbs_file;
++ int lbs_backing_file;
+ int lbs_inode;
+ int lbs_superblock;
+ int lbs_ipc;
+diff --git a/include/linux/security.h b/include/linux/security.h
+index 937840870d86c3..4866ffdb4e6c1d 100644
+--- a/include/linux/security.h
++++ b/include/linux/security.h
+@@ -389,11 +389,17 @@ int security_kernfs_init_security(struct kernfs_node *kn_dir,
+ int security_file_permission(struct file *file, int mask);
+ int security_file_alloc(struct file *file);
+ void security_file_free(struct file *file);
++int security_backing_file_alloc(struct file *backing_file,
++ const struct file *user_file);
++void security_backing_file_free(struct file *backing_file);
+ int security_file_ioctl(struct file *file, unsigned int cmd, unsigned long arg);
+ int security_file_ioctl_compat(struct file *file, unsigned int cmd,
+ unsigned long arg);
+ int security_mmap_file(struct file *file, unsigned long prot,
+ unsigned long flags);
++int security_mmap_backing_file(struct vm_area_struct *vma,
++ struct file *backing_file,
++ struct file *user_file);
+ int security_mmap_addr(unsigned long addr);
+ int security_file_mprotect(struct vm_area_struct *vma, unsigned long reqprot,
+ unsigned long prot);
+@@ -984,6 +990,15 @@ static inline int security_file_alloc(struct file *file)
+ static inline void security_file_free(struct file *file)
+ { }
+
++static inline int security_backing_file_alloc(struct file *backing_file,
++ const struct file *user_file)
++{
++ return 0;
++}
++
++static inline void security_backing_file_free(struct file *backing_file)
++{ }
++
+ static inline int security_file_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+ {
+@@ -1003,6 +1018,13 @@ static inline int security_mmap_file(struct file *file, unsigned long prot,
+ return 0;
+ }
+
++static inline int security_mmap_backing_file(struct vm_area_struct *vma,
++ struct file *backing_file,
++ struct file *user_file)
++{
++ return 0;
++}
++
+ static inline int security_mmap_addr(unsigned long addr)
+ {
+ return cap_mmap_addr(addr);
+diff --git a/security/security.c b/security/security.c
+index 1794860fd614ea..4b61766c3d27bd 100644
+--- a/security/security.c
++++ b/security/security.c
+@@ -78,6 +78,7 @@ struct security_hook_heads security_hook_heads __ro_after_init;
+ static BLOCKING_NOTIFIER_HEAD(blocking_lsm_notifier_chain);
+
+ static struct kmem_cache *lsm_file_cache;
++static struct kmem_cache *lsm_backing_file_cache;
+ static struct kmem_cache *lsm_inode_cache;
+
+ char *lsm_names;
+@@ -200,6 +201,8 @@ static void __init lsm_set_blob_sizes(struct lsm_blob_sizes *needed)
+
+ lsm_set_blob_size(&needed->lbs_cred, &blob_sizes.lbs_cred);
+ lsm_set_blob_size(&needed->lbs_file, &blob_sizes.lbs_file);
++ lsm_set_blob_size(&needed->lbs_backing_file,
++ &blob_sizes.lbs_backing_file);
+ /*
+ * The inode blob gets an rcu_head in addition to
+ * what the modules might need.
+@@ -374,6 +377,7 @@ static void __init ordered_lsm_init(void)
+
+ init_debug("cred blob size = %d\n", blob_sizes.lbs_cred);
+ init_debug("file blob size = %d\n", blob_sizes.lbs_file);
++ init_debug("backing_file blob size = %d\n", blob_sizes.lbs_backing_file);
+ init_debug("inode blob size = %d\n", blob_sizes.lbs_inode);
+ init_debug("ipc blob size = %d\n", blob_sizes.lbs_ipc);
+ init_debug("msg_msg blob size = %d\n", blob_sizes.lbs_msg_msg);
+@@ -388,6 +392,11 @@ static void __init ordered_lsm_init(void)
+ lsm_file_cache = kmem_cache_create("lsm_file_cache",
+ blob_sizes.lbs_file, 0,
+ SLAB_PANIC, NULL);
++ if (blob_sizes.lbs_backing_file)
++ lsm_backing_file_cache = kmem_cache_create(
++ "lsm_backing_file_cache",
++ blob_sizes.lbs_backing_file,
++ 0, SLAB_PANIC, NULL);
+ if (blob_sizes.lbs_inode)
+ lsm_inode_cache = kmem_cache_create("lsm_inode_cache",
+ blob_sizes.lbs_inode, 0,
+@@ -616,6 +625,30 @@ static int lsm_file_alloc(struct file *file)
+ return 0;
+ }
+
++/**
++ * lsm_backing_file_alloc - allocate a composite backing file blob
++ * @backing_file: the backing file
++ *
++ * Allocate the backing file blob for all the modules.
++ *
++ * Returns 0, or -ENOMEM if memory can't be allocated.
++ */
++static int lsm_backing_file_alloc(struct file *backing_file)
++{
++ void *blob;
++
++ if (!lsm_backing_file_cache) {
++ backing_file_set_security(backing_file, NULL);
++ return 0;
++ }
++
++ blob = kmem_cache_zalloc(lsm_backing_file_cache, GFP_KERNEL);
++ backing_file_set_security(backing_file, blob);
++ if (!blob)
++ return -ENOMEM;
++ return 0;
++}
++
+ /**
+ * lsm_inode_alloc - allocate a composite inode blob
+ * @inode: the inode that needs a blob
+@@ -2630,6 +2663,57 @@ void security_file_free(struct file *file)
+ }
+ }
+
++/**
++ * security_backing_file_alloc() - Allocate and setup a backing file blob
++ * @backing_file: the backing file
++ * @user_file: the associated user visible file
++ *
++ * Allocate a backing file LSM blob and perform any necessary initialization of
++ * the LSM blob. There will be some operations where the LSM will not have
++ * access to @user_file after this point, so any important state associated
++ * with @user_file that is important to the LSM should be captured in the
++ * backing file's LSM blob.
++ *
++ * LSM's should avoid taking a reference to @user_file in this hook as it will
++ * result in problems later when the system attempts to drop/put the file
++ * references due to a circular dependency.
++ *
++ * Return: Return 0 if the hook is successful, negative values otherwise.
++ */
++int security_backing_file_alloc(struct file *backing_file,
++ const struct file *user_file)
++{
++ int rc;
++
++ rc = lsm_backing_file_alloc(backing_file);
++ if (rc)
++ return rc;
++ rc = call_int_hook(backing_file_alloc, 0, backing_file, user_file);
++ if (unlikely(rc))
++ security_backing_file_free(backing_file);
++
++ return rc;
++}
++
++/**
++ * security_backing_file_free() - Free a backing file blob
++ * @backing_file: the backing file
++ *
++ * Free any LSM state associate with a backing file's LSM blob, including the
++ * blob itself.
++ */
++void security_backing_file_free(struct file *backing_file)
++{
++ void *blob = backing_file_security(backing_file);
++
++ call_void_hook(backing_file_free, backing_file);
++
++ if (blob) {
++ backing_file_set_security(backing_file, NULL);
++ kmem_cache_free(lsm_backing_file_cache, blob);
++ }
++}
++
+ /**
+ * security_file_ioctl() - Check if an ioctl is allowed
+ * @file: associated file
+@@ -2723,6 +2807,32 @@ int security_mmap_file(struct file *file, unsigned long prot,
+ return ima_file_mmap(file, prot, prot_adj, flags);
+ }
+
++/**
++ * security_mmap_backing_file - Check if mmap'ing a backing file is allowed
++ * @vma: the vm_area_struct for the mmap'd region
++ * @backing_file: the backing file being mmap'd
++ * @user_file: the user file being mmap'd
++ *
++ * Check permissions for a mmap operation on a stacked filesystem. This hook
++ * is called after the security_mmap_file() and is responsible for authorizing
++ * the mmap on @backing_file. It is important to note that the mmap operation
++ * on @user_file has already been authorized and the @vma->vm_file has been
++ * set to @backing_file.
++ *
++ * Return: Returns 0 if permission is granted.
++ */
++int security_mmap_backing_file(struct vm_area_struct *vma,
++ struct file *backing_file,
++ struct file *user_file)
++{
++ /* recommended by the stackable filesystem devs */
++ if (WARN_ON_ONCE(!(backing_file->f_mode & FMODE_BACKING)))
++ return -EIO;
++
++ return call_int_hook(mmap_backing_file, 0, vma, backing_file, user_file);
++}
++EXPORT_SYMBOL_GPL(security_mmap_backing_file);
++
+ /**
+ * security_mmap_addr() - Check if mmap'ing an address is allowed
+ * @addr: address
+--
+2.53.0
+
--- /dev/null
+From bb1643bbb774b8de439075556cf7364333729fbd Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Sat, 27 Jun 2026 14:57:20 +0800
+Subject: selinux: fix overlayfs mmap() and mprotect() access checks
+
+From: Paul Moore <paul@paul-moore.com>
+
+[ Upstream commit 82544d36b1729153c8aeb179e84750f0c085d3b1 ]
+
+The existing SELinux security model for overlayfs is to allow access if
+the current task is able to access the top level file (the "user" file)
+and the mounter's credentials are sufficient to access the lower
+level file (the "backing" file). Unfortunately, the current code does
+not properly enforce these access controls for both mmap() and mprotect()
+operations on overlayfs filesystems.
+
+This patch makes use of the newly created security_mmap_backing_file()
+LSM hook to provide the missing backing file enforcement for mmap()
+operations, and leverages the backing file API and new LSM blob to
+provide the necessary information to properly enforce the mprotect()
+access controls.
+
+Cc: stable@vger.kernel.org
+Acked-by: Amir Goldstein <amir73il@gmail.com>
+Signed-off-by: Paul Moore <paul@paul-moore.com>
+[backing_file_user_path() not available
+Mainline uses backing_file_user_path(file) to obtain the user-visible path
+from a backing file. The 6.6.y version uses &file->f_path directly]
+Signed-off-by: Cai Xinchen <caixinchen1@huawei.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ security/selinux/hooks.c | 242 ++++++++++++++++++++++--------
+ security/selinux/include/objsec.h | 11 ++
+ 2 files changed, 189 insertions(+), 64 deletions(-)
+
+diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
+index 60092d0b013ce0..3f11c5ae8fbf0e 100644
+--- a/security/selinux/hooks.c
++++ b/security/selinux/hooks.c
+@@ -1717,49 +1717,72 @@ static inline int file_path_has_perm(const struct cred *cred,
+ static int bpf_fd_pass(const struct file *file, u32 sid);
+ #endif
+
+-/* Check whether a task can use an open file descriptor to
+- access an inode in a given way. Check access to the
+- descriptor itself, and then use dentry_has_perm to
+- check a particular permission to the file.
+- Access to the descriptor is implicitly granted if it
+- has the same SID as the process. If av is zero, then
+- access to the file is not checked, e.g. for cases
+- where only the descriptor is affected like seek. */
+-static int file_has_perm(const struct cred *cred,
+- struct file *file,
+- u32 av)
++static int __file_has_perm(const struct cred *cred, const struct file *file,
++ u32 av, bool bf_user_file)
++
+ {
+- struct file_security_struct *fsec = selinux_file(file);
+- struct inode *inode = file_inode(file);
+ struct common_audit_data ad;
+- u32 sid = cred_sid(cred);
++ struct inode *inode;
++ u32 ssid = cred_sid(cred);
++ u32 tsid_fd;
+ int rc;
+
+- ad.type = LSM_AUDIT_DATA_FILE;
+- ad.u.file = file;
++ if (bf_user_file) {
++ struct backing_file_security_struct *bfsec;
++ const struct path *path;
+
+- if (sid != fsec->sid) {
+- rc = avc_has_perm(sid, fsec->sid,
+- SECCLASS_FD,
+- FD__USE,
+- &ad);
++ if (WARN_ON(!(file->f_mode & FMODE_BACKING)))
++ return -EIO;
++
++ bfsec = selinux_backing_file(file);
++ path = &file->f_path;
++ tsid_fd = bfsec->uf_sid;
++ inode = d_inode(path->dentry);
++
++ ad.type = LSM_AUDIT_DATA_PATH;
++ ad.u.path = *path;
++ } else {
++ struct file_security_struct *fsec = selinux_file(file);
++
++ tsid_fd = fsec->sid;
++ inode = file_inode(file);
++
++ ad.type = LSM_AUDIT_DATA_FILE;
++ ad.u.file = file;
++ }
++
++ if (ssid != tsid_fd) {
++ rc = avc_has_perm(ssid, tsid_fd, SECCLASS_FD, FD__USE, &ad);
+ if (rc)
+- goto out;
++ return rc;
+ }
+
+ #ifdef CONFIG_BPF_SYSCALL
+- rc = bpf_fd_pass(file, cred_sid(cred));
++ /* regardless of backing vs user file, use the underlying file here */
++ rc = bpf_fd_pass(file, ssid);
+ if (rc)
+ return rc;
+ #endif
+
+ /* av is zero if only checking access to the descriptor. */
+- rc = 0;
+ if (av)
+- rc = inode_has_perm(cred, inode, av, &ad);
++ return inode_has_perm(cred, inode, av, &ad);
+
+-out:
+- return rc;
++ return 0;
++}
++
++/* Check whether a task can use an open file descriptor to
++ access an inode in a given way. Check access to the
++ descriptor itself, and then use dentry_has_perm to
++ check a particular permission to the file.
++ Access to the descriptor is implicitly granted if it
++ has the same SID as the process. If av is zero, then
++ access to the file is not checked, e.g. for cases
++ where only the descriptor is affected like seek. */
++static inline int file_has_perm(const struct cred *cred,
++ const struct file *file, u32 av)
++{
++ return __file_has_perm(cred, file, av, false);
+ }
+
+ /*
+@@ -3638,6 +3661,17 @@ static int selinux_file_alloc_security(struct file *file)
+ return 0;
+ }
+
++static int selinux_backing_file_alloc(struct file *backing_file,
++ const struct file *user_file)
++{
++ struct backing_file_security_struct *bfsec;
++
++ bfsec = selinux_backing_file(backing_file);
++ bfsec->uf_sid = selinux_file(user_file)->sid;
++
++ return 0;
++}
++
+ /*
+ * Check whether a task has the ioctl permission and cmd
+ * operation to an inode.
+@@ -3755,42 +3789,55 @@ static int selinux_file_ioctl_compat(struct file *file, unsigned int cmd,
+
+ static int default_noexec __ro_after_init;
+
+-static int file_map_prot_check(struct file *file, unsigned long prot, int shared)
++static int __file_map_prot_check(const struct cred *cred,
++ const struct file *file, unsigned long prot,
++ bool shared, bool bf_user_file)
+ {
+- const struct cred *cred = current_cred();
+- u32 sid = cred_sid(cred);
+- int rc = 0;
++ struct inode *inode = NULL;
++ bool prot_exec = prot & PROT_EXEC;
++ bool prot_write = prot & PROT_WRITE;
++
++ if (file) {
++ if (bf_user_file)
++ inode = d_inode(file->f_path.dentry);
++ else
++ inode = file_inode(file);
++ }
++
++ if (default_noexec && prot_exec &&
++ (!file || IS_PRIVATE(inode) || (!shared && prot_write))) {
++ int rc;
++ u32 sid = cred_sid(cred);
+
+- if (default_noexec &&
+- (prot & PROT_EXEC) && (!file || IS_PRIVATE(file_inode(file)) ||
+- (!shared && (prot & PROT_WRITE)))) {
+ /*
+- * We are making executable an anonymous mapping or a
+- * private file mapping that will also be writable.
+- * This has an additional check.
++ * We are making executable an anonymous mapping or a private
++ * file mapping that will also be writable.
+ */
+- rc = avc_has_perm(sid, sid, SECCLASS_PROCESS,
+- PROCESS__EXECMEM, NULL);
++ rc = avc_has_perm(sid, sid, SECCLASS_PROCESS, PROCESS__EXECMEM,
++ NULL);
+ if (rc)
+- goto error;
++ return rc;
+ }
+
+ if (file) {
+- /* read access is always possible with a mapping */
++ /* "read" always possible, "write" only if shared */
+ u32 av = FILE__READ;
+-
+- /* write access only matters if the mapping is shared */
+- if (shared && (prot & PROT_WRITE))
++ if (shared && prot_write)
+ av |= FILE__WRITE;
+-
+- if (prot & PROT_EXEC)
++ if (prot_exec)
+ av |= FILE__EXECUTE;
+
+- return file_has_perm(cred, file, av);
++ return __file_has_perm(cred, file, av, bf_user_file);
+ }
+
+-error:
+- return rc;
++ return 0;
++}
++
++static inline int file_map_prot_check(const struct cred *cred,
++ const struct file *file,
++ unsigned long prot, bool shared)
++{
++ return __file_map_prot_check(cred, file, prot, shared, false);
+ }
+
+ static int selinux_mmap_addr(unsigned long addr)
+@@ -3806,36 +3853,80 @@ static int selinux_mmap_addr(unsigned long addr)
+ return rc;
+ }
+
+-static int selinux_mmap_file(struct file *file,
+- unsigned long reqprot __always_unused,
+- unsigned long prot, unsigned long flags)
++static int selinux_mmap_file_common(const struct cred *cred, struct file *file,
++ unsigned long prot, bool shared)
+ {
+- struct common_audit_data ad;
+- int rc;
+-
+ if (file) {
++ int rc;
++ struct common_audit_data ad;
++
+ ad.type = LSM_AUDIT_DATA_FILE;
+ ad.u.file = file;
+- rc = inode_has_perm(current_cred(), file_inode(file),
+- FILE__MAP, &ad);
++ rc = inode_has_perm(cred, file_inode(file), FILE__MAP, &ad);
+ if (rc)
+ return rc;
+ }
+
+- return file_map_prot_check(file, prot,
+- (flags & MAP_TYPE) == MAP_SHARED);
++ return file_map_prot_check(cred, file, prot, shared);
++}
++
++static int selinux_mmap_file(struct file *file,
++ unsigned long reqprot __always_unused,
++ unsigned long prot, unsigned long flags)
++{
++ return selinux_mmap_file_common(current_cred(), file, prot,
++ (flags & MAP_TYPE) == MAP_SHARED);
++}
++
++/**
++ * selinux_mmap_backing_file - Check mmap permissions on a backing file
++ * @vma: memory region
++ * @backing_file: stacked filesystem backing file
++ * @user_file: user visible file
++ *
++ * This is called after selinux_mmap_file() on stacked filesystems, and it
++ * is this function's responsibility to verify access to @backing_file and
++ * setup the SELinux state for possible later use in the mprotect() code path.
++ *
++ * By the time this function is called, mmap() access to @user_file has already
++ * been authorized and @vma->vm_file has been set to point to @backing_file.
++ *
++ * Return zero on success, negative values otherwise.
++ */
++static int selinux_mmap_backing_file(struct vm_area_struct *vma,
++ struct file *backing_file,
++ struct file *user_file __always_unused)
++{
++ unsigned long prot = 0;
++
++ /* translate vma->vm_flags perms into PROT perms */
++ if (vma->vm_flags & VM_READ)
++ prot |= PROT_READ;
++ if (vma->vm_flags & VM_WRITE)
++ prot |= PROT_WRITE;
++ if (vma->vm_flags & VM_EXEC)
++ prot |= PROT_EXEC;
++
++ return selinux_mmap_file_common(backing_file->f_cred, backing_file,
++ prot, vma->vm_flags & VM_SHARED);
+ }
+
+ static int selinux_file_mprotect(struct vm_area_struct *vma,
+ unsigned long reqprot __always_unused,
+ unsigned long prot)
+ {
++ int rc;
+ const struct cred *cred = current_cred();
+ u32 sid = cred_sid(cred);
++ const struct file *file = vma->vm_file;
++ bool backing_file;
++ bool shared = vma->vm_flags & VM_SHARED;
++
++ /* check if we need to trigger the "backing files are awful" mode */
++ backing_file = file && (file->f_mode & FMODE_BACKING);
+
+ if (default_noexec &&
+ (prot & PROT_EXEC) && !(vma->vm_flags & VM_EXEC)) {
+- int rc = 0;
+ /*
+ * We don't use the vma_is_initial_heap() helper as it has
+ * a history of problems and is currently broken on systems
+@@ -3849,11 +3940,15 @@ static int selinux_file_mprotect(struct vm_area_struct *vma,
+ vma->vm_end <= vma->vm_mm->brk) {
+ rc = avc_has_perm(sid, sid, SECCLASS_PROCESS,
+ PROCESS__EXECHEAP, NULL);
+- } else if (!vma->vm_file && (vma_is_initial_stack(vma) ||
++ if (rc)
++ return rc;
++ } else if (!file && (vma_is_initial_stack(vma) ||
+ vma_is_stack_for_current(vma))) {
+ rc = avc_has_perm(sid, sid, SECCLASS_PROCESS,
+ PROCESS__EXECSTACK, NULL);
+- } else if (vma->vm_file && vma->anon_vma) {
++ if (rc)
++ return rc;
++ } else if (file && vma->anon_vma) {
+ /*
+ * We are making executable a file mapping that has
+ * had some COW done. Since pages might have been
+@@ -3861,13 +3956,29 @@ static int selinux_file_mprotect(struct vm_area_struct *vma,
+ * modified content. This typically should only
+ * occur for text relocations.
+ */
+- rc = file_has_perm(cred, vma->vm_file, FILE__EXECMOD);
++ rc = __file_has_perm(cred, file, FILE__EXECMOD,
++ backing_file);
++ if (rc)
++ return rc;
++ if (backing_file) {
++ rc = file_has_perm(file->f_cred, file,
++ FILE__EXECMOD);
++ if (rc)
++ return rc;
++ }
+ }
++ }
++
++ rc = __file_map_prot_check(cred, file, prot, shared, backing_file);
++ if (rc)
++ return rc;
++ if (backing_file) {
++ rc = file_map_prot_check(file->f_cred, file, prot, shared);
+ if (rc)
+ return rc;
+ }
+
+- return file_map_prot_check(vma->vm_file, prot, vma->vm_flags&VM_SHARED);
++ return 0;
+ }
+
+ static int selinux_file_lock(struct file *file, unsigned int cmd)
+@@ -6870,6 +6981,7 @@ static void selinux_bpf_prog_free(struct bpf_prog_aux *aux)
+ struct lsm_blob_sizes selinux_blob_sizes __ro_after_init = {
+ .lbs_cred = sizeof(struct task_security_struct),
+ .lbs_file = sizeof(struct file_security_struct),
++ .lbs_backing_file = sizeof(struct backing_file_security_struct),
+ .lbs_inode = sizeof(struct inode_security_struct),
+ .lbs_ipc = sizeof(struct ipc_security_struct),
+ .lbs_msg_msg = sizeof(struct msg_security_struct),
+@@ -7074,9 +7186,11 @@ static struct security_hook_list selinux_hooks[] __ro_after_init = {
+
+ LSM_HOOK_INIT(file_permission, selinux_file_permission),
+ LSM_HOOK_INIT(file_alloc_security, selinux_file_alloc_security),
++ LSM_HOOK_INIT(backing_file_alloc, selinux_backing_file_alloc),
+ LSM_HOOK_INIT(file_ioctl, selinux_file_ioctl),
+ LSM_HOOK_INIT(file_ioctl_compat, selinux_file_ioctl_compat),
+ LSM_HOOK_INIT(mmap_file, selinux_mmap_file),
++ LSM_HOOK_INIT(mmap_backing_file, selinux_mmap_backing_file),
+ LSM_HOOK_INIT(mmap_addr, selinux_mmap_addr),
+ LSM_HOOK_INIT(file_mprotect, selinux_file_mprotect),
+ LSM_HOOK_INIT(file_lock, selinux_file_lock),
+diff --git a/security/selinux/include/objsec.h b/security/selinux/include/objsec.h
+index 8159fd53c3de2e..541933dd295c07 100644
+--- a/security/selinux/include/objsec.h
++++ b/security/selinux/include/objsec.h
+@@ -60,6 +60,10 @@ struct file_security_struct {
+ u32 pseqno; /* Policy seqno at the time of file open */
+ };
+
++struct backing_file_security_struct {
++ u32 uf_sid; /* associated user file fsec->sid */
++};
++
+ struct superblock_security_struct {
+ u32 sid; /* SID of file system superblock */
+ u32 def_sid; /* default SID for labeling */
+@@ -158,6 +162,13 @@ static inline struct file_security_struct *selinux_file(const struct file *file)
+ return file->f_security + selinux_blob_sizes.lbs_file;
+ }
+
++static inline struct backing_file_security_struct *
++selinux_backing_file(const struct file *backing_file)
++{
++ void *blob = backing_file_security(backing_file);
++ return blob + selinux_blob_sizes.lbs_backing_file;
++}
++
+ static inline struct inode_security_struct *selinux_inode(
+ const struct inode *inode)
+ {
+--
+2.53.0
+
revert-ptp-add-testptp-mask-test.patch
bluetooth-btmtk-validate-wmt-event-skb-length-before.patch
bluetooth-btmtk-accept-too-short-wmt-func_ctrl-event.patch
+fs-prepare-for-adding-lsm-blob-to-backing_file.patch
+lsm-add-backing_file-lsm-hooks.patch
+selinux-fix-overlayfs-mmap-and-mprotect-access-check.patch
+batman-adv-tp_meter-keep-unacked-list-in-ascending-o.patch
+batman-adv-tp_meter-initialize-dup_acks-explicitly.patch
+batman-adv-tp_meter-initialize-dec_cwnd-explicitly.patch
+batman-adv-tp_meter-avoid-window-underflow.patch
+batman-adv-tp_meter-avoid-divide-by-zero-for-dec_cwn.patch
+batman-adv-tp_meter-fix-fast-recovery-precondition.patch
+batman-adv-tp_meter-handle-seqno-wrap-around-for-fas.patch
+batman-adv-tp_meter-add-only-finished-tp_vars-to-lis.patch
+batman-adv-bla-annotate-lasttime-access-with-read-wr.patch
+batman-adv-prevent-elp-transmission-interval-underfl.patch
+batman-adv-tp_meter-initialize-last_recv_time-during.patch
+batman-adv-ensure-bcast-is-writable-before-modifying.patch
+batman-adv-fix-m-b-cast-csum-after-decrementing-ttl.patch
+batman-adv-frag-ensure-fragment-is-writable-before-m.patch
+batman-adv-frag-avoid-underflow-of-ttl.patch
+batman-adv-v-prevent-ogm-aggregation-on-disabled-har.patch
+batman-adv-tp_meter-restrict-number-of-unacked-list-.patch
+batman-adv-tp_meter-annotate-last_recv_time-access-w.patch
+batman-adv-tp_meter-prevent-parallel-modifications-o.patch
+batman-adv-tp_meter-handle-overlapping-packets.patch
+batman-adv-tt-don-t-merge-change-entries-with-differ.patch
+batman-adv-tt-track-roam-count-per-vid.patch
+batman-adv-dat-prevent-false-sharing-between-vlans.patch
+batman-adv-tvlv-enforce-2-byte-alignment.patch
+batman-adv-tvlv-avoid-race-of-cifsnotfound-handler-s.patch
--- /dev/null
+From 96e7f68e827d16e65c8378d4d5ae404b7758082e Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:12:24 +0200
+Subject: batman-adv: bla: annotate lasttime access with READ/WRITE_ONCE
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 98b0fb191c878a64cbaebfe231d96d57576acf8c upstream.
+
+The lasttime field for claim, backbone_gw, and loopdetect tracks the
+jiffies value of the most recent activity and is used to detect timeouts.
+These accesses are not consistently protected by a lock, so
+READ_ONCE/WRITE_ONCE must be used to prevent data races caused by compiler
+optimizations.
+
+Cc: stable@kernel.org
+Fixes: 23721387c409 ("batman-adv: add basic bridge loop avoidance code")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/bridge_loop_avoidance.c | 28 +++++++++++++-------------
+ 1 file changed, 14 insertions(+), 14 deletions(-)
+
+diff --git a/net/batman-adv/bridge_loop_avoidance.c b/net/batman-adv/bridge_loop_avoidance.c
+index ffe854018bd3a5..7f643de42579df 100644
+--- a/net/batman-adv/bridge_loop_avoidance.c
++++ b/net/batman-adv/bridge_loop_avoidance.c
+@@ -512,7 +512,7 @@ batadv_bla_get_backbone_gw(struct batadv_priv *bat_priv, const u8 *orig,
+ return NULL;
+
+ entry->vid = vid;
+- entry->lasttime = jiffies;
++ WRITE_ONCE(entry->lasttime, jiffies);
+ entry->crc = BATADV_BLA_CRC_INIT;
+ entry->bat_priv = bat_priv;
+ spin_lock_init(&entry->crc_lock);
+@@ -580,7 +580,7 @@ batadv_bla_update_own_backbone_gw(struct batadv_priv *bat_priv,
+ if (unlikely(!backbone_gw))
+ return;
+
+- backbone_gw->lasttime = jiffies;
++ WRITE_ONCE(backbone_gw->lasttime, jiffies);
+ batadv_backbone_gw_put(backbone_gw);
+ }
+
+@@ -714,7 +714,7 @@ static void batadv_bla_add_claim(struct batadv_priv *bat_priv,
+ ether_addr_copy(claim->addr, mac);
+ spin_lock_init(&claim->backbone_lock);
+ claim->vid = vid;
+- claim->lasttime = jiffies;
++ WRITE_ONCE(claim->lasttime, jiffies);
+ kref_get(&backbone_gw->refcount);
+ claim->backbone_gw = backbone_gw;
+ kref_init(&claim->refcount);
+@@ -736,7 +736,7 @@ static void batadv_bla_add_claim(struct batadv_priv *bat_priv,
+ return;
+ }
+ } else {
+- claim->lasttime = jiffies;
++ WRITE_ONCE(claim->lasttime, jiffies);
+ if (claim->backbone_gw == backbone_gw)
+ /* no need to register a new backbone */
+ goto claim_free_ref;
+@@ -769,7 +769,7 @@ static void batadv_bla_add_claim(struct batadv_priv *bat_priv,
+ spin_lock_bh(&backbone_gw->crc_lock);
+ backbone_gw->crc ^= crc16(0, claim->addr, ETH_ALEN);
+ spin_unlock_bh(&backbone_gw->crc_lock);
+- backbone_gw->lasttime = jiffies;
++ WRITE_ONCE(backbone_gw->lasttime, jiffies);
+
+ claim_free_ref:
+ batadv_claim_put(claim);
+@@ -858,7 +858,7 @@ static bool batadv_handle_announce(struct batadv_priv *bat_priv, u8 *an_addr,
+ return true;
+
+ /* handle as ANNOUNCE frame */
+- backbone_gw->lasttime = jiffies;
++ WRITE_ONCE(backbone_gw->lasttime, jiffies);
+ crc = ntohs(*((__force __be16 *)(&an_addr[4])));
+
+ batadv_dbg(BATADV_DBG_BLA, bat_priv,
+@@ -1253,7 +1253,7 @@ static void batadv_bla_purge_backbone_gw(struct batadv_priv *bat_priv, int now)
+ head, hash_entry) {
+ if (now)
+ goto purge_now;
+- if (!batadv_has_timed_out(backbone_gw->lasttime,
++ if (!batadv_has_timed_out(READ_ONCE(backbone_gw->lasttime),
+ BATADV_BLA_BACKBONE_TIMEOUT))
+ continue;
+
+@@ -1334,7 +1334,7 @@ static void batadv_bla_purge_claims(struct batadv_priv *bat_priv,
+ primary_if->net_dev->dev_addr))
+ goto skip;
+
+- if (!batadv_has_timed_out(claim->lasttime,
++ if (!batadv_has_timed_out(READ_ONCE(claim->lasttime),
+ BATADV_BLA_CLAIM_TIMEOUT))
+ goto skip;
+
+@@ -1494,7 +1494,7 @@ static void batadv_bla_periodic_work(struct work_struct *work)
+ eth_random_addr(bat_priv->bla.loopdetect_addr);
+ bat_priv->bla.loopdetect_addr[0] = 0xba;
+ bat_priv->bla.loopdetect_addr[1] = 0xbe;
+- bat_priv->bla.loopdetect_lasttime = jiffies;
++ WRITE_ONCE(bat_priv->bla.loopdetect_lasttime, jiffies);
+ atomic_set(&bat_priv->bla.loopdetect_next,
+ BATADV_BLA_LOOPDETECT_PERIODS);
+
+@@ -1515,7 +1515,7 @@ static void batadv_bla_periodic_work(struct work_struct *work)
+ primary_if->net_dev->dev_addr))
+ continue;
+
+- backbone_gw->lasttime = jiffies;
++ WRITE_ONCE(backbone_gw->lasttime, jiffies);
+
+ batadv_bla_send_announce(bat_priv, backbone_gw);
+ if (send_loopdetect)
+@@ -1899,7 +1899,7 @@ batadv_bla_loopdetect_check(struct batadv_priv *bat_priv, struct sk_buff *skb,
+ /* If the packet came too late, don't forward it on the mesh
+ * but don't consider that as loop. It might be a coincidence.
+ */
+- if (batadv_has_timed_out(bat_priv->bla.loopdetect_lasttime,
++ if (batadv_has_timed_out(READ_ONCE(bat_priv->bla.loopdetect_lasttime),
+ BATADV_BLA_LOOPDETECT_TIMEOUT))
+ return true;
+
+@@ -2014,7 +2014,7 @@ bool batadv_bla_rx(struct batadv_priv *bat_priv, struct sk_buff *skb,
+
+ if (own_claim) {
+ /* ... allow it in any case */
+- claim->lasttime = jiffies;
++ WRITE_ONCE(claim->lasttime, jiffies);
+ goto allow;
+ }
+
+@@ -2116,7 +2116,7 @@ bool batadv_bla_tx(struct batadv_priv *bat_priv, struct sk_buff *skb,
+ /* if yes, the client has roamed and we have
+ * to unclaim it.
+ */
+- if (batadv_has_timed_out(claim->lasttime, 100)) {
++ if (batadv_has_timed_out(READ_ONCE(claim->lasttime), 100)) {
+ /* only unclaim if the last claim entry is
+ * older than 100 ms to make sure we really
+ * have a roaming client here.
+@@ -2361,7 +2361,7 @@ batadv_bla_backbone_dump_entry(struct sk_buff *msg, u32 portid,
+ backbone_crc = backbone_gw->crc;
+ spin_unlock_bh(&backbone_gw->crc_lock);
+
+- msecs = jiffies_to_msecs(jiffies - backbone_gw->lasttime);
++ msecs = jiffies_to_msecs(jiffies - READ_ONCE(backbone_gw->lasttime));
+
+ if (is_own)
+ if (nla_put_flag(msg, BATADV_ATTR_BLA_OWN)) {
+--
+2.53.0
+
--- /dev/null
+From d31ab8c648c9cf65c6f0b843643f6ddfab6eafdf Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:12:39 +0200
+Subject: batman-adv: dat: prevent false sharing between VLANs
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 20d7658b74169f86d4ac01b9185b3eadddf71f28 upstream.
+
+The local hash of DAT entries is supposed to be VLAN (VID) aware. But
+the adding to the hash and the search in the hash were not checking the VID
+information of the hash entries. The entries would therefore only be
+correctly separated when batadv_hash_dat() didn't select the same buckets
+for different VIDs.
+
+Cc: stable@kernel.org
+Fixes: be1db4f6615b ("batman-adv: make the Distributed ARP Table vlan aware")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/distributed-arp-table.c | 12 +++++++++---
+ 1 file changed, 9 insertions(+), 3 deletions(-)
+
+diff --git a/net/batman-adv/distributed-arp-table.c b/net/batman-adv/distributed-arp-table.c
+index 0a8bd95e2f99e8..86fb5de5022aca 100644
+--- a/net/batman-adv/distributed-arp-table.c
++++ b/net/batman-adv/distributed-arp-table.c
+@@ -214,10 +214,13 @@ static void batadv_dat_purge(struct work_struct *work)
+ */
+ static bool batadv_compare_dat(const struct hlist_node *node, const void *data2)
+ {
+- const void *data1 = container_of(node, struct batadv_dat_entry,
+- hash_entry);
++ const struct batadv_dat_entry *entry1;
++ const struct batadv_dat_entry *entry2;
+
+- return memcmp(data1, data2, sizeof(__be32)) == 0;
++ entry1 = container_of(node, struct batadv_dat_entry, hash_entry);
++ entry2 = data2;
++
++ return entry1->ip == entry2->ip && entry1->vid == entry2->vid;
+ }
+
+ /**
+@@ -344,6 +347,9 @@ batadv_dat_entry_hash_find(struct batadv_priv *bat_priv, __be32 ip,
+ if (dat_entry->ip != ip)
+ continue;
+
++ if (dat_entry->vid != vid)
++ continue;
++
+ if (!kref_get_unless_zero(&dat_entry->refcount))
+ continue;
+
+--
+2.53.0
+
--- /dev/null
+From dc8162d9a2fb72a3646b361fd3d9fe049049171b Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:12:28 +0200
+Subject: batman-adv: ensure bcast is writable before modifying TTL
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 4cd6d3a4b96a8576f1fed8f9f9f17c2dc2978e0c upstream.
+
+Before batman-adv is allowed to write to an skb, it either has to have its
+own copy of the skb or used skb_cow() to ensure that the data part is not
+shared.
+
+The old implementation used a shared queue and created copies before
+attempting to write to it. But with the new implementation, the broadcast
+packet is already modified when it gets received. Potentially writing to
+shared buffers in this process.
+
+Adding a skb_cow() right before this operation avoids this and can at the
+same time prepare it for the modifications required to rebroadcast the
+packet.
+
+Cc: stable@kernel.org
+Fixes: 3f69339068f9 ("batman-adv: bcast: queue per interface, if needed")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/routing.c | 6 ++++++
+ 1 file changed, 6 insertions(+)
+
+diff --git a/net/batman-adv/routing.c b/net/batman-adv/routing.c
+index 12c16f81cc51df..0672dc30bed3b1 100644
+--- a/net/batman-adv/routing.c
++++ b/net/batman-adv/routing.c
+@@ -1191,6 +1191,12 @@ int batadv_recv_bcast_packet(struct sk_buff *skb,
+ if (batadv_is_my_mac(bat_priv, bcast_packet->orig))
+ goto free_skb;
+
++ /* create a copy of the skb, if needed, to modify it. */
++ if (skb_cow(skb, ETH_HLEN) < 0)
++ goto free_skb;
++
++ bcast_packet = (struct batadv_bcast_packet *)skb->data;
++
+ if (bcast_packet->ttl-- < 2)
+ goto free_skb;
+
+--
+2.53.0
+
--- /dev/null
+From 271603aa8370798e690548246db1a265c5245906 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:12:29 +0200
+Subject: batman-adv: fix (m|b)cast csum after decrementing TTL
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit e728bbdf32660c8f32b8f5e8d09427a2c131ad60 upstream.
+
+The broadcast and multicast packets can be received at the same time by the
+local system and forwarded to other nodes. Both are simply decrementing the
+TTL at the beginning of the receive path - independent of chosen paths
+(receive/forward). But such a modification of the data conflicts with the
+hw csum. This is not a problem when the packet is directly forwarded but
+can cause errors in the local receive path.
+
+Such a problem can then trigger a "hw csum failure". The receiver path must
+therefore ensure that the csum is fixed for each modification of the
+payload before batadv_interface_rx() is reached.
+
+Since all batman-adv packet types with a ttl have it as u8 at offset 2, a
+helper can be used for all of them. But it is only used at the moment for
+batadv_bcast_packet and batadv_mcast_packet because they are the only ones
+which deliver the packet locally but unconditionally modify the TTL.
+
+Cc: stable@kernel.org
+Fixes: 3f69339068f9 ("batman-adv: bcast: queue per interface, if needed")
+Fixes: 07afe1ba288c ("batman-adv: mcast: implement multicast packet reception and forwarding")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/routing.c | 58 ++++++++++++++++++++++++++++++++++++++--
+ 1 file changed, 56 insertions(+), 2 deletions(-)
+
+diff --git a/net/batman-adv/routing.c b/net/batman-adv/routing.c
+index 0672dc30bed3b1..cdcea90db61235 100644
+--- a/net/batman-adv/routing.c
++++ b/net/batman-adv/routing.c
+@@ -8,6 +8,7 @@
+ #include "main.h"
+
+ #include <linux/atomic.h>
++#include <linux/build_bug.h>
+ #include <linux/byteorder/generic.h>
+ #include <linux/compiler.h>
+ #include <linux/errno.h>
+@@ -204,6 +205,59 @@ bool batadv_check_management_packet(struct sk_buff *skb,
+ return true;
+ }
+
++/**
++ * batadv_skb_decrement_ttl() - decrement ttl in a batman-adv header, csum-safe
++ * @skb: the received packet with @skb->data pointing to the batman-adv header
++ *
++ * Supports the following packet types, all of which carry the TTL at offset 2:
++ *
++ * - batadv_ogm_packet
++ * - batadv_ogm2_packet
++ * - batadv_icmp_header
++ * - batadv_icmp_packet
++ * - batadv_icmp_tp_packet
++ * - batadv_icmp_packet_rr
++ * - batadv_unicast_packet
++ * - batadv_frag_packet
++ * - batadv_bcast_packet
++ * - batadv_mcast_packet
++ * - batadv_coded_packet
++ * - batadv_unicast_tvlv_packet
++ *
++ * Return: true if the packet may be forwarded (ttl decremented),
++ * false if it must be dropped (ttl would expire)
++ */
++static bool batadv_skb_decrement_ttl(struct sk_buff *skb)
++{
++ static const size_t ttl_offset = 2;
++ u8 *ttl_pos;
++
++ BUILD_BUG_ON(offsetof(struct batadv_ogm_packet, ttl) != ttl_offset);
++ BUILD_BUG_ON(offsetof(struct batadv_ogm2_packet, ttl) != ttl_offset);
++ BUILD_BUG_ON(offsetof(struct batadv_icmp_header, ttl) != ttl_offset);
++ BUILD_BUG_ON(offsetof(struct batadv_icmp_packet, ttl) != ttl_offset);
++ BUILD_BUG_ON(offsetof(struct batadv_icmp_tp_packet, ttl) != ttl_offset);
++ BUILD_BUG_ON(offsetof(struct batadv_icmp_packet_rr, ttl) != ttl_offset);
++ BUILD_BUG_ON(offsetof(struct batadv_unicast_packet, ttl) != ttl_offset);
++ BUILD_BUG_ON(offsetof(struct batadv_frag_packet, ttl) != ttl_offset);
++ BUILD_BUG_ON(offsetof(struct batadv_bcast_packet, ttl) != ttl_offset);
++ BUILD_BUG_ON(offsetof(struct batadv_mcast_packet, ttl) != ttl_offset);
++ BUILD_BUG_ON(offsetof(struct batadv_coded_packet, ttl) != ttl_offset);
++ BUILD_BUG_ON(offsetof(struct batadv_unicast_tvlv_packet, ttl) != ttl_offset);
++
++ ttl_pos = skb->data + ttl_offset;
++
++ /* would expire on this hop -> drop, leave header + csum untouched */
++ if (*ttl_pos < 2)
++ return false;
++
++ skb_postpull_rcsum(skb, ttl_pos, 1);
++ (*ttl_pos)--;
++ skb_postpush_rcsum(skb, ttl_pos, 1);
++
++ return true;
++}
++
+ /**
+ * batadv_recv_my_icmp_packet() - receive an icmp packet locally
+ * @bat_priv: the bat priv with all the mesh interface information
+@@ -1197,7 +1251,7 @@ int batadv_recv_bcast_packet(struct sk_buff *skb,
+
+ bcast_packet = (struct batadv_bcast_packet *)skb->data;
+
+- if (bcast_packet->ttl-- < 2)
++ if (!batadv_skb_decrement_ttl(skb))
+ goto free_skb;
+
+ orig_node = batadv_orig_hash_find(bat_priv, bcast_packet->orig);
+@@ -1304,7 +1358,7 @@ int batadv_recv_mcast_packet(struct sk_buff *skb,
+ goto free_skb;
+
+ mcast_packet = (struct batadv_mcast_packet *)skb->data;
+- if (mcast_packet->ttl-- < 2)
++ if (!batadv_skb_decrement_ttl(skb))
+ goto free_skb;
+
+ tvlv_buff = (unsigned char *)(skb->data + hdr_size);
+--
+2.53.0
+
--- /dev/null
+From e821ae76c6a01d79effefa9559c830143de0539c Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:12:31 +0200
+Subject: batman-adv: frag: avoid underflow of TTL
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 493d9d2528e1a09b090e4b37f0f553def7bd5ce9 upstream.
+
+Packets with a TTL are using it to limit the amount of time this packet can
+be forwarded. But for batadv_frag_packet, the TTL was always only reduced
+but it was never evaluated. It could even underflow without any effect.
+
+Check the TTL in batadv_frag_skb_fwd() before attempting to prepare it for
+forwarding. This keeps it in sync with the not fragmented unicast packet.
+
+Cc: stable@kernel.org
+Fixes: 610bfc6bc99b ("batman-adv: Receive fragmented packets and merge")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/fragmentation.c | 7 +++++++
+ 1 file changed, 7 insertions(+)
+
+diff --git a/net/batman-adv/fragmentation.c b/net/batman-adv/fragmentation.c
+index 5c2a7629e4e09d..9a5927ecc4746f 100644
+--- a/net/batman-adv/fragmentation.c
++++ b/net/batman-adv/fragmentation.c
+@@ -415,6 +415,13 @@ bool batadv_frag_skb_fwd(struct sk_buff *skb,
+ */
+ total_size = ntohs(packet->total_size);
+ if (total_size > neigh_node->if_incoming->net_dev->mtu) {
++ if (packet->ttl < 2) {
++ kfree_skb(skb);
++ *rx_result = NET_RX_DROP;
++ ret = true;
++ goto out;
++ }
++
+ if (skb_cow(skb, ETH_HLEN) < 0) {
+ kfree_skb(skb);
+ *rx_result = NET_RX_DROP;
+--
+2.53.0
+
--- /dev/null
+From 88dbf2a8c2ce086ac680e2d454702c79fb3a8b09 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:12:30 +0200
+Subject: batman-adv: frag: ensure fragment is writable before modifying TTL
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit b7293c6e8c15b2db77809b25cf8389e35331b27a upstream.
+
+Before batman-adv is allowed to write to an skb, it either has to have its
+own copy of the skb or use skb_cow() to ensure that the data part is not
+shared. But batadv_frag_skb_fwd() modifies the TTL even when it is shared.
+
+Adding a skb_cow() right before this operation avoids this and can at the
+same time prepare it for the modifications required to forward the
+fragment.
+
+Cc: stable@kernel.org
+Fixes: 610bfc6bc99b ("batman-adv: Receive fragmented packets and merge")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/fragmentation.c | 15 ++++++++++++++-
+ net/batman-adv/fragmentation.h | 3 ++-
+ net/batman-adv/routing.c | 3 +--
+ 3 files changed, 17 insertions(+), 4 deletions(-)
+
+diff --git a/net/batman-adv/fragmentation.c b/net/batman-adv/fragmentation.c
+index e9553db423491a..5c2a7629e4e09d 100644
+--- a/net/batman-adv/fragmentation.c
++++ b/net/batman-adv/fragmentation.c
+@@ -384,6 +384,8 @@ bool batadv_frag_skb_buffer(struct sk_buff **skb,
+ * @skb: skb to forward
+ * @recv_if: interface that the skb is received on
+ * @orig_node_src: originator that the skb is received from
++ * @rx_result: set to NET_RX_SUCCESS when the fragment was forwarded and
++ * NET_RX_DROP when it was dropped; only valid when true is returned
+ *
+ * Look up the next-hop of the fragments payload and check if the merged packet
+ * will exceed the MTU towards the next-hop. If so, the fragment is forwarded
+@@ -393,7 +395,8 @@ bool batadv_frag_skb_buffer(struct sk_buff **skb,
+ */
+ bool batadv_frag_skb_fwd(struct sk_buff *skb,
+ struct batadv_hard_iface *recv_if,
+- struct batadv_orig_node *orig_node_src)
++ struct batadv_orig_node *orig_node_src,
++ int *rx_result)
+ {
+ struct batadv_priv *bat_priv = netdev_priv(recv_if->mesh_iface);
+ struct batadv_neigh_node *neigh_node = NULL;
+@@ -412,12 +415,22 @@ bool batadv_frag_skb_fwd(struct sk_buff *skb,
+ */
+ total_size = ntohs(packet->total_size);
+ if (total_size > neigh_node->if_incoming->net_dev->mtu) {
++ if (skb_cow(skb, ETH_HLEN) < 0) {
++ kfree_skb(skb);
++ *rx_result = NET_RX_DROP;
++ ret = true;
++ goto out;
++ }
++
++ packet = (struct batadv_frag_packet *)skb->data;
++
+ batadv_inc_counter(bat_priv, BATADV_CNT_FRAG_FWD);
+ batadv_add_counter(bat_priv, BATADV_CNT_FRAG_FWD_BYTES,
+ skb->len + ETH_HLEN);
+
+ packet->ttl--;
+ batadv_send_unicast_skb(skb, neigh_node);
++ *rx_result = NET_RX_SUCCESS;
+ ret = true;
+ }
+
+diff --git a/net/batman-adv/fragmentation.h b/net/batman-adv/fragmentation.h
+index dbf0871f870303..51e281027ab630 100644
+--- a/net/batman-adv/fragmentation.h
++++ b/net/batman-adv/fragmentation.h
+@@ -19,7 +19,8 @@ void batadv_frag_purge_orig(struct batadv_orig_node *orig,
+ bool (*check_cb)(struct batadv_frag_table_entry *));
+ bool batadv_frag_skb_fwd(struct sk_buff *skb,
+ struct batadv_hard_iface *recv_if,
+- struct batadv_orig_node *orig_node_src);
++ struct batadv_orig_node *orig_node_src,
++ int *rx_result);
+ bool batadv_frag_skb_buffer(struct sk_buff **skb,
+ struct batadv_orig_node *orig_node);
+ int batadv_frag_send_packet(struct sk_buff *skb,
+diff --git a/net/batman-adv/routing.c b/net/batman-adv/routing.c
+index cdcea90db61235..4483f8d9c75831 100644
+--- a/net/batman-adv/routing.c
++++ b/net/batman-adv/routing.c
+@@ -1168,10 +1168,9 @@ int batadv_recv_frag_packet(struct sk_buff *skb,
+
+ /* Route the fragment if it is not for us and too big to be merged. */
+ if (!batadv_is_my_mac(bat_priv, frag_packet->dest) &&
+- batadv_frag_skb_fwd(skb, recv_if, orig_node_src)) {
++ batadv_frag_skb_fwd(skb, recv_if, orig_node_src, &ret)) {
+ /* skb was consumed */
+ skb = NULL;
+- ret = NET_RX_SUCCESS;
+ goto put_orig_node;
+ }
+
+--
+2.53.0
+
--- /dev/null
+From 194cbd473d76a79408f0c1b4dd250a56bc9cf038 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:12:27 +0200
+Subject: batman-adv: gw: don't deselect gateway with active hardif
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit df97a7107b16375a10a36d7a63e9b4291a8ac680 upstream.
+
+The batadv_hardif_cnt() was previously checking if there is an
+batadv_hard_iface->mesh_iface which is has the same mesh_iface. And since
+batadv_hardif_disable_interface() was resetting the
+batadv_hard_iface->mesh_iface after this check, it had to verify whether
+*1* interface was still part of the mesh_iface before it started the
+gateway deselection.
+
+But after batadv_hardif_cnt() is now checking the lower interfaces of
+mesh_iface and batadv_hardif_disable_interface() already removed the
+interface via netdev_upper_dev_unlink() earlier in this function, the check
+must now make sure that *0* interfaces can be found by batadv_hardif_cnt()
+before selected gateway must be deselected. Otherwise the deselection would
+already happen one batadv_hard_iface too early.
+
+Because a 0 hardif count from batadv_hardif_cnt() is equal to an empty
+list, it is possible to replace the counting with a simple list_empty().
+
+Cc: stable@kernel.org
+Fixes: 7dc284702bcd ("batman-adv: store hard_iface as iflink private data")
+Reviewed-by: Nora Schiffer <neocturne@universe-factory.net>
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/hard-interface.c | 28 ++--------------------------
+ 1 file changed, 2 insertions(+), 26 deletions(-)
+
+diff --git a/net/batman-adv/hard-interface.c b/net/batman-adv/hard-interface.c
+index d6732c34aeafc8..c7dbd24ea19985 100644
+--- a/net/batman-adv/hard-interface.c
++++ b/net/batman-adv/hard-interface.c
+@@ -786,30 +786,6 @@ int batadv_hardif_enable_interface(struct batadv_hard_iface *hard_iface,
+ return ret;
+ }
+
+-/**
+- * batadv_hardif_cnt() - get number of interfaces enslaved to mesh interface
+- * @mesh_iface: mesh interface to check
+- *
+- * This function is only using RCU for locking - the result can therefore be
+- * off when another function is modifying the list at the same time. The
+- * caller can use the rtnl_lock to make sure that the count is accurate.
+- *
+- * Return: number of connected/enslaved hard interfaces
+- */
+-static size_t batadv_hardif_cnt(struct net_device *mesh_iface)
+-{
+- struct batadv_hard_iface *hard_iface;
+- struct list_head *iter;
+- size_t count = 0;
+-
+- rcu_read_lock();
+- netdev_for_each_lower_private_rcu(mesh_iface, hard_iface, iter)
+- count++;
+- rcu_read_unlock();
+-
+- return count;
+-}
+-
+ /**
+ * batadv_hardif_disable_interface() - Remove hard interface from mesh interface
+ * @hard_iface: hard interface to be removed
+@@ -850,8 +826,8 @@ void batadv_hardif_disable_interface(struct batadv_hard_iface *hard_iface)
+ netdev_upper_dev_unlink(hard_iface->net_dev, hard_iface->mesh_iface);
+ batadv_hardif_recalc_extra_skbroom(hard_iface->mesh_iface);
+
+- /* nobody uses this interface anymore */
+- if (batadv_hardif_cnt(hard_iface->mesh_iface) <= 1)
++ /* nobody uses this mesh interface anymore */
++ if (list_empty(&hard_iface->mesh_iface->adj_list.lower))
+ batadv_gw_check_client_stop(bat_priv);
+
+ hard_iface->mesh_iface = NULL;
+--
+2.53.0
+
--- /dev/null
+From e6dfffb8565c4514bb23a60d4e965684785bd1e7 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:12:25 +0200
+Subject: batman-adv: prevent ELP transmission interval underflow
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 5e50d4b8ae3ea622122d3c6a38d7f6fe68dfddca upstream.
+
+batadv_v_elp_start_timer() enqeues a delayed work. The time when it starts
+is randomly chosen between (elp_interval - BATADV_JITTER) and
+(elp_interval + BATADV_JITTER). The configured elp_interval must therefore
+be larger or equal to BATADV_JITTER to avoid that it causes an underflow of
+the unsigned integer. If this would happen, then a "fast" ELP interval
+would turn into a "day long" delay.
+
+At the same time, it must not be larger than the maximum value the variable
+can store.
+
+Cc: stable@kernel.org
+Fixes: a10800829040 ("batman-adv: Add elp_interval hardif genl configuration")
+[ Context ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/netlink.c | 8 +++++++-
+ 1 file changed, 7 insertions(+), 1 deletion(-)
+
+diff --git a/net/batman-adv/netlink.c b/net/batman-adv/netlink.c
+index 78c651f634cd39..1d144d8cc0928e 100644
+--- a/net/batman-adv/netlink.c
++++ b/net/batman-adv/netlink.c
+@@ -917,9 +917,15 @@ static int batadv_netlink_set_hardif(struct sk_buff *skb,
+ #ifdef CONFIG_BATMAN_ADV_BATMAN_V
+
+ if (info->attrs[BATADV_ATTR_ELP_INTERVAL]) {
++ u32 elp_interval;
++
+ attr = info->attrs[BATADV_ATTR_ELP_INTERVAL];
++ elp_interval = nla_get_u32(attr);
++
++ elp_interval = min_t(u32, elp_interval, INT_MAX);
++ elp_interval = max_t(u32, elp_interval, BATADV_JITTER);
+
+- atomic_set(&hard_iface->bat_v.elp_interval, nla_get_u32(attr));
++ atomic_set(&hard_iface->bat_v.elp_interval, elp_interval);
+ }
+
+ if (info->attrs[BATADV_ATTR_THROUGHPUT_OVERRIDE]) {
+--
+2.53.0
+
--- /dev/null
+From ad368ca6b053b93eb4848ac734e0593d5d3c7a97 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:12:23 +0200
+Subject: batman-adv: tp_meter: add only finished tp_vars to lists
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 15ccbf685222274f5add1387af58c2a41a95f81e upstream.
+
+When the receiver variables (aka "session") are initialized, then they are
+added to the list of sessions before the timer is set up. A RCU protected
+reader could therefore find the entry and run mod_setup before
+batadv_tp_init_recv() finished the timer initialization.
+
+The same is true for batadv_tp_start(), which must first initialize the
+finish_work and the test_length to avoid a similar problem.
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 18 +++++++++---------
+ 1 file changed, 9 insertions(+), 9 deletions(-)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index 0801dcf93540ac..7bfad65c862e9a 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -1096,21 +1096,21 @@ void batadv_tp_start(struct batadv_priv *bat_priv, const u8 *dst,
+ tp_vars->prerandom_offset = 0;
+ spin_lock_init(&tp_vars->prerandom_lock);
+
+- kref_get(&tp_vars->refcount);
+- hlist_add_head_rcu(&tp_vars->list, &bat_priv->tp_list);
+- spin_unlock_bh(&bat_priv->tp_list_lock);
+-
+ tp_vars->test_length = test_length;
+ if (!tp_vars->test_length)
+ tp_vars->test_length = BATADV_TP_DEF_TEST_LENGTH;
+
++ /* init work item for finished tp tests */
++ INIT_DELAYED_WORK(&tp_vars->finish_work, batadv_tp_sender_finish);
++
++ kref_get(&tp_vars->refcount);
++ hlist_add_head_rcu(&tp_vars->list, &bat_priv->tp_list);
++ spin_unlock_bh(&bat_priv->tp_list_lock);
++
+ batadv_dbg(BATADV_DBG_TP_METER, bat_priv,
+ "Meter: starting throughput meter towards %pM (length=%ums)\n",
+ dst, test_length);
+
+- /* init work item for finished tp tests */
+- INIT_DELAYED_WORK(&tp_vars->finish_work, batadv_tp_sender_finish);
+-
+ /* start tp kthread. This way the write() call issued from userspace can
+ * happily return and avoid to block
+ */
+@@ -1430,10 +1430,10 @@ batadv_tp_init_recv(struct batadv_priv *bat_priv,
+ INIT_LIST_HEAD(&tp_vars->unacked_list);
+
+ kref_get(&tp_vars->refcount);
+- hlist_add_head_rcu(&tp_vars->list, &bat_priv->tp_list);
++ timer_setup(&tp_vars->timer, batadv_tp_receiver_shutdown, 0);
+
+ kref_get(&tp_vars->refcount);
+- timer_setup(&tp_vars->timer, batadv_tp_receiver_shutdown, 0);
++ hlist_add_head_rcu(&tp_vars->list, &bat_priv->tp_list);
+
+ batadv_tp_reset_receiver_timer(tp_vars);
+
+--
+2.53.0
+
--- /dev/null
+From b10da62fe1bf22c0d4f160d236d23da6f962a8cd Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:12:34 +0200
+Subject: batman-adv: tp_meter: annotate last_recv_time access with
+ READ/WRITE_ONCE
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit d67c728f07fca2ee6ffdc6dd4421cf2e8691f4d1 upstream.
+
+The last_recv_time field for batadv_tp_receiver tracks the jiffies value of
+the most recent activity and is used to detect timeouts. These accesses are
+not consistently protected by a lock, so READ_ONCE/WRITE_ONCE must be used
+to prevent data races caused by compiler optimizations.
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 8 ++++----
+ 1 file changed, 4 insertions(+), 4 deletions(-)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index 4f8af909893ab9..e23b75846ebe1a 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -1183,7 +1183,7 @@ static void batadv_tp_receiver_shutdown(struct timer_list *t)
+ bat_priv = tp_vars->bat_priv;
+
+ /* if there is recent activity rearm the timer */
+- if (!batadv_has_timed_out(tp_vars->last_recv_time,
++ if (!batadv_has_timed_out(READ_ONCE(tp_vars->last_recv_time),
+ BATADV_TP_RECV_TIMEOUT)) {
+ /* reset the receiver shutdown timer */
+ batadv_tp_reset_receiver_timer(tp_vars);
+@@ -1424,7 +1424,7 @@ batadv_tp_init_recv(struct batadv_priv *bat_priv,
+ tp_vars = batadv_tp_list_find_session(bat_priv, icmp->orig,
+ icmp->session, BATADV_TP_RECEIVER);
+ if (tp_vars) {
+- tp_vars->last_recv_time = jiffies;
++ WRITE_ONCE(tp_vars->last_recv_time, jiffies);
+ goto out_unlock;
+ }
+
+@@ -1455,7 +1455,7 @@ batadv_tp_init_recv(struct batadv_priv *bat_priv,
+ kref_get(&tp_vars->refcount);
+ timer_setup(&tp_vars->timer, batadv_tp_receiver_shutdown, 0);
+
+- tp_vars->last_recv_time = jiffies;
++ WRITE_ONCE(tp_vars->last_recv_time, jiffies);
+
+ kref_get(&tp_vars->refcount);
+ hlist_add_head_rcu(&tp_vars->list, &bat_priv->tp_list);
+@@ -1506,7 +1506,7 @@ static void batadv_tp_recv_msg(struct batadv_priv *bat_priv,
+ goto out;
+ }
+
+- tp_vars->last_recv_time = jiffies;
++ WRITE_ONCE(tp_vars->last_recv_time, jiffies);
+ }
+
+ /* if the packet is a duplicate, it may be the case that an ACK has been
+--
+2.53.0
+
--- /dev/null
+From 50472f5e86ad53e0eb270d2686fb05c3bfe4fd12 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:12:20 +0200
+Subject: batman-adv: tp_meter: avoid divide-by-zero for dec_cwnd
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 33ccd52f3cc9ed46ce395199f89aa3234dc83314 upstream.
+
+The cwnd is always MSS <= cwnd <= 0x20000000. But the calculation in
+batadv_tp_update_cwnd() assumes unsigned 32 bit arithmetics.
+
+ ((mss * 8) ** 2) / (cwnd * 8)
+
+In case cwnd is actually 0x20000000, it will be shifted by 3 bit to the
+left end up at 0x100000000 or U32_MAX + 1. It will therefore wrap around
+and be 0 - resulting in:
+
+ ((mss * 8) ** 2) / 0
+
+This is of course invalid and cannot be calculated. The calculation should
+must be simplified to avoid this overflow:
+
+ (mss ** 2) * 8 / cwnd
+
+It will keep the precision enhancement from the scaling (by 8) but avoid
+the overflow in the divisor.
+
+In theory, there could still be an overflow in the dividend. It is at the
+moment fixed to BATADV_TP_PLEN in batadv_tp_recv_ack() - so it is not an
+imminent problem. But allowing it to use the whole u32 bit range, would
+mean that it can still use up to 67 bits. To keep this calculation safe for
+32 bit arithmetic, mss must never use more than floor((32 - 3) / 2) bits -
+or in other words: must never be larger than 16383.
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 5 ++++-
+ 1 file changed, 4 insertions(+), 1 deletion(-)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index d3b8f02ca08b14..e4f76c141af3e2 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -154,9 +154,12 @@ static void batadv_tp_update_cwnd(struct batadv_tp_vars *tp_vars, u32 mss)
+ return;
+ }
+
++ /* prevent overflow in (mss * mss) << 3 */
++ mss = min_t(u32, mss, (1U << 14) - 1);
++
+ /* increment CWND at least of 1 (section 3.1 of RFC5681) */
+ tp_vars->dec_cwnd += max_t(u32, 1U << 3,
+- ((mss * mss) << 6) / (tp_vars->cwnd << 3));
++ ((mss * mss) << 3) / tp_vars->cwnd);
+ if (tp_vars->dec_cwnd < (mss << 3)) {
+ spin_unlock_bh(&tp_vars->cwnd_lock);
+ return;
+--
+2.53.0
+
--- /dev/null
+From 1e14937db4cc5ff86a2c73915c4afa6354b9e044 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:12:19 +0200
+Subject: batman-adv: tp_meter: avoid window underflow
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 765947b81fb54b6ebb0bc1cfe55c0fa399e002b8 upstream.
+
+In batadv_tp_avail(), win_left is calculated with 32-bit unsigned
+arithmetic: win_left = win_limit - tp_vars->last_sent;
+
+During Fast Recovery, cwnd is inflated and last_sent advances rapidly. When
+Fast Recovery ends, cwnd drops abruptly back to ss_threshold. If the newly
+shrunk win_limit is less than last_sent, the unsigned subtraction will
+underflow, wrapping to a massive positive value. Instead of returning that
+the window is full (unavailable), it returns that the sender can continue
+sending.
+
+To handle this situation, it must be checked whether the windows end
+sequence number (win_limit) has to be compared with the last sent sequence
+number. If it would be before the last sent sequence number, then more acks
+are needed before the transmission can be started again.
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 7 ++++++-
+ 1 file changed, 6 insertions(+), 1 deletion(-)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index 2ff7aa5ed19fb3..d3b8f02ca08b14 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -817,10 +817,15 @@ static void batadv_tp_recv_ack(struct batadv_priv *bat_priv,
+ static bool batadv_tp_avail(struct batadv_tp_vars *tp_vars,
+ size_t payload_len)
+ {
++ u32 last_sent = READ_ONCE(tp_vars->last_sent);
+ u32 win_left, win_limit;
+
+ win_limit = atomic_read(&tp_vars->last_acked) + tp_vars->cwnd;
+- win_left = win_limit - tp_vars->last_sent;
++
++ if (batadv_seq_before(last_sent, win_limit))
++ win_left = win_limit - last_sent;
++ else
++ win_left = 0;
+
+ return win_left >= payload_len;
+ }
+--
+2.53.0
+
--- /dev/null
+From 0c428129fc44a0745c0dda017f0504b2c9938d95 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:12:21 +0200
+Subject: batman-adv: tp_meter: fix fast recovery precondition
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 2b0d08f08ed3b2174f05c43089ec65f3543a025b upstream.
+
+The fast recovery precondition checks if the recover (initialized to
+BATADV_TP_FIRST_SEQ) is bigger than the received ack. But since recover is
+only updated when this check is successful, it will never enter the fast
+recovery mode.
+
+According to RFC6582 Section 3.2 step 2, the check should actually be
+different:
+
+> When the third duplicate ACK is received, the TCP sender first
+> checks the value of recover to see if the Cumulative
+> Acknowledgment field covers more than recover
+
+The precondition must therefore check if recover is smaller than the
+received ack - basically swapping the operands of the current check.
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index e4f76c141af3e2..77bc69573a5624 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -733,7 +733,7 @@ static void batadv_tp_recv_ack(struct batadv_priv *bat_priv,
+ if (atomic_read(&tp_vars->dup_acks) != 3)
+ goto out;
+
+- if (recv_ack >= tp_vars->recover)
++ if (tp_vars->recover >= recv_ack)
+ goto out;
+
+ /* if this is the third duplicate ACK do Fast Retransmit */
+--
+2.53.0
+
--- /dev/null
+From 889335ce69ea5dbae87aa12923b8dd7e51e60ab1 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:12:36 +0200
+Subject: batman-adv: tp_meter: handle overlapping packets
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit cbde75c38b21f022891525078622587ad557b7c1 upstream.
+
+If the size of the packets would change during the transmission, it could
+happen that some retries of packets are overlapping. In this case, precise
+comparisons of sequence numbers by the receiver would be wrong. It is then
+necessary to check if the start sequence number to the end sequence number
+("seqno + length") would contain a new range.
+
+If this is the case then this is enough to accept this packet. In all other
+cases, the packet still has to be dropped (and not acked).
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+[ Switch to pre-splitted tp_vars structure names ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 25 +++++++++++--------------
+ 1 file changed, 11 insertions(+), 14 deletions(-)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index 6a23050f03d89e..dbaae33db0f1f8 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -1284,7 +1284,8 @@ static int batadv_tp_send_ack(struct batadv_priv *bat_priv, const u8 *dst,
+ /**
+ * batadv_tp_handle_out_of_order() - store an out of order packet
+ * @tp_vars: the private data of the current TP meter session
+- * @skb: the buffer containing the received packet
++ * @seqno: sequence number of new received packet
++ * @payload_len: length of the received packet
+ *
+ * Store the out of order packet in the unacked list for late processing. This
+ * packets are kept in this list so that they can be ACKed at once as soon as
+@@ -1293,22 +1294,17 @@ static int batadv_tp_send_ack(struct batadv_priv *bat_priv, const u8 *dst,
+ * Return: true if the packed has been successfully processed, false otherwise
+ */
+ static bool batadv_tp_handle_out_of_order(struct batadv_tp_vars *tp_vars,
+- const struct sk_buff *skb)
++ u32 seqno, u32 payload_len)
+ __must_hold(&tp_vars->unacked_lock)
+ {
+- const struct batadv_icmp_tp_packet *icmp;
+ struct batadv_tp_unacked *un, *new;
+- u32 payload_len;
+ bool added = false;
+
+ new = kmalloc_obj(*new, GFP_ATOMIC);
+ if (unlikely(!new))
+ return false;
+
+- icmp = (struct batadv_icmp_tp_packet *)skb->data;
+-
+- new->seqno = ntohl(icmp->seqno);
+- payload_len = skb->len - sizeof(struct batadv_unicast_packet);
++ new->seqno = seqno;
+ new->len = payload_len;
+
+ /* if the list is empty immediately attach this new object */
+@@ -1476,7 +1472,7 @@ static void batadv_tp_recv_msg(struct batadv_priv *bat_priv,
+ {
+ const struct batadv_icmp_tp_packet *icmp;
+ struct batadv_tp_vars *tp_vars;
+- size_t packet_size;
++ u32 payload_len;
+ u32 to_ack;
+ u32 seqno;
+
+@@ -1511,15 +1507,17 @@ static void batadv_tp_recv_msg(struct batadv_priv *bat_priv,
+ /* if the packet is a duplicate, it may be the case that an ACK has been
+ * lost. Resend the ACK
+ */
+- if (batadv_seq_before(seqno, tp_vars->last_recv))
++ payload_len = skb->len - sizeof(struct batadv_unicast_packet);
++ to_ack = seqno + payload_len;
++ if (batadv_seq_before(to_ack, tp_vars->last_recv))
+ goto send_ack;
+
+ /* if the packet is out of order enqueue it */
+- if (ntohl(icmp->seqno) != tp_vars->last_recv) {
++ if (batadv_seq_before(tp_vars->last_recv, seqno)) {
+ /* exit immediately (and do not send any ACK) if the packet has
+ * not been enqueued correctly
+ */
+- if (!batadv_tp_handle_out_of_order(tp_vars, skb)) {
++ if (!batadv_tp_handle_out_of_order(tp_vars, seqno, payload_len)) {
+ spin_unlock_bh(&tp_vars->unacked_lock);
+ goto out;
+ }
+@@ -1529,8 +1527,7 @@ static void batadv_tp_recv_msg(struct batadv_priv *bat_priv,
+ }
+
+ /* if everything was fine count the ACKed bytes */
+- packet_size = skb->len - sizeof(struct batadv_unicast_packet);
+- tp_vars->last_recv += packet_size;
++ tp_vars->last_recv = to_ack;
+
+ /* check if this ordered message filled a gap.... */
+ batadv_tp_ack_unordered(tp_vars);
+--
+2.53.0
+
--- /dev/null
+From ba12ba73ab6ec278d8d10821c5609298ea477e4b Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:12:22 +0200
+Subject: batman-adv: tp_meter: handle seqno wrap-around for fast recovery
+ detection
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit f54c85ed42a1b27a516cf2a4728f5a612b799e07 upstream.
+
+The recover variable and the last_sent sequence number are initialized on
+purpose as a really high value which will wrap-around after the first 2000
+bytes. The fast recovery precondition must therefore not use simple integer
+comparisons but use helpers which are aware of the sequence number
+wrap-arounds.
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index 77bc69573a5624..0801dcf93540ac 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -733,7 +733,7 @@ static void batadv_tp_recv_ack(struct batadv_priv *bat_priv,
+ if (atomic_read(&tp_vars->dup_acks) != 3)
+ goto out;
+
+- if (tp_vars->recover >= recv_ack)
++ if (!batadv_seq_before(tp_vars->recover, recv_ack))
+ goto out;
+
+ /* if this is the third duplicate ACK do Fast Retransmit */
+--
+2.53.0
+
--- /dev/null
+From d5ff1d0f45192d289c6b78bc7aa2334b7377b8fb Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:12:18 +0200
+Subject: batman-adv: tp_meter: initialize dec_cwnd explicitly
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit febfb1b86224489535312296ecfa3d4bf467f339 upstream.
+
+When batadv_tp_update_cwnd() is called, dec_cwnd is increased. But dec_cwnd
+is only initialixed (to 0) when a duplicate Ack was received or when cwnd
+is below the ss_threshold.
+
+Just initialize the cwnd during the initialization to avoid any potential
+access of uninitialized data.
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index 0325b951ff8a8e..2ff7aa5ed19fb3 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -1055,6 +1055,8 @@ void batadv_tp_start(struct batadv_priv *bat_priv, const u8 *dst,
+ * mesh_interface, hence its MTU
+ */
+ tp_vars->cwnd = BATADV_TP_PLEN * 3;
++ tp_vars->dec_cwnd = 0;
++
+ /* at the beginning initialise the SS threshold to the biggest possible
+ * window size, hence the AWND size
+ */
+--
+2.53.0
+
--- /dev/null
+From c1a29c5c5fefc0bf5899f771c793854209adc01f Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:12:17 +0200
+Subject: batman-adv: tp_meter: initialize dup_acks explicitly
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit b2b68b32a715e0328662801576974aa37b942b00 upstream.
+
+When an ack with a sequence number equal to the last_acked is received, the
+dup_acks counter is increased to decide whether fast retransmit should be
+performed. Only when the sequence numbers are not equal, the dup_acks is
+set to the initial value (0).
+
+But if the initial packet would have the sequence number
+BATADV_TP_FIRST_SEQ, dup_acks would not be initialized and atomic_inc would
+operate on an undefined starting value. It is therefore required to have it
+explicitly initialized during the start of the sender session.
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index e8941f753a9697..0325b951ff8a8e 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -1045,6 +1045,7 @@ void batadv_tp_start(struct batadv_priv *bat_priv, const u8 *dst,
+ tp_vars->icmp_uid = icmp_uid;
+
+ tp_vars->last_sent = BATADV_TP_FIRST_SEQ;
++ atomic_set(&tp_vars->dup_acks, 0);
+ atomic_set(&tp_vars->last_acked, BATADV_TP_FIRST_SEQ);
+ tp_vars->fast_recovery = false;
+ tp_vars->recover = BATADV_TP_FIRST_SEQ;
+--
+2.53.0
+
--- /dev/null
+From f4e0258629da50d0465ea4a44d140cf08fcb7c3e Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:12:26 +0200
+Subject: batman-adv: tp_meter: initialize last_recv_time during init
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 811cb00fa8cdc3f0a7f6eefc000a6888367c8c8f upstream.
+
+The last_recv_time is the most important indicator for a receiver session
+to figure out whether a session timed out or not. But this information was
+only initialized after the session was added to the tp_receiver_list and
+after the timer was started.
+
+In the worst case, the timer (function) could have tried to access this
+information before the actual initialization was reached. Like rest of the
+variables of the tp_meter receiver session, this field has to be filled out
+before any other (parallel running) context has the chance to access it.
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+[ Context ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 10 +++++++---
+ 1 file changed, 7 insertions(+), 3 deletions(-)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index 7bfad65c862e9a..397b9456353568 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -1403,8 +1403,10 @@ batadv_tp_init_recv(struct batadv_priv *bat_priv,
+
+ tp_vars = batadv_tp_list_find_session(bat_priv, icmp->orig,
+ icmp->session, BATADV_TP_RECEIVER);
+- if (tp_vars)
++ if (tp_vars) {
++ tp_vars->last_recv_time = jiffies;
+ goto out_unlock;
++ }
+
+ if (!atomic_add_unless(&bat_priv->tp_num, 1, BATADV_TP_MAX_NUM)) {
+ batadv_dbg(BATADV_DBG_TP_METER, bat_priv,
+@@ -1432,6 +1434,8 @@ batadv_tp_init_recv(struct batadv_priv *bat_priv,
+ kref_get(&tp_vars->refcount);
+ timer_setup(&tp_vars->timer, batadv_tp_receiver_shutdown, 0);
+
++ tp_vars->last_recv_time = jiffies;
++
+ kref_get(&tp_vars->refcount);
+ hlist_add_head_rcu(&tp_vars->list, &bat_priv->tp_list);
+
+@@ -1480,9 +1484,9 @@ static void batadv_tp_recv_msg(struct batadv_priv *bat_priv,
+ icmp->orig);
+ goto out;
+ }
+- }
+
+- tp_vars->last_recv_time = jiffies;
++ tp_vars->last_recv_time = jiffies;
++ }
+
+ /* if the packet is a duplicate, it may be the case that an ACK has been
+ * lost. Resend the ACK
+--
+2.53.0
+
--- /dev/null
+From 32e3c4739f435b27983632f24739b66c0f08a86f Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:12:16 +0200
+Subject: batman-adv: tp_meter: keep unacked list in ascending ordered
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 5aa8651527ea0b610e7a09fb3b8204c1398b9525 upstream.
+
+When batadv_tp_handle_out_of_order inserts a new entry in the list of
+unacked (out of order) packets, it searches from the entry with the newest
+sequence number towards oldest sequence number. If an entry is found which
+is older than the newly entry, the new entry has to be added after the
+found one to keep the ascending order.
+
+But for this operation list_add_tail() was used. But this function adds an
+entry _before_ another one. As result, the list would contain a lot of
+swapped sequence numbers. The consumer of this list
+(batadv_tp_ack_unordered()) would then fail to correctly ack packets.
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index 0fc4ca78e84ebe..e8941f753a9697 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -1325,7 +1325,7 @@ static bool batadv_tp_handle_out_of_order(struct batadv_tp_vars *tp_vars,
+ * one is attached _after_ it. In this way the list is kept in
+ * ascending order
+ */
+- list_add_tail(&new->list, &un->list);
++ list_add(&new->list, &un->list);
+ added = true;
+ break;
+ }
+--
+2.53.0
+
--- /dev/null
+From 3f0174568d3a659bba18a1577dcbe5395e5f4273 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:12:35 +0200
+Subject: batman-adv: tp_meter: prevent parallel modifications of last_recv
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 6dde0cfcb36e4d5b3de35b75696937478441eed4 upstream.
+
+When last_recv is updated to store the last receive sequence number, it is
+assuming that nothing is modifying in parallel while:
+
+* check for outdated packets is done
+* out of order check is performed (and packets are stored in out-of-order
+ queue)
+* the out-of-order queue was searched for closed gaps
+* sequence number for next ack is calculated
+
+Nothing of that was actually protected. It could therefore happen that the
+last_recv was updated multiple times in parallel and the final sequence
+number was calculated with deltas which had no connection to the sequence
+number they were added to.
+
+Lock this whole region with the same lock which was already used to protect
+the unacked (out-of-order) list.
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+[ Switch to pre-splitted tp_vars structure names ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 22 +++++++++++++---------
+ net/batman-adv/types.h | 2 +-
+ 2 files changed, 14 insertions(+), 10 deletions(-)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index e23b75846ebe1a..6a23050f03d89e 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -1294,6 +1294,7 @@ static int batadv_tp_send_ack(struct batadv_priv *bat_priv, const u8 *dst,
+ */
+ static bool batadv_tp_handle_out_of_order(struct batadv_tp_vars *tp_vars,
+ const struct sk_buff *skb)
++ __must_hold(&tp_vars->unacked_lock)
+ {
+ const struct batadv_icmp_tp_packet *icmp;
+ struct batadv_tp_unacked *un, *new;
+@@ -1310,12 +1311,11 @@ static bool batadv_tp_handle_out_of_order(struct batadv_tp_vars *tp_vars,
+ payload_len = skb->len - sizeof(struct batadv_unicast_packet);
+ new->len = payload_len;
+
+- spin_lock_bh(&tp_vars->unacked_lock);
+ /* if the list is empty immediately attach this new object */
+ if (list_empty(&tp_vars->unacked_list)) {
+ list_add(&new->list, &tp_vars->unacked_list);
+ tp_vars->unacked_count++;
+- goto out;
++ return true;
+ }
+
+ /* otherwise loop over the list and either drop the packet because this
+@@ -1364,9 +1364,6 @@ static bool batadv_tp_handle_out_of_order(struct batadv_tp_vars *tp_vars,
+ tp_vars->unacked_count--;
+ }
+
+-out:
+- spin_unlock_bh(&tp_vars->unacked_lock);
+-
+ return true;
+ }
+
+@@ -1376,6 +1373,7 @@ static bool batadv_tp_handle_out_of_order(struct batadv_tp_vars *tp_vars,
+ * @tp_vars: the private data of the current TP meter session
+ */
+ static void batadv_tp_ack_unordered(struct batadv_tp_vars *tp_vars)
++ __must_hold(&tp_vars->unacked_lock)
+ {
+ struct batadv_tp_unacked *un, *safe;
+ u32 to_ack;
+@@ -1383,7 +1381,6 @@ static void batadv_tp_ack_unordered(struct batadv_tp_vars *tp_vars)
+ /* go through the unacked packet list and possibly ACK them as
+ * well
+ */
+- spin_lock_bh(&tp_vars->unacked_lock);
+ list_for_each_entry_safe(un, safe, &tp_vars->unacked_list, list) {
+ /* the list is ordered, therefore it is possible to stop as soon
+ * there is a gap between the last acked seqno and the seqno of
+@@ -1401,7 +1398,6 @@ static void batadv_tp_ack_unordered(struct batadv_tp_vars *tp_vars)
+ kfree(un);
+ tp_vars->unacked_count--;
+ }
+- spin_unlock_bh(&tp_vars->unacked_lock);
+ }
+
+ /**
+@@ -1481,6 +1477,7 @@ static void batadv_tp_recv_msg(struct batadv_priv *bat_priv,
+ const struct batadv_icmp_tp_packet *icmp;
+ struct batadv_tp_vars *tp_vars;
+ size_t packet_size;
++ u32 to_ack;
+ u32 seqno;
+
+ icmp = (struct batadv_icmp_tp_packet *)skb->data;
+@@ -1509,6 +1506,8 @@ static void batadv_tp_recv_msg(struct batadv_priv *bat_priv,
+ WRITE_ONCE(tp_vars->last_recv_time, jiffies);
+ }
+
++ spin_lock_bh(&tp_vars->unacked_lock);
++
+ /* if the packet is a duplicate, it may be the case that an ACK has been
+ * lost. Resend the ACK
+ */
+@@ -1520,8 +1519,10 @@ static void batadv_tp_recv_msg(struct batadv_priv *bat_priv,
+ /* exit immediately (and do not send any ACK) if the packet has
+ * not been enqueued correctly
+ */
+- if (!batadv_tp_handle_out_of_order(tp_vars, skb))
++ if (!batadv_tp_handle_out_of_order(tp_vars, skb)) {
++ spin_unlock_bh(&tp_vars->unacked_lock);
+ goto out;
++ }
+
+ /* send a duplicate ACK */
+ goto send_ack;
+@@ -1535,11 +1536,14 @@ static void batadv_tp_recv_msg(struct batadv_priv *bat_priv,
+ batadv_tp_ack_unordered(tp_vars);
+
+ send_ack:
++ to_ack = tp_vars->last_recv;
++ spin_unlock_bh(&tp_vars->unacked_lock);
++
+ /* send the ACK. If the received packet was out of order, the ACK that
+ * is going to be sent is a duplicate (the sender will count them and
+ * possibly enter Fast Retransmit as soon as it has reached 3)
+ */
+- batadv_tp_send_ack(bat_priv, icmp->orig, tp_vars->last_recv,
++ batadv_tp_send_ack(bat_priv, icmp->orig, to_ack,
+ icmp->timestamp, icmp->session, icmp->uid);
+ out:
+ batadv_tp_vars_put(tp_vars);
+diff --git a/net/batman-adv/types.h b/net/batman-adv/types.h
+index 4f9f1820e3a92a..3f940a201cdf84 100644
+--- a/net/batman-adv/types.h
++++ b/net/batman-adv/types.h
+@@ -1423,7 +1423,7 @@ struct batadv_tp_vars {
+ /** @unacked_list: list of unacked packets (meta-info only) */
+ struct list_head unacked_list;
+
+- /** @unacked_lock: protect unacked_list */
++ /** @unacked_lock: protect unacked_list + &batadv_tp_receiver.last_recv */
+ spinlock_t unacked_lock;
+
+ /** @unacked_count: number of unacked entries */
+--
+2.53.0
+
--- /dev/null
+From 523e22bf49a45ae523732a740dcd032b7c5ca624 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:12:33 +0200
+Subject: batman-adv: tp_meter: restrict number of unacked list entries
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit e7c775110e1858e5a7471a23a9c9658c0af9df89 upstream.
+
+When the unacked_list is unbound, an attacker could send messages with
+small lengths and appropriated seqno + gaps to force the receiver to
+allocate more and more unacked_list entries. And the end either causing an
+out-of-memory situation or increase the management overhead for the (large)
+list that significant portions of CPU cycles are wasted in searching
+through the list.
+
+When limiting the list to a specific number, it is important to still
+correctly add a new entry to the list. But if the list became larger than
+the limit, the last entry of the list (with the highest seqno) must be
+dropped to still allow the earlier seqnos to finish and therefore to
+continue the process. Otherwise, the process might get stuck with too high
+seqnos which are not handled by batadv_tp_ack_unordered().
+
+Cc: stable@kernel.org
+Fixes: 33a3bb4a3345 ("batman-adv: throughput meter implementation")
+[ Switch to pre-splitted tp_vars structure names ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tp_meter.c | 23 ++++++++++++++++++++++-
+ net/batman-adv/types.h | 3 +++
+ 2 files changed, 25 insertions(+), 1 deletion(-)
+
+diff --git a/net/batman-adv/tp_meter.c b/net/batman-adv/tp_meter.c
+index 397b9456353568..4f8af909893ab9 100644
+--- a/net/batman-adv/tp_meter.c
++++ b/net/batman-adv/tp_meter.c
+@@ -87,6 +87,11 @@
+ #define BATADV_TP_PLEN (BATADV_TP_PACKET_LEN - ETH_HLEN - \
+ sizeof(struct batadv_unicast_packet))
+
++/**
++ * BATADV_TP_MAX_UNACKED - maximum number of packets a receiver didn't yet ack
++ */
++#define BATADV_TP_MAX_UNACKED 100
++
+ static u8 batadv_tp_prerandom[4096] __read_mostly;
+
+ /**
+@@ -1195,6 +1200,7 @@ static void batadv_tp_receiver_shutdown(struct timer_list *t)
+ list_for_each_entry_safe(un, safe, &tp_vars->unacked_list, list) {
+ list_del(&un->list);
+ kfree(un);
++ tp_vars->unacked_count--;
+ }
+ spin_unlock_bh(&tp_vars->unacked_lock);
+
+@@ -1308,6 +1314,7 @@ static bool batadv_tp_handle_out_of_order(struct batadv_tp_vars *tp_vars,
+ /* if the list is empty immediately attach this new object */
+ if (list_empty(&tp_vars->unacked_list)) {
+ list_add(&new->list, &tp_vars->unacked_list);
++ tp_vars->unacked_count++;
+ goto out;
+ }
+
+@@ -1338,12 +1345,24 @@ static bool batadv_tp_handle_out_of_order(struct batadv_tp_vars *tp_vars,
+ */
+ list_add(&new->list, &un->list);
+ added = true;
++ tp_vars->unacked_count++;
+ break;
+ }
+
+ /* received packet with smallest seqno out of order; add it to front */
+- if (!added)
++ if (!added) {
+ list_add(&new->list, &tp_vars->unacked_list);
++ tp_vars->unacked_count++;
++ }
++
++ /* remove the last (biggest) unacked seqno when list is too large */
++ if (tp_vars->unacked_count > BATADV_TP_MAX_UNACKED) {
++ un = list_last_entry(&tp_vars->unacked_list,
++ struct batadv_tp_unacked, list);
++ list_del(&un->list);
++ kfree(un);
++ tp_vars->unacked_count--;
++ }
+
+ out:
+ spin_unlock_bh(&tp_vars->unacked_lock);
+@@ -1380,6 +1399,7 @@ static void batadv_tp_ack_unordered(struct batadv_tp_vars *tp_vars)
+
+ list_del(&un->list);
+ kfree(un);
++ tp_vars->unacked_count--;
+ }
+ spin_unlock_bh(&tp_vars->unacked_lock);
+ }
+@@ -1430,6 +1450,7 @@ batadv_tp_init_recv(struct batadv_priv *bat_priv,
+
+ spin_lock_init(&tp_vars->unacked_lock);
+ INIT_LIST_HEAD(&tp_vars->unacked_list);
++ tp_vars->unacked_count = 0;
+
+ kref_get(&tp_vars->refcount);
+ timer_setup(&tp_vars->timer, batadv_tp_receiver_shutdown, 0);
+diff --git a/net/batman-adv/types.h b/net/batman-adv/types.h
+index 765ba71fe8c69f..4f9f1820e3a92a 100644
+--- a/net/batman-adv/types.h
++++ b/net/batman-adv/types.h
+@@ -1426,6 +1426,9 @@ struct batadv_tp_vars {
+ /** @unacked_lock: protect unacked_list */
+ spinlock_t unacked_lock;
+
++ /** @unacked_count: number of unacked entries */
++ size_t unacked_count;
++
+ /** @last_recv_time: time (jiffies) a msg was received */
+ unsigned long last_recv_time;
+
+--
+2.53.0
+
--- /dev/null
+From d6f9bbc2de4900719a81c75f567bc79acb45823a Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:12:37 +0200
+Subject: batman-adv: tt: don't merge change entries with different VIDs
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit f08e06c2d5c3e2434e7c773f2213f4a7dce6bc1e upstream.
+
+batadv_tt_local_event() merges/cancels events for the same client which
+would conflict or be duplicates. The matching of the queued events only
+compares the MAC address - the VLAN ID stored in each event is ignored.
+
+If a MAC would now appear on multiple VID, the two ADD change events (for
+VID 1 and VID 2) would be merged to a single vid event. The remote can
+therefore not calculate the correct TT table and desync. A full translation
+table exchange is required to recover from this state.
+
+A check of VID is therefore necessary to avoid such wrong merges/cancels.
+
+Cc: stable@kernel.org
+Fixes: c018ad3de61a ("batman-adv: add the VLAN ID attribute to the TT entry")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/translation-table.c | 3 +++
+ 1 file changed, 3 insertions(+)
+
+diff --git a/net/batman-adv/translation-table.c b/net/batman-adv/translation-table.c
+index 9f6e67771ffa80..acd8af4446671f 100644
+--- a/net/batman-adv/translation-table.c
++++ b/net/batman-adv/translation-table.c
+@@ -446,6 +446,9 @@ static void batadv_tt_local_event(struct batadv_priv *bat_priv,
+ if (!batadv_compare_eth(entry->change.addr, common->addr))
+ continue;
+
++ if (entry->change.vid != tt_change_node->change.vid)
++ continue;
++
+ del_op_entry = entry->change.flags & BATADV_TT_CLIENT_DEL;
+ if (del_op_requested != del_op_entry) {
+ /* DEL+ADD in the same orig interval have no effect and
+--
+2.53.0
+
--- /dev/null
+From 0e598bfd4bdb4c923bed647e81511140b39bceb4 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:12:38 +0200
+Subject: batman-adv: tt: track roam count per VID
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 12407d5f61c2653a64f2ff4b22f3c267f8420ef1 upstream.
+
+batadv_tt_check_roam_count() is supposed to track roaming of a TT entry.
+But TT entries are for a MAC + VID. The VID was completely missed and thus
+leads to incorrect detection of ROAM counts when a client MAC exists in
+multiple VLANs.
+
+Cc: stable@kernel.org
+Fixes: c018ad3de61a ("batman-adv: add the VLAN ID attribute to the TT entry")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/translation-table.c | 9 +++++++--
+ net/batman-adv/types.h | 3 +++
+ 2 files changed, 10 insertions(+), 2 deletions(-)
+
+diff --git a/net/batman-adv/translation-table.c b/net/batman-adv/translation-table.c
+index acd8af4446671f..83dfd804a143ab 100644
+--- a/net/batman-adv/translation-table.c
++++ b/net/batman-adv/translation-table.c
+@@ -3442,6 +3442,7 @@ static void batadv_tt_roam_purge(struct batadv_priv *bat_priv)
+ * batadv_tt_check_roam_count() - check if a client has roamed too frequently
+ * @bat_priv: the bat priv with all the mesh interface information
+ * @client: mac address of the roaming client
++ * @vid: VLAN identifier
+ *
+ * This function checks whether the client already reached the
+ * maximum number of possible roaming phases. In this case the ROAMING_ADV
+@@ -3449,7 +3450,7 @@ static void batadv_tt_roam_purge(struct batadv_priv *bat_priv)
+ *
+ * Return: true if the ROAMING_ADV can be sent, false otherwise
+ */
+-static bool batadv_tt_check_roam_count(struct batadv_priv *bat_priv, u8 *client)
++static bool batadv_tt_check_roam_count(struct batadv_priv *bat_priv, u8 *client, u16 vid)
+ {
+ struct batadv_tt_roam_node *tt_roam_node;
+ bool ret = false;
+@@ -3462,6 +3463,9 @@ static bool batadv_tt_check_roam_count(struct batadv_priv *bat_priv, u8 *client)
+ if (!batadv_compare_eth(tt_roam_node->addr, client))
+ continue;
+
++ if (tt_roam_node->vid != vid)
++ continue;
++
+ if (batadv_has_timed_out(tt_roam_node->first_time,
+ BATADV_ROAMING_MAX_TIME))
+ continue;
+@@ -3483,6 +3487,7 @@ static bool batadv_tt_check_roam_count(struct batadv_priv *bat_priv, u8 *client)
+ atomic_set(&tt_roam_node->counter,
+ BATADV_ROAMING_MAX_COUNT - 1);
+ ether_addr_copy(tt_roam_node->addr, client);
++ tt_roam_node->vid = vid;
+
+ list_add(&tt_roam_node->list, &bat_priv->tt.roam_list);
+ ret = true;
+@@ -3519,7 +3524,7 @@ static void batadv_send_roam_adv(struct batadv_priv *bat_priv, u8 *client,
+ /* before going on we have to check whether the client has
+ * already roamed to us too many times
+ */
+- if (!batadv_tt_check_roam_count(bat_priv, client))
++ if (!batadv_tt_check_roam_count(bat_priv, client, vid))
+ goto out;
+
+ batadv_dbg(BATADV_DBG_TT, bat_priv,
+diff --git a/net/batman-adv/types.h b/net/batman-adv/types.h
+index 3f940a201cdf84..4dce996f01ccea 100644
+--- a/net/batman-adv/types.h
++++ b/net/batman-adv/types.h
+@@ -1912,6 +1912,9 @@ struct batadv_tt_roam_node {
+ /** @addr: mac address of the client in the roaming phase */
+ u8 addr[ETH_ALEN];
+
++ /** @vid: VLAN identifier */
++ u16 vid;
++
+ /**
+ * @counter: number of allowed roaming events per client within a single
+ * OGM interval (changes are committed with each OGM)
+--
+2.53.0
+
--- /dev/null
+From 9f855c0e6e12c62a92462b2aef9b08a82442f26a Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:12:41 +0200
+Subject: batman-adv: tvlv: avoid race of cifsnotfound handler state
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit edb557b2ba38fea2c5eb710cf366c797e187218c upstream.
+
+TVLV handlers can have the flag BATADV_TVLV_HANDLER_OGM_CIFNOTFND set to
+signal that the OGM handler should be called (with NULL for data) when the
+specific TVLV container was not found in the OGM. This is used by:
+
+* DAT
+* GW
+* Multicast (OGM + Tracker)
+
+The state whether the handler was executed was stored in the struct
+batadv_tvlv_handler. But the TVLV processing is started without any lock.
+Multiple parallel contexts processing TVLVs would therefore overwrite each
+others BATADV_TVLV_HANDLER_OGM_CALLED flag in the shared
+batadv_tvlv_handler.
+
+Drop the shared BATADV_TVLV_HANDLER_OGM_CALLED flag and instead determine,
+per TVLV buffer, whether a matching container was present by scanning the
+packet's buffer.
+
+Cc: stable@kernel.org
+Fixes: ef26157747d4 ("batman-adv: tvlv - basic infrastructure")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/tvlv.c | 63 ++++++++++++++++++++++++++++++++++++++----
+ net/batman-adv/types.h | 7 -----
+ 2 files changed, 57 insertions(+), 13 deletions(-)
+
+diff --git a/net/batman-adv/tvlv.c b/net/batman-adv/tvlv.c
+index baadf97f98e5f0..afb61a46d7a67c 100644
+--- a/net/batman-adv/tvlv.c
++++ b/net/batman-adv/tvlv.c
+@@ -398,7 +398,6 @@ static int batadv_tvlv_call_handler(struct batadv_priv *bat_priv,
+ tvlv_handler->ogm_handler(bat_priv, orig_node,
+ BATADV_NO_FLAGS,
+ tvlv_value, tvlv_value_len);
+- tvlv_handler->flags |= BATADV_TVLV_HANDLER_OGM_CALLED;
+ break;
+ case BATADV_UNICAST_TVLV:
+ if (!skb)
+@@ -430,6 +429,48 @@ static int batadv_tvlv_call_handler(struct batadv_priv *bat_priv,
+ return NET_RX_SUCCESS;
+ }
+
++/**
++ * batadv_tvlv_containers_contain() - check if a tvlv buffer holds a container
++ * @tvlv_value: tvlv content
++ * @tvlv_value_len: tvlv content length
++ * @type: tvlv container type to look for
++ * @version: tvlv container version to look for
++ *
++ * Return: true if a container of the given type and version is present in the
++ * tvlv buffer, false otherwise.
++ */
++static bool batadv_tvlv_containers_contain(void *tvlv_value,
++ u16 tvlv_value_len, u8 type,
++ u8 version)
++{
++ struct batadv_tvlv_hdr *tvlv_hdr;
++ u16 tvlv_value_cont_len;
++
++ while (tvlv_value_len >= sizeof(*tvlv_hdr)) {
++ tvlv_hdr = tvlv_value;
++ tvlv_value_cont_len = ntohs(tvlv_hdr->len);
++ tvlv_value = tvlv_hdr + 1;
++ tvlv_value_len -= sizeof(*tvlv_hdr);
++
++ if (tvlv_value_cont_len > tvlv_value_len)
++ break;
++
++ /* the next tvlv header is accessed assuming (at least) 2-byte
++ * alignment, so it must start at an even offset.
++ */
++ if (tvlv_value_cont_len & 1)
++ break;
++
++ if (tvlv_hdr->type == type && tvlv_hdr->version == version)
++ return true;
++
++ tvlv_value = (u8 *)tvlv_value + tvlv_value_cont_len;
++ tvlv_value_len -= tvlv_value_cont_len;
++ }
++
++ return false;
++}
++
+ /**
+ * batadv_tvlv_containers_process() - parse the given tvlv buffer to call the
+ * appropriate handlers
+@@ -449,7 +490,9 @@ int batadv_tvlv_containers_process(struct batadv_priv *bat_priv,
+ struct sk_buff *skb, void *tvlv_value,
+ u16 tvlv_value_len)
+ {
++ u16 tvlv_value_start_len = tvlv_value_len;
+ struct batadv_tvlv_handler *tvlv_handler;
++ void *tvlv_value_start = tvlv_value;
+ struct batadv_tvlv_hdr *tvlv_hdr;
+ u16 tvlv_value_cont_len;
+ u8 cifnotfound = BATADV_TVLV_HANDLER_OGM_CIFNOTFND;
+@@ -493,12 +536,20 @@ int batadv_tvlv_containers_process(struct batadv_priv *bat_priv,
+ if (!tvlv_handler->ogm_handler)
+ continue;
+
+- if ((tvlv_handler->flags & BATADV_TVLV_HANDLER_OGM_CIFNOTFND) &&
+- !(tvlv_handler->flags & BATADV_TVLV_HANDLER_OGM_CALLED))
+- tvlv_handler->ogm_handler(bat_priv, orig_node,
+- cifnotfound, NULL, 0);
++ if (!(tvlv_handler->flags & BATADV_TVLV_HANDLER_OGM_CIFNOTFND))
++ continue;
+
+- tvlv_handler->flags &= ~BATADV_TVLV_HANDLER_OGM_CALLED;
++ /* if the corresponding container was present then the handler
++ * was already called from the loop above
++ */
++ if (batadv_tvlv_containers_contain(tvlv_value_start,
++ tvlv_value_start_len,
++ tvlv_handler->type,
++ tvlv_handler->version))
++ continue;
++
++ tvlv_handler->ogm_handler(bat_priv, orig_node,
++ cifnotfound, NULL, 0);
+ }
+ rcu_read_unlock();
+
+diff --git a/net/batman-adv/types.h b/net/batman-adv/types.h
+index 4dce996f01ccea..30c870cacf4374 100644
+--- a/net/batman-adv/types.h
++++ b/net/batman-adv/types.h
+@@ -2245,13 +2245,6 @@ enum batadv_tvlv_handler_flags {
+ * will call this handler even if its type was not found (with no data)
+ */
+ BATADV_TVLV_HANDLER_OGM_CIFNOTFND = BIT(1),
+-
+- /**
+- * @BATADV_TVLV_HANDLER_OGM_CALLED: interval tvlv handling flag - the
+- * API marks a handler as being called, so it won't be called if the
+- * BATADV_TVLV_HANDLER_OGM_CIFNOTFND flag was set
+- */
+- BATADV_TVLV_HANDLER_OGM_CALLED = BIT(2),
+ };
+
+ #endif /* _NET_BATMAN_ADV_TYPES_H_ */
+--
+2.53.0
+
--- /dev/null
+From 01e84cf126f63ab51ef800b816bf69a429f182a0 Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:12:40 +0200
+Subject: batman-adv: tvlv: enforce 2-byte alignment
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit 32a6799255525d6ea4da0f7e9e0e521ad9560a46 upstream.
+
+The fields of an aggregated OGM(v2) are accessed assuming (at least) 2-byte
+alignment, so a following OGM must start at an even offset. As the header
+length is even, an odd tvlv_len would misalign it and trigger unaligned
+accesses on strict-alignment architectures.
+
+Such a misaligned TVLV/OGM/OGMv2 is not created by a normal participant in
+the mesh. Therefore, reject such malformed packets.
+
+Cc: stable@kernel.org
+Fixes: ef26157747d4 ("batman-adv: tvlv - basic infrastructure")
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/bat_iv_ogm.c | 11 ++++++++++-
+ net/batman-adv/bat_v_ogm.c | 11 ++++++++++-
+ net/batman-adv/routing.c | 6 ++++++
+ net/batman-adv/tvlv.c | 6 ++++++
+ 4 files changed, 32 insertions(+), 2 deletions(-)
+
+diff --git a/net/batman-adv/bat_iv_ogm.c b/net/batman-adv/bat_iv_ogm.c
+index b8b1b997960a96..6e79f69c2fedec 100644
+--- a/net/batman-adv/bat_iv_ogm.c
++++ b/net/batman-adv/bat_iv_ogm.c
+@@ -311,14 +311,23 @@ batadv_iv_ogm_aggr_packet(int buff_pos, int packet_len,
+ const struct batadv_ogm_packet *ogm_packet)
+ {
+ int next_buff_pos = 0;
++ u16 tvlv_len;
+
+ /* check if there is enough space for the header */
+ next_buff_pos += buff_pos + sizeof(*ogm_packet);
+ if (next_buff_pos > packet_len)
+ return false;
+
++ tvlv_len = ntohs(ogm_packet->tvlv_len);
++
++ /* the fields of an aggregated OGM are accessed assuming (at least)
++ * 2-byte alignment, so a following OGM must start at an even offset.
++ */
++ if (tvlv_len & 1)
++ return false;
++
+ /* check if there is enough space for the optional TVLV */
+- next_buff_pos += ntohs(ogm_packet->tvlv_len);
++ next_buff_pos += tvlv_len;
+
+ return next_buff_pos <= packet_len;
+ }
+diff --git a/net/batman-adv/bat_v_ogm.c b/net/batman-adv/bat_v_ogm.c
+index 6852bf5da8c558..1f9b2d2b4831ce 100644
+--- a/net/batman-adv/bat_v_ogm.c
++++ b/net/batman-adv/bat_v_ogm.c
+@@ -849,14 +849,23 @@ batadv_v_ogm_aggr_packet(int buff_pos, int packet_len,
+ const struct batadv_ogm2_packet *ogm2_packet)
+ {
+ int next_buff_pos = 0;
++ u16 tvlv_len;
+
+ /* check if there is enough space for the header */
+ next_buff_pos += buff_pos + sizeof(*ogm2_packet);
+ if (next_buff_pos > packet_len)
+ return false;
+
++ tvlv_len = ntohs(ogm2_packet->tvlv_len);
++
++ /* the fields of an aggregated OGMv2 are accessed assuming (at least)
++ * 2-byte alignment, so a following OGMv2 must start at an even offset.
++ */
++ if (tvlv_len & 1)
++ return false;
++
+ /* check if there is enough space for the optional TVLV */
+- next_buff_pos += ntohs(ogm2_packet->tvlv_len);
++ next_buff_pos += tvlv_len;
+
+ return next_buff_pos <= packet_len;
+ }
+diff --git a/net/batman-adv/routing.c b/net/batman-adv/routing.c
+index 4483f8d9c75831..41951c7a1c50b2 100644
+--- a/net/batman-adv/routing.c
++++ b/net/batman-adv/routing.c
+@@ -1366,6 +1366,12 @@ int batadv_recv_mcast_packet(struct sk_buff *skb,
+ if (tvlv_buff_len > skb->len - hdr_size)
+ goto free_skb;
+
++ /* the fields of an multicast payload are accessed assuming (at least)
++ * 2-byte alignment, so a following packet must start at an even offset.
++ */
++ if (tvlv_buff_len & 1)
++ goto free_skb;
++
+ ret = batadv_tvlv_containers_process(bat_priv, BATADV_MCAST, NULL, skb,
+ tvlv_buff, tvlv_buff_len);
+ if (ret >= 0) {
+diff --git a/net/batman-adv/tvlv.c b/net/batman-adv/tvlv.c
+index cc6ac580c62085..baadf97f98e5f0 100644
+--- a/net/batman-adv/tvlv.c
++++ b/net/batman-adv/tvlv.c
+@@ -464,6 +464,12 @@ int batadv_tvlv_containers_process(struct batadv_priv *bat_priv,
+ if (tvlv_value_cont_len > tvlv_value_len)
+ break;
+
++ /* the next tvlv header is accessed assuming (at least) 2-byte
++ * alignment, so it must start at an even offset.
++ */
++ if (tvlv_value_cont_len & 1)
++ break;
++
+ tvlv_handler = batadv_tvlv_handler_get(bat_priv,
+ tvlv_hdr->type,
+ tvlv_hdr->version);
+--
+2.53.0
+
--- /dev/null
+From 748bca1ecf89bc43ed6b143f25c29f48e352a93f Mon Sep 17 00:00:00 2001
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 26 Jun 2026 18:12:32 +0200
+Subject: batman-adv: v: prevent OGM aggregation on disabled hardif
+
+From: Sven Eckelmann <sven@narfation.org>
+
+commit d11c00b95b2a3b3934007fc003dccc6fdcc061ad upstream.
+
+When an interface gets disabled, the worker is correctly disabled by
+batadv_hardif_disable_interface() -> ... -> batadv_v_ogm_iface_disable().
+In this process, the skb aggr_list is also freed.
+
+But batadv_v_ogm_send_meshif() can still queue new skbs (via
+batadv_v_ogm_queue_on_if()) to the aggr_list. This will only stop after all
+cores can no longer find the RCU protected list of hard interfaces. These
+queued skbs will never be freed or consumed by batadv_v_ogm_aggr_work.
+
+The batadv_v_ogm_iface_disable() function must block
+batadv_v_ogm_queue_on_if() to avoid leak of skbs.
+
+Cc: stable@kernel.org
+Fixes: f89255a02f1d ("batman-adv: BATMAN_V: introduce per hard-iface OGMv2 queues")
+[ Context ]
+Signed-off-by: Sven Eckelmann <sven@narfation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+---
+ net/batman-adv/bat_v.c | 1 +
+ net/batman-adv/bat_v_ogm.c | 12 ++++++++++++
+ net/batman-adv/types.h | 6 ++++++
+ 3 files changed, 19 insertions(+)
+
+diff --git a/net/batman-adv/bat_v.c b/net/batman-adv/bat_v.c
+index de94447142642e..17d2a1ccdce67a 100644
+--- a/net/batman-adv/bat_v.c
++++ b/net/batman-adv/bat_v.c
+@@ -817,6 +817,7 @@ void batadv_v_hardif_init(struct batadv_hard_iface *hard_iface)
+
+ hard_iface->bat_v.aggr_len = 0;
+ skb_queue_head_init(&hard_iface->bat_v.aggr_list);
++ hard_iface->bat_v.aggr_list_enabled = false;
+ INIT_DELAYED_WORK(&hard_iface->bat_v.aggr_wq,
+ batadv_v_ogm_aggr_work);
+ }
+diff --git a/net/batman-adv/bat_v_ogm.c b/net/batman-adv/bat_v_ogm.c
+index d66ca77b1aaa3c..6852bf5da8c558 100644
+--- a/net/batman-adv/bat_v_ogm.c
++++ b/net/batman-adv/bat_v_ogm.c
+@@ -252,11 +252,18 @@ static void batadv_v_ogm_queue_on_if(struct batadv_priv *bat_priv,
+ }
+
+ spin_lock_bh(&hard_iface->bat_v.aggr_list.lock);
++ if (!hard_iface->bat_v.aggr_list_enabled) {
++ kfree_skb(skb);
++ goto unlock;
++ }
++
+ if (!batadv_v_ogm_queue_left(skb, hard_iface))
+ batadv_v_ogm_aggr_send(bat_priv, hard_iface);
+
+ hard_iface->bat_v.aggr_len += batadv_v_ogm_len(skb);
+ __skb_queue_tail(&hard_iface->bat_v.aggr_list, skb);
++
++unlock:
+ spin_unlock_bh(&hard_iface->bat_v.aggr_list.lock);
+ }
+
+@@ -417,6 +424,10 @@ int batadv_v_ogm_iface_enable(struct batadv_hard_iface *hard_iface)
+ {
+ struct batadv_priv *bat_priv = netdev_priv(hard_iface->mesh_iface);
+
++ spin_lock_bh(&hard_iface->bat_v.aggr_list.lock);
++ hard_iface->bat_v.aggr_list_enabled = true;
++ spin_unlock_bh(&hard_iface->bat_v.aggr_list.lock);
++
+ batadv_v_ogm_start_queue_timer(hard_iface);
+ batadv_v_ogm_start_timer(bat_priv);
+
+@@ -432,6 +443,7 @@ void batadv_v_ogm_iface_disable(struct batadv_hard_iface *hard_iface)
+ cancel_delayed_work_sync(&hard_iface->bat_v.aggr_wq);
+
+ spin_lock_bh(&hard_iface->bat_v.aggr_list.lock);
++ hard_iface->bat_v.aggr_list_enabled = false;
+ batadv_v_ogm_aggr_list_free(hard_iface);
+ spin_unlock_bh(&hard_iface->bat_v.aggr_list.lock);
+ }
+diff --git a/net/batman-adv/types.h b/net/batman-adv/types.h
+index a01ee46d97f34f..765ba71fe8c69f 100644
+--- a/net/batman-adv/types.h
++++ b/net/batman-adv/types.h
+@@ -130,6 +130,12 @@ struct batadv_hard_iface_bat_v {
+ /** @aggr_list: queue for to be aggregated OGM packets */
+ struct sk_buff_head aggr_list;
+
++ /**
++ * @aggr_list_enabled: aggr_list is active and new skbs can be
++ * enqueued. Protected by aggr_list.lock after initialization
++ */
++ bool aggr_list_enabled:1;
++
+ /** @aggr_len: size of the OGM aggregate (excluding ethernet header) */
+ unsigned int aggr_len;
+
+--
+2.53.0
+
kvm-x86-fix-shadow-paging-use-after-free-due-to-unex.patch
+batman-adv-tp_meter-keep-unacked-list-in-ascending-o.patch
+batman-adv-tp_meter-initialize-dup_acks-explicitly.patch
+batman-adv-tp_meter-initialize-dec_cwnd-explicitly.patch
+batman-adv-tp_meter-avoid-window-underflow.patch
+batman-adv-tp_meter-avoid-divide-by-zero-for-dec_cwn.patch
+batman-adv-tp_meter-fix-fast-recovery-precondition.patch
+batman-adv-tp_meter-handle-seqno-wrap-around-for-fas.patch
+batman-adv-tp_meter-add-only-finished-tp_vars-to-lis.patch
+batman-adv-bla-annotate-lasttime-access-with-read-wr.patch
+batman-adv-prevent-elp-transmission-interval-underfl.patch
+batman-adv-tp_meter-initialize-last_recv_time-during.patch
+batman-adv-gw-don-t-deselect-gateway-with-active-har.patch
+batman-adv-ensure-bcast-is-writable-before-modifying.patch
+batman-adv-fix-m-b-cast-csum-after-decrementing-ttl.patch
+batman-adv-frag-ensure-fragment-is-writable-before-m.patch
+batman-adv-frag-avoid-underflow-of-ttl.patch
+batman-adv-v-prevent-ogm-aggregation-on-disabled-har.patch
+batman-adv-tp_meter-restrict-number-of-unacked-list-.patch
+batman-adv-tp_meter-annotate-last_recv_time-access-w.patch
+batman-adv-tp_meter-prevent-parallel-modifications-o.patch
+batman-adv-tp_meter-handle-overlapping-packets.patch
+batman-adv-tt-don-t-merge-change-entries-with-differ.patch
+batman-adv-tt-track-roam-count-per-vid.patch
+batman-adv-dat-prevent-false-sharing-between-vlans.patch
+batman-adv-tvlv-enforce-2-byte-alignment.patch
+batman-adv-tvlv-avoid-race-of-cifsnotfound-handler-s.patch