Here are various batman-adv bugfixes:

- fix tp_meter counter underflow during shutdown, by Luxiao Xu
 
  - fix tp_meter tp_vars reference leak in receiver shutdown,
    by Sven Eckelmann
 
  - fix various translation table integer handling issues,
    by Sven Eckelmann (3 patches)
 
  - fix various translation table counter issues,
    by Sven Eckelmann (3 patches)
 
  - fix fragment reassembly length accounting, by Ruide Cao
 
  - clear current gateway during teardown, by Ruijie Li
 
  - handle forward allocation error in DAT, by Sven Eckelmann
 
  - tp_meter: avoid use of uninitialized sender variables in tp_meter,
    by Sven Eckelmann
 
  - disallow unicast fragment in fragment, by Sven Eckelmann
 
  - directly shut down tp_meter timer on cleanup, by Sven Eckelmann
 -----BEGIN PGP SIGNATURE-----
 
 iQJKBAABCgA0FiEE1ilQI7G+y+fdhnrfoSvjmEKSnqEFAmoG7TwWHHN3QHNpbW9u
 d3VuZGVybGljaC5kZQAKCRChK+OYQpKeoRImEADOTpZT2sIRH5QMEDpNqt7fstGQ
 QiicauihLL2BpMR8W9xlERR3dvPIn0xTdmLLKwVHJMJQCBES78glIfHcd8K/cmiV
 0Dr1aYZAeK9UzKJWo07lapgRsRN4bmQIq6Gv19nTJ4AVOPR9xP0H+Y1fXT8kQ4pI
 d9UenePjEIv39qxHKWV6as3wMIZ/YzjPpLaP6JBsCp6rsjUkilRTmj6zBXI2zE7/
 tVu4YnSTGuhFCiSti4/UPf9HrYFeY09MymFBl8gq01ftUZsBFP+54iybAdd+IU8/
 WDeQrkO3GtIQ4TjaJQkGEL9zffj69VGymtV8QzlWp0wCrJNYCMsoCHbvb1om6MI1
 cFohLjmqbC+lq4FoHywC8JrK9e2MzC81MRutI5fU7OWMxRHGGQo0hcUPGM5eMv19
 4Tg8iCGIpW4fpuvWk0FM21uixt8UMh11AIbGgkEs2pNARRCQ35GPXBRzECpFeUav
 Gj3ZTvIwkD87t1agqRVUp67eMpjIdESL9LtVIMSrOAyzhnKtNYNLc4NYSzA7it7h
 x9mW0LOkj1Vol0r9nS+W90S6FwJy/yB6F7tix5jaFuyEhTtisWKC2lZlUGsIc3G3
 AxatgNl9IlXi9I+UyXWmAYgFeLkOUAO5hOrWPkNYk1Ro43vC07RYjHb5i28EYcYv
 lm543eF8suN6w5PW+w==
 =C54U
 -----END PGP SIGNATURE-----

Merge tag 'batadv-net-pullrequest-20260515' of https://git.open-mesh.org/batadv

Simon Wunderlich says:

====================
Here are various batman-adv bugfixes:

 - fix tp_meter counter underflow during shutdown, by Luxiao Xu

 - fix tp_meter tp_vars reference leak in receiver shutdown,
   by Sven Eckelmann

 - fix various translation table integer handling issues,
   by Sven Eckelmann (3 patches)

 - fix various translation table counter issues,
   by Sven Eckelmann (3 patches)

 - fix fragment reassembly length accounting, by Ruide Cao

 - clear current gateway during teardown, by Ruijie Li

 - handle forward allocation error in DAT, by Sven Eckelmann

 - tp_meter: avoid use of uninitialized sender variables in tp_meter,
   by Sven Eckelmann

 - disallow unicast fragment in fragment, by Sven Eckelmann

 - directly shut down tp_meter timer on cleanup, by Sven Eckelmann

