xref: /minix3/minix/net/lwip/tcpisn.c (revision 03ac74ede908465cc64c671bbd209e761dc765dc)
1ef8d499eSDavid van Moolenbroek /* LWIP service - tcpisn.c - TCP Initial Sequence Number generation */
2ef8d499eSDavid van Moolenbroek /*
3ef8d499eSDavid van Moolenbroek  * This module implements the TCP ISN algorithm standardized in RFC 6528.  It
4ef8d499eSDavid van Moolenbroek  * currently uses the current time, at clock tick granularity, as source for
5ef8d499eSDavid van Moolenbroek  * the 4-microsecond timer, and SHA256 as the hashing algorithm.  As part of
6ef8d499eSDavid van Moolenbroek  * the input to the hash function, we use an "ISN secret" that can be set
7ef8d499eSDavid van Moolenbroek  * through the (hidden, root-only) net.inet.tcp.isn_secret sysctl(7) node.
8ef8d499eSDavid van Moolenbroek  * Ideally, the secret should remain the same across system reboots; it is left
9ef8d499eSDavid van Moolenbroek  * up to userland to take care of that.
10ef8d499eSDavid van Moolenbroek  *
11ef8d499eSDavid van Moolenbroek  * TODO: while this module provides the strongest possible implementation of
12ef8d499eSDavid van Moolenbroek  * the algorithm, it is also quite heavyweight.  We should consider allowing
13ef8d499eSDavid van Moolenbroek  * for a more configurable level of strength, perhaps with the possibility for
14ef8d499eSDavid van Moolenbroek  * less powerful platforms to revert to simple use of a random number.
15ef8d499eSDavid van Moolenbroek  */
16ef8d499eSDavid van Moolenbroek 
17ef8d499eSDavid van Moolenbroek #include "lwip.h"
18ef8d499eSDavid van Moolenbroek #include "tcpisn.h"
19ef8d499eSDavid van Moolenbroek 
20ef8d499eSDavid van Moolenbroek #include <sys/sha2.h>
21ef8d499eSDavid van Moolenbroek 
22ef8d499eSDavid van Moolenbroek /*
23ef8d499eSDavid van Moolenbroek  * The TCP ISN hash input consists of the TCP 4-tuple of the new connection and
24ef8d499eSDavid van Moolenbroek  * a static secret.  The 4-tuple consists of two IP addresses, at most 16 bytes
25ef8d499eSDavid van Moolenbroek  * (128 bits, for IPv6) each, and two port numbers, two bytes (16 bits) each.
26ef8d499eSDavid van Moolenbroek  * We use the SHA256 input block size of 64 bytes to avoid copying, so that
27ef8d499eSDavid van Moolenbroek  * leaves us with 28 bytes of room for the static secret.  We use 16 bytes, and
28ef8d499eSDavid van Moolenbroek  * leave the rest blank.  As a sidenote, while hardcoding sizes is not nice, we
29ef8d499eSDavid van Moolenbroek  * really need to get the layout exactly right in this case.
30ef8d499eSDavid van Moolenbroek  */
31ef8d499eSDavid van Moolenbroek #define TCPISN_TUPLE_LENGTH	(16 * 2 + 2 * 2)
32ef8d499eSDavid van Moolenbroek 
33ef8d499eSDavid van Moolenbroek #if TCPISN_SECRET_LENGTH > (SHA256_BLOCK_LENGTH - TCPISN_TUPLE_LENGTH)
34ef8d499eSDavid van Moolenbroek #error "TCP ISN secret length exceeds remainder of hash block"
35ef8d499eSDavid van Moolenbroek #endif
36ef8d499eSDavid van Moolenbroek 
37ef8d499eSDavid van Moolenbroek /* We are using memchr() on this, so do not remove the '32' size here! */
38ef8d499eSDavid van Moolenbroek static const uint8_t tcpisn_hextab[32] = "0123456789abcdef0123456789ABCDEF";
39ef8d499eSDavid van Moolenbroek 
40ef8d499eSDavid van Moolenbroek static uint8_t tcpisn_input[SHA256_BLOCK_LENGTH] __aligned(4);
41ef8d499eSDavid van Moolenbroek 
42ef8d499eSDavid van Moolenbroek static int tcpisn_set;
43ef8d499eSDavid van Moolenbroek 
44ef8d499eSDavid van Moolenbroek /*
45ef8d499eSDavid van Moolenbroek  * Initialize the TCP ISN module.
46ef8d499eSDavid van Moolenbroek  */
47ef8d499eSDavid van Moolenbroek void
tcpisn_init(void)48ef8d499eSDavid van Moolenbroek tcpisn_init(void)
49ef8d499eSDavid van Moolenbroek {
50ef8d499eSDavid van Moolenbroek 	time_t boottime;
51ef8d499eSDavid van Moolenbroek 
52ef8d499eSDavid van Moolenbroek 	/*
53ef8d499eSDavid van Moolenbroek 	 * Part of the input to the hash function is kept as is between calls
54ef8d499eSDavid van Moolenbroek 	 * to the TCP ISN hook.  In particular, we zero the entire input here,
55ef8d499eSDavid van Moolenbroek 	 * so that the padding is zero.  We also zero the area where the secret
56ef8d499eSDavid van Moolenbroek 	 * will be stored, but we put in the system boot time as a last effort
57ef8d499eSDavid van Moolenbroek 	 * to try to create at least some minimal amount of unpredictability.
58ef8d499eSDavid van Moolenbroek 	 * The boot time is by no means sufficient though, so issue a warning
59ef8d499eSDavid van Moolenbroek 	 * if a TCP ISN is requested before an actual secret is set.  Note that
60ef8d499eSDavid van Moolenbroek 	 * an actual secret will overwrite the boot time based pseudo-secret.
61ef8d499eSDavid van Moolenbroek 	 */
62ef8d499eSDavid van Moolenbroek 	memset(tcpisn_input, 0, sizeof(tcpisn_input));
63ef8d499eSDavid van Moolenbroek 
64ef8d499eSDavid van Moolenbroek 	(void)getuptime(NULL, NULL, &boottime);
65ef8d499eSDavid van Moolenbroek 	memcpy(&tcpisn_input[TCPISN_TUPLE_LENGTH], &boottime,
66ef8d499eSDavid van Moolenbroek 	    sizeof(boottime));
67ef8d499eSDavid van Moolenbroek 
68ef8d499eSDavid van Moolenbroek 	tcpisn_set = FALSE;
69ef8d499eSDavid van Moolenbroek }
70ef8d499eSDavid van Moolenbroek 
71ef8d499eSDavid van Moolenbroek /*
72ef8d499eSDavid van Moolenbroek  * Set and/or retrieve the ISN secret.  In order to allow the hash value to be
73ef8d499eSDavid van Moolenbroek  * set from the command line, this sysctl(7) node is a hex-encoded string.
74ef8d499eSDavid van Moolenbroek  */
75ef8d499eSDavid van Moolenbroek ssize_t
tcpisn_secret(struct rmib_call * call __unused,struct rmib_node * node __unused,struct rmib_oldp * oldp,struct rmib_newp * newp)76ef8d499eSDavid van Moolenbroek tcpisn_secret(struct rmib_call * call __unused,
77ef8d499eSDavid van Moolenbroek 	struct rmib_node * node __unused, struct rmib_oldp * oldp,
78ef8d499eSDavid van Moolenbroek 	struct rmib_newp * newp)
79ef8d499eSDavid van Moolenbroek {
80ef8d499eSDavid van Moolenbroek 	uint8_t secret[TCPISN_SECRET_HEX_LENGTH], byte, *p;
81*03ac74edSLionel Sambuc 	unsigned int i = 0 /*gcc*/;
82ef8d499eSDavid van Moolenbroek 	int r;
83ef8d499eSDavid van Moolenbroek 
84ef8d499eSDavid van Moolenbroek 	/* First copy out the old (current) ISN secret. */
85ef8d499eSDavid van Moolenbroek 	if (oldp != NULL) {
86ef8d499eSDavid van Moolenbroek 		for (i = 0; i < TCPISN_SECRET_LENGTH; i++) {
87ef8d499eSDavid van Moolenbroek 			byte = tcpisn_input[TCPISN_TUPLE_LENGTH + i];
88ef8d499eSDavid van Moolenbroek 			secret[i * 2] = tcpisn_hextab[byte >> 4];
89ef8d499eSDavid van Moolenbroek 			secret[i * 2 + 1] = tcpisn_hextab[byte & 0xf];
90ef8d499eSDavid van Moolenbroek 		}
91ef8d499eSDavid van Moolenbroek 		secret[i * 2] = '\0';
92ef8d499eSDavid van Moolenbroek 		assert(i * 2 + 1 == sizeof(secret));
93ef8d499eSDavid van Moolenbroek 
94ef8d499eSDavid van Moolenbroek 		if ((r = rmib_copyout(oldp, 0, secret, sizeof(secret))) < 0)
95ef8d499eSDavid van Moolenbroek 			return r;
96ef8d499eSDavid van Moolenbroek 	}
97ef8d499eSDavid van Moolenbroek 
98ef8d499eSDavid van Moolenbroek 	/*
99ef8d499eSDavid van Moolenbroek 	 * Then copy in the new ISN secret.  We require the given string to be
100ef8d499eSDavid van Moolenbroek 	 * exactly as large as we need.
101ef8d499eSDavid van Moolenbroek 	 */
102ef8d499eSDavid van Moolenbroek 	if (newp != NULL) {
103ef8d499eSDavid van Moolenbroek 		/* Copy in the user-given string. */
104ef8d499eSDavid van Moolenbroek 		if ((r = rmib_copyin(newp, secret, sizeof(secret))) != OK)
105ef8d499eSDavid van Moolenbroek 			return r;
106ef8d499eSDavid van Moolenbroek 		if (secret[i * 2] != '\0')
107ef8d499eSDavid van Moolenbroek 			return EINVAL;
108ef8d499eSDavid van Moolenbroek 
109ef8d499eSDavid van Moolenbroek 		/* Hex-decode the given string (in place). */
110ef8d499eSDavid van Moolenbroek 		for (i = 0; i < TCPISN_SECRET_LENGTH; i++) {
111ef8d499eSDavid van Moolenbroek 			if ((p = memchr(tcpisn_hextab, secret[i * 2],
112ef8d499eSDavid van Moolenbroek 			    sizeof(tcpisn_hextab))) == NULL)
113ef8d499eSDavid van Moolenbroek 				return EINVAL;
114ef8d499eSDavid van Moolenbroek 			secret[i] = ((uint8_t)(p - tcpisn_hextab) & 0xf) << 4;
115ef8d499eSDavid van Moolenbroek 			if ((p = memchr(tcpisn_hextab, secret[i * 2 + 1],
116ef8d499eSDavid van Moolenbroek 			    sizeof(tcpisn_hextab))) == NULL)
117ef8d499eSDavid van Moolenbroek 				return EINVAL;
118ef8d499eSDavid van Moolenbroek 			secret[i] |= (uint8_t)(p - tcpisn_hextab) & 0xf;
119ef8d499eSDavid van Moolenbroek 		}
120ef8d499eSDavid van Moolenbroek 
121ef8d499eSDavid van Moolenbroek 		/* Once fully validated, switch to the new secret. */
122ef8d499eSDavid van Moolenbroek 		memcpy(&tcpisn_input[TCPISN_TUPLE_LENGTH], secret,
123ef8d499eSDavid van Moolenbroek 		    TCPISN_SECRET_LENGTH);
124ef8d499eSDavid van Moolenbroek 
125ef8d499eSDavid van Moolenbroek 		tcpisn_set = TRUE;
126ef8d499eSDavid van Moolenbroek 	}
127ef8d499eSDavid van Moolenbroek 
128ef8d499eSDavid van Moolenbroek 	/* Return the length of the node. */
129ef8d499eSDavid van Moolenbroek 	return sizeof(secret);
130ef8d499eSDavid van Moolenbroek }
131ef8d499eSDavid van Moolenbroek 
132ef8d499eSDavid van Moolenbroek /*
133ef8d499eSDavid van Moolenbroek  * Hook to generate an Initial Sequence Number (ISN) for a new TCP connection.
134ef8d499eSDavid van Moolenbroek  */
135ef8d499eSDavid van Moolenbroek uint32_t
lwip_hook_tcp_isn(const ip_addr_t * local_ip,uint16_t local_port,const ip_addr_t * remote_ip,uint16_t remote_port)136ef8d499eSDavid van Moolenbroek lwip_hook_tcp_isn(const ip_addr_t * local_ip, uint16_t local_port,
137ef8d499eSDavid van Moolenbroek 	const ip_addr_t * remote_ip, uint16_t remote_port)
138ef8d499eSDavid van Moolenbroek {
139ef8d499eSDavid van Moolenbroek 	uint8_t output[SHA256_DIGEST_LENGTH] __aligned(4);
140ef8d499eSDavid van Moolenbroek 	SHA256_CTX ctx;
141ef8d499eSDavid van Moolenbroek 	clock_t realtime;
142ef8d499eSDavid van Moolenbroek 	time_t boottime;
143ef8d499eSDavid van Moolenbroek 	uint32_t isn;
144ef8d499eSDavid van Moolenbroek 
145ef8d499eSDavid van Moolenbroek 	if (!tcpisn_set) {
146ef8d499eSDavid van Moolenbroek 		printf("LWIP: warning, no TCP ISN secret has been set\n");
147ef8d499eSDavid van Moolenbroek 
148ef8d499eSDavid van Moolenbroek 		tcpisn_set = TRUE;	/* print the warning only once */
149ef8d499eSDavid van Moolenbroek 	}
150ef8d499eSDavid van Moolenbroek 
151ef8d499eSDavid van Moolenbroek 	if (IP_IS_V6(local_ip)) {
152ef8d499eSDavid van Moolenbroek 		assert(IP_IS_V6(remote_ip));
153ef8d499eSDavid van Moolenbroek 
154ef8d499eSDavid van Moolenbroek 		memcpy(&tcpisn_input[0], &ip_2_ip6(local_ip)->addr, 16);
155ef8d499eSDavid van Moolenbroek 		memcpy(&tcpisn_input[16], &ip_2_ip6(remote_ip)->addr, 16);
156ef8d499eSDavid van Moolenbroek 	} else {
157ef8d499eSDavid van Moolenbroek 		assert(IP_IS_V4(local_ip));
158ef8d499eSDavid van Moolenbroek 		assert(IP_IS_V4(remote_ip));
159ef8d499eSDavid van Moolenbroek 
160ef8d499eSDavid van Moolenbroek 		/*
161ef8d499eSDavid van Moolenbroek 		 * Store IPv4 addresses as IPv4-mapped IPv6 addresses, even
162ef8d499eSDavid van Moolenbroek 		 * though lwIP will never give us an IPv4-mapped IPv6 address,
163ef8d499eSDavid van Moolenbroek 		 * so as to ensure completely disjoint address spaces and thus
164ef8d499eSDavid van Moolenbroek 		 * no potential abuse of IPv6 addresses in order to predict
165ef8d499eSDavid van Moolenbroek 		 * ISNs for IPv4 connections.
166ef8d499eSDavid van Moolenbroek 		 */
167ef8d499eSDavid van Moolenbroek 		memset(&tcpisn_input[0], 0, 10);
168ef8d499eSDavid van Moolenbroek 		tcpisn_input[10] = 0xff;
169ef8d499eSDavid van Moolenbroek 		tcpisn_input[11] = 0xff;
170ef8d499eSDavid van Moolenbroek 		memcpy(&tcpisn_input[12], &ip_2_ip4(local_ip)->addr, 4);
171ef8d499eSDavid van Moolenbroek 		memset(&tcpisn_input[16], 0, 10);
172ef8d499eSDavid van Moolenbroek 		tcpisn_input[26] = 0xff;
173ef8d499eSDavid van Moolenbroek 		tcpisn_input[27] = 0xff;
174ef8d499eSDavid van Moolenbroek 		memcpy(&tcpisn_input[28], &ip_2_ip4(local_ip)->addr, 4);
175ef8d499eSDavid van Moolenbroek 	}
176ef8d499eSDavid van Moolenbroek 
177ef8d499eSDavid van Moolenbroek 	tcpisn_input[32] = local_port >> 8;
178ef8d499eSDavid van Moolenbroek 	tcpisn_input[33] = local_port & 0xff;
179ef8d499eSDavid van Moolenbroek 	tcpisn_input[34] = remote_port >> 8;
180ef8d499eSDavid van Moolenbroek 	tcpisn_input[35] = remote_port & 0xff;
181ef8d499eSDavid van Moolenbroek 
182ef8d499eSDavid van Moolenbroek 	/* The rest of the input (secret and padding) is already filled in. */
183ef8d499eSDavid van Moolenbroek 
184ef8d499eSDavid van Moolenbroek 	SHA256_Init(&ctx); /* this call zeroes a buffer we don't use.. */
185ef8d499eSDavid van Moolenbroek 	SHA256_Update(&ctx, tcpisn_input, sizeof(tcpisn_input));
186ef8d499eSDavid van Moolenbroek 	SHA256_Final(output, &ctx);
187ef8d499eSDavid van Moolenbroek 
188ef8d499eSDavid van Moolenbroek 	/* Arbitrarily take the first 32 bits from the generated hash. */
189ef8d499eSDavid van Moolenbroek 	memcpy(&isn, output, sizeof(isn));
190ef8d499eSDavid van Moolenbroek 
191ef8d499eSDavid van Moolenbroek 	/*
192ef8d499eSDavid van Moolenbroek 	 * Add the current time in 4-microsecond units.  The time value should
193ef8d499eSDavid van Moolenbroek 	 * be wall-clock accurate and stable even across system reboots and
194ef8d499eSDavid van Moolenbroek 	 * downtime.  Do not precompute the boot time part: it may change.
195ef8d499eSDavid van Moolenbroek 	 */
196ef8d499eSDavid van Moolenbroek 	(void)getuptime(NULL, &realtime, &boottime);
197ef8d499eSDavid van Moolenbroek 
198ef8d499eSDavid van Moolenbroek 	isn += (uint32_t)boottime * 250000;
199ef8d499eSDavid van Moolenbroek 	isn += (uint32_t)(((uint64_t)realtime * 250000) / sys_hz());
200ef8d499eSDavid van Moolenbroek 
201ef8d499eSDavid van Moolenbroek 	/* The result is the ISN to use for this connection. */
202ef8d499eSDavid van Moolenbroek 	return isn;
203ef8d499eSDavid van Moolenbroek }
204