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