Subject: kern/5380: bad incremental checksum fixup in netinet/ip_flow.c
To: None <gnats-bugs@gnats.netbsd.org>
From: None <dennis@juniper.net>
List: netbsd-bugs
Date: 04/30/1998 21:08:02
>Number:         5380
>Category:       kern
>Synopsis:       bad incremental checksum fixup in netinet/ip_flow.c
>Confidential:   no
>Severity:       serious
>Priority:       medium
>Responsible:    kern-bug-people (Kernel Bug People)
>State:          open
>Class:          sw-bug
>Submitter-Id:   net
>Arrival-Date:   Thu Apr 30 21:05:00 1998
>Last-Modified:
>Originator:     Dennis Ferguson
>Organization:
Juniper Networks
>Release:        NetBSD-current April 30 1998
>Environment:

>Description:
In netinet/ip_flow.c, the following code fragment is used to fix up
the header checksum after a ttl decrement:

	ip->ip_ttl -= IPTTLDEC;
#if BYTE_ORDER == LITTLE_ENDIAN
	sum = ip->ip_sum + IPTTLDEC;
#endif
#if BYTE_ORDER == BIG_ENDIAN
	sum = ip->ip_sum + (IPTTLDEC << 8);
	sum = (sum & 0xFFFF) + (sum >> 16);
#endif
	if (sum > 0x10000)		/* add in carry if needed */
		sum++;
	ip->ip_sum = sum;		/* bit 16 is dropped */

This is bad in a number of ways.  Assuming IPTTLDEC is 1 (though
the problems are independent of this):

(1) If the incoming checksum is 0xfffe on a little endian machine,
    or 0xfeff on a big endian machine, the code will generate a
    checksum of 0xffff.  While this is not strictly incorrect, note
    that in_cksum() always avoids this and will always produce the
    equivalent 0x0000 in this case.  The reason it is poor form to
    generate a 0xffff header checksum is that it can cause systems
    which do the header checksum check by recomputing it and comparing
    to the value in the header to fail.

(2) If the incoming checksum is 0xffff the updated checksum will be
    incorrect in the little endian case.  The updated checksum will
    be 0x0000, when it should be 0x0001.

(3) The `if (sum > 0x10000) sum++;' does nothing in the big endian
    case and fails to prevent incorrect computations in the little
    endian case.

(4) The big endian case compiles to more instructions than it needs
    to.

(5) The #if's are unnecessary given an htons() macro which compiles
    to a constant when called with a constant argument.

The same problems exist for IPTTLDEC other than 1, only the numbers
change.


>How-To-Repeat:

>Fix:
Given an htons() macro which compiles to a constant with a constant
argument the following code fragment avoids generating a 0xffff
checksum, produces a correct checksum in all cases, avoids the #if's,
and does the operation with a single comparison and addition on
machines of either byte order.

	ip->ip_ttl -= IPTTLDEC;
	if (ip->ip_sum >= htons(0xffff - (IPTTLDEC << 8))) {
		ip->ip_sum += htons(IPTTLDEC << 8) + 1;
	} else {
		ip->ip_sum += htons(IPTTLDEC << 8);
	}

(The same technique can be used to update the ICMP checksum in
an ICMP echo reply, with IPTTLDEC replaced by 3).
>Audit-Trail:
>Unformatted: