diff --git a/Makefile b/Makefile index f19704e6..77057614 100644 --- a/Makefile +++ b/Makefile @@ -183,6 +183,8 @@ EXE=build/tcpecho build/tcp_netcat_poll build/tcp_netcat_select \ build/test-evloop build/test-dns build/test-wolfssl-forwarding \ build/test-ttl-expired build/test-wolfssl build/test-httpd \ build/test-http-smuggle build/test-http-arg-oob \ + build/test-http-close-notify \ + build/test-freertos-close-last-ack \ build/test-posix-errno \ build/ipfilter-logger \ build/test-esp build/esp-server @@ -423,6 +425,22 @@ build/test-http-arg-oob: src/test/test_http_arg_oob.c src/http/httpd.c @echo "[LD] $@" @$(CC) $(CFLAGS) -o $@ src/test/test_http_arg_oob.c $(LDFLAGS) -lwolfssl +# Standalone regression test for TLS close_notify on every close path (F-5732). +# It #includes httpd.c directly and stubs the wolfSSL teardown calls to record +# their order, so it does not link the real wolfSSL library. +build/test-http-close-notify:CFLAGS+=-Wno-cpp -DWOLFSSL_DEBUG -DWOLFSSL_WOLFIP -DWOLFIP_ENABLE_HTTP -Isrc/http +build/test-http-close-notify: src/test/test_http_close_notify.c src/http/httpd.c + @mkdir -p build || true + @echo "[LD] $@" + @$(CC) $(CFLAGS) -o $@ src/test/test_http_close_notify.c $(LDFLAGS) + +# Standalone regression test for the FreeRTOS BSD close() wrapper when +# CB_EVENT_CLOSED is delivered synchronously during LAST_ACK teardown. +build/test-freertos-close-last-ack: src/test/test_freertos_close_last_ack.c src/port/freeRTOS/bsd_socket.c + @mkdir -p build || true + @echo "[LD] $@" + @$(CC) -Isrc/test/freertos_mocks $(CFLAGS) -o $@ src/test/test_freertos_close_last_ack.c $(LDFLAGS) + build/%.o: src/%.c @mkdir -p `dirname $@` || true @echo "[CC] $<" diff --git a/src/http/httpd.c b/src/http/httpd.c index 19b36e7f..47c1b4b2 100644 --- a/src/http/httpd.c +++ b/src/http/httpd.c @@ -106,6 +106,8 @@ static struct http_url *http_find_url(struct httpd *httpd, const char *path) { return NULL; } +static void http_close_client(struct http_client *hc); + void http_send_response_headers(struct http_client *hc, int status_code, const char *status_text, const char *content_type, size_t content_length) { char txt_response[HTTP_TX_BUF_LEN]; @@ -133,10 +135,7 @@ void http_send_response_headers(struct http_client *hc, int status_code, const c } if (rc <= 0) { /* Error – close connection */ - wolfSSL_free(hc->ssl); - hc->ssl = NULL; - wolfIP_sock_close(hc->httpd->ipstack, hc->client_sd); - hc->client_sd = 0; + http_close_client(hc); } } @@ -150,10 +149,7 @@ void http_send_response_body(struct http_client *hc, const void *body, size_t le rc = wolfIP_sock_send(hc->httpd->ipstack, hc->client_sd, body, len, 0); if (rc <= 0) { - wolfSSL_free(hc->ssl); - hc->ssl = NULL; - wolfIP_sock_close(hc->httpd->ipstack, hc->client_sd); - hc->client_sd = 0; + http_close_client(hc); } } @@ -169,6 +165,21 @@ static int http_write_response(struct http_client *hc, const void *buf, size_t l return wolfIP_sock_send(s, hc->client_sd, buf, len, 0); } +static void http_close_client(struct http_client *hc) +{ + if (!hc) + return; + + if (hc->ssl) { + wolfSSL_shutdown(hc->ssl); + wolfSSL_CleanupIO_wolfIP(hc->ssl); + wolfSSL_free(hc->ssl); + hc->ssl = NULL; + } + wolfIP_sock_close(hc->httpd->ipstack, hc->client_sd); + hc->client_sd = 0; +} + void http_send_response_chunk(struct http_client *hc, const void *chunk, size_t len) { char txt_chunk[8]; memset(txt_chunk, 0, sizeof(txt_chunk)); @@ -178,10 +189,7 @@ void http_send_response_chunk(struct http_client *hc, const void *chunk, size_t if ((http_write_response(hc, txt_chunk, strlen(txt_chunk)) <= 0) || (http_write_response(hc, chunk, len) <= 0) || (http_write_response(hc, "\r\n", 2) <= 0)) { - wolfSSL_free(hc->ssl); - hc->ssl = NULL; - wolfIP_sock_close(hc->httpd->ipstack, hc->client_sd); - hc->client_sd = 0; + http_close_client(hc); } } @@ -194,10 +202,7 @@ void http_send_response_chunk_end(struct http_client *hc) { else rc = wolfIP_sock_send(hc->httpd->ipstack, hc->client_sd, "0\r\n\r\n", 5, 0); if (rc <= 0) { - wolfSSL_free(hc->ssl); - hc->ssl = NULL; - wolfIP_sock_close(hc->httpd->ipstack, hc->client_sd); - hc->client_sd = 0; + http_close_client(hc); } } @@ -483,11 +488,7 @@ static void http_recv_cb(int sd, uint16_t event, void *arg) { return; fail_close: - if (hc->ssl) { - wolfSSL_free(hc->ssl); - hc->ssl = NULL; - } - wolfIP_sock_close(hc->httpd->ipstack, sd); + http_close_client(hc); /* wolfIP_sock_close on an ESTABLISHED socket only starts the active close * (FIN_WAIT_1) and returns -EAGAIN; the socket lingers, still carrying this * callback and its arg. Once we zero client_sd the slot is reused by the @@ -495,7 +496,6 @@ static void http_recv_cb(int sd, uint16_t event, void *arg) { * different live connection's state. Deregister it here so a late segment * on the half-closed socket can no longer fire http_recv_cb. */ wolfIP_register_callback(hc->httpd->ipstack, sd, NULL, NULL); - hc->client_sd = 0; } static void http_accept_cb(int sd, uint16_t event, void *arg) { diff --git a/src/port/freeRTOS/bsd_socket.c b/src/port/freeRTOS/bsd_socket.c index 60151b4c..1b455b22 100644 --- a/src/port/freeRTOS/bsd_socket.c +++ b/src/port/freeRTOS/bsd_socket.c @@ -50,6 +50,7 @@ typedef struct { int internal_fd; SemaphoreHandle_t ready_sem; volatile uint16_t wait_events; + volatile uint16_t seen_events; } wolfip_bsd_fd_entry; static struct wolfIP *g_ipstack; @@ -131,6 +132,7 @@ static int wolfip_bsd_fd_alloc(int internal_fd) g_fds[i].internal_fd = internal_fd; g_fds[i].ready_sem = sem; g_fds[i].wait_events = 0; + g_fds[i].seen_events = 0; return i; } } @@ -148,6 +150,7 @@ static void wolfip_bsd_fd_free(int public_fd) g_fds[public_fd].internal_fd = -1; g_fds[public_fd].ready_sem = NULL; g_fds[public_fd].wait_events = 0; + g_fds[public_fd].seen_events = 0; } static void wolfip_bsd_socket_cb(int internal_fd, uint16_t events, void *arg) @@ -158,6 +161,7 @@ static void wolfip_bsd_socket_cb(int internal_fd, uint16_t events, void *arg) if (entry == NULL) { return; } + entry->seen_events |= events; g_cb_log_count++; if ((events & CB_EVENT_CLOSED) != 0u || (g_cb_log_count & 0x1Fu) == 0u) { printf("[sock_cb] ifd=%d events=0x%04x wait=0x%04x cb_count=%lu\n", @@ -173,6 +177,7 @@ static void wolfip_bsd_socket_cb(int internal_fd, uint16_t events, void *arg) static void wolfip_bsd_prepare_wait_locked(wolfip_bsd_fd_entry *entry, uint16_t wait_events) { + entry->seen_events = 0; entry->wait_events = wait_events; while (xSemaphoreTake(entry->ready_sem, 0) == pdTRUE) { } @@ -655,6 +660,17 @@ int close(int sockfd) xSemaphoreGive(g_lock); return ret; } + if ((ret == -1) && IS_SOCKET_TCP(entry->internal_fd) && + ((entry->seen_events & CB_EVENT_CLOSED) != 0u)) { + /* The TCP core can destroy the socket immediately after delivering + * CB_EVENT_CLOSED (e.g. final ACK in LAST_ACK), so the retry sees + * the already-zeroed descriptor and wolfIP_sock_close() returns -1. + * Treat that as a completed close and release the wrapper slot. */ + wolfIP_register_callback(g_ipstack, entry->internal_fd, NULL, NULL); + wolfip_bsd_fd_free(sockfd); + xSemaphoreGive(g_lock); + return 0; + } if (ret != -WOLFIP_EAGAIN) { xSemaphoreGive(g_lock); wolfip_bsd_set_error(ret); diff --git a/src/port/stm32h563/tls_server.c b/src/port/stm32h563/tls_server.c index 1bb6886b..421b1595 100644 --- a/src/port/stm32h563/tls_server.c +++ b/src/port/stm32h563/tls_server.c @@ -78,6 +78,7 @@ static void tls_client_handle_data(tls_client_t *client, uint16_t event); /* External functions from wolfssl_io.c */ extern int wolfSSL_SetIO_wolfIP_CTX(WOLFSSL_CTX *ctx, struct wolfIP *s); extern int wolfSSL_SetIO_wolfIP(WOLFSSL *ssl, int fd); +extern void wolfSSL_CleanupIO_wolfIP(WOLFSSL *ssl); /* Debug output helper */ static void debug_print(const char *msg) @@ -237,6 +238,7 @@ static void tls_client_free(tls_client_t *client) { if (client->ssl) { wolfSSL_shutdown(client->ssl); + wolfSSL_CleanupIO_wolfIP(client->ssl); wolfSSL_free(client->ssl); client->ssl = NULL; } @@ -319,7 +321,14 @@ static void tls_listen_cb(int fd, uint16_t event, void *arg) } /* Associate SSL with socket */ - wolfSSL_SetIO_wolfIP(client->ssl, client_fd); + if (wolfSSL_SetIO_wolfIP(client->ssl, client_fd) != 0) { + debug_print("TLS: SetIO failed\n"); + wolfSSL_free(client->ssl); + client->ssl = NULL; + wolfIP_sock_close(server.stack, client_fd); + client->state = TLS_CLIENT_STATE_FREE; + return; + } client->fd = client_fd; client->state = TLS_CLIENT_STATE_HANDSHAKE; diff --git a/src/port/stm32h753/main.c b/src/port/stm32h753/main.c index d183e354..b75dc475 100644 --- a/src/port/stm32h753/main.c +++ b/src/port/stm32h753/main.c @@ -204,6 +204,14 @@ static int https_status_handler(struct httpd *httpd, struct http_client *hc, #define RNG_SR_CECS (1u << 1) #define RNG_SR_SECS (1u << 2) +/* Cortex-M7 DWT cycle counter (used to seed the RNG fallback with runtime + * entropy so a degraded device does not emit a globally-identical sequence) */ +#define DWT_CTRL (*(volatile uint32_t *)0xE0001000UL) +#define DWT_CYCCNT (*(volatile uint32_t *)0xE0001004UL) +#define DWT_CTRL_CYCCNTENA (1u << 0) +#define CM_DEMCR (*(volatile uint32_t *)0xE000EDFCUL) +#define CM_DEMCR_TRCENA (1u << 24) + /* USART3 for debug output (ST-Link VCP on NUCLEO-H753ZI: PD8=TX, PD9=RX) */ #define USART3_BASE 0x40004800UL #define USART3_CR1 (*(volatile uint32_t *)(USART3_BASE + 0x00)) @@ -369,8 +377,21 @@ uint32_t wolfIP_getrandom(void) uint32_t val; if (rng_get_word(&val) == 0) return val; - /* Fallback LFSR if HW RNG fails */ - static uint32_t lfsr = 0x1A2B3C4DU; + /* HW RNG failed: fall back to an xorshift LFSR seeded from runtime state + * rather than a compile-time constant, so a degraded device does not emit + * the same globally-known sequence (and thus predictable TCP ISNs) on + * every unit. The DWT cycle counter (CPU clock, free running once the + * stack is up) and any residual RNG_DR bits vary per device and power-up. + * Not a cryptographic RNG. */ + static uint32_t lfsr = 0u; + if (lfsr == 0u) { + CM_DEMCR |= CM_DEMCR_TRCENA; + DWT_CTRL |= DWT_CTRL_CYCCNTENA; + lfsr = DWT_CYCCNT ^ RNG_DR ^ 0x1A2B3C4DU; + if (lfsr == 0u) + lfsr = 0x1A2B3C4DU; /* LFSR seed must never be zero */ + } + lfsr ^= DWT_CYCCNT; /* mix in timing jitter on each call */ lfsr ^= lfsr << 13; lfsr ^= lfsr >> 17; lfsr ^= lfsr << 5; diff --git a/src/port/va416xx/main.c b/src/port/va416xx/main.c index 568b627b..4d94e03e 100644 --- a/src/port/va416xx/main.c +++ b/src/port/va416xx/main.c @@ -83,20 +83,31 @@ static int client_fd = -1; /* wolfIP random number generator (required by stack) */ /* ========================================================================= */ +/* SysTick Current Value Register: 24-bit free-running down-counter reloaded + * every 1ms (reload = SYSCLK/1000 = 100000 at 100MHz), so each read yields + * ~17 bits of fast-varying timing jitter. */ +#define SYST_CVR (*(volatile uint32_t *)0xE000E018UL) + uint32_t wolfIP_getrandom(void) { static uint32_t lfsr; static int seeded = 0; if (!seeded) { - /* Seed from boot time so ISNs and ephemeral ports vary per power-up. - * HAL_time_ms at first wolfIP call is typically 1-5 s into boot. - * Note: not cryptographically secure; suitable for embedded demo use. */ - lfsr = (uint32_t)HAL_time_ms; + /* Seed from boot time mixed with the SysTick current value so the + * initial state is not confined to the trivially-enumerable boot + * window (HAL_time_ms at first wolfIP call is typically 1-5 s into + * boot). Note: not cryptographically secure; suitable for embedded + * demo use. */ + lfsr = (uint32_t)HAL_time_ms ^ (SYST_CVR << 8); if (lfsr == 0U) lfsr = 0x1A2B3C4DU; /* LFSR must never be zero */ seeded = 1; } + /* Mix in SysTick timing jitter each call so a single observed output + * cannot be inverted to predict subsequent ISNs/ports/xids (xorshift32 + * on its own is bijective). */ + lfsr ^= SYST_CVR; lfsr ^= lfsr << 13; lfsr ^= lfsr >> 17; lfsr ^= lfsr << 5; diff --git a/src/port/wolfssh_io.c b/src/port/wolfssh_io.c index 38a9db7a..133c9ea4 100644 --- a/src/port/wolfssh_io.c +++ b/src/port/wolfssh_io.c @@ -70,10 +70,15 @@ static int wolfssh_io_recv(WOLFSSH *ssh, void *buf, word32 sz, void *ctx) } ret = wolfIP_sock_recv(desc->stack, desc->fd, buf, (int)sz, 0); - if (ret == -WOLFIP_EAGAIN || ret == -1) { + /* Only -WOLFIP_EAGAIN means "would block" (no data queued yet). A -1 is + * the "not established" / torn-down case from wolfIP_sock_recvfrom (peer + * RST drove the socket to TCP_CLOSED) and must be reported as a fatal + * close, otherwise wolfSSH retries the dead connection forever and the + * SSH handshake state machine is wedged in KEY_EXCHANGE indefinitely. */ + if (ret == -WOLFIP_EAGAIN) { return WS_CBIO_ERR_WANT_READ; } - if (ret == 0) { + if (ret == 0 || ret == -1) { return WS_CBIO_ERR_CONN_CLOSE; } if (ret < 0) { @@ -94,10 +99,15 @@ static int wolfssh_io_send(WOLFSSH *ssh, void *buf, word32 sz, void *ctx) } ret = wolfIP_sock_send(desc->stack, desc->fd, buf, (int)sz, 0); - if (ret == -WOLFIP_EAGAIN || ret == -1) { + /* Only -WOLFIP_EAGAIN means "would block" (TX buffer full, nothing + * queued). A -1 is the "not established" / torn-down case from + * wolfIP_sock_sendto (peer RST) and must be reported as a fatal close, + * otherwise wolfSSH retries the dead connection forever and its io_desc + * slot is never released. */ + if (ret == -WOLFIP_EAGAIN) { return WS_CBIO_ERR_WANT_WRITE; } - if (ret == 0) { + if (ret == 0 || ret == -1) { return WS_CBIO_ERR_CONN_CLOSE; } if (ret < 0) { diff --git a/src/port/wolfssl_io.c b/src/port/wolfssl_io.c index 0a3bfab0..e8a0512c 100644 --- a/src/port/wolfssl_io.c +++ b/src/port/wolfssl_io.c @@ -72,7 +72,12 @@ static int wolfIP_io_recv(WOLFSSL* ssl, char* buf, int sz, void* ctx) return WOLFSSL_CBIO_ERR_GENERAL; ret = wolfIP_sock_recv(desc->stack, desc->fd, buf, sz, 0); - if (ret == -WOLFIP_EAGAIN || ret == -1) + /* Only -WOLFIP_EAGAIN means "would block": wolfIP_sock_recvfrom returns it + * (via queue_pop) for an established socket with an empty RX queue. A -1 is + * the "not established" / torn-down case and must be reported as a fatal + * close, otherwise wolfSSL keeps retrying a dead connection forever and the + * owning session is never released. */ + if (ret == -WOLFIP_EAGAIN) return WOLFSSL_CBIO_ERR_WANT_READ; if (ret <= 0) return WOLFSSL_CBIO_ERR_CONN_CLOSE; @@ -89,7 +94,11 @@ static int wolfIP_io_send(WOLFSSL* ssl, char* buf, int sz, void* ctx) return WOLFSSL_CBIO_ERR_GENERAL; ret = wolfIP_sock_send(desc->stack, desc->fd, buf, sz, 0); - if (ret == -WOLFIP_EAGAIN || ret == -1) + /* Only -WOLFIP_EAGAIN means "would block" (TX buffer full, nothing queued). + * A -1 is the "not established" / torn-down case from wolfIP_sock_sendto and + * must be reported as a fatal close, otherwise wolfSSL retries the dead + * connection forever and its session is never released. */ + if (ret == -WOLFIP_EAGAIN) return WOLFSSL_CBIO_ERR_WANT_WRITE; if (ret <= 0) return WOLFSSL_CBIO_ERR_CONN_CLOSE; @@ -135,3 +144,24 @@ int wolfSSL_SetIO_wolfIP(WOLFSSL* ssl, int fd) } return -1; } + +/* Release the io_descs[] slot allocated by wolfSSL_SetIO_wolfIP() for this + * session. Must be called on every TLS teardown path (before wolfSSL_free) + * or the static pool leaks one slot per connection and is exhausted after + * MAX_WOLFIP_CTX sessions. */ +void wolfSSL_CleanupIO_wolfIP(WOLFSSL* ssl) +{ + struct wolfip_io_desc *desc; + + if (!ssl) + return; + + desc = (struct wolfip_io_desc *)wolfSSL_GetIOReadCtx(ssl); + if (!desc) + return; + + wolfSSL_SetIOReadCtx(ssl, NULL); + wolfSSL_SetIOWriteCtx(ssl, NULL); + desc->fd = 0; + desc->stack = NULL; +} diff --git a/src/test/freertos_mocks/FreeRTOS.h b/src/test/freertos_mocks/FreeRTOS.h new file mode 100644 index 00000000..5c5bf8de --- /dev/null +++ b/src/test/freertos_mocks/FreeRTOS.h @@ -0,0 +1,17 @@ +#ifndef TEST_FREERTOS_H +#define TEST_FREERTOS_H + +#include + +typedef int BaseType_t; +typedef unsigned int UBaseType_t; +typedef uint32_t TickType_t; + +#define pdTRUE 1 +#define pdFALSE 0 +#define pdPASS 1 +#define portMAX_DELAY ((TickType_t)0xffffffffu) +#define portTICK_PERIOD_MS 1u +#define pdMS_TO_TICKS(ms) ((TickType_t)(ms)) + +#endif diff --git a/src/test/freertos_mocks/config.h b/src/test/freertos_mocks/config.h new file mode 100644 index 00000000..8290bb8b --- /dev/null +++ b/src/test/freertos_mocks/config.h @@ -0,0 +1,3 @@ +#ifndef TEST_FREERTOS_CONFIG_H +#define TEST_FREERTOS_CONFIG_H +#endif diff --git a/src/test/freertos_mocks/semphr.h b/src/test/freertos_mocks/semphr.h new file mode 100644 index 00000000..a9d17b85 --- /dev/null +++ b/src/test/freertos_mocks/semphr.h @@ -0,0 +1,14 @@ +#ifndef TEST_SEMPHR_H +#define TEST_SEMPHR_H + +#include "FreeRTOS.h" + +typedef struct MockSemaphore *SemaphoreHandle_t; + +SemaphoreHandle_t xSemaphoreCreateBinary(void); +SemaphoreHandle_t xSemaphoreCreateMutex(void); +BaseType_t xSemaphoreTake(SemaphoreHandle_t sem, TickType_t ticks); +BaseType_t xSemaphoreGive(SemaphoreHandle_t sem); +void vSemaphoreDelete(SemaphoreHandle_t sem); + +#endif diff --git a/src/test/freertos_mocks/task.h b/src/test/freertos_mocks/task.h new file mode 100644 index 00000000..c25d2994 --- /dev/null +++ b/src/test/freertos_mocks/task.h @@ -0,0 +1,15 @@ +#ifndef TEST_TASK_H +#define TEST_TASK_H + +#include "FreeRTOS.h" + +typedef void (*TaskFunction_t)(void *); +typedef void * TaskHandle_t; + +BaseType_t xTaskCreate(TaskFunction_t task, const char *name, + uint16_t stack_words, void *arg, UBaseType_t priority, TaskHandle_t *handle); +void vTaskDelay(TickType_t ticks); +TickType_t xTaskGetTickCount(void); +void vTaskDelete(TaskHandle_t handle); + +#endif diff --git a/src/test/freertos_mocks/wolfip.h b/src/test/freertos_mocks/wolfip.h new file mode 100644 index 00000000..0c71b985 --- /dev/null +++ b/src/test/freertos_mocks/wolfip.h @@ -0,0 +1,58 @@ +#ifndef TEST_FREERTOS_WOLFIP_H +#define TEST_FREERTOS_WOLFIP_H + +#include +#include + +typedef uint32_t socklen_t; + +struct wolfIP { + int dummy; +}; + +struct wolfIP_sockaddr { + uint16_t sa_family; + char sa_data[14]; +}; + +typedef void (*tsocket_cb)(int fd, uint16_t event, void *arg); + +#define AF_INET 2 +#define IPSTACK_SOCK_STREAM 1 +#define IPSTACK_SOCK_DGRAM 2 + +#define WOLFIP_EAGAIN 11 +#define WOLFIP_EINVAL 22 +#define WOLFIP_ENOMEM 12 + +#define MARK_TCP_SOCKET 0x100 +#define IS_SOCKET_TCP(fd) (((fd) & MARK_TCP_SOCKET) == MARK_TCP_SOCKET) + +#define CB_EVENT_READABLE 0x0001 +#define CB_EVENT_WRITABLE 0x0002 +#define CB_EVENT_CLOSED 0x0004 + +int wolfIP_poll(struct wolfIP *ipstack, uint64_t now_ms); +int wolfIP_sock_socket(struct wolfIP *s, int domain, int type, int protocol); +int wolfIP_sock_bind(struct wolfIP *s, int fd, const struct wolfIP_sockaddr *addr, socklen_t len); +int wolfIP_sock_listen(struct wolfIP *s, int fd, int backlog); +int wolfIP_sock_accept(struct wolfIP *s, int fd, struct wolfIP_sockaddr *addr, socklen_t *len); +int wolfIP_sock_connect(struct wolfIP *s, int fd, const struct wolfIP_sockaddr *addr, socklen_t len); +int wolfIP_sock_send(struct wolfIP *s, int fd, const void *buf, size_t len, int flags); +int wolfIP_sock_sendto(struct wolfIP *s, int fd, const void *buf, size_t len, int flags, + const struct wolfIP_sockaddr *dest_addr, socklen_t len2); +int wolfIP_sock_recv(struct wolfIP *s, int fd, void *buf, size_t len, int flags); +int wolfIP_sock_recvfrom(struct wolfIP *s, int fd, void *buf, size_t len, int flags, + struct wolfIP_sockaddr *src_addr, socklen_t *len2); +int wolfIP_sock_setsockopt(struct wolfIP *s, int fd, int level, int optname, + const void *optval, socklen_t optlen); +int wolfIP_sock_getsockopt(struct wolfIP *s, int fd, int level, int optname, + void *optval, socklen_t *optlen); +int wolfIP_sock_getsockname(struct wolfIP *s, int fd, struct wolfIP_sockaddr *addr, socklen_t *len); +int wolfIP_sock_getpeername(struct wolfIP *s, int fd, struct wolfIP_sockaddr *addr, socklen_t *len); +int wolfIP_sock_can_write(struct wolfIP *s, int fd); +int wolfIP_sock_can_read(struct wolfIP *s, int fd); +int wolfIP_sock_close(struct wolfIP *s, int fd); +void wolfIP_register_callback(struct wolfIP *s, int fd, tsocket_cb cb, void *arg); + +#endif diff --git a/src/test/test_freertos_close_last_ack.c b/src/test/test_freertos_close_last_ack.c new file mode 100644 index 00000000..da17c804 --- /dev/null +++ b/src/test/test_freertos_close_last_ack.c @@ -0,0 +1,256 @@ +/* Regression test for the FreeRTOS BSD close() wrapper when the core delivers + * CB_EVENT_CLOSED synchronously during LAST_ACK teardown. */ + +#include +#include +#include + +#include "FreeRTOS.h" +#include "semphr.h" +#include "task.h" +#include "wolfip.h" + +struct MockSemaphore { + int count; +}; + +static int sem_allocs; +static int sem_frees; +static tsocket_cb registered_cb; +static void *registered_arg; +static int fake_internal_fd = MARK_TCP_SOCKET; +static int close_calls; + +SemaphoreHandle_t xSemaphoreCreateBinary(void) +{ + struct MockSemaphore *sem = calloc(1, sizeof(*sem)); + if (sem != NULL) + sem_allocs++; + return sem; +} + +SemaphoreHandle_t xSemaphoreCreateMutex(void) +{ + return xSemaphoreCreateBinary(); +} + +BaseType_t xSemaphoreTake(SemaphoreHandle_t sem, TickType_t ticks) +{ + if (sem == NULL) + return pdFALSE; + if (ticks == portMAX_DELAY && sem->count == 0 && registered_cb != NULL) { + tsocket_cb cb = registered_cb; + void *arg = registered_arg; + + registered_cb = NULL; + registered_arg = NULL; + cb(fake_internal_fd, CB_EVENT_CLOSED, arg); + } + if (sem->count > 0) { + sem->count--; + return pdTRUE; + } + return pdFALSE; +} + +BaseType_t xSemaphoreGive(SemaphoreHandle_t sem) +{ + if (sem == NULL) + return pdFALSE; + sem->count++; + return pdTRUE; +} + +void vSemaphoreDelete(SemaphoreHandle_t sem) +{ + if (sem != NULL) { + sem_frees++; + free(sem); + } +} + +BaseType_t xTaskCreate(TaskFunction_t task, const char *name, + uint16_t stack_words, void *arg, UBaseType_t priority, TaskHandle_t *handle) +{ + (void)task; + (void)name; + (void)stack_words; + (void)arg; + (void)priority; + (void)handle; + return pdPASS; +} + +void vTaskDelay(TickType_t ticks) +{ + (void)ticks; +} + +TickType_t xTaskGetTickCount(void) +{ + return 0; +} + +void vTaskDelete(TaskHandle_t handle) +{ + (void)handle; +} + +int wolfIP_poll(struct wolfIP *ipstack, uint64_t now_ms) +{ + (void)ipstack; + (void)now_ms; + return 10; +} + +int wolfIP_sock_socket(struct wolfIP *s, int domain, int type, int protocol) +{ + (void)s; + (void)domain; + (void)type; + (void)protocol; + return fake_internal_fd; +} + +int wolfIP_sock_bind(struct wolfIP *s, int fd, const struct wolfIP_sockaddr *addr, socklen_t len) +{ + (void)s; (void)fd; (void)addr; (void)len; + return 0; +} + +int wolfIP_sock_listen(struct wolfIP *s, int fd, int backlog) +{ + (void)s; (void)fd; (void)backlog; + return 0; +} + +int wolfIP_sock_accept(struct wolfIP *s, int fd, struct wolfIP_sockaddr *addr, socklen_t *len) +{ + (void)s; (void)fd; (void)addr; (void)len; + return -WOLFIP_EAGAIN; +} + +int wolfIP_sock_connect(struct wolfIP *s, int fd, const struct wolfIP_sockaddr *addr, socklen_t len) +{ + (void)s; (void)fd; (void)addr; (void)len; + return -WOLFIP_EAGAIN; +} + +int wolfIP_sock_send(struct wolfIP *s, int fd, const void *buf, size_t len, int flags) +{ + (void)s; (void)fd; (void)buf; (void)len; (void)flags; + return -WOLFIP_EAGAIN; +} + +int wolfIP_sock_sendto(struct wolfIP *s, int fd, const void *buf, size_t len, int flags, + const struct wolfIP_sockaddr *dest_addr, socklen_t len2) +{ + (void)s; (void)fd; (void)buf; (void)len; (void)flags; (void)dest_addr; (void)len2; + return -WOLFIP_EAGAIN; +} + +int wolfIP_sock_recv(struct wolfIP *s, int fd, void *buf, size_t len, int flags) +{ + (void)s; (void)fd; (void)buf; (void)len; (void)flags; + return -WOLFIP_EAGAIN; +} + +int wolfIP_sock_recvfrom(struct wolfIP *s, int fd, void *buf, size_t len, int flags, + struct wolfIP_sockaddr *src_addr, socklen_t *len2) +{ + (void)s; (void)fd; (void)buf; (void)len; (void)flags; (void)src_addr; (void)len2; + return -WOLFIP_EAGAIN; +} + +int wolfIP_sock_setsockopt(struct wolfIP *s, int fd, int level, int optname, + const void *optval, socklen_t optlen) +{ + (void)s; (void)fd; (void)level; (void)optname; (void)optval; (void)optlen; + return 0; +} + +int wolfIP_sock_getsockopt(struct wolfIP *s, int fd, int level, int optname, + void *optval, socklen_t *optlen) +{ + (void)s; (void)fd; (void)level; (void)optname; (void)optval; (void)optlen; + return 0; +} + +int wolfIP_sock_getsockname(struct wolfIP *s, int fd, struct wolfIP_sockaddr *addr, socklen_t *len) +{ + (void)s; (void)fd; (void)addr; (void)len; + return 0; +} + +int wolfIP_sock_getpeername(struct wolfIP *s, int fd, struct wolfIP_sockaddr *addr, socklen_t *len) +{ + (void)s; (void)fd; (void)addr; (void)len; + return 0; +} + +int wolfIP_sock_can_write(struct wolfIP *s, int fd) +{ + (void)s; (void)fd; + return 0; +} + +int wolfIP_sock_can_read(struct wolfIP *s, int fd) +{ + (void)s; (void)fd; + return 0; +} + +int wolfIP_sock_close(struct wolfIP *s, int fd) +{ + (void)s; + if (fd != fake_internal_fd) + return -WOLFIP_EINVAL; + close_calls++; + if (close_calls == 1) + return -WOLFIP_EAGAIN; + return -1; +} + +void wolfIP_register_callback(struct wolfIP *s, int fd, tsocket_cb cb, void *arg) +{ + (void)s; + (void)fd; + registered_cb = cb; + registered_arg = arg; +} + +#include "../port/freeRTOS/bsd_socket.c" + +int main(void) +{ + struct wolfIP stack; + int fd; + int rc; + + memset(&stack, 0, sizeof(stack)); + if (wolfip_freertos_socket_init(&stack, 1, 128) != 0) { + printf("init failed\n"); + return 1; + } + + fd = socket(AF_INET, SOCK_STREAM, 0); + if (fd < 0) { + printf("socket failed\n"); + return 1; + } + + rc = close(fd); + if (rc != 0) { + printf("close failed rc=%d close_calls=%d sem_allocs=%d sem_frees=%d\n", + rc, close_calls, sem_allocs, sem_frees); + return 1; + } + if (close_calls != 2 || sem_frees != 1) { + printf("unexpected state close_calls=%d sem_allocs=%d sem_frees=%d\n", + close_calls, sem_allocs, sem_frees); + return 1; + } + + printf("test_freertos_close_last_ack: passed\n"); + return 0; +} diff --git a/src/test/test_http_arg_oob.c b/src/test/test_http_arg_oob.c index e037c965..c5e2df03 100644 --- a/src/test/test_http_arg_oob.c +++ b/src/test/test_http_arg_oob.c @@ -56,6 +56,8 @@ int wolfSSL_SetIO_wolfIP(WOLFSSL *ssl, int fd) { (void)ssl; (void)fd; return 0; } int wolfSSL_SetIO_wolfIP_CTX(WOLFSSL_CTX *ctx, struct wolfIP *s) { (void)ctx; (void)s; return 0; } +void wolfSSL_CleanupIO_wolfIP(WOLFSSL *ssl) +{ (void)ssl; } #define CHECK(cond) do { if (!(cond)) { \ printf("FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); failures++; } } while (0) diff --git a/src/test/test_http_close_notify.c b/src/test/test_http_close_notify.c new file mode 100644 index 00000000..c506001f --- /dev/null +++ b/src/test/test_http_close_notify.c @@ -0,0 +1,207 @@ +/* test_http_close_notify.c + * + * Copyright (C) 2024 wolfSSL Inc. + * + * This file is part of wolfIP TCP/IP stack. + * + * wolfIP is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfIP is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA + * + * + * Regression test for F-5732: every TLS connection-close path in httpd.c must + * send the close_notify alert (wolfSSL_shutdown) before tearing the session + * down (wolfSSL_CleanupIO_wolfIP / wolfSSL_free). Skipping close_notify lets a + * network-adjacent attacker truncate an HTTPS response indistinguishably from a + * legitimate close (RFC 5246 7.2.1 / CWE-325). These stubs record call order so + * the test fails if any close site frees the session without a prior shutdown. + */ + +#include +#include +#include + +/* Pull in the unit under test directly; the close paths are not exported. */ +#include "httpd.c" + +/* --- ordered call tracking ----------------------------------------------- */ +static int seq; +static int shutdown_seq; +static int cleanup_seq; +static int free_seq; +static int tls_teardown_calls; +static int sock_send_result; + +static void reset_tracking(void) +{ + seq = 0; + shutdown_seq = 0; + cleanup_seq = 0; + free_seq = 0; + tls_teardown_calls = 0; + sock_send_result = -1; +} + +/* --- stubs for the wolfIP symbols referenced by httpd.c ------------------ */ +int wolfIP_sock_socket(struct wolfIP *s, int d, int t, int p) +{ (void)s; (void)d; (void)t; (void)p; return -1; } +int wolfIP_sock_bind(struct wolfIP *s, int fd, const struct wolfIP_sockaddr *a, socklen_t l) +{ (void)s; (void)fd; (void)a; (void)l; return -1; } +int wolfIP_sock_listen(struct wolfIP *s, int fd, int b) +{ (void)s; (void)fd; (void)b; return -1; } +int wolfIP_sock_accept(struct wolfIP *s, int fd, struct wolfIP_sockaddr *a, socklen_t *l) +{ (void)s; (void)fd; (void)a; (void)l; return -1; } +int wolfIP_sock_send(struct wolfIP *s, int fd, const void *b, size_t l, int f) +{ (void)s; (void)fd; (void)b; (void)l; (void)f; return sock_send_result; } +int wolfIP_sock_recv(struct wolfIP *s, int fd, void *b, size_t l, int f) +{ (void)s; (void)fd; (void)b; (void)l; (void)f; return -1; } +int wolfIP_sock_close(struct wolfIP *s, int fd) +{ (void)s; (void)fd; return 0; } +void wolfIP_register_callback(struct wolfIP *s, int fd, tsocket_cb cb, void *arg) +{ (void)s; (void)fd; (void)cb; (void)arg; } + +/* --- stubs for the wolfSSL symbols referenced by httpd.c ----------------- */ +WOLFSSL *wolfSSL_new(WOLFSSL_CTX *ctx) +{ (void)ctx; return NULL; } +int wolfSSL_SetIO_wolfIP(WOLFSSL *ssl, int fd) +{ (void)ssl; (void)fd; return 0; } +int wolfSSL_SetIO_wolfIP_CTX(WOLFSSL_CTX *ctx, struct wolfIP *s) +{ (void)ctx; (void)s; return 0; } +/* All I/O fails, forcing every send/recv onto its error-close path. */ +int wolfSSL_write(WOLFSSL *ssl, const void *data, int sz) +{ (void)ssl; (void)data; (void)sz; return -1; } +int wolfSSL_read(WOLFSSL *ssl, void *data, int sz) +{ (void)ssl; (void)data; (void)sz; return -1; } +int wolfSSL_get_error(WOLFSSL *ssl, int ret) +{ (void)ssl; (void)ret; return 0; /* not WANT_READ -> close */ } +int wolfSSL_shutdown(WOLFSSL *ssl) +{ (void)ssl; tls_teardown_calls++; shutdown_seq = ++seq; return 0; } +void wolfSSL_CleanupIO_wolfIP(WOLFSSL *ssl) +{ (void)ssl; tls_teardown_calls++; cleanup_seq = ++seq; } +void wolfSSL_free(WOLFSSL *ssl) +{ (void)ssl; tls_teardown_calls++; free_seq = ++seq; } + +/* --- test harness -------------------------------------------------------- */ +#define CHECK(cond) do { if (!(cond)) { \ + printf("FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); failures++; } } while (0) + +static int failures; +static int ssl_marker; /* opaque non-NULL session handle */ + +static void check_close_emitted_close_notify(const char *site) +{ + /* close_notify must precede session/IO teardown on every close path. */ + if (shutdown_seq == 0) { + printf("FAIL %s: wolfSSL_shutdown (close_notify) never called\n", site); + failures++; + return; + } + CHECK(shutdown_seq < cleanup_seq); /* shutdown before IO cleanup */ + CHECK(shutdown_seq < free_seq); /* shutdown before free */ +} + +static void check_plain_http_close_skips_tls(const char *site, + struct http_client *hc) +{ + if (tls_teardown_calls != 0) { + printf("FAIL %s: plain HTTP close called wolfSSL teardown\n", site); + failures++; + } + CHECK(hc->ssl == NULL); + CHECK(hc->client_sd == 0); +} + +int main(void) +{ + struct httpd httpd; + struct http_client hc; + + failures = 0; + memset(&httpd, 0, sizeof(httpd)); + + /* http_send_response_headers error close */ + reset_tracking(); + memset(&hc, 0, sizeof(hc)); + hc.httpd = &httpd; hc.client_sd = 1; hc.ssl = (WOLFSSL *)&ssl_marker; + http_send_response_headers(&hc, 200, "OK", "text/plain", 0); + check_close_emitted_close_notify("http_send_response_headers"); + + /* http_send_response_body error close */ + reset_tracking(); + memset(&hc, 0, sizeof(hc)); + hc.httpd = &httpd; hc.client_sd = 1; hc.ssl = (WOLFSSL *)&ssl_marker; + http_send_response_body(&hc, "x", 1); + check_close_emitted_close_notify("http_send_response_body"); + + /* http_send_response_chunk error close */ + reset_tracking(); + memset(&hc, 0, sizeof(hc)); + hc.httpd = &httpd; hc.client_sd = 1; hc.ssl = (WOLFSSL *)&ssl_marker; + http_send_response_chunk(&hc, "x", 1); + check_close_emitted_close_notify("http_send_response_chunk"); + + /* http_send_response_chunk_end error close */ + reset_tracking(); + memset(&hc, 0, sizeof(hc)); + hc.httpd = &httpd; hc.client_sd = 1; hc.ssl = (WOLFSSL *)&ssl_marker; + http_send_response_chunk_end(&hc); + check_close_emitted_close_notify("http_send_response_chunk_end"); + + /* http_recv_cb fail_close (read error) */ + reset_tracking(); + memset(&hc, 0, sizeof(hc)); + hc.httpd = &httpd; hc.client_sd = 1; hc.ssl = (WOLFSSL *)&ssl_marker; + http_recv_cb(1, 0, &hc); + check_close_emitted_close_notify("http_recv_cb fail_close"); + + /* http_send_response_headers plain HTTP close must not touch wolfSSL */ + reset_tracking(); + memset(&hc, 0, sizeof(hc)); + hc.httpd = &httpd; hc.client_sd = 1; hc.ssl = NULL; + http_send_response_headers(&hc, 200, "OK", "text/plain", 0); + check_plain_http_close_skips_tls("http_send_response_headers plain", &hc); + + /* http_send_response_body plain HTTP close must not touch wolfSSL */ + reset_tracking(); + memset(&hc, 0, sizeof(hc)); + hc.httpd = &httpd; hc.client_sd = 1; hc.ssl = NULL; + http_send_response_body(&hc, "x", 1); + check_plain_http_close_skips_tls("http_send_response_body plain", &hc); + + /* http_send_response_chunk plain HTTP close must not touch wolfSSL */ + reset_tracking(); + memset(&hc, 0, sizeof(hc)); + hc.httpd = &httpd; hc.client_sd = 1; hc.ssl = NULL; + http_send_response_chunk(&hc, "x", 1); + check_plain_http_close_skips_tls("http_send_response_chunk plain", &hc); + + /* http_send_response_chunk_end plain HTTP close must not touch wolfSSL */ + reset_tracking(); + memset(&hc, 0, sizeof(hc)); + hc.httpd = &httpd; hc.client_sd = 1; hc.ssl = NULL; + http_send_response_chunk_end(&hc); + check_plain_http_close_skips_tls("http_send_response_chunk_end plain", &hc); + + /* http_recv_cb plain HTTP close must not touch wolfSSL */ + reset_tracking(); + memset(&hc, 0, sizeof(hc)); + hc.httpd = &httpd; hc.client_sd = 1; hc.ssl = NULL; + http_recv_cb(1, 0, &hc); + check_plain_http_close_skips_tls("http_recv_cb fail_close plain", &hc); + + if (failures == 0) + printf("test_http_close_notify: all checks passed\n"); + else + printf("test_http_close_notify: %d check(s) failed\n", failures); + return failures ? 1 : 0; +} diff --git a/src/test/test_http_smuggle.c b/src/test/test_http_smuggle.c index 3ec238cd..aa668f4f 100644 --- a/src/test/test_http_smuggle.c +++ b/src/test/test_http_smuggle.c @@ -54,6 +54,8 @@ int wolfSSL_SetIO_wolfIP(WOLFSSL *ssl, int fd) { (void)ssl; (void)fd; return 0; } int wolfSSL_SetIO_wolfIP_CTX(WOLFSSL_CTX *ctx, struct wolfIP *s) { (void)ctx; (void)s; return 0; } +void wolfSSL_CleanupIO_wolfIP(WOLFSSL *ssl) +{ (void)ssl; } /* --- test harness --------------------------------------------------------- */ static int handler_calls; diff --git a/src/test/unit/mocks/wolfssh/ssh.h b/src/test/unit/mocks/wolfssh/ssh.h new file mode 100644 index 00000000..ab8fc009 --- /dev/null +++ b/src/test/unit/mocks/wolfssh/ssh.h @@ -0,0 +1,47 @@ +/* Mock wolfssh/ssh.h for unit tests. + * Only includes pieces needed by src/port/wolfssh_io.c tests. + */ +#ifndef WOLFSSH_SSH_H +#define WOLFSSH_SSH_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef unsigned int word32; + +typedef struct WOLFSSH_CTX { + int id; +} WOLFSSH_CTX; + +typedef struct WOLFSSH { + WOLFSSH_CTX *ctx; + void *rctx; + void *wctx; +} WOLFSSH; + +typedef int (*WS_CallbackIORecv)(WOLFSSH *ssh, void *buf, word32 sz, void *ctx); +typedef int (*WS_CallbackIOSend)(WOLFSSH *ssh, void *buf, word32 sz, void *ctx); + +/* Return codes (subset, matching real wolfSSH values). */ +#define WS_SUCCESS (0) +#define WS_BAD_ARGUMENT (-2) +#define WS_MEMORY_E (-6) + +/* Custom IO callback error codes (subset, matching real wolfSSH values). */ +#define WS_CBIO_ERR_GENERAL (-1) +#define WS_CBIO_ERR_WANT_READ (-2) +#define WS_CBIO_ERR_WANT_WRITE (-3) +#define WS_CBIO_ERR_CONN_CLOSE (-6) + +void wolfSSH_SetIORecv(WOLFSSH_CTX *ctx, WS_CallbackIORecv cb); +void wolfSSH_SetIOSend(WOLFSSH_CTX *ctx, WS_CallbackIOSend cb); +void wolfSSH_SetIOReadCtx(WOLFSSH *ssh, void *ctx); +void wolfSSH_SetIOWriteCtx(WOLFSSH *ssh, void *ctx); +void *wolfSSH_GetIOReadCtx(WOLFSSH *ssh); + +#ifdef __cplusplus +} +#endif + +#endif /* WOLFSSH_SSH_H */ diff --git a/src/test/unit/mocks/wolfssl/ssl.h b/src/test/unit/mocks/wolfssl/ssl.h index f1545323..021d98e2 100644 --- a/src/test/unit/mocks/wolfssl/ssl.h +++ b/src/test/unit/mocks/wolfssl/ssl.h @@ -30,6 +30,7 @@ int wolfSSL_SetIORecv(WOLFSSL_CTX *ctx, CallbackIORecv cb); int wolfSSL_SetIOSend(WOLFSSL_CTX *ctx, CallbackIOSend cb); int wolfSSL_SetIOReadCtx(WOLFSSL *ssl, void *ctx); int wolfSSL_SetIOWriteCtx(WOLFSSL *ssl, void *ctx); +void *wolfSSL_GetIOReadCtx(WOLFSSL *ssl); WOLFSSL_CTX *wolfSSL_get_SSL_CTX(WOLFSSL *ssl); #ifdef __cplusplus diff --git a/src/test/unit/unit.c b/src/test/unit/unit.c index 71d3d134..3abcf1ce 100644 --- a/src/test/unit/unit.c +++ b/src/test/unit/unit.c @@ -597,6 +597,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_utils, test_tcp_rst_syn_rcvd_returns_to_listen); tcase_add_test(tc_utils, test_tcp_fin_wait_1_to_closing); tcase_add_test(tc_utils, test_tcp_last_ack_closes_socket); + tcase_add_test(tc_utils, test_tcp_last_ack_closes_socket_delivers_closed_event); tcase_add_test(tc_utils, test_tcp_last_ack_partial_ack_keeps_socket_and_timer); tcase_add_test(tc_utils, test_tcp_ack_acks_data_and_sets_writable); tcase_add_test(tc_utils, test_tcp_ack_duplicate_resend_clears_sent); @@ -949,6 +950,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_wolfssl, test_wolfssl_io_setio_invalid_ctx); tcase_add_test(tc_wolfssl, test_wolfssl_io_setio_invalid_fd); tcase_add_test(tc_wolfssl, test_wolfssl_io_setio_no_stack); + tcase_add_test(tc_wolfssl, test_wolfssl_io_cleanup_frees_slot); tcase_add_test(tc_wolfssl, test_wolfssl_io_recv_behaviors); tcase_add_test(tc_wolfssl, test_wolfssl_io_recv_invalid_desc); tcase_add_test(tc_wolfssl, test_wolfssl_io_recv_fragmented_sequence); @@ -958,6 +960,8 @@ Suite *wolf_suite(void) tcase_add_test(tc_wolfssl, test_wolfssl_io_send_behaviors); tcase_add_test(tc_wolfssl, test_wolfssl_io_send_invalid_desc); tcase_add_test(tc_wolfssl, test_wolfssl_io_send_want_write_keeps_buffer); + tcase_add_test(tc_wolfssl, test_wolfssh_io_send_behaviors); + tcase_add_test(tc_wolfssl, test_wolfssh_io_recv_behaviors); /* Branch-coverage tests backported from the trimmed wolfIP suite. */ tcase_add_test(tc_core, test_socket_from_fd_invalid_inputs); @@ -1200,6 +1204,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_core, test_tcp_parse_options_sack_permitted_parsed); tcase_add_test(tc_core, test_tcp_input_syn_rcvd_rst_bad_seq_ignored); tcase_add_test(tc_core, test_tcp_input_syn_rcvd_rst_good_seq_reverts_to_listen); + tcase_add_test(tc_core, test_tcp_input_syn_rcvd_rst_good_seq_nonlistener_closes); tcase_add_test(tc_core, test_tcp_input_time_wait_sends_ack_on_any_segment); tcase_add_test(tc_core, test_tcp_input_last_ack_unacceptable_sends_ack); tcase_add_test(tc_core, test_tcp_input_last_ack_syn_sends_challenge_ack); @@ -1283,6 +1288,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_core, test_poll_tx_udp_sends_on_arp_hit); tcase_add_test(tc_core, test_poll_tx_udp_filter_ip_blocks_send); tcase_add_test(tc_core, test_poll_tx_udp_eagain_retains_queue); + tcase_add_test(tc_core, test_poll_tx_udp_drain_sets_writable); tcase_add_test(tc_core, test_poll_tx_udp_broadcast_sets_ff_mac); tcase_add_test(tc_core, test_poll_tx_udp_loopback_path_no_crash); tcase_add_test(tc_core, test_poll_tx_icmp_sends_on_arp_hit); diff --git a/src/test/unit/unit_esp.c b/src/test/unit/unit_esp.c index 8f95e427..1e09c7d8 100644 --- a/src/test/unit/unit_esp.c +++ b/src/test/unit/unit_esp.c @@ -1505,6 +1505,78 @@ START_TEST(test_wrap_rejects_ip_len_below_header) } END_TEST +/* + * esp_transport_wrap must honour the actual IHL for packets carrying IP + * options (IHL > 5), e.g. the Router-Alert option emitted by + * igmp_send_report (ver_ihl = 0x46). rfc4303 sec 3.1.1 inserts the ESP + * header after the IP header AND its options, so option bytes must stay in + * the clear in front of the ESP header and must not be counted as payload. + * + * Regression for the IP_HEADER_LEN=20 constant: with the bug the SPI is + * written over the option bytes at ip->data[0] and the 4 option bytes are + * dragged into the (over-counted) ESP payload. + */ +START_TEST(test_wrap_preserves_ip_options) +{ + static uint8_t buf[LINK_MTU + 256]; + /* IPv4 Router-Alert option (type 0x94, len 4). */ + static const uint8_t opt[4] = { 0x94, 0x04, 0x00, 0x00 }; + uint8_t ref[32]; + struct wolfIP_ip_packet *ip = (struct wolfIP_ip_packet *)buf; + uint16_t ip_len; + uint16_t orig_ip_len; + uint32_t i; + int ret; + + for (i = 0U; i < sizeof(ref); i++) + ref[i] = (uint8_t)(0x40U + (i & 0x3FU)); + + esp_setup(); + + /* HMAC-only outbound SA so the wrapped layout stays in cleartext and is + * inspectable (iv_len == 0, no encryption). */ + ret = wolfIP_esp_sa_new_hmac(0, (uint8_t *)spi_rt, + atoip4(T_SRC), atoip4(T_DST), + ESP_AUTH_SHA256_RFC4868, k_auth16, + sizeof(k_auth16), ESP_ICVLEN_HMAC_128); + ck_assert_int_eq(ret, 0); + + /* Build an IHL=6 packet by hand: 20-byte header + 4 option bytes. */ + memset(buf, 0, sizeof(buf)); + ip->eth.type = ee16(0x0800U); + ip->ver_ihl = 0x46U; /* IPv4, 24-byte header */ + ip->tos = 0U; + orig_ip_len = (uint16_t)(IP_HEADER_LEN + sizeof(opt) + sizeof(ref)); + ip->len = ee16(orig_ip_len); + ip->ttl = 64U; + ip->proto = WI_IPPROTO_UDP; + ip->src = ee32(atoip4(T_SRC)); + ip->dst = ee32(atoip4(T_DST)); + memcpy(ip->data, opt, sizeof(opt)); + memcpy(ip->data + sizeof(opt), ref, sizeof(ref)); + ip->csum = 0U; + iphdr_set_checksum(ip); + + ip_len = orig_ip_len; + ret = esp_transport_wrap(ip, &ip_len); + ck_assert_int_eq(ret, 0); + + /* IHL must be unchanged: the IP header (incl. options) is preserved. */ + ck_assert_uint_eq(ip->ver_ihl, 0x46U); + + /* Option bytes must remain in the clear, in front of the ESP header. */ + ck_assert_mem_eq(ip->data, opt, sizeof(opt)); + + /* The ESP SPI must follow the options, not overwrite them. */ + ck_assert_mem_eq(ip->data + sizeof(opt), spi_rt, ESP_SPI_LEN); + + /* ip->len / *ip_len must be measured from the real header length. */ + ck_assert_uint_eq((uint16_t)(ip_len - (IP_HEADER_LEN + sizeof(opt))), + (uint16_t)(ee16(ip->len) - (IP_HEADER_LEN + sizeof(opt)))); + ck_assert_uint_eq(ee16(ip->len), ip_len); +} +END_TEST + START_TEST(test_ip_recv_esp_transport_delivers_udp_payload) { static uint8_t buf[LINK_MTU + 256]; @@ -2031,6 +2103,7 @@ static Suite *esp_suite(void) tc = tcase_create("no_sa"); tcase_add_test(tc, test_wrap_no_matching_sa); tcase_add_test(tc, test_wrap_rejects_ip_len_below_header); + tcase_add_test(tc, test_wrap_preserves_ip_options); suite_add_tcase(s, tc); /* TCP immediate-send ESP regression */ diff --git a/src/test/unit/unit_shared.c b/src/test/unit/unit_shared.c index 2fcda51c..690d8742 100644 --- a/src/test/unit/unit_shared.c +++ b/src/test/unit/unit_shared.c @@ -176,6 +176,13 @@ int wolfSSL_SetIOWriteCtx(WOLFSSL *ssl, void *ctx) return 0; } +void *wolfSSL_GetIOReadCtx(WOLFSSL *ssl) +{ + if (!ssl) + return NULL; + return ssl->rctx; +} + WOLFSSL_CTX *wolfSSL_get_SSL_CTX(WOLFSSL *ssl) { if (!ssl) @@ -240,6 +247,68 @@ static int test_wolfIP_sock_send(struct wolfIP *s, int fd, const void *buf, int #undef wolfIP_sock_recv #undef wolfIP_sock_send +/* wolfSSH IO glue mocks. */ +#include + +void wolfSSH_SetIORecv(WOLFSSH_CTX *ctx, WS_CallbackIORecv cb) +{ + (void)ctx; + (void)cb; +} + +void wolfSSH_SetIOSend(WOLFSSH_CTX *ctx, WS_CallbackIOSend cb) +{ + (void)ctx; + (void)cb; +} + +void wolfSSH_SetIOReadCtx(WOLFSSH *ssh, void *ctx) +{ + if (ssh) + ssh->rctx = ctx; +} + +void wolfSSH_SetIOWriteCtx(WOLFSSH *ssh, void *ctx) +{ + if (ssh) + ssh->wctx = ctx; +} + +void *wolfSSH_GetIOReadCtx(WOLFSSH *ssh) +{ + if (!ssh) + return NULL; + return ssh->rctx; +} + +/* wolfssh_io.c reuses the same static names (io_descs / io_desc_alloc / + * io_desc_free) as wolfssl_io.c above; rename them to avoid a collision in + * this single translation unit, and route its socket IO to the same mocks. */ +#define io_descs wolfssh_io_descs +#define io_desc_alloc wolfssh_io_desc_alloc +#define io_desc_free wolfssh_io_desc_free +#define wolfIP_sock_recv test_wolfIP_sock_recv +#define wolfIP_sock_send test_wolfIP_sock_send +#include "../../port/wolfssh_io.c" +#undef wolfIP_sock_recv +#undef wolfIP_sock_send +#undef io_descs +#undef io_desc_alloc +#undef io_desc_free + +static void reset_wolfssh_io_state(void) +{ + memset(wolfssh_io_descs, 0, sizeof(wolfssh_io_descs)); + test_recv_ret = 0; + test_send_ret = 0; + test_recv_fill_len = 0; + test_send_capture_len = 0; + test_send_last_len = 0; + test_recv_steps_len = 0; + test_recv_step = 0; + test_recv_step_total = 0; +} + static void reset_wolfssl_io_state(void) { memset(ctx_map, 0, sizeof(ctx_map)); diff --git a/src/test/unit/unit_tests_poll_dispatcher.c b/src/test/unit/unit_tests_poll_dispatcher.c index 13769e52..dc169bd8 100644 --- a/src/test/unit/unit_tests_poll_dispatcher.c +++ b/src/test/unit/unit_tests_poll_dispatcher.c @@ -865,6 +865,56 @@ START_TEST(test_poll_tx_udp_eagain_retains_queue) } END_TEST +START_TEST(test_poll_tx_udp_drain_sets_writable) +{ + struct wolfIP s; + int udp_sd; + struct tsocket *ts; + struct wolfIP_sockaddr_in sin; + uint8_t payload[1400]; + uint8_t peer_mac[6] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x99}; + int rc; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + wolfIP_filter_set_callback(NULL, NULL); + + s.arp.neighbors[0].ip = 0x0A000002U; + s.arp.neighbors[0].if_idx = TEST_PRIMARY_IF; + memcpy(s.arp.neighbors[0].mac, peer_mac, 6); + + memset(payload, 0xAB, sizeof(payload)); + udp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_gt(udp_sd, 0); + ts = &s.udpsockets[SOCKET_UNMARK(udp_sd)]; + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = ee16(5004); + sin.sin_addr.s_addr = ee32(0x0A000002U); + + /* Fill the UDP txbuf until sendto() reports the buffer full. */ + do { + rc = wolfIP_sock_sendto(&s, udp_sd, payload, sizeof(payload), 0, + (struct wolfIP_sockaddr *)&sin, sizeof(sin)); + } while (rc > 0); + ck_assert_int_eq(rc, -WOLFIP_EAGAIN); + ts->if_idx = TEST_PRIMARY_IF; + + /* A blocked sendto() would now be waiting for CB_EVENT_WRITABLE. */ + ts->events = 0; + + /* Poll drains the queue over the wire (mock_send succeeds). */ + (void)wolfIP_poll(&s, 200); + + /* Draining freed txbuf space, so the drain must raise CB_EVENT_WRITABLE to + * wake a blocked sender. Before the fix this bit was never set for + * non-loopback UDP sockets and the sender deadlocked. */ + ck_assert_uint_ne((unsigned)(ts->events & CB_EVENT_WRITABLE), 0U); +} +END_TEST + START_TEST(test_poll_tx_udp_broadcast_sets_ff_mac) { struct wolfIP s; diff --git a/src/test/unit/unit_tests_proto.c b/src/test/unit/unit_tests_proto.c index f700e468..4cc2ee76 100644 --- a/src/test/unit/unit_tests_proto.c +++ b/src/test/unit/unit_tests_proto.c @@ -4200,6 +4200,43 @@ START_TEST(test_wolfssl_io_setio_no_stack) } END_TEST +/* Regression for F-5781: io_descs[] slots must be released on teardown, or the + * static pool is exhausted after MAX_WOLFIP_CTX sessions and every later TLS + * handshake fails (unauthenticated DoS). */ +START_TEST(test_wolfssl_io_cleanup_frees_slot) +{ + struct wolfIP s; + WOLFSSL_CTX ctx; + WOLFSSL ssl[MAX_WOLFIP_CTX]; + WOLFSSL extra; + int i; + + memset(&ctx, 0, sizeof(ctx)); + memset(ssl, 0, sizeof(ssl)); + memset(&extra, 0, sizeof(extra)); + reset_wolfssl_io_state(); + wolfSSL_SetIO_wolfIP_CTX(&ctx, &s); + + /* Fill every slot in the pool. */ + for (i = 0; i < MAX_WOLFIP_CTX; i++) { + ssl[i].ctx = &ctx; + ck_assert_int_eq(wolfSSL_SetIO_wolfIP(&ssl[i], 10 + i), 0); + } + + /* Pool exhausted: a new session cannot be set up. */ + extra.ctx = &ctx; + ck_assert_int_eq(wolfSSL_SetIO_wolfIP(&extra, 99), -1); + + /* Tearing down one session must release its slot and clear its IO ctx. */ + wolfSSL_CleanupIO_wolfIP(&ssl[0]); + ck_assert_ptr_null(ssl[0].rctx); + ck_assert_ptr_null(ssl[0].wctx); + + /* The freed slot is now reusable. */ + ck_assert_int_eq(wolfSSL_SetIO_wolfIP(&extra, 99), 0); +} +END_TEST + START_TEST(test_wolfssl_io_recv_behaviors) { struct wolfIP s; @@ -4215,9 +4252,11 @@ START_TEST(test_wolfssl_io_recv_behaviors) ret = wolfIP_io_recv(NULL, buf, sizeof(buf), &desc); ck_assert_int_eq(ret, WOLFSSL_CBIO_ERR_WANT_READ); + /* -1 is the torn-down ("not established") case: it must be a fatal close, + * not a retryable WANT_READ, or wolfSSL spins on a dead connection. */ test_recv_ret = -1; ret = wolfIP_io_recv(NULL, buf, sizeof(buf), &desc); - ck_assert_int_eq(ret, WOLFSSL_CBIO_ERR_WANT_READ); + ck_assert_int_eq(ret, WOLFSSL_CBIO_ERR_CONN_CLOSE); test_recv_ret = 0; ret = wolfIP_io_recv(NULL, buf, sizeof(buf), &desc); @@ -4380,9 +4419,11 @@ START_TEST(test_wolfssl_io_send_behaviors) ret = wolfIP_io_send(NULL, buf, 4, &desc); ck_assert_int_eq(ret, WOLFSSL_CBIO_ERR_WANT_WRITE); + /* -1 is the torn-down ("not established") case: it must be a fatal close, + * not a retryable WANT_WRITE, or wolfSSL spins on a dead connection. */ test_send_ret = -1; ret = wolfIP_io_send(NULL, buf, 4, &desc); - ck_assert_int_eq(ret, WOLFSSL_CBIO_ERR_WANT_WRITE); + ck_assert_int_eq(ret, WOLFSSL_CBIO_ERR_CONN_CLOSE); test_send_ret = 0; ret = wolfIP_io_send(NULL, buf, 4, &desc); @@ -4427,6 +4468,76 @@ START_TEST(test_wolfssl_io_send_invalid_desc) } END_TEST +START_TEST(test_wolfssh_io_send_behaviors) +{ + struct wolfIP s; + struct wolfssh_io_desc desc; + char buf[4] = {0x11, 0x22, 0x33, 0x44}; + int ret; + + memset(&desc, 0, sizeof(desc)); + desc.stack = &s; + desc.fd = 4; + desc.in_use = 1; + + reset_wolfssh_io_state(); + test_send_ret = -WOLFIP_EAGAIN; + ret = wolfssh_io_send(NULL, buf, sizeof(buf), &desc); + ck_assert_int_eq(ret, WS_CBIO_ERR_WANT_WRITE); + + /* -1 is the torn-down ("not established", peer RST) case from + * wolfIP_sock_sendto: it must be a fatal close, not a retryable + * WANT_WRITE, or wolfSSH spins on a dead connection and never frees the + * io_desc slot (unauthenticated DoS). */ + test_send_ret = -1; + ret = wolfssh_io_send(NULL, buf, sizeof(buf), &desc); + ck_assert_int_eq(ret, WS_CBIO_ERR_CONN_CLOSE); + + test_send_ret = 0; + ret = wolfssh_io_send(NULL, buf, sizeof(buf), &desc); + ck_assert_int_eq(ret, WS_CBIO_ERR_CONN_CLOSE); + + test_send_ret = 4; + ret = wolfssh_io_send(NULL, buf, sizeof(buf), &desc); + ck_assert_int_eq(ret, 4); +} +END_TEST + +START_TEST(test_wolfssh_io_recv_behaviors) +{ + struct wolfIP s; + struct wolfssh_io_desc desc; + char buf[4]; + int ret; + + memset(&desc, 0, sizeof(desc)); + desc.stack = &s; + desc.fd = 4; + desc.in_use = 1; + + reset_wolfssh_io_state(); + test_recv_ret = -WOLFIP_EAGAIN; + ret = wolfssh_io_recv(NULL, buf, sizeof(buf), &desc); + ck_assert_int_eq(ret, WS_CBIO_ERR_WANT_READ); + + /* -1 is the torn-down ("not established", peer RST) case from + * wolfIP_sock_recvfrom: it must be a fatal close, not a retryable + * WANT_READ, or wolfSSH spins on a dead connection and the SSH handshake + * state machine is wedged in KEY_EXCHANGE forever (unauthenticated DoS). */ + test_recv_ret = -1; + ret = wolfssh_io_recv(NULL, buf, sizeof(buf), &desc); + ck_assert_int_eq(ret, WS_CBIO_ERR_CONN_CLOSE); + + test_recv_ret = 0; + ret = wolfssh_io_recv(NULL, buf, sizeof(buf), &desc); + ck_assert_int_eq(ret, WS_CBIO_ERR_CONN_CLOSE); + + test_recv_ret = 4; + ret = wolfssh_io_recv(NULL, buf, sizeof(buf), &desc); + ck_assert_int_eq(ret, 4); +} +END_TEST + START_TEST(test_tcp_listen_rejects_wrong_interface) { struct wolfIP s; diff --git a/src/test/unit/unit_tests_tcp_ack.c b/src/test/unit/unit_tests_tcp_ack.c index 21785a37..49717d36 100644 --- a/src/test/unit/unit_tests_tcp_ack.c +++ b/src/test/unit/unit_tests_tcp_ack.c @@ -3826,6 +3826,64 @@ START_TEST(test_tcp_last_ack_closes_socket) } END_TEST +/* LAST_ACK final ACK must deliver CB_EVENT_CLOSED to a registered callback so a + * caller blocked on the socket close (e.g. the FreeRTOS BSD close()) is woken. + * Delivery + teardown are deferred from the RX path to wolfIP_poll() Step 3 so + * the callback runs on a shallow stack (a callback that reaches into the C + * library from deep in packet processing overflowed the poll task stack). */ +START_TEST(test_tcp_last_ack_closes_socket_delivers_closed_event) +{ + struct wolfIP s; + struct tsocket *ts; + ip4 local_ip = 0x0A000001U; + ip4 remote_ip = 0x0A0000E3U; + uint16_t local_port = 6668; + uint16_t remote_port = 7779; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, local_ip, 0xFFFFFF00U, 0); + + ts = &s.tcpsockets[0]; + memset(ts, 0, sizeof(*ts)); + ts->proto = WI_IPPROTO_TCP; + ts->S = &s; + ts->sock.tcp.state = TCP_LAST_ACK; + ts->sock.tcp.last = 9; + ts->local_ip = local_ip; + ts->remote_ip = remote_ip; + ts->src_port = local_port; + ts->dst_port = remote_port; + ts->sock.tcp.ack = 10; + ts->callback = test_socket_cb; + ts->callback_arg = NULL; + queue_init(&ts->sock.tcp.rxbuf, ts->rxmem, RXBUF_SIZE, ts->sock.tcp.ack); + + socket_cb_calls = 0; + socket_cb_last_fd = -1; + socket_cb_last_events = 0; + + inject_tcp_segment(&s, TEST_PRIMARY_IF, remote_ip, local_ip, remote_port, local_port, + 10, 10, TCP_FLAG_ACK); + + /* RX path moved it to TCP_CLOSED and queued CB_EVENT_CLOSED, but deferred + * the callback + teardown: the socket still exists and the callback has not + * fired yet. */ + ck_assert_int_eq(ts->sock.tcp.state, TCP_CLOSED); + ck_assert_int_ne(ts->proto, 0); + ck_assert_uint_eq(ts->events & CB_EVENT_CLOSED, CB_EVENT_CLOSED); + ck_assert_int_eq(socket_cb_calls, 0); + + /* poll Step 3 delivers CB_EVENT_CLOSED on a shallow stack, then reaps. */ + (void)wolfIP_poll(&s, 1); + + ck_assert_int_eq(socket_cb_calls, 1); + ck_assert_int_eq(socket_cb_last_fd, 0 | MARK_TCP_SOCKET); + ck_assert_uint_eq(socket_cb_last_events & CB_EVENT_CLOSED, CB_EVENT_CLOSED); + ck_assert_int_eq(ts->proto, 0); /* torn down after the event was delivered */ +} +END_TEST + START_TEST(test_tcp_last_ack_partial_ack_keeps_socket_and_timer) { struct wolfIP s; diff --git a/src/test/unit/unit_tests_tcp_state.c b/src/test/unit/unit_tests_tcp_state.c index bf273b47..4ab5cc1b 100644 --- a/src/test/unit/unit_tests_tcp_state.c +++ b/src/test/unit/unit_tests_tcp_state.c @@ -535,7 +535,7 @@ START_TEST(test_tcp_input_syn_rcvd_rst_bad_seq_ignored) } END_TEST -/* RST in SYN_RCVD with matching seq → revert to LISTEN */ +/* RST in SYN_RCVD with matching seq on a listener → revert to LISTEN */ START_TEST(test_tcp_input_syn_rcvd_rst_good_seq_reverts_to_listen) { struct wolfIP s; @@ -553,6 +553,7 @@ START_TEST(test_tcp_input_syn_rcvd_rst_good_seq_reverts_to_listen) ts->proto = WI_IPPROTO_TCP; ts->S = &s; ts->sock.tcp.state = TCP_SYN_RCVD; + ts->sock.tcp.is_listener = 1; /* listening socket: must revert, not close */ ts->sock.tcp.ack = 2; /* rcv_nxt = 2 */ ts->local_ip = local_ip; ts->remote_ip = remote_ip; @@ -570,6 +571,59 @@ START_TEST(test_tcp_input_syn_rcvd_rst_good_seq_reverts_to_listen) } END_TEST +/* RST in SYN_RCVD with matching seq on an accepted (non-listener) socket → + * the half-open connection is torn down and CB_EVENT_CLOSED is delivered, so a + * blocked consumer wakes instead of waiting on a phantom LISTEN socket. */ +START_TEST(test_tcp_input_syn_rcvd_rst_good_seq_nonlistener_closes) +{ + struct wolfIP s; + struct tsocket *ts; + ip4 local_ip = 0x0A000001U; + ip4 remote_ip = 0x0A0000A1U; + uint16_t lport = 8080, rport = 40000; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, local_ip, 0xFFFFFF00U, 0); + + ts = &s.tcpsockets[0]; + memset(ts, 0, sizeof(*ts)); + ts->proto = WI_IPPROTO_TCP; + ts->S = &s; + ts->sock.tcp.state = TCP_SYN_RCVD; + ts->sock.tcp.is_listener = 0; /* accepted clone: must close, not revert */ + ts->sock.tcp.ack = 2; /* rcv_nxt = 2 */ + ts->local_ip = local_ip; + ts->remote_ip = remote_ip; + ts->src_port = lport; + ts->dst_port = rport; + ts->sock.tcp.tmr_rto = NO_TIMER; + ts->callback = test_socket_cb; + ts->callback_arg = NULL; + fifo_init(&ts->sock.tcp.txbuf, ts->txmem, TXBUF_SIZE); + + socket_cb_calls = 0; + socket_cb_last_events = 0; + + /* RST with seq == rcv_nxt: socket must be destroyed, not reverted. The + * teardown + CB_EVENT_CLOSED are deferred from the RX path to poll Step 3 + * so the callback runs on a shallow stack. */ + inject_tcp_segment(&s, TEST_PRIMARY_IF, remote_ip, local_ip, + rport, lport, 2, 0, TCP_FLAG_RST); + + ck_assert_int_eq(ts->sock.tcp.state, TCP_CLOSED); + ck_assert_int_ne(ts->proto, 0); /* not reverted to LISTEN, not yet reaped */ + ck_assert_uint_eq(ts->events & CB_EVENT_CLOSED, CB_EVENT_CLOSED); + ck_assert_int_eq(socket_cb_calls, 0); + + (void)wolfIP_poll(&s, 1); + + ck_assert_int_eq(ts->proto, 0); /* socket destroyed after event delivered */ + ck_assert_int_eq(socket_cb_calls, 1); + ck_assert_uint_ne(socket_cb_last_events & CB_EVENT_CLOSED, 0); +} +END_TEST + /* Time-wait state re-ACKs any incoming segment */ START_TEST(test_tcp_input_time_wait_sends_ack_on_any_segment) { diff --git a/src/wolfesp.c b/src/wolfesp.c index e5e221e0..61226504 100644 --- a/src/wolfesp.c +++ b/src/wolfesp.c @@ -1581,20 +1581,30 @@ esp_transport_wrap(struct wolfIP_ip_packet *ip, uint16_t * ip_len) { uint8_t block_len = 0; uint16_t orig_ip_len = *ip_len; - uint16_t orig_payload_len = orig_ip_len - IP_HEADER_LEN; + uint16_t ip_hdr_len = (uint16_t)((ip->ver_ihl & 0x0f) * 4); + uint16_t orig_payload_len; uint16_t payload_len = 0; - uint8_t * payload = ip->data; + uint8_t * esp_base; + uint8_t * payload; uint8_t pad_len = 0; uint32_t seq_n = 0; /* sequence num in network order */ uint16_t icv_offset = 0; wolfIP_esp_sa * esp_sa = NULL; uint8_t iv_len = 0; - if (orig_ip_len < IP_HEADER_LEN) { + if (ip_hdr_len < IP_HEADER_LEN || orig_ip_len < ip_hdr_len) { ESP_LOG("error: ip_len below header: %u\n", orig_ip_len); return -1; } + /* rfc4303 sec 3.1.1: in transport mode the ESP header is inserted after + * the IP header AND any options it carries. Use the actual IHL so option + * bytes (e.g. igmp_send_report's Router-Alert, ver_ihl=0x46) stay in the + * clear in front of the ESP header and are not counted as payload. */ + orig_payload_len = (uint16_t)(orig_ip_len - ip_hdr_len); + esp_base = ip->data + (ip_hdr_len - IP_HEADER_LEN); + payload = esp_base; + /* todo: priority, proto / port filtering. currently this grabs * the first dst and src match. */ for (size_t i = 0; i < out_sa_num; ++i) { @@ -1629,8 +1639,8 @@ esp_transport_wrap(struct wolfIP_ip_packet *ip, uint16_t * ip_len) iv_len = esp_iv_len_from_enc(esp_sa->enc); /* move ip payload back to make room for ESP header (SPI, SEQ) + IV. */ - memmove(ip->data + ESP_SPI_LEN + ESP_SEQ_LEN + iv_len, - ip->data, orig_payload_len); + memmove(esp_base + ESP_SPI_LEN + ESP_SEQ_LEN + iv_len, + esp_base, orig_payload_len); /* Copy in SPI and sequence number fields. */ memcpy(payload, esp_sa->spi, sizeof(esp_sa->spi)); @@ -1704,7 +1714,7 @@ esp_transport_wrap(struct wolfIP_ip_packet *ip, uint16_t * ip_len) payload += ESP_NEXT_HEADER_LEN; /* calculate final esp payload length. */ - payload_len = orig_ip_len - IP_HEADER_LEN; + payload_len = orig_payload_len; payload_len += ESP_SPI_LEN + ESP_SEQ_LEN + iv_len + pad_len + ESP_PADDING_LEN + ESP_NEXT_HEADER_LEN + esp_sa->icv_len; @@ -1716,22 +1726,22 @@ esp_transport_wrap(struct wolfIP_ip_packet *ip, uint16_t * ip_len) switch(esp_sa->enc) { #ifndef NO_DES3 case ESP_ENC_CBC_DES3: - err = esp_des3_rfc2451_enc(esp_sa, ip->data, payload_len); + err = esp_des3_rfc2451_enc(esp_sa, esp_base, payload_len); break; #endif /* !NO_DES3 */ case ESP_ENC_CBC_AES: - err = esp_aes_rfc3602_enc(esp_sa, ip->data, payload_len); + err = esp_aes_rfc3602_enc(esp_sa, esp_base, payload_len); break; #if defined(WOLFSSL_AESGCM_STREAM) case ESP_ENC_GCM_RFC4106: - err = esp_aes_rfc4106_enc(esp_sa, ip->data, payload_len); + err = esp_aes_rfc4106_enc(esp_sa, esp_base, payload_len); break; #endif /*WOLFSSL_AESGCM_STREAM */ case ESP_ENC_GCM_RFC4543: - err = esp_aes_rfc4543_enc(esp_sa, ip->data, payload_len); + err = esp_aes_rfc4543_enc(esp_sa, esp_base, payload_len); break; case ESP_ENC_NONE: @@ -1759,8 +1769,8 @@ esp_transport_wrap(struct wolfIP_ip_packet *ip, uint16_t * ip_len) uint8_t * icv = NULL; byte hash[WC_SHA256_DIGEST_SIZE]; memset(hash, 0, sizeof(hash)); - icv = ip->data + icv_offset; - err = esp_calc_icv_hmac(hash, esp_sa, ip->data, payload_len); + icv = esp_base + icv_offset; + err = esp_calc_icv_hmac(hash, esp_sa, esp_base, payload_len); if (err == 0) { memcpy(icv, hash, esp_sa->icv_len); } @@ -1785,10 +1795,10 @@ esp_transport_wrap(struct wolfIP_ip_packet *ip, uint16_t * ip_len) } } - *ip_len = payload_len + IP_HEADER_LEN; + *ip_len = payload_len + ip_hdr_len; #ifdef DEBUG_ESP - wolfIP_print_esp(esp_sa, ip->data, payload_len, pad_len, ip->proto); + wolfIP_print_esp(esp_sa, esp_base, payload_len, pad_len, ip->proto); #endif /* DEBUG_ESP */ /* update len, set proto to ESP 0x32 (50), recalculate iphdr checksum. */ diff --git a/src/wolfip.c b/src/wolfip.c index df45e0e9..153fe375 100644 --- a/src/wolfip.c +++ b/src/wolfip.c @@ -4641,7 +4641,19 @@ static void tcp_ack(struct tsocket *t, const struct wolfIP_tcp_seg *tcp) if (t->sock.tcp.state == TCP_LAST_ACK && tcp_seq_leq(fin_acked, ack)) { tcp_ctrl_rto_stop(t); t->sock.tcp.state = TCP_CLOSED; - close_socket(t); + /* The peer's final ACK tears the socket down here, deep in the RX + * path, before wolfIP_poll() Step 3 dispatches socket callbacks. + * Do NOT invoke the user callback from here: a callback that touches + * the C library (e.g. the FreeRTOS BSD layer's printf -> malloc) adds + * frames at the bottom of the RX call chain and overflows the poll + * task stack. Instead defer CB_EVENT_CLOSED delivery and the final + * close_socket() to Step 3, which dispatches from a shallow stack and + * then reaps TCP_CLOSED sockets. A caller blocked on the socket close + * (e.g. the FreeRTOS BSD close()) is woken from there. */ + if (t->callback) + t->events |= CB_EVENT_CLOSED; + else + close_socket(t); return; } if (t->sock.tcp.state == TCP_FIN_WAIT_1 && tcp_seq_leq(fin_acked, ack)) { @@ -4900,6 +4912,12 @@ static void tcp_input(struct wolfIP *S, unsigned int if_idx, struct tsocket *t = &S->tcpsockets[i]; if (t->proto == 0 || t->S == NULL) continue; + /* A socket moved to TCP_CLOSED by the RX path with CB_EVENT_CLOSED + * still pending has only deferred its teardown to wolfIP_poll() + * Step 3 (so the close callback runs on a shallow stack). Ignore any + * further input for it until Step 3 delivers the event and reaps it. */ + if (t->sock.tcp.state == TCP_CLOSED && (t->events & CB_EVENT_CLOSED)) + continue; if (t->src_port == ee16(tcp->dst_port)) { /* TCP segment sanity checks */ iplen = ee16(tcp->ip.len); @@ -4998,12 +5016,29 @@ static void tcp_input(struct wolfIP *S, unsigned int if_idx, /* RFC 9293: only accept RST if SEQ matches rcv_nxt */ if (seg_seq != rcv_nxt) continue; - /* RST on a half-open connection: fall back to listening state. */ - t->sock.tcp.state = TCP_LISTEN; - t->events &= ~CB_EVENT_READABLE; - t->remote_ip = IPADDR_ANY; - t->dst_port = 0; - t->sock.tcp.ack = 0; + if (t->sock.tcp.is_listener) { + /* RST on a half-open connection of a listening socket: + * fall back to LISTEN to keep the server open. */ + t->sock.tcp.state = TCP_LISTEN; + t->events &= ~CB_EVENT_READABLE; + t->remote_ip = IPADDR_ANY; + t->dst_port = 0; + t->sock.tcp.ack = 0; + continue; + } + /* An accepted (cloned) connection has no listen role: a peer + * RST before the handshake completed must tear it down, not + * resurrect it as a phantom listener. Defer CB_EVENT_CLOSED + * delivery and the close_socket() to wolfIP_poll() Step 3 + * (shallow stack) rather than invoking the callback from + * deep in the RX path, which can overflow the poll task + * stack; a consumer blocked on the new socket is woken from + * there. */ + t->sock.tcp.state = TCP_CLOSED; + if (t->callback) + t->events |= CB_EVENT_CLOSED; + else + close_socket(t); continue; } if (t->sock.tcp.state == TCP_SYN_SENT) { @@ -9860,11 +9895,25 @@ int wolfIP_poll(struct wolfIP *s, uint64_t now) /* Step 3: handle DHCP and application callbacks */ for (i = 0; i < MAX_TCPSOCKETS; i++) { struct tsocket *ts = &s->tcpsockets[i]; - if ((ts->sock.tcp.state != TCP_CLOSED) && (ts->callback) && (ts->events)) { + if ((ts->callback == NULL) || (ts->events == 0)) + continue; + /* A socket the RX path moved to TCP_CLOSED is dispatched here only when + * it deferred a CB_EVENT_CLOSED for delivery on this shallow stack + * (LAST_ACK final ACK, or RST on a half-open accepted socket); the + * teardown was deferred too so the callback would not run deep in + * packet processing. Any other TCP_CLOSED socket is left alone. */ + if ((ts->sock.tcp.state == TCP_CLOSED) && !(ts->events & CB_EVENT_CLOSED)) + continue; + { uint16_t events = ts->events; ts->events = 0; ts->callback(i | MARK_TCP_SOCKET, events, ts->callback_arg); } + /* Now that CB_EVENT_CLOSED has been delivered, reap the deferred-close + * socket. A socket closed elsewhere is already memset (callback NULL) + * and never reaches this branch. */ + if (ts->sock.tcp.state == TCP_CLOSED) + close_socket(ts); } for (i = 0; i < MAX_UDPSOCKETS; i++) { struct tsocket *ts = &s->udpsockets[i]; @@ -10063,6 +10112,7 @@ int wolfIP_poll(struct wolfIP *s, uint64_t now) for (i = 0; i < MAX_UDPSOCKETS; i++) { struct tsocket *t = &s->udpsockets[i]; struct pkt_desc *desc = fifo_peek(&t->sock.udp.txbuf); + int tx_drained = 0; while (desc) { struct wolfIP_udp_datagram *udp = (struct wolfIP_udp_datagram *)(t->txmem + desc->pos + sizeof(*desc)); unsigned int tx_if = wolfIP_socket_if_idx(t); @@ -10135,8 +10185,15 @@ int wolfIP_poll(struct wolfIP *s, uint64_t now) } #endif fifo_pop(&t->sock.udp.txbuf); + tx_drained = 1; desc = fifo_peek(&t->sock.udp.txbuf); } + /* Draining the txbuf frees space; raise CB_EVENT_WRITABLE so a sender + * blocked on a full buffer (e.g. the FreeRTOS BSD shim's sendto()) is + * woken. The loopback path is handled separately via + * wolfIP_notify_loopback_space_available(). */ + if (tx_drained && tx_has_writable_space(t)) + t->events |= CB_EVENT_WRITABLE; } for (i = 0; i < MAX_ICMPSOCKETS; i++) { struct tsocket *t = &s->icmpsockets[i]; diff --git a/wolfip.h b/wolfip.h index 55c0a24d..1a5b8e7c 100644 --- a/wolfip.h +++ b/wolfip.h @@ -526,6 +526,7 @@ static inline void iptoa(ip4 ip, char *buf) #include int wolfSSL_SetIO_wolfIP(WOLFSSL* ssl, int fd); int wolfSSL_SetIO_wolfIP_CTX(WOLFSSL_CTX *ctx, struct wolfIP *s); + void wolfSSL_CleanupIO_wolfIP(WOLFSSL* ssl); #ifdef WOLFIP_ESP #include