Browse Source

[tcpip] Avoid generating positive zero for transmitted UDP checksums

TCP/IP checksum fields are one's complement values and therefore have
two possible representations of zero: positive zero (0x0000) and
negative zero (0xffff).

In RFC768, UDP over IPv4 exploits this redundancy to repurpose the
positive representation of zero (0x0000) to mean "no checksum
calculated"; checksums are optional for UDP over IPv4.

In RFC2460, checksums are made mandatory for UDP over IPv4.  The
wording of the RFC is such that the UDP header is mandated to use only
the negative representation of zero (0xffff), rather than simply
requiring the checksum to be correct but allowing for either
representation of zero to be used.

In RFC1071, an example algorithm is given for calculating the TCP/IP
checksum.  This algorithm happens to produce only the positive
representation of zero (0x0000); this is an artifact of the way that
unsigned arithmetic is used to calculate a signed one's complement
sum (and its final negation).

A common misconception has developed (exemplified in RFC1624) that
this artifact is part of the specification.  Many people have assumed
that the checksum field should never contain the negative
representation of zero (0xffff).

A sensible receiver will calculate the checksum over the whole packet
and verify that the result is zero (in whichever representation of
zero happens to be generated by the receiver's algorithm).  Such a
receiver will not care which representation of zero happens to be used
in the checksum field.

However, there are receivers in existence which will verify the
received checksum the hard way: by calculating the checksum over the
remainder of the packet and comparing the result against the checksum
field.  If the representation of zero used by the receiver's algorithm
does not match the representation of zero used by the transmitter (and
so placed in the checksum field), and if the receiver does not
explicitly allow for both representations to compare as equal, then
the receiver may reject packets with a valid checksum.

For UDP, the combined RFCs effectively mandate that we should generate
only the negative representation of zero in the checksum field.

For IP, TCP and ICMP, the RFCs do not mandate which representation of
zero should be used, but the misconceptions which have grown up around
RFC1071 and RFC1624 suggest that it would be least surprising to
generate only the positive representation of zero in the checksum
field.

Fix by ensuring that all of our checksum algorithms generate only the
positive representation of zero, and explicitly inverting this in the
case of transmitted UDP packets.

Reported-by: Wissam Shoukair <wissams@mellanox.com>
Tested-by: Wissam Shoukair <wissams@mellanox.com>
Signed-off-by: Michael Brown <mcb30@ipxe.org>
tags/v1.20.1
Michael Brown 8 years ago
parent
commit
8baefad659
5 changed files with 61 additions and 4 deletions
  1. 36
    2
      src/include/ipxe/tcpip.h
  2. 4
    1
      src/net/ipv4.c
  3. 2
    0
      src/net/ipv6.c
  4. 1
    0
      src/net/udp.c
  5. 18
    1
      src/tests/tcpip_test.c

+ 36
- 2
src/include/ipxe/tcpip.h View File

19
 struct net_device;
19
 struct net_device;
20
 struct ip_statistics;
20
 struct ip_statistics;
21
 
21
 
22
+/** Positive zero checksum value */
23
+#define TCPIP_POSITIVE_ZERO_CSUM 0x0000
24
+
25
+/** Negative zero checksum value */
26
+#define TCPIP_NEGATIVE_ZERO_CSUM 0xffff
27
+
22
 /** Empty checksum value
28
 /** Empty checksum value
23
  *
29
  *
24
- * This is the TCP/IP checksum over a zero-length block of data.
30
+ * All of our TCP/IP checksum algorithms will return only the positive
31
+ * representation of zero (0x0000) for a zero checksum over non-zero
32
+ * input data.  This property arises since the end-around carry used
33
+ * to mimic one's complement addition using unsigned arithmetic
34
+ * prevents the running total from ever returning to 0x0000.  The
35
+ * running total will therefore use only the negative representation
36
+ * of zero (0xffff).  Since the return value is the one's complement
37
+ * negation of the running total (calculated by simply bit-inverting
38
+ * the running total), the return value will therefore use only the
39
+ * positive representation of zero (0x0000).
40
+ *
41
+ * It is a very common misconception (found in many places such as
42
+ * RFC1624) that this is a property guaranteed by the underlying
43
+ * mathematics.  It is not; the choice of which zero representation is
44
+ * used is merely an artifact of the software implementation of the
45
+ * checksum algorithm.
46
+ *
47
+ * For consistency, we choose to use the positive representation of
48
+ * zero (0x0000) for the checksum of a zero-length block of data.
49
+ * This ensures that all of our TCP/IP checksum algorithms will return
50
+ * only the positive representation of zero (0x0000) for a zero
51
+ * checksum (regardless of the input data).
25
  */
52
  */
26
-#define TCPIP_EMPTY_CSUM 0xffff
53
+#define TCPIP_EMPTY_CSUM TCPIP_POSITIVE_ZERO_CSUM
27
 
54
 
28
 /** TCP/IP address flags */
55
 /** TCP/IP address flags */
29
 enum tcpip_st_flags {
56
 enum tcpip_st_flags {
88
         int ( * rx ) ( struct io_buffer *iobuf, struct net_device *netdev,
115
         int ( * rx ) ( struct io_buffer *iobuf, struct net_device *netdev,
89
 		       struct sockaddr_tcpip *st_src,
116
 		       struct sockaddr_tcpip *st_src,
90
 		       struct sockaddr_tcpip *st_dest, uint16_t pshdr_csum );
117
 		       struct sockaddr_tcpip *st_dest, uint16_t pshdr_csum );
118
+	/** Preferred zero checksum value
119
+	 *
120
+	 * The checksum is a one's complement value: zero may be
121
+	 * represented by either positive zero (0x0000) or negative
122
+	 * zero (0xffff).
123
+	 */
124
+	uint16_t zero_csum;
91
         /** 
125
         /** 
92
 	 * Transport-layer protocol number
126
 	 * Transport-layer protocol number
93
 	 *
127
 	 *

+ 4
- 1
src/net/ipv4.c View File

358
 			       ( ( netdev->rx_stats.good & 0xf ) << 0 ) );
358
 			       ( ( netdev->rx_stats.good & 0xf ) << 0 ) );
359
 
359
 
360
 	/* Fix up checksums */
360
 	/* Fix up checksums */
361
-	if ( trans_csum )
361
+	if ( trans_csum ) {
362
 		*trans_csum = ipv4_pshdr_chksum ( iobuf, *trans_csum );
362
 		*trans_csum = ipv4_pshdr_chksum ( iobuf, *trans_csum );
363
+		if ( ! *trans_csum )
364
+			*trans_csum = tcpip_protocol->zero_csum;
365
+	}
363
 	iphdr->chksum = tcpip_chksum ( iphdr, sizeof ( *iphdr ) );
366
 	iphdr->chksum = tcpip_chksum ( iphdr, sizeof ( *iphdr ) );
364
 
367
 
365
 	/* Print IP4 header for debugging */
368
 	/* Print IP4 header for debugging */

+ 2
- 0
src/net/ipv6.c View File

522
 		*trans_csum = ipv6_pshdr_chksum ( iphdr, len,
522
 		*trans_csum = ipv6_pshdr_chksum ( iphdr, len,
523
 						  tcpip_protocol->tcpip_proto,
523
 						  tcpip_protocol->tcpip_proto,
524
 						  *trans_csum );
524
 						  *trans_csum );
525
+		if ( ! *trans_csum )
526
+			*trans_csum = tcpip_protocol->zero_csum;
525
 	}
527
 	}
526
 
528
 
527
 	/* Print IPv6 header for debugging */
529
 	/* Print IPv6 header for debugging */

+ 1
- 0
src/net/udp.c View File

328
 struct tcpip_protocol udp_protocol __tcpip_protocol = {
328
 struct tcpip_protocol udp_protocol __tcpip_protocol = {
329
 	.name = "UDP",
329
 	.name = "UDP",
330
 	.rx = udp_rx,
330
 	.rx = udp_rx,
331
+	.zero_csum = TCPIP_NEGATIVE_ZERO_CSUM,
331
 	.tcpip_proto = IP_UDP,
332
 	.tcpip_proto = IP_UDP,
332
 };
333
 };
333
 
334
 

+ 18
- 1
src/tests/tcpip_test.c View File

94
 /** Double byte */
94
 /** Double byte */
95
 TCPIP_TEST ( two_bytes, DATA ( 0xba, 0xbe ) );
95
 TCPIP_TEST ( two_bytes, DATA ( 0xba, 0xbe ) );
96
 
96
 
97
+/** Positive zero data */
98
+TCPIP_TEST ( positive_zero, DATA ( 0x00, 0x00 ) );
99
+
100
+/** Negative zero data */
101
+TCPIP_TEST ( negative_zero, DATA ( 0xff, 0xff ) );
102
+
97
 /** Final wrap-around carry (big-endian) */
103
 /** Final wrap-around carry (big-endian) */
98
 TCPIP_TEST ( final_carry_big,
104
 TCPIP_TEST ( final_carry_big,
99
 	     DATA ( 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 ) );
105
 	     DATA ( 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 ) );
126
  *
132
  *
127
  * This is a reference implementation taken from RFC1071 (and modified
133
  * This is a reference implementation taken from RFC1071 (and modified
128
  * to fix compilation without warnings under gcc).
134
  * to fix compilation without warnings under gcc).
135
+ *
136
+ * The initial value of the one's complement @c sum is changed from
137
+ * positive zero (0x0000) to negative zero (0xffff).  This ensures
138
+ * that the return value will always use the positive representation
139
+ * of zero (0x0000).  Without this change, the return value would use
140
+ * negative zero (0xffff) if the input data is zero length (or all
141
+ * zeros) but positive zero (0x0000) for any other data which sums to
142
+ * zero.
129
  */
143
  */
130
 static uint16_t rfc_tcpip_chksum ( const void *data, size_t len ) {
144
 static uint16_t rfc_tcpip_chksum ( const void *data, size_t len ) {
131
-	unsigned long sum = 0;
145
+	unsigned long sum = 0xffff;
132
 
146
 
133
         while ( len > 1 )  {
147
         while ( len > 1 )  {
134
 		sum += *( ( uint16_t * ) data );
148
 		sum += *( ( uint16_t * ) data );
142
 	while ( sum >> 16 )
156
 	while ( sum >> 16 )
143
 		sum = ( ( sum & 0xffff ) + ( sum >> 16 ) );
157
 		sum = ( ( sum & 0xffff ) + ( sum >> 16 ) );
144
 
158
 
159
+	assert ( sum != 0x0000 );
145
 	return ~sum;
160
 	return ~sum;
146
 }
161
 }
147
 
162
 
227
 	tcpip_ok ( &empty );
242
 	tcpip_ok ( &empty );
228
 	tcpip_ok ( &one_byte );
243
 	tcpip_ok ( &one_byte );
229
 	tcpip_ok ( &two_bytes );
244
 	tcpip_ok ( &two_bytes );
245
+	tcpip_ok ( &positive_zero );
246
+	tcpip_ok ( &negative_zero );
230
 	tcpip_ok ( &final_carry_big );
247
 	tcpip_ok ( &final_carry_big );
231
 	tcpip_ok ( &final_carry_little );
248
 	tcpip_ok ( &final_carry_little );
232
 	tcpip_random_ok ( &random_aligned );
249
 	tcpip_random_ok ( &random_aligned );

Loading…
Cancel
Save