]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
batman-adv: fix fragment reassembly length accounting
authorRuide Cao <caoruide123@gmail.com>
Wed, 13 May 2026 03:58:15 +0000 (11:58 +0800)
committerSven Eckelmann <sven@narfation.org>
Thu, 14 May 2026 16:33:29 +0000 (18:33 +0200)
batman-adv keeps a running payload length for queued fragments and uses it
to validate a fragment chain before reassembly.

That accounting currently allows the accumulated fragment length to be
truncated during updates. As a result, malformed fragment chains can
bypass the intended validation and drive reassembly with inconsistent
length state, leading to a local denial of service.

Fix the accounting by storing the accumulated length in a length-typed
field and rejecting update overflows before the existing validation logic
runs.

The fix was verified against the original reproducer and against valid
fragment reassembly paths.

Fixes: 610bfc6bc99b ("batman-adv: Receive fragmented packets and merge")
Cc: stable@kernel.org
Reported-by: Yuan Tan <yuantan098@gmail.com>
Reported-by: Yifan Wu <yifanwucs@gmail.com>
Reported-by: Juefei Pu <tomapufckgml@gmail.com>
Reported-by: Xin Liu <bird@lzu.edu.cn>
Signed-off-by: Ruide Cao <caoruide123@gmail.com>
Tested-by: Ren Wei <enjou1224z@gmail.com>
Signed-off-by: Ren Wei <n05ec@lzu.edu.cn>
Signed-off-by: Sven Eckelmann <sven@narfation.org>
net/batman-adv/fragmentation.c
net/batman-adv/types.h

index f4e45cc2581642d023f115d6cbcf58991d1606af..1152c2ce0c1ea2f004b0c165b977748fdab1c0bc 100644 (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
index 739439e2b23506d20ad1155a34d68fd7a38cbb59..c8c3e8064f00cb9f643828537a1369cc650e7cb3 100644 (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;