* tag 'batadv-net-pullrequest-20260515' of https://git.open-mesh.org/batadv:
  batman-adv: tp_meter: directly shut down timer on cleanup
  batman-adv: frag: disallow unicast fragment in fragment
  batman-adv: tp_meter: avoid use of uninit sender vars
  batman-adv: dat: handle forward allocation error
  batman-adv: clear current gateway during teardown
  batman-adv: fix fragment reassembly length accounting
  batman-adv: tt: prevent TVLV entry number overflow
  batman-adv: tt: avoid empty VLAN responses
  batman-adv: tt: fix TOCTOU race for reported vlans
  batman-adv: tt: fix negative last_changeset_len
  batman-adv: tt: fix negative tt_buff_len
  batman-adv: tt: reject oversized local TVLV buffers
  batman-adv: tp_meter: fix tp_vars reference leak in receiver shutdown
  batman-adv: fix tp_meter counter underflow during shutdown
====================

Link: https://patch.msgid.link/20260515095540.325586-1-sw@simonwunderlich.de
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Jakub Kicinski 2026-05-18 16:50:03 -07:00
commit aeff7e8e50
6 changed files with 133 additions and 32 deletions

View File

@ -696,6 +696,9 @@ static bool batadv_dat_forward_data(struct batadv_priv *bat_priv,
goto free_orig;
tmp_skb = pskb_copy_for_clone(skb, GFP_ATOMIC);
if (!tmp_skb)
goto free_neigh;
if (!batadv_send_skb_prepare_unicast_4addr(bat_priv, tmp_skb,
cand[i].orig_node,
packet_subtype)) {

View File

@ -17,6 +17,7 @@
#include <linux/lockdep.h>
#include <linux/minmax.h>
#include <linux/netdevice.h>
#include <linux/overflow.h>
#include <linux/skbuff.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
@ -80,9 +81,9 @@ void batadv_frag_purge_orig(struct batadv_orig_node *orig_node,
*
* Return: the maximum size of payload that can be fragmented.
*/
static int batadv_frag_size_limit(void)
static size_t batadv_frag_size_limit(void)
{
int limit = BATADV_FRAG_MAX_FRAG_SIZE;
size_t limit = BATADV_FRAG_MAX_FRAG_SIZE;
limit -= sizeof(struct batadv_frag_packet);
limit *= BATADV_FRAG_MAX_FRAGMENTS;
@ -143,7 +144,9 @@ static bool batadv_frag_insert_packet(struct batadv_orig_node *orig_node,
struct batadv_frag_packet *frag_packet;
u8 bucket;
u16 seqno, hdr_size = sizeof(struct batadv_frag_packet);
bool overflow = false;
bool ret = false;
size_t data_len;
/* Linearize packet to avoid linearizing 16 packets in a row when doing
* the later merge. Non-linear merge should be added to remove this
@ -153,6 +156,7 @@ static bool batadv_frag_insert_packet(struct batadv_orig_node *orig_node,
goto err;
frag_packet = (struct batadv_frag_packet *)skb->data;
data_len = skb->len - hdr_size;
seqno = ntohs(frag_packet->seqno);
bucket = seqno % BATADV_FRAG_BUFFER_COUNT;
@ -171,7 +175,7 @@ static bool batadv_frag_insert_packet(struct batadv_orig_node *orig_node,
spin_lock_bh(&chain->lock);
if (batadv_frag_init_chain(chain, seqno)) {
hlist_add_head(&frag_entry_new->list, &chain->fragment_list);
chain->size = skb->len - hdr_size;
chain->size = data_len;
chain->timestamp = jiffies;
chain->total_size = ntohs(frag_packet->total_size);
ret = true;
@ -188,7 +192,11 @@ static bool batadv_frag_insert_packet(struct batadv_orig_node *orig_node,
if (frag_entry_curr->no < frag_entry_new->no) {
hlist_add_before(&frag_entry_new->list,
&frag_entry_curr->list);
chain->size += skb->len - hdr_size;
if (check_add_overflow(chain->size, data_len,
&chain->size))
overflow = true;
chain->timestamp = jiffies;
ret = true;
goto out;
@ -201,13 +209,16 @@ static bool batadv_frag_insert_packet(struct batadv_orig_node *orig_node,
/* Reached the end of the list, so insert after 'frag_entry_last'. */
if (likely(frag_entry_last)) {
hlist_add_behind(&frag_entry_new->list, &frag_entry_last->list);
chain->size += skb->len - hdr_size;
if (check_add_overflow(chain->size, data_len, &chain->size))
overflow = true;
chain->timestamp = jiffies;
ret = true;
}
out:
if (chain->size > batadv_frag_size_limit() ||
if (overflow || chain->size > batadv_frag_size_limit() ||
chain->total_size != ntohs(frag_packet->total_size) ||
chain->total_size > batadv_frag_size_limit()) {
/* Clear chain if total size of either the list or the packet
@ -293,6 +304,31 @@ batadv_frag_merge_packets(struct hlist_head *chain)
return skb_out;
}
/**
* batadv_skb_is_frag() - check if newly merged skb is gain a unicast packet
* @skb: newly merged skb
*
* Return: if newly skb is of type BATADV_UNICAST_FRAG
*/
static bool batadv_skb_is_frag(struct sk_buff *skb)
{
struct batadv_ogm_packet *batadv_ogm_packet;
/* packet should hold at least type and version */
if (unlikely(!pskb_may_pull(skb, 2)))
return false;
batadv_ogm_packet = (struct batadv_ogm_packet *)skb->data;
if (batadv_ogm_packet->version != BATADV_COMPAT_VERSION)
return false;
if (batadv_ogm_packet->packet_type != BATADV_UNICAST_FRAG)
return false;
return true;
}
/**
* batadv_frag_skb_buffer() - buffer fragment for later merge
* @skb: skb to buffer
@ -326,6 +362,16 @@ bool batadv_frag_skb_buffer(struct sk_buff **skb,
if (!skb_out)
goto out_err;
/* fragment in fragment is not allowed. otherwise it is possible
* to exhaust the stack when receiving a matryoshka-style
* "fragments in a fragment packet"
*/
if (batadv_skb_is_frag(skb_out)) {
kfree_skb(skb_out);
skb_out = NULL;
goto out_err;
}
out:
ret = true;
out_err:

View File

@ -478,10 +478,14 @@ void batadv_gw_node_delete(struct batadv_priv *bat_priv,
*/
void batadv_gw_node_free(struct batadv_priv *bat_priv)
{
struct batadv_gw_node *curr_gw;
struct batadv_gw_node *gw_node;
struct hlist_node *node_tmp;
spin_lock_bh(&bat_priv->gw.list_lock);
curr_gw = rcu_replace_pointer(bat_priv->gw.curr_gw, NULL, true);
batadv_gw_node_put(curr_gw);
hlist_for_each_entry_safe(gw_node, node_tmp,
&bat_priv->gw.gateway_list, list) {
hlist_del_init_rcu(&gw_node->list);

View File

@ -8,6 +8,7 @@
#include "main.h"
#include <linux/atomic.h>
#include <linux/bug.h>
#include <linux/build_bug.h>
#include <linux/byteorder/generic.h>
#include <linux/cache.h>
@ -400,13 +401,7 @@ static void batadv_tp_sender_cleanup(struct batadv_tp_vars *tp_vars)
batadv_tp_list_detach(tp_vars);
/* kill the timer and remove its reference */
timer_delete_sync(&tp_vars->timer);
/* the worker might have rearmed itself therefore we kill it again. Note
* that if the worker should run again before invoking the following
* timer_delete(), it would not re-arm itself once again because the status
* is OFF now
*/
timer_delete(&tp_vars->timer);
timer_shutdown_sync(&tp_vars->timer);
batadv_tp_vars_put(tp_vars);
}
@ -451,7 +446,7 @@ static void batadv_tp_sender_end(struct batadv_priv *bat_priv,
static void batadv_tp_sender_shutdown(struct batadv_tp_vars *tp_vars,
enum batadv_tp_meter_reason reason)
{
if (!atomic_dec_and_test(&tp_vars->sending))
if (atomic_xchg(&tp_vars->sending, 0) != 1)
return;
tp_vars->reason = reason;
@ -663,6 +658,9 @@ static void batadv_tp_recv_ack(struct batadv_priv *bat_priv,
if (unlikely(!tp_vars))
return;
if (unlikely(tp_vars->role != BATADV_TP_SENDER))
goto out;
if (unlikely(atomic_read(&tp_vars->sending) == 0))
goto out;
@ -885,7 +883,7 @@ static int batadv_tp_send(void *arg)
"Meter: %s() cannot send packets (%d)\n",
__func__, err);
/* ensure nobody else tries to stop the thread now */
if (atomic_dec_and_test(&tp_vars->sending))
if (atomic_xchg(&tp_vars->sending, 0) == 1)
tp_vars->reason = err;
break;
}
@ -1100,12 +1098,16 @@ void batadv_tp_stop(struct batadv_priv *bat_priv, const u8 *dst,
if (!tp_vars) {
batadv_dbg(BATADV_DBG_TP_METER, bat_priv,
"Meter: trying to interrupt an already over connection\n");
goto out;
goto out_put_orig_node;
}
if (unlikely(tp_vars->role != BATADV_TP_SENDER))
goto out_put_tp_vars;
batadv_tp_sender_shutdown(tp_vars, return_value);
out_put_tp_vars:
batadv_tp_vars_put(tp_vars);
out:
out_put_orig_node:
batadv_orig_node_put(orig_node);
}
@ -1156,6 +1158,9 @@ static void batadv_tp_receiver_shutdown(struct timer_list *t)
spin_unlock_bh(&tp_vars->unacked_lock);
/* drop reference of timer */
if (WARN_ON(atomic_xchg(&tp_vars->receiving, 0) != 1))
return;
batadv_tp_vars_put(tp_vars);
}
@ -1374,6 +1379,7 @@ batadv_tp_init_recv(struct batadv_priv *bat_priv,
ether_addr_copy(tp_vars->other_end, icmp->orig);
tp_vars->role = BATADV_TP_RECEIVER;
atomic_set(&tp_vars->receiving, 1);
memcpy(tp_vars->session, icmp->session, sizeof(tp_vars->session));
tp_vars->last_recv = BATADV_TP_FIRST_SEQ;
tp_vars->bat_priv = bat_priv;
@ -1546,8 +1552,12 @@ void batadv_tp_stop_all(struct batadv_priv *bat_priv)
break;
case BATADV_TP_RECEIVER:
batadv_tp_list_detach(tp_var);
if (timer_shutdown_sync(&tp_var->timer))
batadv_tp_vars_put(tp_var);
timer_shutdown_sync(&tp_var->timer);
if (atomic_xchg(&tp_var->receiving, 0) != 1)
break;
batadv_tp_vars_put(tp_var);
break;
}

View File

@ -797,24 +797,33 @@ batadv_tt_prepare_tvlv_global_data(struct batadv_orig_node *orig_node,
s32 *tt_len)
{
u16 num_vlan = 0;
u16 num_entries = 0;
u16 tvlv_len = 0;
unsigned int change_offset;
struct batadv_tvlv_tt_vlan_data *tt_vlan;
struct batadv_orig_node_vlan *vlan;
u16 total_entries = 0;
u8 *tt_change_ptr;
int vlan_entries;
u16 sum_entries;
spin_lock_bh(&orig_node->vlan_list_lock);
hlist_for_each_entry(vlan, &orig_node->vlan_list, list) {
vlan_entries = atomic_read(&vlan->tt.num_entries);
if (check_add_overflow(vlan_entries, total_entries, &sum_entries)) {
*tt_len = 0;
goto out;
}
total_entries = sum_entries;
num_vlan++;
num_entries += atomic_read(&vlan->tt.num_entries);
}
change_offset = struct_size(*tt_data, vlan_data, num_vlan);
/* if tt_len is negative, allocate the space needed by the full table */
if (*tt_len < 0)
*tt_len = batadv_tt_len(num_entries);
*tt_len = batadv_tt_len(total_entries);
if (change_offset > U16_MAX || *tt_len > U16_MAX - change_offset) {
*tt_len = 0;
@ -835,14 +844,26 @@ batadv_tt_prepare_tvlv_global_data(struct batadv_orig_node *orig_node,
(*tt_data)->num_vlan = htons(num_vlan);
tt_vlan = (*tt_data)->vlan_data;
num_vlan = 0;
hlist_for_each_entry(vlan, &orig_node->vlan_list, list) {
vlan_entries = atomic_read(&vlan->tt.num_entries);
if (vlan_entries < 1)
continue;
tt_vlan->vid = htons(vlan->vid);
tt_vlan->crc = htonl(vlan->tt.crc);
tt_vlan->reserved = 0;
tt_vlan++;
num_vlan++;
}
/* recalculate in case number of VLANs reduced */
change_offset = struct_size(*tt_data, vlan_data, num_vlan);
tvlv_len = *tt_len + change_offset;
(*tt_data)->num_vlan = htons(num_vlan);
tt_change_ptr = (u8 *)*tt_data + change_offset;
*tt_change = (struct batadv_tvlv_tt_change *)tt_change_ptr;
@ -877,21 +898,25 @@ batadv_tt_prepare_tvlv_local_data(struct batadv_priv *bat_priv,
{
struct batadv_tvlv_tt_vlan_data *tt_vlan;
struct batadv_meshif_vlan *vlan;
size_t change_offset;
u16 num_vlan = 0;
u16 vlan_entries = 0;
u16 total_entries = 0;
u16 tvlv_len;
u8 *tt_change_ptr;
int change_offset;
int vlan_entries;
u16 sum_entries;
spin_lock_bh(&bat_priv->meshif_vlan_list_lock);
hlist_for_each_entry(vlan, &bat_priv->meshif_vlan_list, list) {
vlan_entries = atomic_read(&vlan->tt.num_entries);
if (vlan_entries < 1)
continue;
if (check_add_overflow(vlan_entries, total_entries, &sum_entries)) {
tvlv_len = 0;
goto out;
}
total_entries = sum_entries;
num_vlan++;
total_entries += vlan_entries;
}
change_offset = struct_size(*tt_data, vlan_data, num_vlan);
@ -900,8 +925,10 @@ batadv_tt_prepare_tvlv_local_data(struct batadv_priv *bat_priv,
if (*tt_len < 0)
*tt_len = batadv_tt_len(total_entries);
tvlv_len = *tt_len;
tvlv_len += change_offset;
if (check_add_overflow(*tt_len, change_offset, &tvlv_len)) {
tvlv_len = 0;
goto out;
}
*tt_data = kmalloc(tvlv_len, GFP_ATOMIC);
if (!*tt_data) {
@ -914,6 +941,7 @@ batadv_tt_prepare_tvlv_local_data(struct batadv_priv *bat_priv,
(*tt_data)->num_vlan = htons(num_vlan);
tt_vlan = (*tt_data)->vlan_data;
num_vlan = 0;
hlist_for_each_entry(vlan, &bat_priv->meshif_vlan_list, list) {
vlan_entries = atomic_read(&vlan->tt.num_entries);
if (vlan_entries < 1)
@ -924,8 +952,15 @@ batadv_tt_prepare_tvlv_local_data(struct batadv_priv *bat_priv,
tt_vlan->reserved = 0;
tt_vlan++;
num_vlan++;
}
/* recalculate in case number of VLANs reduced */
change_offset = struct_size(*tt_data, vlan_data, num_vlan);
tvlv_len = *tt_len + change_offset;
(*tt_data)->num_vlan = htons(num_vlan);
tt_change_ptr = (u8 *)*tt_data + change_offset;
*tt_change = (struct batadv_tvlv_tt_change *)tt_change_ptr;

View File

@ -301,7 +301,7 @@ struct batadv_frag_table_entry {
u16 seqno;
/** @size: accumulated size of packets in list */
u16 size;
size_t size;
/** @total_size: expected size of the assembled packet */
u16 total_size;
@ -452,7 +452,7 @@ struct batadv_orig_node {
* @tt_buff_len: length of the last tt changeset this node received
* from the orig node
*/
s16 tt_buff_len;
u16 tt_buff_len;
/** @tt_buff_lock: lock that protects tt_buff and tt_buff_len */
spinlock_t tt_buff_lock;
@ -993,7 +993,7 @@ struct batadv_priv_tt {
* @last_changeset_len: length of last tt changeset this host has
* generated
*/
s16 last_changeset_len;
u16 last_changeset_len;
/**
* @last_changeset_lock: lock protecting last_changeset &
@ -1323,6 +1323,9 @@ struct batadv_tp_vars {
/** @sending: sending binary semaphore: 1 if sending, 0 is not */
atomic_t sending;
/** @receiving: receiving binary semaphore: 1 if receiving, 0 is not */
atomic_t receiving;
/** @reason: reason for a stopped session */
enum batadv_tp_meter_reason reason;