Skip to content

Commit ad68d5a

Browse files
authored
Merge pull request #130 from danielinux/fenrir-fixes-2026-06-09
Fenrir fixes 2026 06 09
2 parents 517f751 + a267f4c commit ad68d5a

29 files changed

Lines changed: 1286 additions & 60 deletions

Makefile

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,8 @@ EXE=build/tcpecho build/tcp_netcat_poll build/tcp_netcat_select \
183183
build/test-evloop build/test-dns build/test-wolfssl-forwarding \
184184
build/test-ttl-expired build/test-wolfssl build/test-httpd \
185185
build/test-http-smuggle build/test-http-arg-oob \
186+
build/test-http-close-notify \
187+
build/test-freertos-close-last-ack \
186188
build/test-posix-errno \
187189
build/ipfilter-logger \
188190
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
423425
@echo "[LD] $@"
424426
@$(CC) $(CFLAGS) -o $@ src/test/test_http_arg_oob.c $(LDFLAGS) -lwolfssl
425427

428+
# Standalone regression test for TLS close_notify on every close path (F-5732).
429+
# It #includes httpd.c directly and stubs the wolfSSL teardown calls to record
430+
# their order, so it does not link the real wolfSSL library.
431+
build/test-http-close-notify:CFLAGS+=-Wno-cpp -DWOLFSSL_DEBUG -DWOLFSSL_WOLFIP -DWOLFIP_ENABLE_HTTP -Isrc/http
432+
build/test-http-close-notify: src/test/test_http_close_notify.c src/http/httpd.c
433+
@mkdir -p build || true
434+
@echo "[LD] $@"
435+
@$(CC) $(CFLAGS) -o $@ src/test/test_http_close_notify.c $(LDFLAGS)
436+
437+
# Standalone regression test for the FreeRTOS BSD close() wrapper when
438+
# CB_EVENT_CLOSED is delivered synchronously during LAST_ACK teardown.
439+
build/test-freertos-close-last-ack: src/test/test_freertos_close_last_ack.c src/port/freeRTOS/bsd_socket.c
440+
@mkdir -p build || true
441+
@echo "[LD] $@"
442+
@$(CC) -Isrc/test/freertos_mocks $(CFLAGS) -o $@ src/test/test_freertos_close_last_ack.c $(LDFLAGS)
443+
426444
build/%.o: src/%.c
427445
@mkdir -p `dirname $@` || true
428446
@echo "[CC] $<"

src/http/httpd.c

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,8 @@ static struct http_url *http_find_url(struct httpd *httpd, const char *path) {
106106
return NULL;
107107
}
108108

109+
static void http_close_client(struct http_client *hc);
110+
109111
void http_send_response_headers(struct http_client *hc, int status_code, const char *status_text, const char *content_type, size_t content_length)
110112
{
111113
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
133135
}
134136
if (rc <= 0) {
135137
/* Error – close connection */
136-
wolfSSL_free(hc->ssl);
137-
hc->ssl = NULL;
138-
wolfIP_sock_close(hc->httpd->ipstack, hc->client_sd);
139-
hc->client_sd = 0;
138+
http_close_client(hc);
140139
}
141140
}
142141

@@ -150,10 +149,7 @@ void http_send_response_body(struct http_client *hc, const void *body, size_t le
150149
rc = wolfIP_sock_send(hc->httpd->ipstack, hc->client_sd, body, len, 0);
151150

152151
if (rc <= 0) {
153-
wolfSSL_free(hc->ssl);
154-
hc->ssl = NULL;
155-
wolfIP_sock_close(hc->httpd->ipstack, hc->client_sd);
156-
hc->client_sd = 0;
152+
http_close_client(hc);
157153
}
158154
}
159155

@@ -169,6 +165,21 @@ static int http_write_response(struct http_client *hc, const void *buf, size_t l
169165
return wolfIP_sock_send(s, hc->client_sd, buf, len, 0);
170166
}
171167

