[LWN Logo]

Date:	Wed, 24 Mar 1999 23:19:37 -0500
From:	John McDonald <jmcdonal@UNF.EDU>
Subject:      DoS for Linux 2.1.89 - 2.2.3: 0 length fragment bug
To:	BUGTRAQ@NETSPACE.ORG

Hi,

The recent release of the Linux 2.2.4 kernel fixed a remote denial of
service problem in the IP fragment handling code. If you are running a
Linux kernel between 2.1.89 and 2.2.3, it would probably be a good idea to
get the latest version. In case that isn't feasible for you, I've included
a patch in this post. The impact of this problem is that a remote attacker
can effectively disable a target's IP connectivity. However, for the
attack to succeed, the attacker will have to deliver several thousand
packets to the target, which can take up to several minutes. A quick
exploit and the patch are appended to the end of this post.

The problem starts in ip_glue() in ip_fragment.c:

	/* Copy the data portions of all fragments into the new buffer. */
	fp = qp->fragments;
	count = qp->ihlen;
	while(fp) {
		if ((fp->len < 0) || ((count + fp->len) > skb->len))
			goto out_invalid;
		memcpy((ptr + fp->offset), fp->ptr, fp->len);
		if (count == qp->ihlen) {
			skb->dst = dst_clone(fp->skb->dst);
			skb->dev = fp->skb->dev;
		}
		count += fp->len;
		fp = fp->next;
	}

The problem in this code is that if you can get a fragment into the
qp->fragments list that has a length of 0, and is the first fragment in the
list, then the call to dst_clone() will happen an extra time. The first time
through the loop, count will necessarily equal qp->ihlen, causing
dst_clone() to be called. However, if fp->len happens to equal 0, then count
+= fp->len won't increase it, and the next time through the loop, count will
still equal qp->ihlen. dst_clone() increments a usage count on an element in
the routing cache. Our 0 length fragment will cause this element in the
cache to become stranded. The kernel will not free it when it does the
garbage collection of the cache because it will think it is currently in
use.

The other component of the problem is that the call to allocate a new entry
in the routing cache does a check to see if the hashtable that comprises the
cache is at a saturated state. If it is, it proceeds to do a garbage
collection. If the number of entries in the cache, after this garbage
collection, is still higher than the threshold, then dst_alloc() will fail.
So, if we generate enough stranded entries in the routing cache (4096 in
2.2.3) via our malicious frags, then all further calls to dst_alloc will
fail.

We can get a 0 length fragment into the head of the list by doing the
following:

Send a fragment at offset 0, with a length of X, and IP_MF set. This creates
our list.

Send a 0 length fragment at offset 0, where the ip header length is equal to
the ip total length, and IP_MF is set. This will be treated as coming before
the fragment already in our list, because it has an offset equal to the
offset of the existing fragment. It doesn't overlap any, because it's end is
equal to the following fragment's offset.

Send a fragment at offset X, with IP_MF not set. This will mark the end of
our set of fragments. ip_done() will return true because it will see the
first frag going from 0 to 0, the second going from 0 to X, and the third
going from X to the end. Our fragments will get passed into ip_glue().

-horizon

Here is the patch:

--- linux-2.2.3/net/ipv4/ip_fragment.c  Wed Mar 24 22:48:26 1999
+++ linux/net/ipv4/ip_fragment.c        Wed Mar 24 22:44:24 1999
@@ -17,6 +17,7 @@
  *             xxxx            :       Overlapfrag bug.
  *             Ultima          :       ip_expire() kernel panic.
  *             Bill Hawes      :       Frag accounting and evictor fixes.
+ *             John McDonald   :       0 length frag bug.
  */

 #include <linux/types.h>
@@ -357,7 +358,7 @@
        fp = qp->fragments;
        count = qp->ihlen;
        while(fp) {
-               if ((fp->len < 0) || ((count + fp->len) > skb->len))
+               if ((fp->len <= 0) || ((count + fp->len) > skb->len))
                        goto out_invalid;
                memcpy((ptr + fp->offset), fp->ptr, fp->len);
                if (count == qp->ihlen) {

And here is the exploit:

[removed from this version -- LWN editor.  See the bugtraq archives
for a copy of the exploit.]