]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
firewire: net: Fix fragmented datagram reassembly
authorRuoyu Wang <ruoyuw560@gmail.com>
Tue, 7 Jul 2026 15:04:54 +0000 (23:04 +0800)
committerTakashi Sakamoto <o-takashi@sakamocchi.jp>
Mon, 13 Jul 2026 11:19:06 +0000 (20:19 +0900)
fwnet_frag_new() keeps a sorted list of received fragments for a partial
datagram. When a new fragment is adjacent to an existing fragment, the
code checks whether the new fragment also closes the gap to the next or
previous list entry.

Those neighbor lookups currently assume that the current fragment always
has a real next or previous fragment. At a list edge, the next or
previous entry is the list head, not a struct fwnet_fragment_info.

The gap checks also compare against the old edge of the current fragment
instead of the edge after adding the new fragment. As a result, a
fragment that bridges two existing ranges may leave two adjacent ranges
unmerged, so fwnet_pd_is_complete() can miss a complete datagram.

Check for the list head before looking up the neighboring fragment, and
compare the neighbor against the new fragment's far edge when deciding
whether to merge all three ranges.

This issue was found by a static analysis checker and confirmed by
manual source review.

Fixes: c76acec6d551 ("firewire: add IPv4 support")
Signed-off-by: Ruoyu Wang <ruoyuw560@gmail.com>
Link: https://lore.kernel.org/r/20260707150454.2265951-1-ruoyuw560@gmail.com
Signed-off-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
drivers/firewire/net.c

index e5361f4f8bbd95553b3932757a2ce944a415ad27..961f1a0460257a1691ffc9dfef4e4ed41a97551e 100644 (file)
@@ -297,31 +297,34 @@ static struct fwnet_fragment_info *fwnet_frag_new(
                if (fi->offset + fi->len == offset) {
                        /* The new fragment can be tacked on to the end */
                        /* Did the new fragment plug a hole? */
-                       fi2 = list_entry(fi->fi_link.next,
-                                        struct fwnet_fragment_info, fi_link);
-                       if (fi->offset + fi->len == fi2->offset) {
-                               /* glue fragments together */
-                               fi->len += len + fi2->len;
-                               list_del(&fi2->fi_link);
-                               kfree(fi2);
-                       } else {
-                               fi->len += len;
+                       if (!list_is_last(&fi->fi_link, &pd->fi_list)) {
+                               fi2 = list_next_entry(fi, fi_link);
+                               if (offset + len == fi2->offset) {
+                                       /* glue fragments together */
+                                       fi->len += len + fi2->len;
+                                       list_del(&fi2->fi_link);
+                                       kfree(fi2);
+
+                                       return fi;
+                               }
                        }
+                       fi->len += len;
 
                        return fi;
                }
                if (offset + len == fi->offset) {
                        /* The new fragment can be tacked on to the beginning */
                        /* Did the new fragment plug a hole? */
-                       fi2 = list_entry(fi->fi_link.prev,
-                                        struct fwnet_fragment_info, fi_link);
-                       if (fi2->offset + fi2->len == fi->offset) {
-                               /* glue fragments together */
-                               fi2->len += fi->len + len;
-                               list_del(&fi->fi_link);
-                               kfree(fi);
-
-                               return fi2;
+                       if (!list_is_first(&fi->fi_link, &pd->fi_list)) {
+                               fi2 = list_prev_entry(fi, fi_link);
+                               if (fi2->offset + fi2->len == offset) {
+                                       /* glue fragments together */
+                                       fi2->len += fi->len + len;
+                                       list_del(&fi->fi_link);
+                                       kfree(fi);
+
+                                       return fi2;
+                               }
                        }
                        fi->offset = offset;
                        fi->len += len;