ipv6: discard overlapping fragment
authorNicolas Dichtel <nicolas.dichtel@6wind.com>
Fri, 3 Sep 2010 05:13:05 +0000 (05:13 +0000)
committerDavid S. Miller <davem@davemloft.net>
Tue, 7 Sep 2010 20:57:21 +0000 (13:57 -0700)
RFC5722 prohibits reassembling fragments when some data overlaps.

Bug spotted by Zhang Zuotao <zuotao.zhang@6wind.com>.

Signed-off-by: Nicolas Dichtel <nicolas.dichtel@6wind.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
net/ipv6/reassembly.c

index 545c4141b755ee91277c6bd2912de4f42813bb05..64cfef1b0a4c556ccf63c2f912c86ec3a1599b82 100644 (file)
@@ -149,13 +149,6 @@ int ip6_frag_match(struct inet_frag_queue *q, void *a)
 }
 EXPORT_SYMBOL(ip6_frag_match);
 
-/* Memory Tracking Functions. */
-static void frag_kfree_skb(struct netns_frags *nf, struct sk_buff *skb)
-{
-       atomic_sub(skb->truesize, &nf->mem);
-       kfree_skb(skb);
-}
-
 void ip6_frag_init(struct inet_frag_queue *q, void *a)
 {
        struct frag_queue *fq = container_of(q, struct frag_queue, q);
@@ -346,58 +339,22 @@ static int ip6_frag_queue(struct frag_queue *fq, struct sk_buff *skb,
        }
 
 found:
-       /* We found where to put this one.  Check for overlap with
-        * preceding fragment, and, if needed, align things so that
-        * any overlaps are eliminated.
+       /* RFC5722, Section 4:
+        *                                  When reassembling an IPv6 datagram, if
+        *   one or more its constituent fragments is determined to be an
+        *   overlapping fragment, the entire datagram (and any constituent
+        *   fragments, including those not yet received) MUST be silently
+        *   discarded.
         */
-       if (prev) {
-               int i = (FRAG6_CB(prev)->offset + prev->len) - offset;
 
-               if (i > 0) {
-                       offset += i;
-                       if (end <= offset)
-                               goto err;
-                       if (!pskb_pull(skb, i))
-                               goto err;
-                       if (skb->ip_summed != CHECKSUM_UNNECESSARY)
-                               skb->ip_summed = CHECKSUM_NONE;
-               }
-       }
+       /* Check for overlap with preceding fragment. */
+       if (prev &&
+           (FRAG6_CB(prev)->offset + prev->len) - offset > 0)
+               goto discard_fq;
 
-       /* Look for overlap with succeeding segments.
-        * If we can merge fragments, do it.
-        */
-       while (next && FRAG6_CB(next)->offset < end) {
-               int i = end - FRAG6_CB(next)->offset; /* overlap is 'i' bytes */
-
-               if (i < next->len) {
-                       /* Eat head of the next overlapped fragment
-                        * and leave the loop. The next ones cannot overlap.
-                        */
-                       if (!pskb_pull(next, i))
-                               goto err;
-                       FRAG6_CB(next)->offset += i;    /* next fragment */
-                       fq->q.meat -= i;
-                       if (next->ip_summed != CHECKSUM_UNNECESSARY)
-                               next->ip_summed = CHECKSUM_NONE;
-                       break;
-               } else {
-                       struct sk_buff *free_it = next;
-
-                       /* Old fragment is completely overridden with
-                        * new one drop it.
-                        */
-                       next = next->next;
-
-                       if (prev)
-                               prev->next = next;
-                       else
-                               fq->q.fragments = next;
-
-                       fq->q.meat -= free_it->len;
-                       frag_kfree_skb(fq->q.net, free_it);
-               }
-       }
+       /* Look for overlap with succeeding segment. */
+       if (next && FRAG6_CB(next)->offset < end)
+               goto discard_fq;
 
        FRAG6_CB(skb)->offset = offset;
 
@@ -436,6 +393,8 @@ found:
        write_unlock(&ip6_frags.lock);
        return -1;
 
+discard_fq:
+       fq_kill(fq);
 err:
        IP6_INC_STATS(net, ip6_dst_idev(skb_dst(skb)),
                      IPSTATS_MIB_REASMFAILS);