From 559aeb03356738bc0f642cf6e99aeaa34b26a7ed Mon Sep 17 00:00:00 2001 From: Frederic Pillon Date: Mon, 19 Oct 2026 16:46:30 +0200 Subject: [PATCH 1/8] fix(utility): initialize accepted flag in tcp_accept_callback The accepted flag is read after the accept logic but was never set on every path, so it could hold an indeterminate value. Changing it to a boolean type and initializing it to false. Signed-off-by: Frederic Pillon Assisted-by: GitHub Copilot: Claude Haiku 5.5 --- src/utility/stm32_eth.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utility/stm32_eth.cpp b/src/utility/stm32_eth.cpp index e1ead30..1ff0811 100644 --- a/src/utility/stm32_eth.cpp +++ b/src/utility/stm32_eth.cpp @@ -770,7 +770,7 @@ err_t tcp_connected_callback(void *arg, struct tcp_pcb *tpcb, err_t err) err_t tcp_accept_callback(void *arg, struct tcp_pcb *newpcb, err_t err) { err_t ret_err; - uint8_t accepted; + bool accepted = false; struct tcp_struct **tcpClient = (struct tcp_struct **)arg; /* set priority for the newly accepted tcp connection newpcb */ @@ -789,7 +789,7 @@ err_t tcp_accept_callback(void *arg, struct tcp_pcb *newpcb, err_t err) for (uint16_t i = 0; i < MAX_CLIENT; i++) { if (tcpClient[i] == NULL) { tcpClient[i] = client; - accepted = 1; + accepted = true; break; } } From 0124fa897ca0272d68127bef861a2f5703817f78 Mon Sep 17 00:00:00 2001 From: Frederic Pillon Date: Mon, 19 Oct 2026 16:46:39 +0200 Subject: [PATCH 2/8] fix(utility): avoid NULL client in tcp_accept_callback on alloc failure When mem_malloc() fails for the new connection structure, the error path called tcp_connection_close() with a NULL client and dereferenced it. Close only the new pcb with tcp_close(), since there is no client structure to update. Signed-off-by: Frederic Pillon Assisted-by: GitHub Copilot: Claude Haiku 5.5 --- src/utility/stm32_eth.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/utility/stm32_eth.cpp b/src/utility/stm32_eth.cpp index 1ff0811..bee685c 100644 --- a/src/utility/stm32_eth.cpp +++ b/src/utility/stm32_eth.cpp @@ -818,8 +818,7 @@ err_t tcp_accept_callback(void *arg, struct tcp_pcb *newpcb, err_t err) } } else { /* close tcp connection */ - tcp_connection_close(newpcb, client); - mem_free(client); + tcp_close(newpcb); /* return memory error */ ret_err = ERR_MEM; From 7e995856bf43e6648d972c53d2fca9d9c4e06170 Mon Sep 17 00:00:00 2001 From: Frederic Pillon Date: Mon, 19 Oct 2026 16:46:45 +0200 Subject: [PATCH 3/8] fix(utility): define _stm32_eth_scheduler for all STM32 core versions _stm32_eth_scheduler() was declared and defined only for STM32 cores newer than 0x01060100, but scheduler_callback() calls it unconditionally, so older cores failed to build. Define it for all versions and keep the stm32_eth_scheduler() wrapper for the newer timer path. Older cores get a wrapper that calls the shared body from the polling loops. Signed-off-by: Frederic Pillon Assisted-by: GitHub Copilot: Claude Haiku 5.5 --- src/utility/stm32_eth.cpp | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/utility/stm32_eth.cpp b/src/utility/stm32_eth.cpp index bee685c..939bcc7 100644 --- a/src/utility/stm32_eth.cpp +++ b/src/utility/stm32_eth.cpp @@ -112,9 +112,7 @@ static err_t tcp_recv_callback(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, static err_t tcp_sent_callback(void *arg, struct tcp_pcb *tpcb, u16_t len); static void tcp_err_callback(void *arg, err_t err); static void TIM_scheduler_Config(void); -#if defined(STM32_CORE_VERSION) && (STM32_CORE_VERSION > 0x01060100) - void _stm32_eth_scheduler(void); -#endif +void _stm32_eth_scheduler(void); /** * @brief Configure the network interface. @@ -286,27 +284,28 @@ uint8_t stm32_eth_link_up(void) return netif_is_link_up(&gnetif); } -#if defined(STM32_CORE_VERSION) && (STM32_CORE_VERSION > 0x01060100) -/* refresh() generates a timer update event, which runs _stm32_eth_scheduler() - from the timer interrupt. Does nothing until the timer has been created. */ void stm32_eth_scheduler(void) { +#if defined(STM32_CORE_VERSION) && (STM32_CORE_VERSION > 0x01060100) + /* refresh() generates a timer update event, which runs _stm32_eth_scheduler() + from the timer interrupt. Does nothing until the timer has been created. */ if (EthTim != NULL) { EthTim->refresh(); } +#else + _stm32_eth_scheduler(); +#endif } /** - * @brief Run one LwIP scheduler pass. Called only from the timer callback. + * @brief Run one LwIP scheduler pass. * * Reads received packets (unless ETH_INPUT_USE_IT is defined), checks the * link status every TIME_CHECK_ETH_LINK_STATE ms, handles LwIP timeouts and - * runs the periodic DHCP processing. + * runs the periodic DHCP processing. Called by the timer callback and, on + * older cores, by stm32_eth_scheduler(). */ void _stm32_eth_scheduler(void) -#else -void stm32_eth_scheduler(void) -#endif { /* Read a received packet from the Ethernet buffers and send it to the lwIP for handling */ From dddabd262c269ab81303973024fa9e081f9d2547 Mon Sep 17 00:00:00 2001 From: Frederic Pillon Date: Mon, 19 Oct 2026 16:46:49 +0200 Subject: [PATCH 4/8] fix(client): handle NULL connection in EthernetClient::operator== operator== read _tcp_client->pcb without checking for NULL. Two clients with no connection both have a NULL _tcp_client, so comparing them crashed. Only compare the pcb when both have one. Signed-off-by: Frederic Pillon Assisted-by: GitHub Copilot: Claude Haiku 5.5 --- src/EthernetClient.cpp | 6 +++++- src/EthernetClient.h | 4 ++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/EthernetClient.cpp b/src/EthernetClient.cpp index ea79f8f..08d2aba 100644 --- a/src/EthernetClient.cpp +++ b/src/EthernetClient.cpp @@ -227,7 +227,11 @@ EthernetClient::operator bool() bool EthernetClient::operator==(const EthernetClient &rhs) { - return _tcp_client == rhs._tcp_client && _tcp_client->pcb == rhs._tcp_client->pcb; + bool equal = _tcp_client == rhs._tcp_client; + if (_tcp_client != NULL && rhs._tcp_client != NULL) { + equal = equal && (_tcp_client->pcb == rhs._tcp_client->pcb); + } + return equal; } uint8_t EthernetClient::getSocketNumber() diff --git a/src/EthernetClient.h b/src/EthernetClient.h index 54b70ff..bc13271 100644 --- a/src/EthernetClient.h +++ b/src/EthernetClient.h @@ -176,8 +176,8 @@ class EthernetClient : public Client { /** * @brief Tests whether two clients refer to the same connection. * - * Two clients with no connection structure compare equal, and the function - * then reads a NULL pointer. Only compare clients that have a connection. + * Two clients with no connection structure compare equal. A client with no + * connection never equals a client that has one. * * @param rhs Client to compare with. * @return true if both clients use the same connection structure and the From ea1952a753ce61e46ba3a387dc8dd03f36ef0a51 Mon Sep 17 00:00:00 2001 From: Frederic Pillon Date: Mon, 19 Oct 2026 16:46:59 +0200 Subject: [PATCH 5/8] fix(client): avoid double free of TCP connection in stop() stop() freed _tcp_client unconditionally, but a client created from a server-owned tcp_struct does not own it. EthernetServer::accept() frees those slots, so the same memory was freed twice. Track ownership with _owns_tcp_client, set only in connect(), and free only owned connections. Signed-off-by: Frederic Pillon Assisted-by: GitHub Copilot: Claude Haiku 5.5 --- src/EthernetClient.cpp | 6 +++++- src/EthernetClient.h | 5 ++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/EthernetClient.cpp b/src/EthernetClient.cpp index 08d2aba..733b85b 100644 --- a/src/EthernetClient.cpp +++ b/src/EthernetClient.cpp @@ -57,6 +57,7 @@ int EthernetClient::connect(IPAddress ip, uint16_t port) if (_tcp_client == NULL) { return 0; } + _owns_tcp_client = true; } /* Creates a new TCP protocol control block */ @@ -200,7 +201,10 @@ void EthernetClient::stop() tcp_connection_close(_tcp_client->pcb, _tcp_client); } } - mem_free(_tcp_client); + if (_owns_tcp_client) { + mem_free(_tcp_client); + _owns_tcp_client = false; + } _tcp_client = NULL; } } diff --git a/src/EthernetClient.h b/src/EthernetClient.h index bc13271..b7375b2 100644 --- a/src/EthernetClient.h +++ b/src/EthernetClient.h @@ -131,7 +131,9 @@ class EthernetClient : public Client { * @brief Closes the connection and releases the client structure. * * The connection is closed unless it is already in TCP_CLOSING state. The - * structure is freed and the client is left with no connection. + * structure is freed only if this client allocated it in connect(). A + * structure shared with EthernetServer is left for the server to free, and + * the client is left with no connection. */ virtual void stop(); /** @@ -253,6 +255,7 @@ class EthernetClient : public Client { private: struct tcp_struct *_tcp_client; + bool _owns_tcp_client = false; // true if connect() allocated _tcp_client uint16_t _connectionTimeout = 10000; }; From 4ee68be579ced7ab04b74073d853f7472c8da7f7 Mon Sep 17 00:00:00 2001 From: Frederic Pillon Date: Mon, 19 Oct 2026 16:47:03 +0200 Subject: [PATCH 6/8] fix(udp): release UDP socket when IGMP join fails in begin() begin() returned 0 after a failed igmp_joingroup() but left the UDP pcb allocated and bound. Call stop() before returning so the socket is not leaked. Signed-off-by: Frederic Pillon Assisted-by: GitHub Copilot: Claude Haiku 5.5 --- src/EthernetUdp.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/EthernetUdp.cpp b/src/EthernetUdp.cpp index 106518b..55356e3 100644 --- a/src/EthernetUdp.cpp +++ b/src/EthernetUdp.cpp @@ -76,6 +76,7 @@ uint8_t EthernetUDP::begin(IPAddress ip, uint16_t port, bool multicast) #if LWIP_IGMP if ((multicast) && (ERR_OK != igmp_joingroup(IP_ADDR_ANY, &ipaddr))) { + stop(); return 0; } #endif From 9a61eb608d654a6c17c85a2d09c2fa9f5a3526f9 Mon Sep 17 00:00:00 2001 From: Frederic Pillon Date: Mon, 19 Oct 2026 16:47:08 +0200 Subject: [PATCH 7/8] fix(udp): keep buffered data when write() fails write(buffer, size) assigned the result of stm32_new_data() directly to _data. On allocation failure that dropped the packet already buffered. Store the result in a temporary and assign _data only on success. Signed-off-by: Frederic Pillon Assisted-by: GitHub Copilot: Claude Haiku 5.5 --- src/EthernetUdp.cpp | 5 +++-- src/EthernetUdp.h | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/EthernetUdp.cpp b/src/EthernetUdp.cpp index 55356e3..e1b3d29 100644 --- a/src/EthernetUdp.cpp +++ b/src/EthernetUdp.cpp @@ -162,10 +162,11 @@ size_t EthernetUDP::write(uint8_t byte) size_t EthernetUDP::write(const uint8_t *buffer, size_t size) { - _data = stm32_new_data(_data, buffer, size); - if (_data == NULL) { + struct pbuf *data = stm32_new_data(_data, buffer, size); + if (data == NULL) { return 0; } + _data = data; return size; } diff --git a/src/EthernetUdp.h b/src/EthernetUdp.h index 6525ad4..b6ab492 100644 --- a/src/EthernetUdp.h +++ b/src/EthernetUdp.h @@ -161,7 +161,7 @@ class EthernetUDP : public UDP { * @brief Appends a block of bytes to the outgoing packet. * * Data accumulates until endPacket() sends it. On failure, the data - * already buffered for the packet is discarded. + * already buffered for the packet is kept and the new bytes are not added. * * @param buffer Bytes to append. * @param size Number of bytes in buffer. From 53707cee87497a73921bcef85a32c4b7a8d829b8 Mon Sep 17 00:00:00 2001 From: Frederic Pillon Date: Mon, 19 Oct 2026 16:47:08 +0200 Subject: [PATCH 8/8] fix(udp): initialize all EthernetUDP members in constructor The default constructor left the port, pbuf and UDP pcb members uninitialized until begin() was called. Initialize them in the member initializer list, in declaration order. Signed-off-by: Frederic Pillon Assisted-by: GitHub Copilot: Claude Haiku 5.5 --- src/EthernetUdp.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/EthernetUdp.cpp b/src/EthernetUdp.cpp index e1b3d29..1746fef 100644 --- a/src/EthernetUdp.cpp +++ b/src/EthernetUdp.cpp @@ -40,7 +40,10 @@ #include "lwip/igmp.h" #include "lwip/ip_addr.h" -EthernetUDP::EthernetUDP() {} +EthernetUDP::EthernetUDP() + : _port(0), _remotePort(0), _sendtoPort(0), _data(NULL), _udp(), _remaining(0) +{ +} uint8_t EthernetUDP::begin(uint16_t port) {