168+
static void http_close_client(struct http_client *hc)
169+
{
170+
if (!hc)
171+
return;
172+
173+
if (hc->ssl) {
174+
wolfSSL_shutdown(hc->ssl);
175+
wolfSSL_CleanupIO_wolfIP(hc->ssl);
176+
wolfSSL_free(hc->ssl);
177+
hc->ssl = NULL;
178+
}
179+
wolfIP_sock_close(hc->httpd->ipstack, hc->client_sd);
180+
hc->client_sd = 0;
181+
}
182+
172183
void http_send_response_chunk(struct http_client *hc, const void *chunk, size_t len) {
173184
char txt_chunk[8];
174185
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
178189
if ((http_write_response(hc, txt_chunk, strlen(txt_chunk)) <= 0) ||
179190
(http_write_response(hc, chunk, len) <= 0) ||
180191
(http_write_response(hc, "\r\n", 2) <= 0)) {
181-
wolfSSL_free(hc->ssl);
182-
hc->ssl = NULL;
183-
wolfIP_sock_close(hc->httpd->ipstack, hc->client_sd);
184-
hc->client_sd = 0;
192+
http_close_client(hc);
185193
}
186194
}
187195

@@ -194,10 +202,7 @@ void http_send_response_chunk_end(struct http_client *hc) {
194202
else
195203
rc = wolfIP_sock_send(hc->httpd->ipstack, hc->client_sd, "0\r\n\r\n", 5, 0);
196204
if (rc <= 0) {
197-
wolfSSL_free(hc->ssl);
198-
hc->ssl = NULL;
199-
wolfIP_sock_close(hc->httpd->ipstack, hc->client_sd);
200-
hc->client_sd = 0;
205+
http_close_client(hc);
201206
}
202207
}
203208

@@ -483,19 +488,14 @@ static void http_recv_cb(int sd, uint16_t event, void *arg) {
483488
return;
484489

485490
fail_close:
486-
if (hc->ssl) {
487-
wolfSSL_free(hc->ssl);
488-
hc->ssl = NULL;
489-
}
490-
wolfIP_sock_close(hc->httpd->ipstack, sd);
491+
http_close_client(hc);
491492
/* wolfIP_sock_close on an ESTABLISHED socket only starts the active close
492493
* (FIN_WAIT_1) and returns -EAGAIN; the socket lingers, still carrying this
493494
* callback and its arg. Once we zero client_sd the slot is reused by the
494495
* next accept, so the lingering socket's callback_arg would dangle onto a
495496
* different live connection's state. Deregister it here so a late segment
496497
* on the half-closed socket can no longer fire http_recv_cb. */
497498
wolfIP_register_callback(hc->httpd->ipstack, sd, NULL, NULL);
498-
hc->client_sd = 0;
499499
}
500500

501501
static void http_accept_cb(int sd, uint16_t event, void *arg) {

src/port/freeRTOS/bsd_socket.c

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ typedef struct {
5050
int internal_fd;
5151
SemaphoreHandle_t ready_sem;
5252
volatile uint16_t wait_events;
53+
volatile uint16_t seen_events;
5354
} wolfip_bsd_fd_entry;
5455

5556
static struct wolfIP *g_ipstack;
@@ -131,6 +132,7 @@ static int wolfip_bsd_fd_alloc(int internal_fd)
131132
g_fds[i].internal_fd = internal_fd;
132133
g_fds[i].ready_sem = sem;
133134
g_fds[i].wait_events = 0;
135+
g_fds[i].seen_events = 0;
134136
return i;
135137
}
136138
}
@@ -148,6 +150,7 @@ static void wolfip_bsd_fd_free(int public_fd)
148150
g_fds[public_fd].internal_fd = -1;
149151
g_fds[public_fd].ready_sem = NULL;
150152
g_fds[public_fd].wait_events = 0;
153+
g_fds[public_fd].seen_events = 0;
151154
}
152155

153156
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)
158161
if (entry == NULL) {
159162
return;
160163
}
164+
entry->seen_events |= events;
161165
g_cb_log_count++;
162166
if ((events & CB_EVENT_CLOSED) != 0u || (g_cb_log_count & 0x1Fu) == 0u) {
163167
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)
173177

