From 8690ed2eb051085863e9a0ceac6131b92d16c601 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Desbiens?= Date: Sun, 18 Oct 2026 21:53:54 -0400 Subject: [PATCH] Refused a UDP reply from an IPv6 address that is no longer valid A received IPv6 datagram carries the address it arrived on, and _nxd_udp_socket_send uses that address as the source of a reply without looking one up. Once the address has been deleted, or IPv6 disabled, the entry is no longer attached to an interface. _nx_ipv6_packet_send then fails its NX_ASSERT that the interface is not NULL, with the IP mutex held: the caller sleeps forever and the IP thread waits for the mutex, so the IP instance is dead. With NX_ENABLE_INTERFACE_CAPABILITY the send dereferences the NULL interface first. Measured on two IP instances on the RAM driver: a datagram received on 2001::4, the address deleted, the received packet sent back with nxd_udp_socket_send. In v6_build and v6_only_build the caller is left in TX_SLEEP and the IP thread suspended on the mutex; in v6_full_build the process takes a segmentation fault. The send now returns NX_NO_INTERFACE_ADDRESS when the packet carries an address that is not valid, before anything else uses it. That is the status the function's header already documents for this case, and the one nxd_udp_socket_source_send and the TCP send path return for an address that is not valid. The packet stays with the caller. An address set again and still being checked for duplicates is refused the same way, where until now the datagram was dropped and the send reported success. The product file header is unchanged. netx_udp_ipv6_reply_invalid_address_test replies with a received packet after the address is deleted, after IPv6 is disabled, and after the address is deleted and set again, and checks each refusal, that the refused packet can be sent once the address is valid, and that datagrams cross both ways afterwards. The reply is sent from a helper thread with a bounded wait. Without the change the test fails within a second, or with the segmentation fault; with it, it passes. Assisted-by: Claude Code (Opus 5.5) --- common/src/nxd_udp_socket_send.c | 6 + test/cmake/netxduo/regression/CMakeLists.txt | 1 + ...netx_udp_ipv6_reply_invalid_address_test.c | 360 ++++++++++++++++++ 3 files changed, 367 insertions(+) create mode 100644 test/regression/netxduo_test/netx_udp_ipv6_reply_invalid_address_test.c diff --git a/common/src/nxd_udp_socket_send.c b/common/src/nxd_udp_socket_send.c index afb126918..8dc8fe93e 100644 --- a/common/src/nxd_udp_socket_send.c +++ b/common/src/nxd_udp_socket_send.c @@ -400,6 +400,12 @@ UINT compute_checksum = 1; return(status); } } + else if (packet_ptr -> nx_packet_address.nx_packet_ipv6_address_ptr -> nxd_ipv6_address_state != NX_IPV6_ADDR_STATE_VALID) + { + + /* The packet carries the address it was received on, which is no longer valid. */ + return(NX_NO_INTERFACE_ADDRESS); + } /* Fill in the IP src/dest address */ ip_dest_addr = &ip_address -> nxd_ip_address.v6[0]; diff --git a/test/cmake/netxduo/regression/CMakeLists.txt b/test/cmake/netxduo/regression/CMakeLists.txt index 6230cbf3d..8693fe53c 100644 --- a/test/cmake/netxduo/regression/CMakeLists.txt +++ b/test/cmake/netxduo/regression/CMakeLists.txt @@ -364,6 +364,7 @@ set(netxduo_test_cases ${SOURCE_DIR}/netxduo_test/netx_ipv6_disable_test.c ${SOURCE_DIR}/netxduo_test/netx_ipv6_disable_tcp_test.c ${SOURCE_DIR}/netxduo_test/netx_ipv6_dad_failure_notify_send_test.c + ${SOURCE_DIR}/netxduo_test/netx_udp_ipv6_reply_invalid_address_test.c ${SOURCE_DIR}/netxduo_test/netx_icmp_multiple_ping6_test1.c ${SOURCE_DIR}/netxduo_test/netx_ip_address_get_test.c ${SOURCE_DIR}/netxduo_test/netx_10_24_02_test.c diff --git a/test/regression/netxduo_test/netx_udp_ipv6_reply_invalid_address_test.c b/test/regression/netxduo_test/netx_udp_ipv6_reply_invalid_address_test.c new file mode 100644 index 000000000..a683e3848 --- /dev/null +++ b/test/regression/netxduo_test/netx_udp_ipv6_reply_invalid_address_test.c @@ -0,0 +1,360 @@ +/*************************************************************************** + * Copyright (c) 2026 Eclipse ThreadX contributors + * + * This program and the accompanying materials are made available under the + * terms of the MIT License which is available at + * https://opensource.org/licenses/MIT. + * + * AI Disclosure: This file was largely AI-generated by Claude Code (Opus 5.5). + * The AI-generated portions may be considered public domain (CC0-1.0) + * and not subject to the project's licence. The human contributor has + * reviewed and verified that the code is correct. + * + * SPDX-License-Identifier: MIT and CC0-1.0 + **************************************************************************/ + +/* This NetX test concentrates on sending back a received IPv6 UDP datagram + once the address it arrived on is no longer valid. + + A received datagram carries the address it arrived on, and + nxd_udp_socket_send uses that address as the source of a reply instead of + looking one up. If the address has been deleted, or IPv6 disabled, the + entry is no longer attached to an interface; if it has been set again it is + still being checked for duplicates. In each case the send must be refused + with NX_NO_INTERFACE_ADDRESS, the packet left to the caller, and the IP + instance must carry on. A deleted entry used as a source would stop the IP + thread for good, with the IP mutex held. + + ip_1 sends to ip_0's 2001::4, and ip_0 replies with the packet it received. + The reply is sent by a helper thread and waited for with a bound, so the + test reports an error rather than hanging if the send never returns. After + each refusal ip_0 gets its address back, and a datagram and its reply cross + both ways again. */ + +#include "tx_api.h" +#include "nx_api.h" + +extern void test_control_return(UINT status); + +#ifdef FEATURE_NX_IPV6 + +#define DEMO_STACK_SIZE 2048 + +static TX_THREAD thread_0; +static TX_THREAD thread_1; + +static NX_PACKET_POOL pool_0; +static NX_IP ip_0; +static NX_IP ip_1; +static NX_UDP_SOCKET socket_0; +static NX_UDP_SOCKET socket_1; + +static ULONG error_counter; +static NXD_ADDRESS address_0; +static NXD_ADDRESS address_1; + +/* The reply handed to thread 1, and what came of it. */ +static NX_PACKET *reply_ptr; +static UINT reply_status; +static volatile UINT reply_done; + +static void thread_0_entry(ULONG thread_input); +static void thread_1_entry(ULONG thread_input); +static UINT datagram_receive(NX_PACKET **packet_ptr); +static UINT reply_send(NX_PACKET *packet_ptr); +static UINT address_restore(VOID); +static VOID round_trip(VOID); + +extern void _nx_ram_network_driver_1500(struct NX_IP_DRIVER_STRUCT *driver_req); + +#ifdef CTEST +VOID test_application_define(void *first_unused_memory) +#else +void netx_udp_ipv6_reply_invalid_address_test_application_define(void *first_unused_memory) +#endif +{ + +CHAR *pointer; +UINT status; + + pointer = (CHAR *) first_unused_memory; + + error_counter = 0; + + tx_thread_create(&thread_0, "thread 0", thread_0_entry, 0, + pointer, DEMO_STACK_SIZE, + 4, 4, TX_NO_TIME_SLICE, TX_AUTO_START); + pointer = pointer + DEMO_STACK_SIZE; + + /* Sends the replies; higher priority, so it runs as soon as it is resumed. */ + tx_thread_create(&thread_1, "thread 1", thread_1_entry, 0, + pointer, DEMO_STACK_SIZE, + 3, 3, TX_NO_TIME_SLICE, TX_DONT_START); + pointer = pointer + DEMO_STACK_SIZE; + + nx_system_initialize(); + + status = nx_packet_pool_create(&pool_0, "NetX Main Packet Pool", 1536, pointer, 1536 * 12); + pointer = pointer + 1536 * 12; + if (status) + error_counter++; + + status = nx_ip_create(&ip_0, "NetX IP Instance 0", IP_ADDRESS(1, 2, 3, 4), 0xFFFFFF00UL, &pool_0, + _nx_ram_network_driver_1500, pointer, 2048, 1); + pointer = pointer + 2048; + status += nx_ip_create(&ip_1, "NetX IP Instance 1", IP_ADDRESS(1, 2, 3, 5), 0xFFFFFF00UL, &pool_0, + _nx_ram_network_driver_1500, pointer, 2048, 1); + pointer = pointer + 2048; +#ifndef NX_DISABLE_IPV4 + status += nx_arp_enable(&ip_0, (void *) pointer, 1024); + pointer = pointer + 1024; + status += nx_arp_enable(&ip_1, (void *) pointer, 1024); + pointer = pointer + 1024; +#endif /* NX_DISABLE_IPV4 */ + status += nx_udp_enable(&ip_0); + status += nx_udp_enable(&ip_1); + status += nxd_ipv6_enable(&ip_0); + status += nxd_ipv6_enable(&ip_1); + if (status) + error_counter++; +} + + +static void thread_0_entry(ULONG thread_input) +{ + +NX_PACKET *packet_ptr; + + NX_PARAMETER_NOT_USED(thread_input); + + printf("NetX Test: UDP IPv6 Reply Invalid Address Test......................"); + + /* Let the IP threads bring the links up. */ + tx_thread_sleep(NX_IP_PERIODIC_RATE); + + /* The addresses are valid at once, since ICMPv6 is not yet enabled. */ + address_0.nxd_ip_version = NX_IP_VERSION_V6; + address_0.nxd_ip_address.v6[0] = 0x20010000; + address_0.nxd_ip_address.v6[1] = 0; + address_0.nxd_ip_address.v6[2] = 0; + address_0.nxd_ip_address.v6[3] = 4; + address_1 = address_0; + address_1.nxd_ip_address.v6[3] = 5; + if (nxd_ipv6_address_set(&ip_0, 0, &address_0, 64, NX_NULL) || + nxd_ipv6_address_set(&ip_1, 0, &address_1, 64, NX_NULL) || + nxd_icmp_enable(&ip_0) || nxd_icmp_enable(&ip_1)) + error_counter++; + + if (nx_udp_socket_create(&ip_0, &socket_0, "Socket 0", NX_IP_NORMAL, NX_FRAGMENT_OKAY, 0x80, 5) || + nx_udp_socket_bind(&socket_0, 0x88, NX_NO_WAIT) || + nx_udp_socket_create(&ip_1, &socket_1, "Socket 1", NX_IP_NORMAL, NX_FRAGMENT_OKAY, 0x80, 5) || + nx_udp_socket_bind(&socket_1, 0x89, NX_NO_WAIT)) + error_counter++; + + /* Both ways work to begin with. */ + round_trip(); + + /* 1. The address deleted. */ + if (datagram_receive(&packet_ptr) == NX_SUCCESS) + { + if (nxd_ipv6_address_delete(&ip_0, 0)) + error_counter++; + if (reply_send(packet_ptr) != NX_NO_INTERFACE_ADDRESS) + error_counter++; + nx_packet_release(packet_ptr); + if (address_restore() == NX_SUCCESS) + round_trip(); + } + + /* 2. IPv6 disabled. */ + if (datagram_receive(&packet_ptr) == NX_SUCCESS) + { + if (nxd_ipv6_disable(&ip_0)) + error_counter++; + if (reply_send(packet_ptr) != NX_NO_INTERFACE_ADDRESS) + error_counter++; + nx_packet_release(packet_ptr); + + /* Disabling IPv6 also stopped ICMPv6. */ + if (nxd_ipv6_enable(&ip_0) || nxd_icmp_enable(&ip_0)) + error_counter++; + if (address_restore() == NX_SUCCESS) + round_trip(); + } + +#ifndef NX_DISABLE_IPV6_DAD + /* 3. The address deleted and set again: it is checked for duplicates + before it is valid. Once it is, the refused packet goes out. */ + if (datagram_receive(&packet_ptr) == NX_SUCCESS) + { + if (nxd_ipv6_address_delete(&ip_0, 0) || + nxd_ipv6_address_set(&ip_0, 0, &address_0, 64, NX_NULL)) + error_counter++; + if (ip_0.nx_ipv6_address[0].nxd_ipv6_address_state != NX_IPV6_ADDR_STATE_TENTATIVE) + error_counter++; + if (reply_send(packet_ptr) != NX_NO_INTERFACE_ADDRESS) + { + error_counter++; + } + else if (address_restore() == NX_SUCCESS) + { + if (reply_send(packet_ptr) != NX_SUCCESS) + error_counter++; + else if (nx_udp_socket_receive(&socket_1, &packet_ptr, NX_IP_PERIODIC_RATE)) + error_counter++; + else + nx_packet_release(packet_ptr); + } + } +#endif /* NX_DISABLE_IPV6_DAD */ + + if (error_counter) + { + printf("ERROR!\n"); + test_control_return(1); + } + else + { + printf("SUCCESS!\n"); + test_control_return(0); + } +} + + +/* Sends each reply it is given back to ip_1, then waits for the next. */ +static void thread_1_entry(ULONG thread_input) +{ + + NX_PARAMETER_NOT_USED(thread_input); + + while (1) + { + reply_status = nxd_udp_socket_send(&socket_0, reply_ptr, &address_1, 0x89); + reply_done = NX_TRUE; + tx_thread_suspend(&thread_1); + } +} + + +/* ip_1 sends a datagram to ip_0, which receives it. */ +static UINT datagram_receive(NX_PACKET **packet_ptr) +{ + +NX_PACKET *send_ptr; + + if (nx_packet_allocate(&pool_0, &send_ptr, NX_IPv6_UDP_PACKET, NX_NO_WAIT) || + nx_packet_data_append(send_ptr, "request", 7, &pool_0, NX_NO_WAIT)) + { + error_counter++; + return(NX_NOT_SUCCESSFUL); + } + if (nxd_udp_socket_send(&socket_1, send_ptr, &address_0, 0x88)) + { + nx_packet_release(send_ptr); + error_counter++; + return(NX_NOT_SUCCESSFUL); + } + if (nx_udp_socket_receive(&socket_0, packet_ptr, NX_IP_PERIODIC_RATE)) + { + error_counter++; + return(NX_NOT_SUCCESSFUL); + } + return(NX_SUCCESS); +} + + +/* Has thread 1 send the packet as a reply. If the send has not returned + within a second, the IP instance is stuck: the test stops there. A refused + packet is still the caller's; a sent one is not, so the pointer is cleared. */ +static UINT reply_send(NX_PACKET *packet_ptr) +{ + +UINT i; + + reply_ptr = packet_ptr; + reply_done = NX_FALSE; + tx_thread_resume(&thread_1); + for (i = 0; (i < NX_IP_PERIODIC_RATE) && (reply_done == NX_FALSE); i++) + tx_thread_sleep(1); + + if (reply_done == NX_FALSE) + { + printf("ERROR!\n"); + test_control_return(1); + } + + return(reply_status); +} + + +/* Gives ip_0 its address back and waits, bounded, until it is valid. */ +static UINT address_restore(VOID) +{ + +UINT i; + + if ((ip_0.nx_ipv6_address[0].nxd_ipv6_address_state == NX_IPV6_ADDR_STATE_UNKNOWN) && + nxd_ipv6_address_set(&ip_0, 0, &address_0, 64, NX_NULL)) + { + error_counter++; + return(NX_NOT_SUCCESSFUL); + } + for (i = 0; (i < 10 * NX_IP_PERIODIC_RATE) && + (ip_0.nx_ipv6_address[0].nxd_ipv6_address_state != NX_IPV6_ADDR_STATE_VALID); i++) + tx_thread_sleep(1); + if (ip_0.nx_ipv6_address[0].nxd_ipv6_address_state != NX_IPV6_ADDR_STATE_VALID) + { + error_counter++; + return(NX_NOT_SUCCESSFUL); + } + return(NX_SUCCESS); +} + + +/* A datagram from ip_1 to ip_0, and the received packet sent back as the + reply, from the address it arrived on. */ +static VOID round_trip(VOID) +{ + +NX_PACKET *packet_ptr; +NXD_ADDRESS source; +UINT port; + + if (datagram_receive(&packet_ptr)) + return; + + if (reply_send(packet_ptr) != NX_SUCCESS) + { + nx_packet_release(packet_ptr); + error_counter++; + return; + } + + if (nx_udp_socket_receive(&socket_1, &packet_ptr, NX_IP_PERIODIC_RATE)) + { + error_counter++; + return; + } + if (nxd_udp_packet_info_extract(packet_ptr, &source, NX_NULL, &port, NX_NULL) || + (source.nxd_ip_version != NX_IP_VERSION_V6) || + (source.nxd_ip_address.v6[0] != 0x20010000) || (source.nxd_ip_address.v6[3] != 4) || + (port != 0x88)) + error_counter++; + nx_packet_release(packet_ptr); +} + +#else + +#ifdef CTEST +VOID test_application_define(void *first_unused_memory) +#else +void netx_udp_ipv6_reply_invalid_address_test_application_define(void *first_unused_memory) +#endif +{ + + /* Print out test information banner. */ + printf("NetX Test: UDP IPv6 Reply Invalid Address Test......................N/A\n"); + test_control_return(3); +} + +#endif