diff --git a/components/lwip/CMakeLists.txt b/components/lwip/CMakeLists.txt index 632d866ed..826d7f1e5 100644 --- a/components/lwip/CMakeLists.txt +++ b/components/lwip/CMakeLists.txt @@ -5,6 +5,7 @@ set(COMPONENT_ADD_INCLUDEDIRS port/esp32/include port/esp32/include/arch include_compat + port/esp32/tcp_isn ) set(COMPONENT_SRCS "apps/dhcpserver/dhcpserver.c" @@ -123,6 +124,10 @@ if(CONFIG_PPP_SUPPORT) "lwip/src/netif/ppp/polarssl/sha1.c") endif() +if(CONFIG_LWIP_TCP_ISN_HOOK) + list(APPEND COMPONENT_SRCS "port/esp32/tcp_isn/tcp_isn.c") +endif() + set(COMPONENT_REQUIRES vfs) set(COMPONENT_PRIV_REQUIRES ethernet tcpip_adapter nvs_flash) diff --git a/components/lwip/Kconfig b/components/lwip/Kconfig index f307ef04a..7ad19499c 100644 --- a/components/lwip/Kconfig +++ b/components/lwip/Kconfig @@ -296,6 +296,17 @@ menu "LWIP" menu "TCP" + config LWIP_TCP_ISN_HOOK + bool "Enable TCP ISN Hook" + default y + help + Enables custom TCP ISN hook to randomize initial sequence + number in TCP connection. This is recommended as default + lwIP implementation (`tcp_next_iss`) is not very strong, + as it does not take into consideration any platform + specific entropy source. + + config LWIP_MAX_ACTIVE_TCP int "Maximum active TCP Connections" range 1 1024 diff --git a/components/lwip/component.mk b/components/lwip/component.mk index f43250baa..dca378477 100644 --- a/components/lwip/component.mk +++ b/components/lwip/component.mk @@ -9,7 +9,8 @@ COMPONENT_ADD_INCLUDEDIRS := \ lwip/src/include \ port/esp32/include \ port/esp32/include/arch \ - include_compat + include_compat \ + port/esp32/tcp_isn COMPONENT_SRCDIRS := \ apps/dhcpserver \ @@ -30,6 +31,10 @@ ifdef CONFIG_PPP_SUPPORT COMPONENT_SRCDIRS += lwip/src/netif/ppp lwip/src/netif/ppp/polarssl endif +ifdef CONFIG_LWIP_TCP_ISN_HOOK + COMPONENT_SRCDIRS += port/esp32/tcp_isn +endif + CFLAGS += -Wno-address # lots of LWIP source files evaluate macros that check address of stack variables ifeq ($(GCC_NOT_5_2_0), 1) diff --git a/components/lwip/port/esp32/include/lwipopts.h b/components/lwip/port/esp32/include/lwipopts.h index ad9a776f8..f213aeef1 100644 --- a/components/lwip/port/esp32/include/lwipopts.h +++ b/components/lwip/port/esp32/include/lwipopts.h @@ -396,6 +396,17 @@ */ #define LWIP_TCP_RTO_TIME CONFIG_LWIP_TCP_RTO_TIME +/** + * Set TCP hook for Initial Sequence Number (ISN) + */ +#ifdef CONFIG_LWIP_TCP_ISN_HOOK +#include +struct ip_addr; +u32_t lwip_hook_tcp_isn(const struct ip_addr *local_ip, u16_t local_port, + const struct ip_addr *remote_ip, u16_t remote_port); +#define LWIP_HOOK_TCP_ISN lwip_hook_tcp_isn +#endif + /* ---------------------------------- ---------- Pbuf options ---------- diff --git a/components/lwip/port/esp32/tcp_isn/tcp_isn.c b/components/lwip/port/esp32/tcp_isn/tcp_isn.c new file mode 100644 index 000000000..87fddbf1a --- /dev/null +++ b/components/lwip/port/esp32/tcp_isn/tcp_isn.c @@ -0,0 +1,186 @@ +/** + * @file + * + * Reference implementation of the TCP ISN algorithm standardized in RFC 6528. + * Produce TCP Initial Sequence Numbers by combining an MD5-generated hash + * based on the new TCP connection's identity and a stable secret, with the + * current time at 4-microsecond granularity. + * + * Specifically, the implementation uses MD5 to compute a hash of the input + * buffer, which contains both the four-tuple of the new TCP connection (local + * and remote IP address and port), as well as a 16-byte secret to make the + * results unpredictable to external parties. The secret must be given at + * initialization time and should ideally remain the same across system + * reboots. To be sure: the spoofing-resistance of the resulting ISN depends + * mainly on the strength of the supplied secret! + * + * The implementation takes 32 bits from the computed hash, and adds to it the + * current time, in 4-microsecond units. The current time is computed from a + * boot time given at initialization, and the current uptime as provided by + * sys_now(). Thus, it assumes that sys_now() returns a time value that is + * relative to the boot time, i.e., that it starts at 0 at system boot, and + * only ever increases monotonically. + * + * For efficiency reasons, a single MD5 input buffer is used, and partially + * filled in at initialization time. Specifically, of this 64-byte buffer, the + * first 36 bytes are used for the four-way TCP tuple data, followed by the + * 16-byte secret, followed by 12-byte zero padding. The 64-byte size of the + * buffer should achieve the best performance for the actual MD5 computation. + * + * Basic usage: + * + * 1. in your lwipopts.h, add the following lines: + * + * #include + * struct ip_addr; + * u32_t lwip_hook_tcp_isn(const struct ip_addr *local_ip, u16_t local_port, + * const struct ip_addr *remote_ip, u16_t remote_port); + * "#define LWIP_HOOK_TCP_ISN lwip_hook_tcp_isn"; + * + * 2. from your own code, call lwip_init_tcp_isn() at initialization time, with + * appropriate parameters. + */ + +/* + * Copyright (c) 2016 The MINIX 3 Project. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT + * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT + * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * Author: David van Moolenbroek + */ + +#include "tcp_isn.h" +#include "lwip/ip_addr.h" +#include "lwip/sys.h" +#include +#include "rom/md5_hash.h" +#include "soc/soc_memory_layout.h" + +#ifdef LWIP_HOOK_TCP_ISN + +static u8_t input[64]; +static u32_t base_time; + +/** + * Initialize the TCP ISN module, with the boot time and a secret. + * + * @param boot_time Wall clock boot time of the system, in seconds. + * @param secret_16_bytes A 16-byte secret used to randomize the TCP ISNs. + */ +void +lwip_init_tcp_isn(u32_t boot_time, const u8_t *secret_16_bytes) +{ + /* Initialize the input buffer with the secret and trailing zeroes. */ + memset(input, 0, sizeof(input)); + + MEMCPY(&input[36], secret_16_bytes, 16); + + /* Save the boot time in 4-us units. Overflow is no problem here. */ + base_time = boot_time * 250000; +} + +/** + * Hook to generate an Initial Sequence Number (ISN) for a new TCP connection. + * + * @param local_ip The local IP address. + * @param local_port The local port number, in host-byte order. + * @param remote_ip The remote IP address. + * @param remote_port The remote port number, in host-byte order. + * @return The ISN to use for the new TCP connection. + */ +u32_t +lwip_hook_tcp_isn(const ip_addr_t *local_ip, u16_t local_port, + const ip_addr_t *remote_ip, u16_t remote_port) +{ + u8_t output[16]; + u32_t isn; + +#if LWIP_IPV4 && LWIP_IPV6 + if (IP_IS_V6(local_ip)) +#endif /* LWIP_IPV4 && LWIP_IPV6 */ +#if LWIP_IPV6 + { + const ip6_addr_t *local_ip6, *remote_ip6; + + local_ip6 = ip_2_ip6(local_ip); + remote_ip6 = ip_2_ip6(remote_ip); + + SMEMCPY(&input[0], &local_ip6->addr, 16); + SMEMCPY(&input[16], &remote_ip6->addr, 16); + } +#endif /* LWIP_IPV6 */ +#if LWIP_IPV4 && LWIP_IPV6 + else +#endif /* LWIP_IPV4 && LWIP_IPV6 */ +#if LWIP_IPV4 + { + const ip4_addr_t *local_ip4, *remote_ip4; + + local_ip4 = ip_2_ip4(local_ip); + remote_ip4 = ip_2_ip4(remote_ip); + + /* Represent IPv4 addresses as IPv4-mapped IPv6 addresses, to ensure that + * the IPv4 and IPv6 address spaces are completely disjoint. */ + memset(&input[0], 0, 10); + input[10] = 0xff; + input[11] = 0xff; + SMEMCPY(&input[12], &local_ip4->addr, 4); + memset(&input[16], 0, 10); + input[26] = 0xff; + input[27] = 0xff; + SMEMCPY(&input[28], &remote_ip4->addr, 4); + } +#endif /* LWIP_IPV4 */ + + input[32] = (u8_t)(local_port >> 8); + input[33] = (u8_t)(local_port & 0xff); + input[34] = (u8_t)(remote_port >> 8); + input[35] = (u8_t)(remote_port & 0xff); + + /* The secret and padding are already filled in. */ + + /* + * Generate the hash using ROM MD5 APIs + * This hook is invoked in the context of TCP/IP (tiT) task and + * it is unlikely that its stack would be placed in SPIRAM. Hence + * even with SPIRAM enabled case and ESP32 revision < 3, using ROM + * APIs should not create any issues. + */ +#if CONFIG_SPIRAM_ALLOW_STACK_EXTERNAL_MEMORY + assert(!esp_ptr_external_ram(get_sp())); +#endif + + struct MD5Context ctx; + MD5Init(&ctx); + MD5Update(&ctx, input, sizeof(input)); + MD5Final(output, &ctx); + + /* Arbitrarily take the first 32 bits from the generated hash. */ + MEMCPY(&isn, output, sizeof(isn)); + + /* Add the current time in 4-microsecond units. */ + return isn + base_time + sys_now() * 250; +} + +#endif /* LWIP_HOOK_TCP_ISN */ diff --git a/components/lwip/port/esp32/tcp_isn/tcp_isn.h b/components/lwip/port/esp32/tcp_isn/tcp_isn.h new file mode 100644 index 000000000..a40c4a1ad --- /dev/null +++ b/components/lwip/port/esp32/tcp_isn/tcp_isn.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2016 The MINIX 3 Project. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT + * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT + * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * Author: David van Moolenbroek + */ + +#ifndef LWIP_TCP_ISN_H +#define LWIP_TCP_ISN_H + +#include "lwip/opt.h" +#include "lwip/ip_addr.h" + +#ifdef __cplusplus +extern "C" { +#endif + +void lwip_init_tcp_isn(u32_t boot_time, const u8_t *secret_16_bytes); +u32_t lwip_hook_tcp_isn(const ip_addr_t *local_ip, u16_t local_port, + const ip_addr_t *remote_ip, u16_t remote_port); + +#ifdef __cplusplus +} +#endif + +#endif /* LWIP_TCP_ISN_H */ diff --git a/components/tcpip_adapter/tcpip_adapter_lwip.c b/components/tcpip_adapter/tcpip_adapter_lwip.c index f25aad925..03f9bfb64 100644 --- a/components/tcpip_adapter/tcpip_adapter_lwip.c +++ b/components/tcpip_adapter/tcpip_adapter_lwip.c @@ -33,6 +33,10 @@ #include "netif/wlanif.h" #include "netif/ethernetif.h" +#if CONFIG_LWIP_TCP_ISN_HOOK +#include "tcp_isn.h" +#endif + #include "dhcpserver/dhcpserver.h" #include "dhcpserver/dhcpserver_options.h" @@ -102,6 +106,18 @@ void tcpip_adapter_init(void) int ret; if (tcpip_inited == false) { +#if CONFIG_LWIP_TCP_ISN_HOOK + uint8_t rand_buf[16]; + /* + * This is early startup code where WiFi/BT is yet to be enabled and hence + * relevant entropy source is not available. However, bootloader enables + * SAR ADC based entropy source at its initialization, and our requirement + * of random bytes is pretty small (16), so we can assume that following + * API will provide sufficiently random data. + */ + esp_fill_random(rand_buf, sizeof(rand_buf)); + lwip_init_tcp_isn(esp_log_timestamp(), rand_buf); +#endif tcpip_inited = true; tcpip_init(NULL, NULL);