174178
static void wolfip_bsd_prepare_wait_locked(wolfip_bsd_fd_entry *entry, uint16_t wait_events)
175179
{
180+
entry->seen_events = 0;
176181
entry->wait_events = wait_events;
177182
while (xSemaphoreTake(entry->ready_sem, 0) == pdTRUE) {
178183
}
@@ -655,6 +660,17 @@ int close(int sockfd)
655660
xSemaphoreGive(g_lock);
656661
return ret;
657662
}
663+
if ((ret == -1) && IS_SOCKET_TCP(entry->internal_fd) &&
664+
((entry->seen_events & CB_EVENT_CLOSED) != 0u)) {
665+
/* The TCP core can destroy the socket immediately after delivering
666+
* CB_EVENT_CLOSED (e.g. final ACK in LAST_ACK), so the retry sees
667+
* the already-zeroed descriptor and wolfIP_sock_close() returns -1.
668+
* Treat that as a completed close and release the wrapper slot. */
669+
wolfIP_register_callback(g_ipstack, entry->internal_fd, NULL, NULL);
670+
wolfip_bsd_fd_free(sockfd);
671+
xSemaphoreGive(g_lock);
672+
return 0;
673+
}
658674
if (ret != -WOLFIP_EAGAIN) {
659675
xSemaphoreGive(g_lock);
660676
wolfip_bsd_set_error(ret);

src/port/stm32h563/tls_server.c

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ static void tls_client_handle_data(tls_client_t *client, uint16_t event);
7878
/* External functions from wolfssl_io.c */
7979
extern int wolfSSL_SetIO_wolfIP_CTX(WOLFSSL_CTX *ctx, struct wolfIP *s);
8080
extern int wolfSSL_SetIO_wolfIP(WOLFSSL *ssl, int fd);
81+
extern void wolfSSL_CleanupIO_wolfIP(WOLFSSL *ssl);
8182

8283
/* Debug output helper */
8384
static void debug_print(const char *msg)
@@ -237,6 +238,7 @@ static void tls_client_free(tls_client_t *client)
237238
{
238239
if (client->ssl) {
239240
wolfSSL_shutdown(client->ssl);
241+
wolfSSL_CleanupIO_wolfIP(client->ssl);
240242
wolfSSL_free(client->ssl);
241243
client->ssl = NULL;
242244
}
@@ -319,7 +321,14 @@ static void tls_listen_cb(int fd, uint16_t event, void *arg)
319321
}
320322

321323
/* Associate SSL with socket */
322-
wolfSSL_SetIO_wolfIP(client->ssl, client_fd);
324+
if (wolfSSL_SetIO_wolfIP(client->ssl, client_fd) != 0) {
325+
debug_print("TLS: SetIO failed\n");
326+
wolfSSL_free(client->ssl);
327+
client->ssl = NULL;
328+
wolfIP_sock_close(server.stack, client_fd);
329+
client->state = TLS_CLIENT_STATE_FREE;
330+
return;
331+
}
323332

324333
client->fd = client_fd;
325334
client->state = TLS_CLIENT_STATE_HANDSHAKE;

src/port/stm32h753/main.c

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,14 @@ static int https_status_handler(struct httpd *httpd, struct http_client *hc,
204204
#define RNG_SR_CECS (1u << 1)
205205
#define RNG_SR_SECS (1u << 2)
206206

207+
/* Cortex-M7 DWT cycle counter (used to seed the RNG fallback with runtime
208+
* entropy so a degraded device does not emit a globally-identical sequence) */
209+
#define DWT_CTRL (*(volatile uint32_t *)0xE0001000UL)
210+
#define DWT_CYCCNT (*(volatile uint32_t *)0xE0001004UL)
211+
#define DWT_CTRL_CYCCNTENA (1u << 0)
212+
#define CM_DEMCR (*(volatile uint32_t *)0xE000EDFCUL)
213+
#define CM_DEMCR_TRCENA (1u << 24)
214+
207215
/* USART3 for debug output (ST-Link VCP on NUCLEO-H753ZI: PD8=TX, PD9=RX) */
208216
#define USART3_BASE 0x40004800UL
209217
#define USART3_CR1 (*(volatile uint32_t *)(USART3_BASE + 0x00))
@@ -369,8 +377,21 @@ uint32_t wolfIP_getrandom(void)
369377
uint32_t val;
370378
if (rng_get_word(&val) == 0)
371379
return val;
372-
/* Fallback LFSR if HW RNG fails */
373-
static uint32_t lfsr = 0x1A2B3C4DU;
380+
/* HW RNG failed: fall back to an xorshift LFSR seeded from runtime state
381+
* rather than a compile-time constant, so a degraded device does not emit
382+
* the same globally-known sequence (and thus predictable TCP ISNs) on
383+
* every unit. The DWT cycle counter (CPU clock, free running once the
384+
* stack is up) and any residual RNG_DR bits vary per device and power-up.
385+
* Not a cryptographic RNG. */
386+
static uint32_t lfsr = 0u;
387+
if (lfsr == 0u) {
388+
CM_DEMCR |= CM_DEMCR_TRCENA;
389+
DWT_CTRL |= DWT_CTRL_CYCCNTENA;
390+
lfsr = DWT_CYCCNT ^ RNG_DR ^ 0x1A2B3C4DU;
391+
if (lfsr == 0u)
392+
lfsr = 0x1A2B3C4DU; /* LFSR seed must never be zero */
393+
}
394+
lfsr ^= DWT_CYCCNT; /* mix in timing jitter on each call */
374395
lfsr ^= lfsr << 13;
375396
lfsr ^= lfsr >> 17;
376397
lfsr ^= lfsr << 5;

src/port/va416xx/main.c

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -83,20 +83,31 @@ static int client_fd = -1;
8383
/* wolfIP random number generator (required by stack) */
8484
/* ========================================================================= */
8585

86+
/* SysTick Current Value Register: 24-bit free-running down-counter reloaded
87+
* every 1ms (reload = SYSCLK/1000 = 100000 at 100MHz), so each read yields
88+
* ~17 bits of fast-varying timing jitter. */
89+
#define SYST_CVR (*(volatile uint32_t *)0xE000E018UL)
90+
8691
uint32_t wolfIP_getrandom(void)
8792
{
8893
static uint32_t lfsr;
8994
static int seeded = 0;
9095

9196
if (!seeded) {
92-
/* Seed from boot time so ISNs and ephemeral ports vary per power-up.
93-
* HAL_time_ms at first wolfIP call is typically 1-5 s into boot.
94-
* Note: not cryptographically secure; suitable for embedded demo use. */
95-
lfsr = (uint32_t)HAL_time_ms;
97+
/* Seed from boot time mixed with the SysTick current value so the
98+
* initial state is not confined to the trivially-enumerable boot
99+
* window (HAL_time_ms at first wolfIP call is typically 1-5 s into
100+
* boot). Note: not cryptographically secure; suitable for embedded
101+
* demo use. */
102+
lfsr = (uint32_t)HAL_time_ms ^ (SYST_CVR << 8);
96103
if (lfsr == 0U)
97104
lfsr = 0x1A2B3C4DU; /* LFSR must never be zero */
98105
seeded = 1;
99106
}
107+
/* Mix in SysTick timing jitter each call so a single observed output
108+
* cannot be inverted to predict subsequent ISNs/ports/xids (xorshift32
109+
* on its own is bijective). */
110+
lfsr ^= SYST_CVR;
100111
lfsr ^= lfsr << 13;
101112
lfsr ^= lfsr >> 17;
102113
lfsr ^= lfsr << 5;

src/port/wolfssh_io.c

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,10 +70,15 @@ static int wolfssh_io_recv(WOLFSSH *ssh, void *buf, word32 sz, void *ctx)
7070
}
7171

7272
ret = wolfIP_sock_recv(desc->stack, desc->fd, buf, (int)sz, 0);
73-
if (ret == -WOLFIP_EAGAIN || ret == -1) {
73+
/* Only -WOLFIP_EAGAIN means "would block" (no data queued yet). A -1 is
74+
* the "not established" / torn-down case from wolfIP_sock_recvfrom (peer
75+
* RST drove the socket to TCP_CLOSED) and must be reported as a fatal
76+
* close, otherwise wolfSSH retries the dead connection forever and the
77+
* SSH handshake state machine is wedged in KEY_EXCHANGE indefinitely. */
78+
if (ret == -WOLFIP_EAGAIN) {
7479
return WS_CBIO_ERR_WANT_READ;
7580
}
76-
if (ret == 0) {
81+
if (ret == 0 || ret == -1) {
7782
return WS_CBIO_ERR_CONN_CLOSE;
7883
}
7984
if (ret < 0) {
@@ -94,10 +99,15 @@ static int wolfssh_io_send(WOLFSSH *ssh, void *buf, word32 sz, void *ctx)
9499
}
95100

96101
ret = wolfIP_sock_send(desc->stack, desc->fd, buf, (int)sz, 0);
97-
if (ret == -WOLFIP_EAGAIN || ret == -1) {
102+
/* Only -WOLFIP_EAGAIN means "would block" (TX buffer full, nothing
103+
* queued). A -1 is the "not established" / torn-down case from
104+
* wolfIP_sock_sendto (peer RST) and must be reported as a fatal close,
105+
* otherwise wolfSSH retries the dead connection forever and its io_desc
106+
* slot is never released. */
107+
if (ret == -WOLFIP_EAGAIN) {
98108
return WS_CBIO_ERR_WANT_WRITE;
99109
}
100-
if (ret == 0) {
110+
if (ret == 0 || ret == -1) {
101111
return WS_CBIO_ERR_CONN_CLOSE;
102112
}
103113
if (ret < 0) {

src/port/wolfssl_io.c

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,12 @@ static int wolfIP_io_recv(WOLFSSL* ssl, char* buf, int sz, void* ctx)
7272
return WOLFSSL_CBIO_ERR_GENERAL;
7373

7474
ret = wolfIP_sock_recv(desc->stack, desc->fd, buf, sz, 0);
75-
if (ret == -WOLFIP_EAGAIN || ret == -1)
75+
/* Only -WOLFIP_EAGAIN means "would block": wolfIP_sock_recvfrom returns it
76+
* (via queue_pop) for an established socket with an empty RX queue. A -1 is
77+
* the "not established" / torn-down case and must be reported as a fatal
78+
* close, otherwise wolfSSL keeps retrying a dead connection forever and the
79+
* owning session is never released. */
80+
if (ret == -WOLFIP_EAGAIN)
7681
return WOLFSSL_CBIO_ERR_WANT_READ;
7782
if (ret <= 0)
7883
return WOLFSSL_CBIO_ERR_CONN_CLOSE;
@@ -89,7 +94,11 @@ static int wolfIP_io_send(WOLFSSL* ssl, char* buf, int sz, void* ctx)
8994
return WOLFSSL_CBIO_ERR_GENERAL;
9095

9196
ret = wolfIP_sock_send(desc->stack, desc->fd, buf, sz, 0);
92-
if (ret == -WOLFIP_EAGAIN || ret == -1)
97+
/* Only -WOLFIP_EAGAIN means "would block" (TX buffer full, nothing queued).
98+
* A -1 is the "not established" / torn-down case from wolfIP_sock_sendto and
99+
* must be reported as a fatal close, otherwise wolfSSL retries the dead
100+
* connection forever and its session is never released. */
101+
if (ret == -WOLFIP_EAGAIN)
93102
return WOLFSSL_CBIO_ERR_WANT_WRITE;
94103
if (ret <= 0)
95104
return WOLFSSL_CBIO_ERR_CONN_CLOSE;
@@ -135,3 +144,24 @@ int wolfSSL_SetIO_wolfIP(WOLFSSL* ssl, int fd)
135144
}
136145
return -1;
137146
}
147+
148+
/* Release the io_descs[] slot allocated by wolfSSL_SetIO_wolfIP() for this
149+
* session. Must be called on every TLS teardown path (before wolfSSL_free)
150+
* or the static pool leaks one slot per connection and is exhausted after
151+
* MAX_WOLFIP_CTX sessions. */
152+
void wolfSSL_CleanupIO_wolfIP(WOLFSSL* ssl)
153+
{
154+
struct wolfip_io_desc *desc;
155+
156+
if (!ssl)
157+
return;
158+
159+
desc = (struct wolfip_io_desc *)wolfSSL_GetIOReadCtx(ssl);
160+
if (!desc)
161+
return;
162+
163+
wolfSSL_SetIOReadCtx(ssl, NULL);
164+
wolfSSL_SetIOWriteCtx(ssl, NULL);
165+
desc->fd = 0;
166+
desc->stack = NULL;
167+
}

0 commit comments

Comments
 (0)