From fe53230879b77b1acf50b1569d29725dcd834310 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Mon, 30 Mar 2026 11:00:43 +0200 Subject: [PATCH 01/20] allow TCP send in close-wait F/1591 --- src/test/unit/unit.c | 1 + src/test/unit/unit_tests_api.c | 29 +++++++++++++++++++++++++++++ src/wolfip.c | 3 ++- 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/src/test/unit/unit.c b/src/test/unit/unit.c index b11b18c..2f6b82d 100644 --- a/src/test/unit/unit.c +++ b/src/test/unit/unit.c @@ -209,6 +209,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_utils, test_sock_sendto_error_paths); tcase_add_test(tc_utils, test_sock_sendto_null_buf_or_len_zero); tcase_add_test(tc_utils, test_sock_sendto_tcp_not_established); + tcase_add_test(tc_utils, test_sock_sendto_tcp_close_wait_allowed); tcase_add_test(tc_utils, test_sock_recvfrom_tcp_states); tcase_add_test(tc_utils, test_sock_recvfrom_tcp_close_wait_empty_returns_zero); tcase_add_test(tc_utils, test_sock_recvfrom_tcp_close_wait_with_data); diff --git a/src/test/unit/unit_tests_api.c b/src/test/unit/unit_tests_api.c index c6e30b3..084889c 100644 --- a/src/test/unit/unit_tests_api.c +++ b/src/test/unit/unit_tests_api.c @@ -2628,6 +2628,35 @@ START_TEST(test_sock_sendto_tcp_not_established) } END_TEST +START_TEST(test_sock_sendto_tcp_close_wait_allowed) +{ + struct wolfIP s; + int tcp_sd; + struct tsocket *ts; + struct pkt_desc *desc; + uint8_t buf[4] = {1, 2, 3, 4}; + + wolfIP_init(&s); + mock_link_init(&s); + + tcp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_STREAM, WI_IPPROTO_TCP); + ck_assert_int_gt(tcp_sd, 0); + ts = &s.tcpsockets[SOCKET_UNMARK(tcp_sd)]; + ts->sock.tcp.state = TCP_CLOSE_WAIT; + ts->sock.tcp.peer_mss = TCP_MSS; + ts->src_port = 1234; + ts->dst_port = 4321; + ts->local_ip = 0x0A000001U; + ts->remote_ip = 0x0A000002U; + fifo_init(&ts->sock.tcp.txbuf, ts->txmem, TXBUF_SIZE); + + ck_assert_int_eq(wolfIP_sock_sendto(&s, tcp_sd, buf, sizeof(buf), 0, NULL, 0), + (int)sizeof(buf)); + desc = fifo_peek(&ts->sock.tcp.txbuf); + ck_assert_ptr_nonnull(desc); +} +END_TEST + START_TEST(test_sock_sendto_udp_sets_dest_and_assigns) { struct wolfIP s; diff --git a/src/wolfip.c b/src/wolfip.c index 221678f..ffe9a37 100644 --- a/src/wolfip.c +++ b/src/wolfip.c @@ -4396,7 +4396,8 @@ int wolfIP_sock_sendto(struct wolfIP *s, int sockfd, const void *buf, size_t len return -WOLFIP_EINVAL; ts = &s->tcpsockets[SOCKET_UNMARK(sockfd)]; - if (ts->sock.tcp.state != TCP_ESTABLISHED) + if (ts->sock.tcp.state != TCP_ESTABLISHED && + ts->sock.tcp.state != TCP_CLOSE_WAIT) return -1; while (sent < len) { From 3329b84125db25d5809ed9a96e522b6df09ca042 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Mon, 30 Mar 2026 11:03:36 +0200 Subject: [PATCH 02/20] Fix DHCP option byte order handling F/1592 --- src/test/unit/unit.c | 1 + src/test/unit/unit_tests_dns_dhcp.c | 18 +++++++++++++++++ src/wolfip.c | 31 +++++++++++++++-------------- 3 files changed, 35 insertions(+), 15 deletions(-) diff --git a/src/test/unit/unit.c b/src/test/unit/unit.c index 2f6b82d..570857a 100644 --- a/src/test/unit/unit.c +++ b/src/test/unit/unit.c @@ -697,6 +697,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_proto, test_udp_recvfrom_null_addrlen); tcase_add_test(tc_proto, test_udp_recvfrom_src_equals_local_ip_does_not_persist_remote); tcase_add_test(tc_proto, test_dns_query_and_callback_a); + tcase_add_test(tc_proto, test_dhcp_option_u32_macros_round_trip_wire_order); tcase_add_test(tc_proto, test_dhcp_parse_offer_and_ack); tcase_add_test(tc_proto, test_dhcp_schedule_lease_timer_defaults_t1_t2); tcase_add_test(tc_proto, test_dhcp_schedule_lease_timer_small_lease_clamps_t1_t2); diff --git a/src/test/unit/unit_tests_dns_dhcp.c b/src/test/unit/unit_tests_dns_dhcp.c index 35f0eea..2295417 100644 --- a/src/test/unit/unit_tests_dns_dhcp.c +++ b/src/test/unit/unit_tests_dns_dhcp.c @@ -53,6 +53,24 @@ static void build_dhcp_ack_msg(struct dhcp_msg *msg, uint32_t server_ip, uint32_ opt->len = 0; } +START_TEST(test_dhcp_option_u32_macros_round_trip_wire_order) +{ + struct dhcp_option opt; + uint32_t value = 0x0A000001U; + + memset(&opt, 0, sizeof(opt)); + opt.len = 4; + + DHCP_OPT_u32_to_data(&opt, value); + + ck_assert_uint_eq(opt.data[0], 0x0AU); + ck_assert_uint_eq(opt.data[1], 0x00U); + ck_assert_uint_eq(opt.data[2], 0x00U); + ck_assert_uint_eq(opt.data[3], 0x01U); + ck_assert_uint_eq(DHCP_OPT_data_to_u32(&opt), value); +} +END_TEST + START_TEST(test_wolfip_static_instance_apis) { struct wolfIP *s = NULL; diff --git a/src/wolfip.c b/src/wolfip.c index ffe9a37..dfa2b71 100644 --- a/src/wolfip.c +++ b/src/wolfip.c @@ -5291,8 +5291,10 @@ static void dhcp_cancel_timer(struct wolfIP *s) } #define DHCP_OPT_data_to_u32(opt) \ - ((opt)->data[0] | ((opt)->data[1] << 8) | \ - ((opt)->data[2] << 16) | ((opt)->data[3] << 24)) + (((uint32_t)(opt)->data[0] << 24) | \ + ((uint32_t)(opt)->data[1] << 16) | \ + ((uint32_t)(opt)->data[2] << 8) | \ + ((uint32_t)(opt)->data[3] << 0)) #define DHCP_OPT_u32_to_data(opt, v) \ do { \ @@ -5303,10 +5305,9 @@ static void dhcp_cancel_timer(struct wolfIP *s) } while (0) /* Default netmask (returned if the offer does not deliver one) - * must be in network order (same order as DHCP_OPT_data_to_u32 on the field, - * if present). + * is stored in host order, matching DHCP_OPT_data_to_u32(). */ -#define DHCP_DEFAULT_24BIT_NETMASK (0x00FFFFFFu) +#define DHCP_DEFAULT_24BIT_NETMASK (0xFFFFFF00u) static int dhcp_parse_offer(struct wolfIP *s, struct dhcp_msg *msg, uint32_t msg_len) { @@ -5373,7 +5374,7 @@ static int dhcp_parse_offer(struct wolfIP *s, struct dhcp_msg *msg, uint32_t msg if (code == DHCP_OPTION_SERVER_ID) { if (len < 4) return -1; - s->dhcp_server_ip = ee32(DHCP_OPT_data_to_u32(inner)); + s->dhcp_server_ip = DHCP_OPT_data_to_u32(inner); } if (code == DHCP_OPTION_SUBNET_MASK) { if (len < 4) @@ -5387,7 +5388,7 @@ static int dhcp_parse_offer(struct wolfIP *s, struct dhcp_msg *msg, uint32_t msg ip = ee32(msg->yiaddr); if (primary) { primary->ip = ip; - primary->mask = ee32(netmask); + primary->mask = netmask; } s->dhcp_ip = ip; dhcp_cancel_timer(s); @@ -5511,39 +5512,39 @@ static int dhcp_parse_ack(struct wolfIP *s, struct dhcp_msg *msg, uint32_t msg_l if (len < 4) return -1; data = DHCP_OPT_data_to_u32(inner); - s->dhcp_server_ip = ee32(data); + s->dhcp_server_ip = data; } else if (primary && code == DHCP_OPTION_OFFER_IP) { if (len < 4) return -1; data = DHCP_OPT_data_to_u32(inner); - primary->ip = ee32(data); + primary->ip = data; } else if (primary && code == DHCP_OPTION_SUBNET_MASK) { if (len < 4) return -1; data = DHCP_OPT_data_to_u32(inner); - primary->mask = ee32(data); + primary->mask = data; } else if (primary && code == DHCP_OPTION_ROUTER) { if (len < 4) return -1; data = DHCP_OPT_data_to_u32(inner); - primary->gw = ee32(data); + primary->gw = data; } else if ((code == DHCP_OPTION_DNS) && (s->dns_server == 0)) { if (len < 4) return -1; data = DHCP_OPT_data_to_u32(inner); - s->dns_server = ee32(data); + s->dns_server = data; } else if (code == DHCP_OPTION_LEASE_TIME) { if (len < 4) return -1; - lease_s = ee32(DHCP_OPT_data_to_u32(inner)); + lease_s = DHCP_OPT_data_to_u32(inner); } else if (code == DHCP_OPTION_RENEWAL_TIME) { if (len < 4) return -1; - renew_s = ee32(DHCP_OPT_data_to_u32(inner)); + renew_s = DHCP_OPT_data_to_u32(inner); } else if (code == DHCP_OPTION_REBIND_TIME) { if (len < 4) return -1; - rebind_s = ee32(DHCP_OPT_data_to_u32(inner)); + rebind_s = DHCP_OPT_data_to_u32(inner); } opt += 2 + len; } From 055457dc324aeb63b95285f8ba285507e58d586e Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Mon, 30 Mar 2026 11:06:47 +0200 Subject: [PATCH 03/20] Fix PAWS timestamp wrap comparison F/1583 --- src/test/unit/unit.c | 1 + src/test/unit/unit_tests_proto.c | 86 ++++++++++++++++++++++++++++++++ src/wolfip.c | 2 +- 3 files changed, 88 insertions(+), 1 deletion(-) diff --git a/src/test/unit/unit.c b/src/test/unit/unit.c index 570857a..546ed32 100644 --- a/src/test/unit/unit.c +++ b/src/test/unit/unit.c @@ -719,6 +719,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_proto, test_regression_syn_on_last_ack_not_silently_processed); tcase_add_test(tc_proto, test_regression_fast_recovery_cwnd_ssthresh_rfc5681); tcase_add_test(tc_proto, test_regression_paws_rejects_stale_timestamp); + tcase_add_test(tc_proto, test_regression_paws_accepts_wrapped_newer_timestamp); tcase_add_test(tc_proto, test_regression_dhcp_nak_restarts_configuration); tcase_add_test(tc_proto, test_regression_dns_rcode_error_aborts_query); tcase_add_test(tc_proto, test_regression_udp_checksum_zero_substituted_with_ffff); diff --git a/src/test/unit/unit_tests_proto.c b/src/test/unit/unit_tests_proto.c index ef49308..0cf2060 100644 --- a/src/test/unit/unit_tests_proto.c +++ b/src/test/unit/unit_tests_proto.c @@ -4788,6 +4788,92 @@ START_TEST(test_regression_paws_rejects_stale_timestamp) } END_TEST +/* RFC 7323 §5.2: TSval ordering is modulo 2^32, so after wrap a low + * TSval can still be newer than TS.Recent. PAWS must not reject such + * segments just because the raw unsigned value is smaller. */ +START_TEST(test_regression_paws_accepts_wrapped_newer_timestamp) +{ + struct wolfIP s; + struct tsocket *ts; + uint8_t buf[sizeof(struct wolfIP_tcp_seg) + TCP_OPTIONS_LEN + 4]; + struct wolfIP_tcp_seg *seg = (struct wolfIP_tcp_seg *)buf; + struct tcp_opt_ts *tsopt; + uint8_t payload[4] = {0xCA, 0xFE, 0xBA, 0xBE}; + uint8_t out[sizeof(payload)] = {0}; + uint32_t original_ack; + uint32_t tcp_hlen = TCP_HEADER_LEN + TCP_OPTIONS_LEN; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + last_frame_sent_size = 0; + + s.arp.neighbors[0].ip = 0x0A000002U; + s.arp.neighbors[0].if_idx = TEST_PRIMARY_IF; + memcpy(s.arp.neighbors[0].mac, + (uint8_t[]){0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF}, 6); + + ts = &s.tcpsockets[0]; + memset(ts, 0, sizeof(*ts)); + ts->proto = WI_IPPROTO_TCP; + ts->S = &s; + ts->if_idx = TEST_PRIMARY_IF; + ts->sock.tcp.state = TCP_ESTABLISHED; + ts->sock.tcp.ack = 100; + ts->sock.tcp.seq = 1000; + ts->sock.tcp.snd_una = 1000; + ts->sock.tcp.cwnd = TCP_MSS; + ts->sock.tcp.peer_rwnd = TCP_MSS; + ts->sock.tcp.ts_enabled = 1; + ts->sock.tcp.last_ts = ee32(0xFFFFFFF0U); /* TS.Recent just before wrap */ + ts->src_port = 1234; + ts->dst_port = 4321; + ts->local_ip = 0x0A000001U; + ts->remote_ip = 0x0A000002U; + queue_init(&ts->sock.tcp.rxbuf, ts->rxmem, RXBUF_SIZE, ts->sock.tcp.ack); + fifo_init(&ts->sock.tcp.txbuf, ts->txmem, TXBUF_SIZE); + + original_ack = ts->sock.tcp.ack; + + memset(buf, 0, sizeof(buf)); + seg->ip.ver_ihl = 0x45; + seg->ip.ttl = 64; + seg->ip.proto = WI_IPPROTO_TCP; + seg->ip.len = ee16(IP_HEADER_LEN + tcp_hlen + sizeof(payload)); + seg->ip.src = ee32(ts->remote_ip); + seg->ip.dst = ee32(ts->local_ip); + seg->dst_port = ee16(ts->src_port); + seg->src_port = ee16(ts->dst_port); + seg->hlen = (uint8_t)(tcp_hlen << 2); + seg->flags = TCP_FLAG_ACK; + seg->seq = ee32(100); /* == rcv_nxt, in-window */ + seg->ack = ee32(ts->sock.tcp.seq); + seg->win = ee16(65535); + + tsopt = (struct tcp_opt_ts *)seg->data; + tsopt->opt = TCP_OPTION_TS; + tsopt->len = TCP_OPTION_TS_LEN; + tsopt->val = ee32(0x00000010U); /* Newer than 0xFFFFFFF0 modulo 2^32 */ + tsopt->ecr = ee32(500); + tsopt->pad = TCP_OPTION_NOP; + tsopt->eoo = TCP_OPTION_NOP; + + memcpy(seg->data + TCP_OPTIONS_LEN, payload, sizeof(payload)); + fix_tcp_checksums(seg); + + tcp_input(&s, TEST_PRIMARY_IF, seg, + (uint32_t)(ETH_HEADER_LEN + IP_HEADER_LEN + tcp_hlen + sizeof(payload))); + + (void)wolfIP_poll(&s, 200); + + ck_assert_uint_eq(ts->sock.tcp.ack, original_ack + (uint32_t)sizeof(payload)); + ck_assert_uint_eq(queue_pop(&ts->sock.tcp.rxbuf, out, sizeof(out)), + (uint32_t)sizeof(payload)); + ck_assert_mem_eq(out, payload, sizeof(payload)); + ck_assert_uint_eq(ee32(ts->sock.tcp.last_ts), 0x00000010U); +} +END_TEST + /* RFC 2131 s4.4.1: if the client receives a DHCPNAK, it must restart * the configuration process. The current code silently ignores NAKs diff --git a/src/wolfip.c b/src/wolfip.c index dfa2b71..1ca337f 100644 --- a/src/wolfip.c +++ b/src/wolfip.c @@ -3791,7 +3791,7 @@ static void tcp_input(struct wolfIP *S, unsigned int if_idx, struct tcp_parsed_opts po; tcp_parse_options(tcp, frame_len, &po); if (po.ts_found && - po.ts_val < ee32(t->sock.tcp.last_ts)) { + tcp_seq_lt(po.ts_val, ee32(t->sock.tcp.last_ts))) { tcp_send_ack(t); continue; } From 712094de483f1514116b42d4f17102bf264ff66a Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Mon, 30 Mar 2026 11:12:34 +0200 Subject: [PATCH 04/20] Fix TCP fast recovery ACK handling F/1584 --- src/test/unit/unit_tests_proto.c | 102 +++++++++++++++++++++++++++---- src/wolfip.c | 43 ++++++++++++- 2 files changed, 133 insertions(+), 12 deletions(-) diff --git a/src/test/unit/unit_tests_proto.c b/src/test/unit/unit_tests_proto.c index 0cf2060..94c992f 100644 --- a/src/test/unit/unit_tests_proto.c +++ b/src/test/unit/unit_tests_proto.c @@ -4587,11 +4587,13 @@ START_TEST(test_regression_fast_recovery_cwnd_ssthresh_rfc5681) struct wolfIP s; struct tsocket *ts; struct wolfIP_tcp_seg seg; + struct pkt_desc *desc; uint32_t smss; uint32_t flight_size; uint32_t expected_ssthresh; uint32_t expected_cwnd; uint32_t cwnd_after_3rd; + uint8_t txbuf[ETH_HEADER_LEN + IP_HEADER_LEN + TCP_HEADER_LEN + TCP_MSS]; int i; wolfIP_init(&s); @@ -4624,19 +4626,32 @@ START_TEST(test_regression_fast_recovery_cwnd_ssthresh_rfc5681) queue_init(&ts->sock.tcp.rxbuf, ts->rxmem, RXBUF_SIZE, ts->sock.tcp.ack); fifo_init(&ts->sock.tcp.txbuf, ts->txmem, TXBUF_SIZE); - /* Enqueue a sent segment at seq=snd_una so the retransmit path has - * something to work with (payload=8 bytes). */ - { - uint32_t saved_seq = ts->sock.tcp.seq; - struct pkt_desc *desc; - ts->sock.tcp.seq = ts->sock.tcp.snd_una; - enqueue_tcp_tx(ts, 8, TCP_FLAG_ACK | TCP_FLAG_PSH); - desc = fifo_peek(&ts->sock.tcp.txbuf); - ck_assert_ptr_nonnull(desc); + /* Enqueue four full-sized sent segments so partial ACK handling can + * advance cumulatively while still leaving data outstanding. */ + memset(txbuf, 0xAB, sizeof(txbuf)); + for (i = 0; i < 4; i++) { + struct wolfIP_tcp_seg *out = (struct wolfIP_tcp_seg *)txbuf; + uint32_t total_len = IP_HEADER_LEN + TCP_HEADER_LEN + smss; + uint32_t frame_len = ETH_HEADER_LEN + total_len; + memset(txbuf, 0, ETH_HEADER_LEN + IP_HEADER_LEN + TCP_HEADER_LEN); + out->ip.len = ee16((uint16_t)total_len); + out->hlen = TCP_HEADER_LEN << 2; + out->flags = TCP_FLAG_ACK | TCP_FLAG_PSH; + out->seq = ee32(ts->sock.tcp.snd_una + (i * smss)); + out->ack = ee32(ts->sock.tcp.ack); + out->src_port = ee16(ts->src_port); + out->dst_port = ee16(ts->dst_port); + memset((uint8_t *)out->ip.data + TCP_HEADER_LEN, 0xAB, smss); + ck_assert_int_eq(fifo_push(&ts->sock.tcp.txbuf, out, frame_len), 0); + } + desc = fifo_peek(&ts->sock.tcp.txbuf); + while (desc) { desc->flags |= PKT_FLAG_SENT; - ts->sock.tcp.seq = 1000 + flight_size; - (void)saved_seq; + desc = fifo_next(&ts->sock.tcp.txbuf, desc); + if (desc == fifo_peek(&ts->sock.tcp.txbuf)) + break; } + ts->sock.tcp.seq = 1000 + flight_size; /* --- Phase 1: send 3 duplicate ACKs to enter fast recovery --- */ for (i = 0; i < 3; i++) { @@ -4693,6 +4708,71 @@ START_TEST(test_regression_fast_recovery_cwnd_ssthresh_rfc5681) /* (c) cwnd should be inflated by exactly SMSS, not recomputed */ ck_assert_uint_eq(ts->sock.tcp.cwnd, cwnd_after_3rd + smss); + + /* Simulate the fast retransmit having been sent so a partial ACK can + * acknowledge it and expose the next hole. */ + desc = fifo_peek(&ts->sock.tcp.txbuf); + ck_assert_ptr_nonnull(desc); + ck_assert_int_ne(desc->flags & PKT_FLAG_RETRANS, 0); + desc->flags |= PKT_FLAG_SENT; + desc->flags &= ~PKT_FLAG_RETRANS; + desc->flags |= PKT_FLAG_WAS_RETRANS; + + /* --- Phase 3: a partial ACK should stay in recovery and mark the next + * missing segment for retransmission. */ + memset(&seg, 0, sizeof(seg)); + seg.ip.ver_ihl = 0x45; + seg.ip.ttl = 64; + seg.ip.proto = WI_IPPROTO_TCP; + seg.ip.len = ee16(IP_HEADER_LEN + TCP_HEADER_LEN); + seg.ip.src = ee32(ts->remote_ip); + seg.ip.dst = ee32(ts->local_ip); + seg.dst_port = ee16(ts->src_port); + seg.src_port = ee16(ts->dst_port); + seg.hlen = TCP_HEADER_LEN << 2; + seg.flags = TCP_FLAG_ACK; + seg.seq = ee32(ts->sock.tcp.ack); + seg.ack = ee32(1000 + smss); + seg.win = ee16(65535); + fix_tcp_checksums(&seg); + + tcp_input(&s, TEST_PRIMARY_IF, &seg, + (uint32_t)(ETH_HEADER_LEN + IP_HEADER_LEN + TCP_HEADER_LEN)); + + ck_assert_uint_eq(ts->sock.tcp.snd_una, 1000 + smss); + ck_assert_uint_eq(ts->sock.tcp.cwnd, cwnd_after_3rd + smss); + desc = fifo_peek(&ts->sock.tcp.txbuf); + ck_assert_ptr_nonnull(desc); + ck_assert_uint_eq(ee32(((struct wolfIP_tcp_seg *)(ts->txmem + desc->pos + sizeof(*desc)))->seq), + 1000 + smss); + ck_assert_int_ne(desc->flags & PKT_FLAG_RETRANS, 0); + + /* Simulate the second retransmission being sent, then ACK all data that + * was outstanding when recovery began. */ + desc->flags |= PKT_FLAG_SENT; + desc->flags &= ~PKT_FLAG_RETRANS; + desc->flags |= PKT_FLAG_WAS_RETRANS; + + memset(&seg, 0, sizeof(seg)); + seg.ip.ver_ihl = 0x45; + seg.ip.ttl = 64; + seg.ip.proto = WI_IPPROTO_TCP; + seg.ip.len = ee16(IP_HEADER_LEN + TCP_HEADER_LEN); + seg.ip.src = ee32(ts->remote_ip); + seg.ip.dst = ee32(ts->local_ip); + seg.dst_port = ee16(ts->src_port); + seg.src_port = ee16(ts->dst_port); + seg.hlen = TCP_HEADER_LEN << 2; + seg.flags = TCP_FLAG_ACK; + seg.seq = ee32(ts->sock.tcp.ack); + seg.ack = ee32(1000 + flight_size); + seg.win = ee16(65535); + fix_tcp_checksums(&seg); + + tcp_input(&s, TEST_PRIMARY_IF, &seg, + (uint32_t)(ETH_HEADER_LEN + IP_HEADER_LEN + TCP_HEADER_LEN)); + + ck_assert_uint_eq(ts->sock.tcp.cwnd, ts->sock.tcp.ssthresh); } END_TEST diff --git a/src/wolfip.c b/src/wolfip.c index 1ca337f..907f4e5 100644 --- a/src/wolfip.c +++ b/src/wolfip.c @@ -1049,11 +1049,13 @@ enum tcp_state { struct tcpsocket { enum tcp_state state; uint32_t last_ts, rtt, rto, cwnd, cwnd_count, ssthresh, tmr_rto, rto_backoff, - tmr_persist, seq, ack, last_ack, last, bytes_in_flight, snd_una; + tmr_persist, seq, ack, last_ack, last, bytes_in_flight, snd_una, + recovery_point; uint32_t srtt, rttvar; uint32_t last_early_rexmit_ack; uint8_t rto_initialized; uint8_t dup_acks; + uint8_t fast_recovery; uint8_t early_rexmit_done; uint8_t persist_backoff; uint8_t persist_active; @@ -1994,7 +1996,9 @@ static struct tsocket *tcp_new_socket(struct wolfIP *s) t->sock.tcp.tmr_persist = NO_TIMER; t->sock.tcp.bytes_in_flight = 0; t->sock.tcp.snd_una = t->sock.tcp.seq; + t->sock.tcp.recovery_point = t->sock.tcp.snd_una; t->sock.tcp.dup_acks = 0; + t->sock.tcp.fast_recovery = 0; t->sock.tcp.early_rexmit_done = 0; t->sock.tcp.persist_backoff = 0; t->sock.tcp.persist_active = 0; @@ -3354,6 +3358,8 @@ static void tcp_ack(struct tsocket *t, const struct wolfIP_tcp_seg *tcp) struct pkt_desc *desc; int ack_count = 0; int ack_advanced = 0; + int recovery_partial_ack = 0; + int recovery_exit_ack = 0; uint32_t inflight_pre = t->sock.tcp.bytes_in_flight; if (t->sock.tcp.state == TCP_LAST_ACK && tcp_seq_leq(fin_acked, ack)) { @@ -3398,6 +3404,7 @@ static void tcp_ack(struct tsocket *t, const struct wolfIP_tcp_seg *tcp) tcp_seq_leq(t->sock.tcp.snd_una, ack) && tcp_seq_leq(ack, t->sock.tcp.seq)) { uint32_t delta; + uint32_t smss = tcp_cc_mss(t); if (ack >= t->sock.tcp.snd_una) delta = ack - t->sock.tcp.snd_una; else @@ -3428,6 +3435,30 @@ static void tcp_ack(struct tsocket *t, const struct wolfIP_tcp_seg *tcp) if (t->sock.tcp.bytes_in_flight < inflight_pre) { t->events |= CB_EVENT_WRITABLE; } + if (t->sock.tcp.fast_recovery) { + if (tcp_seq_lt(ack, t->sock.tcp.recovery_point)) { + recovery_partial_ack = 1; + t->sock.tcp.dup_acks = 3; + if (delta >= t->sock.tcp.cwnd) + t->sock.tcp.cwnd = smss; + else + t->sock.tcp.cwnd -= delta; + t->sock.tcp.cwnd += smss; + if (t->sock.tcp.cwnd < t->sock.tcp.ssthresh + smss) + t->sock.tcp.cwnd = t->sock.tcp.ssthresh + smss; + t->sock.tcp.cwnd_count = 0; + (void)tcp_mark_unsacked_for_retransmit(t, ack); + } else { + recovery_exit_ack = 1; + t->sock.tcp.fast_recovery = 0; + t->sock.tcp.recovery_point = ack; + t->sock.tcp.dup_acks = 0; + t->sock.tcp.cwnd = t->sock.tcp.ssthresh; + t->sock.tcp.cwnd_count = 0; + } + } else { + t->sock.tcp.dup_acks = 0; + } ack_advanced = 1; } if (ack_count > 0) { @@ -3463,6 +3494,8 @@ static void tcp_ack(struct tsocket *t, const struct wolfIP_tcp_seg *tcp) { uint32_t smss = tcp_cc_mss(t); if (ack_advanced && + !recovery_partial_ack && + !recovery_exit_ack && ((t->sock.tcp.cwnd <= inflight_pre + smss) || (t->sock.tcp.cwnd <= 2 * smss))) { if (t->sock.tcp.cwnd < t->sock.tcp.ssthresh) { @@ -3508,6 +3541,8 @@ static void tcp_ack(struct tsocket *t, const struct wolfIP_tcp_seg *tcp) t->sock.tcp.ssthresh = 2 * smss; t->sock.tcp.cwnd = t->sock.tcp.ssthresh + 3 * smss; t->sock.tcp.cwnd_count = 0; + t->sock.tcp.fast_recovery = 1; + t->sock.tcp.recovery_point = t->sock.tcp.seq; (void)tcp_mark_unsacked_for_retransmit(t, ack); } else { /* RFC 5681 §3.2 step 4: inflate cwnd by SMSS for each @@ -3720,6 +3755,8 @@ static void tcp_input(struct wolfIP *S, unsigned int if_idx, t->sock.tcp.ack = tcp_seq_inc(ee32(tcp->seq), 1); t->sock.tcp.seq = ee32(tcp->ack); t->sock.tcp.snd_una = t->sock.tcp.seq; + t->sock.tcp.recovery_point = t->sock.tcp.snd_una; + t->sock.tcp.fast_recovery = 0; t->sock.tcp.cwnd = tcp_initial_cwnd(t->sock.tcp.peer_rwnd, tcp_cc_mss(t)); t->sock.tcp.ssthresh = tcp_initial_ssthresh(t->sock.tcp.peer_rwnd); if (tx_has_writable_space(t)) @@ -4003,6 +4040,8 @@ static void tcp_rto_cb(void *arg) if (ts->sock.tcp.ssthresh < (2 * smss)) ts->sock.tcp.ssthresh = 2 * smss; } + ts->sock.tcp.fast_recovery = 0; + ts->sock.tcp.recovery_point = ts->sock.tcp.snd_una; ptmr = &tmr; ptmr->expires = ts->S->last_tick + (ts->sock.tcp.rto << ts->sock.tcp.rto_backoff); @@ -4317,6 +4356,8 @@ int wolfIP_sock_accept(struct wolfIP *s, int sockfd, struct wolfIP_sockaddr *add newts->sock.tcp.ack = ts->sock.tcp.ack; newts->sock.tcp.seq = ts->sock.tcp.seq; newts->sock.tcp.snd_una = newts->sock.tcp.seq; + newts->sock.tcp.recovery_point = newts->sock.tcp.snd_una; + newts->sock.tcp.fast_recovery = 0; newts->sock.tcp.last_ts = ts->sock.tcp.last_ts; newts->sock.tcp.peer_rwnd = ts->sock.tcp.peer_rwnd; newts->sock.tcp.cwnd = tcp_initial_cwnd(newts->sock.tcp.peer_rwnd, tcp_cc_mss(newts)); From 10f4014bac5c117bbb1572bc005e611433ab4df2 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Mon, 30 Mar 2026 11:19:01 +0200 Subject: [PATCH 05/20] Handle inbound ICMP dest-unreach for TCP F/1585 --- src/test/unit/unit.c | 2 + src/test/unit/unit_tests_dns_dhcp.c | 107 ++++++++++++++++++++++++++++ src/wolfip.c | 73 +++++++++++++++++++ 3 files changed, 182 insertions(+) diff --git a/src/test/unit/unit.c b/src/test/unit/unit.c index 546ed32..3afec7e 100644 --- a/src/test/unit/unit.c +++ b/src/test/unit/unit.c @@ -689,6 +689,8 @@ Suite *wolf_suite(void) tcase_add_test(tc_proto, test_icmp_input_echo_request_ip_filter_drop); tcase_add_test(tc_proto, test_icmp_input_echo_request_eth_filter_drop); tcase_add_test(tc_proto, test_icmp_input_filter_drop_receiving); + tcase_add_test(tc_proto, test_icmp_input_dest_unreach_port_unreachable_closes_matching_tcp_socket); + tcase_add_test(tc_proto, test_icmp_input_dest_unreach_frag_needed_reduces_tcp_peer_mss); tcase_add_test(tc_proto, test_udp_sendto_and_recvfrom); tcase_add_test(tc_proto, test_udp_sendto_respects_mtu_api); tcase_add_test(tc_proto, test_udp_recvfrom_sets_remote_ip); diff --git a/src/test/unit/unit_tests_dns_dhcp.c b/src/test/unit/unit_tests_dns_dhcp.c index 2295417..0f2d8a5 100644 --- a/src/test/unit/unit_tests_dns_dhcp.c +++ b/src/test/unit/unit_tests_dns_dhcp.c @@ -1990,6 +1990,113 @@ START_TEST(test_icmp_input_filter_drop_receiving) wolfIP_filter_set_icmp_mask(0); } END_TEST + +START_TEST(test_icmp_input_dest_unreach_port_unreachable_closes_matching_tcp_socket) +{ + struct wolfIP s; + struct tsocket *ts; + struct wolfIP_icmp_dest_unreachable_packet icmp; + struct wolfIP_tcp_seg *orig; + uint32_t frame_len; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + ts = &s.tcpsockets[0]; + memset(ts, 0, sizeof(*ts)); + ts->proto = WI_IPPROTO_TCP; + ts->S = &s; + ts->sock.tcp.state = TCP_ESTABLISHED; + ts->local_ip = 0x0A000001U; + ts->remote_ip = 0x0A000002U; + ts->src_port = 1234; + ts->dst_port = 4321; + + memset(&icmp, 0, sizeof(icmp)); + icmp.ip.src = ee32(0x0A0000FEU); + icmp.ip.dst = ee32(ts->local_ip); + icmp.ip.ttl = 64; + icmp.ip.proto = WI_IPPROTO_ICMP; + icmp.ip.len = ee16(IP_HEADER_LEN + ICMP_DEST_UNREACH_SIZE); + icmp.type = ICMP_DEST_UNREACH; + icmp.code = ICMP_PORT_UNREACH; + + orig = (struct wolfIP_tcp_seg *)icmp.orig_packet; + orig->ip.ver_ihl = 0x45; + orig->ip.proto = WI_IPPROTO_TCP; + orig->ip.src = ee32(ts->local_ip); + orig->ip.dst = ee32(ts->remote_ip); + orig->ip.len = ee16(IP_HEADER_LEN + TCP_HEADER_LEN); + orig->src_port = ee16(ts->src_port); + orig->dst_port = ee16(ts->dst_port); + orig->hlen = TCP_HEADER_LEN << 2; + + icmp.csum = ee16(icmp_checksum((struct wolfIP_icmp_packet *)&icmp, + ICMP_DEST_UNREACH_SIZE)); + frame_len = (uint32_t)(ETH_HEADER_LEN + IP_HEADER_LEN + ICMP_DEST_UNREACH_SIZE); + + icmp_input(&s, TEST_PRIMARY_IF, (struct wolfIP_ip_packet *)&icmp, frame_len); + + ck_assert_uint_eq(ts->proto, 0U); +} +END_TEST + +START_TEST(test_icmp_input_dest_unreach_frag_needed_reduces_tcp_peer_mss) +{ + struct wolfIP s; + struct tsocket *ts; + struct wolfIP_icmp_dest_unreachable_packet icmp; + struct wolfIP_tcp_seg *orig; + uint32_t frame_len; + uint16_t next_hop_mtu; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + ts = &s.tcpsockets[0]; + memset(ts, 0, sizeof(*ts)); + ts->proto = WI_IPPROTO_TCP; + ts->S = &s; + ts->sock.tcp.state = TCP_ESTABLISHED; + ts->local_ip = 0x0A000001U; + ts->remote_ip = 0x0A000002U; + ts->src_port = 1234; + ts->dst_port = 4321; + ts->sock.tcp.peer_mss = 1200U; + + memset(&icmp, 0, sizeof(icmp)); + icmp.ip.src = ee32(0x0A0000FEU); + icmp.ip.dst = ee32(ts->local_ip); + icmp.ip.ttl = 64; + icmp.ip.proto = WI_IPPROTO_ICMP; + icmp.ip.len = ee16(IP_HEADER_LEN + ICMP_DEST_UNREACH_SIZE); + icmp.type = ICMP_DEST_UNREACH; + icmp.code = 4; + next_hop_mtu = ee16(576U); + memcpy(&icmp.unused[2], &next_hop_mtu, sizeof(next_hop_mtu)); + + orig = (struct wolfIP_tcp_seg *)icmp.orig_packet; + orig->ip.ver_ihl = 0x45; + orig->ip.proto = WI_IPPROTO_TCP; + orig->ip.src = ee32(ts->local_ip); + orig->ip.dst = ee32(ts->remote_ip); + orig->ip.len = ee16(IP_HEADER_LEN + TCP_HEADER_LEN); + orig->src_port = ee16(ts->src_port); + orig->dst_port = ee16(ts->dst_port); + orig->hlen = TCP_HEADER_LEN << 2; + + icmp.csum = ee16(icmp_checksum((struct wolfIP_icmp_packet *)&icmp, + ICMP_DEST_UNREACH_SIZE)); + frame_len = (uint32_t)(ETH_HEADER_LEN + IP_HEADER_LEN + ICMP_DEST_UNREACH_SIZE); + + icmp_input(&s, TEST_PRIMARY_IF, (struct wolfIP_ip_packet *)&icmp, frame_len); + + ck_assert_uint_eq(ts->sock.tcp.peer_mss, 536U); +} +END_TEST + START_TEST(test_dns_send_query_errors) { struct wolfIP s; diff --git a/src/wolfip.c b/src/wolfip.c index 907f4e5..0e0e3e3 100644 --- a/src/wolfip.c +++ b/src/wolfip.c @@ -77,7 +77,9 @@ struct wolfIP_icmp_packet; #define ICMP_ECHO_REQUEST 8 #define ICMP_TTL_EXCEEDED 11 #define ICMP_DEST_UNREACH 3 +#define ICMP_PROT_UNREACH 2 #define ICMP_PORT_UNREACH 3 +#define ICMP_FRAG_NEEDED 4 #define WI_IPPROTO_ICMP 0x01 #define WI_IPPROTO_TCP 0x06 @@ -1955,6 +1957,74 @@ static void icmp_try_recv(struct wolfIP *s, unsigned int if_idx, } } +static void icmp_try_deliver_tcp_error(struct wolfIP *s, + const struct wolfIP_icmp_packet *icmp) +{ + const struct wolfIP_ip_packet *orig_ip; + const struct wolfIP_tcp_seg *orig_tcp; + uint32_t icmp_len; + uint32_t avail; + uint32_t orig_hlen; + int i; + + if (!s || !icmp) + return; + if ((icmp->type != ICMP_DEST_UNREACH) && (icmp->type != ICMP_TTL_EXCEEDED)) + return; + + icmp_len = (uint32_t)(ee16(icmp->ip.len) - IP_HEADER_LEN); + if (icmp_len <= ICMP_HEADER_LEN) + return; + avail = icmp_len - ICMP_HEADER_LEN; + if (avail < IP_HEADER_LEN) + return; + + orig_ip = (const struct wolfIP_ip_packet *)((const uint8_t *)icmp + + sizeof(struct wolfIP_icmp_packet)); + orig_hlen = (uint32_t)((orig_ip->ver_ihl & 0x0FU) << 2); + if (orig_hlen < IP_HEADER_LEN || orig_hlen > avail) + return; + if (orig_ip->proto != WI_IPPROTO_TCP) + return; + if (avail < (orig_hlen + 8U)) + return; + + orig_tcp = (const struct wolfIP_tcp_seg *)orig_ip; + for (i = 0; i < MAX_TCPSOCKETS; i++) { + struct tsocket *t = &s->tcpsockets[i]; + + if (t->proto != WI_IPPROTO_TCP) + continue; + if (t->sock.tcp.state == TCP_CLOSED || t->sock.tcp.state == TCP_LISTEN) + continue; + if (t->local_ip != ee32(orig_ip->src) || t->remote_ip != ee32(orig_ip->dst)) + continue; + if (t->src_port != ee16(orig_tcp->src_port) || + t->dst_port != ee16(orig_tcp->dst_port)) + continue; + + if (icmp->type == ICMP_DEST_UNREACH) { + if (icmp->code == ICMP_FRAG_NEEDED) { + uint16_t next_hop_mtu = 0; + + memcpy(&next_hop_mtu, &icmp->unused[2], sizeof(next_hop_mtu)); + next_hop_mtu = ee16(next_hop_mtu); + if (next_hop_mtu > (IP_HEADER_LEN + TCP_HEADER_LEN)) { + uint16_t new_mss = + (uint16_t)(next_hop_mtu - (IP_HEADER_LEN + TCP_HEADER_LEN)); + + if (t->sock.tcp.peer_mss == 0 || new_mss < t->sock.tcp.peer_mss) + t->sock.tcp.peer_mss = new_mss; + } + } else if (icmp->code == ICMP_PROT_UNREACH || + icmp->code == ICMP_PORT_UNREACH) { + close_socket(t); + } + } + break; + } +} + /* TCP */ static uint32_t tcp_initial_cwnd(uint32_t peer_rwnd, uint32_t smss) { @@ -5190,7 +5260,10 @@ static void icmp_input(struct wolfIP *s, unsigned int if_idx, struct wolfIP_ip_p } #endif wolfIP_ll_send_frame(s, if_idx, ip, len); + return; } + icmp_try_deliver_tcp_error(s, icmp); + icmp_try_recv(s, if_idx, icmp, len); } static int dhcp_send_discover(struct wolfIP *s); From 54a1785ead9853563dd250fd728674fa9a33b563 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Mon, 30 Mar 2026 11:22:01 +0200 Subject: [PATCH 06/20] fix ICMP writable-space threshold F/1593 --- src/test/unit/unit.c | 1 + src/test/unit/unit_tests_proto.c | 16 ++++++++++++++++ src/wolfip.c | 2 +- 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/test/unit/unit.c b/src/test/unit/unit.c index 3afec7e..63bc4e4 100644 --- a/src/test/unit/unit.c +++ b/src/test/unit/unit.c @@ -107,6 +107,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_utils, test_wolfip_getdev_ex_api); tcase_add_test(tc_utils, test_wolfip_ll_frame_mtu_enforces_minimum); tcase_add_test(tc_utils, test_transport_capacity_helpers_cover_guard_paths); + tcase_add_test(tc_utils, test_tx_has_writable_space_icmp_accepts_minimal_packet); tcase_add_test(tc_utils, test_wolfip_if_for_local_ip_single_interface_falls_back_to_zero); tcase_add_test(tc_utils, test_wolfip_mtu_set_get_api); tcase_add_test(tc_utils, test_wolfip_ll_at_and_ipconf_at_invalid); diff --git a/src/test/unit/unit_tests_proto.c b/src/test/unit/unit_tests_proto.c index 94c992f..a866ffc 100644 --- a/src/test/unit/unit_tests_proto.c +++ b/src/test/unit/unit_tests_proto.c @@ -2733,6 +2733,22 @@ START_TEST(test_transport_capacity_helpers_cover_guard_paths) } END_TEST +START_TEST(test_tx_has_writable_space_icmp_accepts_minimal_packet) +{ + struct tsocket ts; + + memset(&ts, 0, sizeof(ts)); + ts.proto = WI_IPPROTO_ICMP; + fifo_init(&ts.sock.udp.txbuf, ts.txmem, TXBUF_SIZE); + + /* Exact room for one descriptor plus the minimal queued ICMP frame. */ + ts.sock.udp.txbuf.size = + (uint32_t)(sizeof(struct pkt_desc) + sizeof(struct wolfIP_icmp_packet)); + + ck_assert_int_ne(tx_has_writable_space(&ts), 0); +} +END_TEST + START_TEST(test_wolfip_if_for_local_ip_single_interface_falls_back_to_zero) { struct wolfIP s; diff --git a/src/wolfip.c b/src/wolfip.c index 0e0e3e3..d05bad4 100644 --- a/src/wolfip.c +++ b/src/wolfip.c @@ -1248,7 +1248,7 @@ static inline int tx_has_writable_space(const struct tsocket *t) return fifo_can_push_len((const struct fifo *)&t->sock.udp.txbuf, min_len); } if (t->proto == WI_IPPROTO_ICMP) { - min_len = (uint32_t)(sizeof(struct wolfIP_icmp_packet) + ICMP_HEADER_LEN); + min_len = (uint32_t)sizeof(struct wolfIP_icmp_packet); return fifo_can_push_len((const struct fifo *)&t->sock.udp.txbuf, min_len); } return 0; From 7e7797354f80d97c837e20c63d91e5c8c9df1804 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Mon, 30 Mar 2026 11:25:18 +0200 Subject: [PATCH 07/20] Fix UDP and ICMP sendto FIFO guard F/1594 --- src/wolfip.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/wolfip.c b/src/wolfip.c index d05bad4..4890d8f 100644 --- a/src/wolfip.c +++ b/src/wolfip.c @@ -227,7 +227,7 @@ static void fifo_init(struct fifo *f, uint8_t *data, uint32_t size) } /* Return the number of bytes available */ -static uint32_t fifo_space(struct fifo *f) +static uint32_t __attribute__((unused)) fifo_space(struct fifo *f) { if (fifo_is_empty(f)) return f->size; @@ -4577,6 +4577,7 @@ int wolfIP_sock_sendto(struct wolfIP *s, int sockfd, const void *buf, size_t len unsigned int if_idx; struct ipconf *conf; uint32_t ip_mtu; + uint32_t frame_len; if (SOCKET_UNMARK(sockfd) >= MAX_UDPSOCKETS) return -WOLFIP_EINVAL; @@ -4613,7 +4614,8 @@ int wolfIP_sock_sendto(struct wolfIP *s, int sockfd, const void *buf, size_t len if (ip_mtu <= (IP_HEADER_LEN + UDP_HEADER_LEN) || len > ip_mtu - IP_HEADER_LEN - UDP_HEADER_LEN) return -1; /* Fragmentation not supported */ - if (fifo_space(&ts->sock.udp.txbuf) < len) { + frame_len = (uint32_t)sizeof(struct wolfIP_udp_datagram) + (uint32_t)len; + if (!fifo_can_push_len(&ts->sock.udp.txbuf, frame_len)) { return -WOLFIP_EAGAIN; } @@ -4622,7 +4624,7 @@ int wolfIP_sock_sendto(struct wolfIP *s, int sockfd, const void *buf, size_t len udp->len = ee16(len + UDP_HEADER_LEN); udp->csum = 0; memcpy(udp->data, buf, len); - if (fifo_push(&ts->sock.udp.txbuf, udp, sizeof(struct wolfIP_udp_datagram) + len) < 0) + if (fifo_push(&ts->sock.udp.txbuf, udp, frame_len) < 0) return -WOLFIP_EAGAIN; return len; } else if (IS_SOCKET_ICMP(sockfd)) { @@ -4631,6 +4633,7 @@ int wolfIP_sock_sendto(struct wolfIP *s, int sockfd, const void *buf, size_t len struct ipconf *conf; uint32_t payload_len = (uint32_t)len; uint32_t ip_mtu; + uint32_t frame_len; if (SOCKET_UNMARK(sockfd) >= MAX_ICMPSOCKETS) return -WOLFIP_EINVAL; ts = &s->icmpsockets[SOCKET_UNMARK(sockfd)]; @@ -4672,7 +4675,8 @@ int wolfIP_sock_sendto(struct wolfIP *s, int sockfd, const void *buf, size_t len payload_len < ICMP_HEADER_LEN || payload_len > (ip_mtu - IP_HEADER_LEN)) return -WOLFIP_EINVAL; - if (fifo_space(&ts->sock.udp.txbuf) < payload_len) { + frame_len = (uint32_t)sizeof(struct wolfIP_ip_packet) + payload_len; + if (!fifo_can_push_len(&ts->sock.udp.txbuf, frame_len)) { return -WOLFIP_EAGAIN; } if (sizeof(struct wolfIP_ip_packet) + payload_len > sizeof(frame)) @@ -4682,7 +4686,7 @@ int wolfIP_sock_sendto(struct wolfIP *s, int sockfd, const void *buf, size_t len icmp_set_echo_id(icmp, ts->src_port); icmp->csum = 0; icmp->csum = ee16(icmp_checksum(icmp, (uint16_t)payload_len)); - if (fifo_push(&ts->sock.udp.txbuf, icmp, sizeof(struct wolfIP_ip_packet) + payload_len) < 0) + if (fifo_push(&ts->sock.udp.txbuf, icmp, frame_len) < 0) return -WOLFIP_EAGAIN; return (int)payload_len; } else return -1; From fb080b017bb46d9e64f98c0261ee74e48d72c7d7 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Mon, 30 Mar 2026 11:27:28 +0200 Subject: [PATCH 08/20] Reject truncated DNS UDP responses F/1586 --- src/test/unit/unit.c | 1 + src/test/unit/unit_tests_dns_dhcp.c | 56 +++++++++++++++++++++++++++++ src/wolfip.c | 11 ++++-- 3 files changed, 66 insertions(+), 2 deletions(-) diff --git a/src/test/unit/unit.c b/src/test/unit/unit.c index 63bc4e4..5dc7cd0 100644 --- a/src/test/unit/unit.c +++ b/src/test/unit/unit.c @@ -335,6 +335,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_utils, test_udp_try_recv_unmatched_port_sends_icmp_unreachable); tcase_add_test(tc_utils, test_udp_try_recv_unmatched_nonlocal_dst_does_not_send_icmp); tcase_add_test(tc_utils, test_dns_callback_bad_flags); + tcase_add_test(tc_utils, test_dns_callback_truncated_response_aborts_query); tcase_add_test(tc_utils, test_dns_callback_bad_name); tcase_add_test(tc_utils, test_dns_callback_short_header_ignored); tcase_add_test(tc_utils, test_dns_callback_wrong_id_ignored); diff --git a/src/test/unit/unit_tests_dns_dhcp.c b/src/test/unit/unit_tests_dns_dhcp.c index 0f2d8a5..820df0a 100644 --- a/src/test/unit/unit_tests_dns_dhcp.c +++ b/src/test/unit/unit_tests_dns_dhcp.c @@ -4424,6 +4424,62 @@ START_TEST(test_dns_callback_bad_flags) } END_TEST +START_TEST(test_dns_callback_truncated_response_aborts_query) +{ + struct wolfIP s; + uint8_t response[128]; + int pos; + struct dns_header *hdr = (struct dns_header *)response; + struct dns_question *q; + struct dns_rr *rr; + const uint8_t ip_bytes[4] = {0x0A, 0x00, 0x00, 0x42}; + + wolfIP_init(&s); + mock_link_init(&s); + s.dns_server = 0x0A000001U; + s.dns_query_type = DNS_QUERY_TYPE_A; + s.dns_id = 0x1234; + s.dns_lookup_cb = test_dns_lookup_cb; + dns_lookup_calls = 0; + dns_lookup_ip = 0; + s.dns_udp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_gt(s.dns_udp_sd, 0); + + memset(response, 0, sizeof(response)); + hdr->id = ee16(s.dns_id); + hdr->flags = ee16(0x8300); + hdr->qdcount = ee16(1); + hdr->ancount = ee16(1); + pos = sizeof(struct dns_header); + response[pos++] = 7; memcpy(&response[pos], "example", 7); pos += 7; + response[pos++] = 3; memcpy(&response[pos], "com", 3); pos += 3; + response[pos++] = 0; + q = (struct dns_question *)(response + pos); + q->qtype = ee16(DNS_A); + q->qclass = ee16(1); + pos += sizeof(struct dns_question); + response[pos++] = 0xC0; + response[pos++] = (uint8_t)sizeof(struct dns_header); + rr = (struct dns_rr *)(response + pos); + rr->type = ee16(DNS_A); + rr->class = ee16(1); + rr->ttl = ee32(60); + rr->rdlength = ee16(4); + pos += sizeof(struct dns_rr); + memcpy(&response[pos], ip_bytes, sizeof(ip_bytes)); + pos += sizeof(ip_bytes); + + enqueue_udp_rx(&s.udpsockets[SOCKET_UNMARK(s.dns_udp_sd)], response, (uint16_t)pos, DNS_PORT); + dns_callback(s.dns_udp_sd, CB_EVENT_READABLE, &s); + + ck_assert_int_eq(dns_lookup_calls, 0); + ck_assert_uint_eq(dns_lookup_ip, 0U); + ck_assert_uint_eq(s.dns_id, 0); + ck_assert_int_eq(s.dns_query_type, DNS_QUERY_TYPE_NONE); + ck_assert_ptr_eq(s.dns_lookup_cb, NULL); +} +END_TEST + START_TEST(test_dns_callback_bad_name) { struct wolfIP s; diff --git a/src/wolfip.c b/src/wolfip.c index 4890d8f..440ac41 100644 --- a/src/wolfip.c +++ b/src/wolfip.c @@ -6546,6 +6546,7 @@ void wolfIP_recv_ex(struct wolfIP *s, unsigned int if_idx, void *buf, uint32_t l #define DNS_A 0x01 /* A record only */ #define DNS_PTR 0x0C #define DNS_RD 0x0100 /* Recursion desired */ +#define DNS_TC 0x0200 /* Truncated response */ #define DNS_QUERY_TYPE_NONE 0 #define DNS_QUERY_TYPE_A 1 #define DNS_QUERY_TYPE_PTR 2 @@ -6789,6 +6790,7 @@ void dns_callback(int dns_sd, uint16_t ev, void *arg) struct wolfIP *s = (struct wolfIP *)arg; char buf[MAX_DNS_RESPONSE]; struct dns_header *hdr = (struct dns_header *)buf; + uint16_t flags; int dns_len; int pos; int qcount; @@ -6808,10 +6810,15 @@ void dns_callback(int dns_sd, uint16_t ev, void *arg) return; if (ee16(hdr->id) != s->dns_id) return; + flags = ee16(hdr->flags); /* Parse DNS response */ - if ((ee16(hdr->flags) & 0x8100) == 0x8100) { + if ((flags & 0x8100) == 0x8100) { + if ((flags & DNS_TC) != 0) { + dns_abort_query(s); + return; + } /* RFC 1035 s4.1.1: RCODE != 0 is an error; abort query. */ - if ((ee16(hdr->flags) & 0x000F) != 0) { + if ((flags & 0x000F) != 0) { dns_abort_query(s); return; } From fdb84aef3779a031dfe24b6f6eedc1d1ad475e40 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Mon, 30 Mar 2026 11:32:07 +0200 Subject: [PATCH 09/20] Add FIN_WAIT_2 socket timeout F/1587 --- src/test/unit/unit.c | 2 +- src/test/unit/unit_tests_dns_dhcp.c | 14 ++++++-- src/wolfip.c | 54 +++++++++++++++++++++++++++-- 3 files changed, 65 insertions(+), 5 deletions(-) diff --git a/src/test/unit/unit.c b/src/test/unit/unit.c index 5dc7cd0..6b67e09 100644 --- a/src/test/unit/unit.c +++ b/src/test/unit/unit.c @@ -413,7 +413,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_utils, test_tcp_ctrl_state_needs_rto_fin_wait_1_waits_for_payload_drain); tcase_add_test(tc_utils, test_tcp_rto_cb_fin_wait_1_with_data_uses_data_recovery); tcase_add_test(tc_utils, test_tcp_rto_cb_fin_wait_1_no_data_requeues_finack); - tcase_add_test(tc_utils, test_tcp_ack_fin_wait_1_ack_of_fin_moves_to_fin_wait_2_and_stops_timer); + tcase_add_test(tc_utils, test_tcp_ack_fin_wait_1_ack_of_fin_moves_to_fin_wait_2_and_arms_timeout); tcase_add_test(tc_utils, test_tcp_ack_closing_ack_of_fin_moves_to_time_wait_and_stops_timer); tcase_add_test(tc_utils, test_tcp_rto_cb_control_retry_cap_closes_socket); tcase_add_test(tc_utils, test_tcp_rto_cb_cancels_existing_timer); diff --git a/src/test/unit/unit_tests_dns_dhcp.c b/src/test/unit/unit_tests_dns_dhcp.c index 820df0a..9f5dcce 100644 --- a/src/test/unit/unit_tests_dns_dhcp.c +++ b/src/test/unit/unit_tests_dns_dhcp.c @@ -3177,14 +3177,16 @@ START_TEST(test_tcp_rto_cb_fin_wait_1_no_data_requeues_finack) } END_TEST -START_TEST(test_tcp_ack_fin_wait_1_ack_of_fin_moves_to_fin_wait_2_and_stops_timer) +START_TEST(test_tcp_ack_fin_wait_1_ack_of_fin_moves_to_fin_wait_2_and_arms_timeout) { struct wolfIP s; struct tsocket *ts; struct wolfIP_tcp_seg ackseg; struct wolfIP_timer tmr; + uint64_t timeout_at; wolfIP_init(&s); + s.last_tick = 1000U; ts = &s.tcpsockets[0]; memset(ts, 0, sizeof(*ts)); ts->proto = WI_IPPROTO_TCP; @@ -3213,9 +3215,17 @@ START_TEST(test_tcp_ack_fin_wait_1_ack_of_fin_moves_to_fin_wait_2_and_stops_time tcp_ack(ts, &ackseg); ck_assert_int_eq(ts->sock.tcp.state, TCP_FIN_WAIT_2); - ck_assert_int_eq(ts->sock.tcp.tmr_rto, NO_TIMER); + ck_assert_int_ne(ts->sock.tcp.tmr_rto, NO_TIMER); ck_assert_uint_eq(ts->sock.tcp.ctrl_rto_active, 0); ck_assert_uint_eq(ts->sock.tcp.ctrl_rto_retries, 0); + ck_assert_uint_eq(ts->sock.tcp.fin_wait_2_timeout_active, 1); + timeout_at = find_timer_expiry(&s, ts->sock.tcp.tmr_rto); + ck_assert_uint_eq(timeout_at, s.last_tick + TCP_FIN_WAIT_2_TIMEOUT_MS); + + (void)wolfIP_poll(&s, timeout_at); + + ck_assert_int_eq(ts->proto, 0); + ck_assert_int_eq(ts->sock.tcp.tmr_rto, NO_TIMER); } END_TEST diff --git a/src/wolfip.c b/src/wolfip.c index 440ac41..e2bf174 100644 --- a/src/wolfip.c +++ b/src/wolfip.c @@ -133,6 +133,9 @@ struct wolfIP_icmp_packet; #define TCP_RTO_G_MS 1U #define TCP_PERSIST_MIN_MS 1000U #define TCP_PERSIST_MAX_MS 60000U +#ifndef TCP_FIN_WAIT_2_TIMEOUT_MS +#define TCP_FIN_WAIT_2_TIMEOUT_MS 60000U +#endif /* Arbitrary upper limit to avoid monopolizing the CPU during poll loops. */ #define WOLFIP_POLL_BUDGET 128 @@ -1063,6 +1066,7 @@ struct tcpsocket { uint8_t persist_active; uint8_t ctrl_rto_retries; uint8_t ctrl_rto_active; + uint8_t fin_wait_2_timeout_active; ip4 local_ip, remote_ip; uint32_t peer_rwnd; uint16_t peer_mss; @@ -1116,6 +1120,8 @@ static void tcp_rto_update_from_sample(struct tsocket *t, uint32_t sample_ms); static void tcp_rto_cb(void *arg); static void tcp_ctrl_rto_start(struct tsocket *t, uint64_t now); static void tcp_ctrl_rto_stop(struct tsocket *t); +static void tcp_fin_wait_2_timeout_start(struct tsocket *t, uint64_t now); +static void tcp_fin_wait_2_timeout_stop(struct tsocket *t); static int tcp_ctrl_state_needs_rto(const struct tsocket *t); static int tcp_has_pending_unsent_payload(struct tsocket *t); static inline struct wolfIP_ll_dev *wolfIP_ll_at(struct wolfIP *s, unsigned int if_idx); @@ -2687,6 +2693,7 @@ static void tcp_ctrl_rto_start(struct tsocket *t, uint64_t now) uint64_t shift_rto; if (!t || t->proto != WI_IPPROTO_TCP) return; + t->sock.tcp.fin_wait_2_timeout_active = 0; if (t->sock.tcp.tmr_rto != NO_TIMER) { timer_binheap_cancel(&t->S->timers, t->sock.tcp.tmr_rto); t->sock.tcp.tmr_rto = NO_TIMER; @@ -2699,6 +2706,34 @@ static void tcp_ctrl_rto_start(struct tsocket *t, uint64_t now) t->sock.tcp.ctrl_rto_active = 1; } +static void tcp_fin_wait_2_timeout_start(struct tsocket *t, uint64_t now) +{ + struct wolfIP_timer tmr = {0}; + + if (!t || t->proto != WI_IPPROTO_TCP) + return; + if (t->sock.tcp.tmr_rto != NO_TIMER) { + timer_binheap_cancel(&t->S->timers, t->sock.tcp.tmr_rto); + t->sock.tcp.tmr_rto = NO_TIMER; + } + tmr.expires = now + TCP_FIN_WAIT_2_TIMEOUT_MS; + tmr.arg = t; + tmr.cb = tcp_rto_cb; + t->sock.tcp.tmr_rto = timers_binheap_insert(&t->S->timers, tmr); + t->sock.tcp.fin_wait_2_timeout_active = 1; +} + +static void tcp_fin_wait_2_timeout_stop(struct tsocket *t) +{ + if (!t || t->proto != WI_IPPROTO_TCP) + return; + if (t->sock.tcp.tmr_rto != NO_TIMER) { + timer_binheap_cancel(&t->S->timers, t->sock.tcp.tmr_rto); + t->sock.tcp.tmr_rto = NO_TIMER; + } + t->sock.tcp.fin_wait_2_timeout_active = 0; +} + static int tcp_has_pending_unsent_payload(struct tsocket *t) { struct pkt_desc *desc; @@ -3441,6 +3476,7 @@ static void tcp_ack(struct tsocket *t, const struct wolfIP_tcp_seg *tcp) if (t->sock.tcp.state == TCP_FIN_WAIT_1 && tcp_seq_leq(fin_acked, ack)) { t->sock.tcp.state = TCP_FIN_WAIT_2; tcp_ctrl_rto_stop(t); + tcp_fin_wait_2_timeout_start(t, t->S->last_tick); } if (t->sock.tcp.state == TCP_CLOSING && tcp_seq_leq(fin_acked, ack)) { t->sock.tcp.state = TCP_TIME_WAIT; @@ -3491,11 +3527,13 @@ static void tcp_ack(struct tsocket *t, const struct wolfIP_tcp_seg *tcp) * stop the current RTO timer. If bytes remain in-flight and no new * send happens immediately, we must re-arm RTO here to avoid stalls. */ t->sock.tcp.rto_backoff = 0; - if (t->sock.tcp.tmr_rto != NO_TIMER) { + if (!t->sock.tcp.fin_wait_2_timeout_active && + t->sock.tcp.tmr_rto != NO_TIMER) { timer_binheap_cancel(&t->S->timers, t->sock.tcp.tmr_rto); t->sock.tcp.tmr_rto = NO_TIMER; } - if (t->sock.tcp.bytes_in_flight > 0) { + if (!t->sock.tcp.fin_wait_2_timeout_active && + t->sock.tcp.bytes_in_flight > 0) { struct wolfIP_timer new_tmr = { 0 }; new_tmr.cb = tcp_rto_cb; new_tmr.expires = t->S->last_tick + t->sock.tcp.rto; @@ -3943,6 +3981,7 @@ static void tcp_input(struct wolfIP *S, unsigned int if_idx, } else if (t->sock.tcp.state == TCP_FIN_WAIT_1) { t->sock.tcp.state = TCP_CLOSING; } else if (t->sock.tcp.state == TCP_FIN_WAIT_2) { + tcp_fin_wait_2_timeout_stop(t); t->sock.tcp.state = TCP_TIME_WAIT; } if (tcplen > 0) { @@ -3989,6 +4028,16 @@ static void tcp_rto_cb(void *arg) uint32_t prev_in_flight; if (ts->proto != WI_IPPROTO_TCP) return; + if (ts->sock.tcp.fin_wait_2_timeout_active) { + if (ts->sock.tcp.state != TCP_FIN_WAIT_2) { + tcp_fin_wait_2_timeout_stop(ts); + return; + } + tcp_fin_wait_2_timeout_stop(ts); + ts->sock.tcp.state = TCP_CLOSED; + close_socket(ts); + return; + } if (tcp_ctrl_state_needs_rto(ts) || ts->sock.tcp.ctrl_rto_active) { if (!tcp_ctrl_state_needs_rto(ts)) { tcp_ctrl_rto_stop(ts); @@ -4919,6 +4968,7 @@ int wolfIP_sock_close(struct wolfIP *s, int sockfd) ts->sock.tcp.state = TCP_CLOSING; return -WOLFIP_EAGAIN; } else if (ts->sock.tcp.state == TCP_FIN_WAIT_2) { + tcp_fin_wait_2_timeout_stop(ts); ts->sock.tcp.state = TCP_TIME_WAIT; return -WOLFIP_EAGAIN; } else if (ts->sock.tcp.state != TCP_CLOSED) { From 9f9becf06f6e3207bdcd2e96184ce3531469b35c Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Mon, 30 Mar 2026 11:36:25 +0200 Subject: [PATCH 10/20] Set DHCP secs from elapsed process time F/1588 --- src/test/unit/unit.c | 1 + src/test/unit/unit_tests_dns_dhcp.c | 39 +++++++++++++++++++++++++++++ src/wolfip.c | 27 ++++++++++++++++++++ 3 files changed, 67 insertions(+) diff --git a/src/test/unit/unit.c b/src/test/unit/unit.c index 6b67e09..6784555 100644 --- a/src/test/unit/unit.c +++ b/src/test/unit/unit.c @@ -321,6 +321,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_utils, test_dhcp_send_request_rebinding_broadcasts_to_lease_expiry); tcase_add_test(tc_utils, test_dhcp_send_request_send_failure_retries_next_tick); tcase_add_test(tc_utils, test_dhcp_send_discover_send_failure_retries_next_tick); + tcase_add_test(tc_utils, test_dhcp_messages_set_secs_from_process_start); tcase_add_test(tc_utils, test_dhcp_poll_offer_and_ack); tcase_add_test(tc_utils, test_dhcp_poll_renewing_ack_binds_client); tcase_add_test(tc_utils, test_dhcp_poll_rebinding_ack_binds_client); diff --git a/src/test/unit/unit_tests_dns_dhcp.c b/src/test/unit/unit_tests_dns_dhcp.c index 9f5dcce..b9926d1 100644 --- a/src/test/unit/unit_tests_dns_dhcp.c +++ b/src/test/unit/unit_tests_dns_dhcp.c @@ -975,6 +975,45 @@ START_TEST(test_dhcp_send_discover_send_failure_retries_next_tick) } END_TEST +START_TEST(test_dhcp_messages_set_secs_from_process_start) +{ + struct wolfIP s; + struct tsocket *ts; + struct pkt_desc *desc; + struct wolfIP_udp_datagram *udp; + struct dhcp_msg *msg; + + wolfIP_init(&s); + mock_link_init(&s); + s.dhcp_xid = 1U; + s.dhcp_udp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_gt(s.dhcp_udp_sd, 0); + ts = &s.udpsockets[SOCKET_UNMARK(s.dhcp_udp_sd)]; + + s.dhcp_state = DHCP_DISCOVER_SENT; + s.dhcp_start_tick = 1000U; + s.last_tick = 3500U; + ck_assert_int_eq(dhcp_send_discover(&s), 0); + + desc = fifo_peek(&ts->sock.udp.txbuf); + ck_assert_ptr_nonnull(desc); + udp = (struct wolfIP_udp_datagram *)(ts->txmem + desc->pos + sizeof(*desc)); + msg = (struct dhcp_msg *)udp->data; + ck_assert_uint_eq(ee16(msg->secs), 2U); + + ck_assert_ptr_nonnull(fifo_pop(&ts->sock.udp.txbuf)); + s.dhcp_state = DHCP_REQUEST_SENT; + s.last_tick = 4200U; + ck_assert_int_eq(dhcp_send_request(&s), 0); + + desc = fifo_peek(&ts->sock.udp.txbuf); + ck_assert_ptr_nonnull(desc); + udp = (struct wolfIP_udp_datagram *)(ts->txmem + desc->pos + sizeof(*desc)); + msg = (struct dhcp_msg *)udp->data; + ck_assert_uint_eq(ee16(msg->secs), 3U); +} +END_TEST + START_TEST(test_sock_connect_tcp_src_port_low) { struct wolfIP s; diff --git a/src/wolfip.c b/src/wolfip.c index e2bf174..4606293 100644 --- a/src/wolfip.c +++ b/src/wolfip.c @@ -1212,6 +1212,7 @@ struct wolfIP { uint64_t dhcp_renew_at; /* Renewal time (T1) */ uint64_t dhcp_rebind_at; /* Rebind time (T2) */ uint64_t dhcp_lease_expires; /* Lease expiration time */ + uint64_t dhcp_start_tick; /* Start time of current DHCP acquisition/renewal */ ip4 dns_server; uint16_t dns_id; int dns_udp_sd; @@ -5348,6 +5349,24 @@ static void dhcp_schedule_retry_timer(struct wolfIP *s, uint64_t deadline) dhcp_schedule_timer_at(s, next); } +static uint16_t dhcp_elapsed_secs(const struct wolfIP *s) +{ + uint64_t elapsed_ms; + uint64_t elapsed_secs; + + if (!s) + return 0; + + elapsed_ms = s->last_tick; + if (s->last_tick >= s->dhcp_start_tick) + elapsed_ms = s->last_tick - s->dhcp_start_tick; + + elapsed_secs = elapsed_ms / 1000U; + if (elapsed_secs > UINT16_MAX) + return UINT16_MAX; + return (uint16_t)elapsed_secs; +} + static void dhcp_schedule_lease_timer(struct wolfIP *s, uint32_t lease_s, uint32_t renew_s, @@ -5418,6 +5437,7 @@ static void dhcp_timer_cb(void *arg) break; } s->dhcp_state = DHCP_RENEWING; + s->dhcp_start_tick = s->last_tick; s->dhcp_timeout_count = 0; ret = dhcp_send_request(s); if (ret >= 0) @@ -5426,6 +5446,7 @@ static void dhcp_timer_cb(void *arg) case DHCP_RENEWING: if (s->dhcp_rebind_at != 0 && s->last_tick >= s->dhcp_rebind_at) { s->dhcp_state = DHCP_REBINDING; + s->dhcp_start_tick = s->last_tick; s->dhcp_timeout_count = 0; } ret = dhcp_send_request(s); @@ -5790,6 +5811,7 @@ static int dhcp_send_request(struct wolfIP *s) req.htype = 1; /* Ethernet */ req.hlen = 6; /* MAC */ req.xid = ee32(s->dhcp_xid); + req.secs = ee16(dhcp_elapsed_secs(s)); req.magic = ee32(DHCP_MAGIC); if ((renewing || rebinding) && primary) req.ciaddr = ee32(primary->ip); @@ -5877,12 +5899,17 @@ static int dhcp_send_discover(struct wolfIP *s) uint64_t retry_at = s ? (s->last_tick + 1U) : 0; int ret; uint32_t opt_sz = 0; + + if (s && s->dhcp_state == DHCP_OFF) + s->dhcp_start_tick = s->last_tick; + /* Prepare DHCP discover */ memset(&disc, 0, sizeof(struct dhcp_msg)); disc.op = BOOT_REQUEST; disc.htype = 1; /* Ethernet */ disc.hlen = 6; /* MAC */ disc.xid = ee32(s->dhcp_xid); + disc.secs = ee16(dhcp_elapsed_secs(s)); disc.magic = ee32(DHCP_MAGIC); { struct wolfIP_ll_dev *ll = wolfIP_ll_at(s, WOLFIP_PRIMARY_IF_IDX); From a6661cd211cd0013c50379abab68fb734358c62d Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Mon, 30 Mar 2026 11:39:17 +0200 Subject: [PATCH 11/20] Validate DHCP reply op field F/1589 --- src/test/unit/unit.c | 2 + src/test/unit/unit_tests_dns_dhcp.c | 6 +++ src/test/unit/unit_tests_tcp_ack.c | 70 +++++++++++++++++++++++++++++ src/wolfip.c | 4 ++ 4 files changed, 82 insertions(+) diff --git a/src/test/unit/unit.c b/src/test/unit/unit.c index 6784555..6366b9d 100644 --- a/src/test/unit/unit.c +++ b/src/test/unit/unit.c @@ -362,6 +362,8 @@ Suite *wolf_suite(void) tcase_add_test(tc_utils, test_dhcp_parse_ack_missing_end_rejected); tcase_add_test(tc_utils, test_dhcp_parse_offer_bad_magic_rejected); tcase_add_test(tc_utils, test_dhcp_parse_ack_bad_magic_rejected); + tcase_add_test(tc_utils, test_dhcp_parse_offer_rejects_boot_request_op); + tcase_add_test(tc_utils, test_dhcp_parse_ack_rejects_boot_request_op); tcase_add_test(tc_utils, test_dhcp_parse_offer_zero_len_option_rejected); tcase_add_test(tc_utils, test_dhcp_parse_ack_zero_len_option_rejected); tcase_add_test(tc_utils, test_dhcp_poll_no_data_and_wrong_state); diff --git a/src/test/unit/unit_tests_dns_dhcp.c b/src/test/unit/unit_tests_dns_dhcp.c index b9926d1..456e330 100644 --- a/src/test/unit/unit_tests_dns_dhcp.c +++ b/src/test/unit/unit_tests_dns_dhcp.c @@ -15,6 +15,7 @@ static void build_dhcp_ack_msg(struct dhcp_msg *msg, uint32_t server_ip, uint32_ struct dhcp_option *opt; memset(msg, 0, sizeof(*msg)); + msg->op = BOOT_REPLY; msg->magic = ee32(DHCP_MAGIC); opt = (struct dhcp_option *)msg->options; opt->code = DHCP_OPTION_MSG_TYPE; @@ -99,6 +100,7 @@ START_TEST(test_dhcp_parse_offer_and_ack) ck_assert_ptr_nonnull(primary); memset(&msg, 0, sizeof(msg)); + msg.op = BOOT_REPLY; msg.magic = ee32(DHCP_MAGIC); msg.yiaddr = ee32(offer_ip); opt = (struct dhcp_option *)msg.options; @@ -131,6 +133,7 @@ START_TEST(test_dhcp_parse_offer_and_ack) s.last_tick = 1000U; memset(&msg, 0, sizeof(msg)); + msg.op = BOOT_REPLY; msg.magic = ee32(DHCP_MAGIC); opt = (struct dhcp_option *)msg.options; opt->code = DHCP_OPTION_MSG_TYPE; @@ -242,6 +245,7 @@ START_TEST(test_dhcp_parse_offer_defaults_mask_when_missing) ck_assert_ptr_nonnull(primary); memset(&msg, 0, sizeof(msg)); + msg.op = BOOT_REPLY; msg.magic = ee32(DHCP_MAGIC); msg.yiaddr = ee32(offer_ip); opt = (struct dhcp_option *)msg.options; @@ -3918,6 +3922,7 @@ START_TEST(test_dhcp_poll_offer_and_ack) ts = &s.udpsockets[SOCKET_UNMARK(s.dhcp_udp_sd)]; memset(&msg, 0, sizeof(msg)); + msg.op = BOOT_REPLY; msg.magic = ee32(DHCP_MAGIC); msg.yiaddr = ee32(0x0A000064U); opt = (struct dhcp_option *)msg.options; @@ -3949,6 +3954,7 @@ START_TEST(test_dhcp_poll_offer_and_ack) ck_assert_int_eq(s.dhcp_state, DHCP_REQUEST_SENT); memset(&msg, 0, sizeof(msg)); + msg.op = BOOT_REPLY; msg.magic = ee32(DHCP_MAGIC); opt = (struct dhcp_option *)msg.options; opt->code = DHCP_OPTION_MSG_TYPE; diff --git a/src/test/unit/unit_tests_tcp_ack.c b/src/test/unit/unit_tests_tcp_ack.c index 870e41d..002154d 100644 --- a/src/test/unit/unit_tests_tcp_ack.c +++ b/src/test/unit/unit_tests_tcp_ack.c @@ -402,6 +402,7 @@ START_TEST(test_dhcp_parse_offer_truncated_option_rejected) wolfIP_init(&s); memset(&msg, 0, sizeof(msg)); + msg.op = BOOT_REPLY; msg.magic = ee32(DHCP_MAGIC); opt = (struct dhcp_option *)msg.options; opt->code = DHCP_OPTION_MSG_TYPE; @@ -419,6 +420,7 @@ START_TEST(test_dhcp_parse_offer_len_lt_four_rejected) wolfIP_init(&s); memset(&msg, 0, sizeof(msg)); + msg.op = BOOT_REPLY; msg.magic = ee32(DHCP_MAGIC); opt = (struct dhcp_option *)msg.options; opt->code = DHCP_OPTION_MSG_TYPE; @@ -446,6 +448,7 @@ START_TEST(test_dhcp_parse_offer_ignores_short_unknown_option) ck_assert_ptr_nonnull(primary); memset(&msg, 0, sizeof(msg)); + msg.op = BOOT_REPLY; msg.magic = ee32(DHCP_MAGIC); msg.yiaddr = ee32(offer_ip); opt = (struct dhcp_option *)msg.options; @@ -488,6 +491,7 @@ START_TEST(test_dhcp_parse_offer_ignores_zero_len_unknown_option) ck_assert_ptr_nonnull(primary); memset(&msg, 0, sizeof(msg)); + msg.op = BOOT_REPLY; msg.magic = ee32(DHCP_MAGIC); msg.yiaddr = ee32(offer_ip); opt = (struct dhcp_option *)msg.options; @@ -523,6 +527,7 @@ START_TEST(test_dhcp_parse_offer_missing_end_rejected) wolfIP_init(&s); memset(&msg, 0, sizeof(msg)); + msg.op = BOOT_REPLY; msg.magic = ee32(DHCP_MAGIC); opt = (struct dhcp_option *)msg.options; opt->code = DHCP_OPTION_MSG_TYPE; @@ -541,6 +546,7 @@ START_TEST(test_dhcp_parse_offer_msg_type_len_ne_1_rejected) wolfIP_init(&s); memset(&msg, 0, sizeof(msg)); + msg.op = BOOT_REPLY; msg.magic = ee32(DHCP_MAGIC); opt = (struct dhcp_option *)msg.options; opt->code = DHCP_OPTION_MSG_TYPE; @@ -563,6 +569,7 @@ START_TEST(test_dhcp_parse_ack_truncated_option_rejected) wolfIP_init(&s); memset(&msg, 0, sizeof(msg)); + msg.op = BOOT_REPLY; msg.magic = ee32(DHCP_MAGIC); opt = (struct dhcp_option *)msg.options; opt->code = DHCP_OPTION_MSG_TYPE; @@ -581,6 +588,7 @@ START_TEST(test_dhcp_parse_ack_msg_type_len_ne_1_rejected) wolfIP_init(&s); memset(&msg, 0, sizeof(msg)); + msg.op = BOOT_REPLY; msg.magic = ee32(DHCP_MAGIC); opt = (struct dhcp_option *)msg.options; opt->code = DHCP_OPTION_MSG_TYPE; @@ -603,6 +611,7 @@ START_TEST(test_dhcp_parse_ack_len_lt_four_rejected) wolfIP_init(&s); memset(&msg, 0, sizeof(msg)); + msg.op = BOOT_REPLY; msg.magic = ee32(DHCP_MAGIC); opt = (struct dhcp_option *)msg.options; opt->code = DHCP_OPTION_MSG_TYPE; @@ -630,6 +639,7 @@ START_TEST(test_dhcp_parse_ack_ignores_short_unknown_option) ck_assert_ptr_nonnull(primary); memset(&msg, 0, sizeof(msg)); + msg.op = BOOT_REPLY; msg.magic = ee32(DHCP_MAGIC); opt = (struct dhcp_option *)msg.options; opt->code = DHCP_OPTION_MSG_TYPE; @@ -677,6 +687,7 @@ START_TEST(test_dhcp_parse_ack_ignores_zero_len_unknown_option) ck_assert_ptr_nonnull(primary); memset(&msg, 0, sizeof(msg)); + msg.op = BOOT_REPLY; msg.magic = ee32(DHCP_MAGIC); opt = (struct dhcp_option *)msg.options; opt->code = DHCP_OPTION_MSG_TYPE; @@ -717,6 +728,7 @@ START_TEST(test_dhcp_parse_ack_missing_end_rejected) wolfIP_init(&s); memset(&msg, 0, sizeof(msg)); + msg.op = BOOT_REPLY; msg.magic = ee32(DHCP_MAGIC); opt = (struct dhcp_option *)msg.options; opt->code = DHCP_OPTION_MSG_TYPE; @@ -740,6 +752,7 @@ START_TEST(test_dhcp_parse_offer_rejects_mismatched_xid) s.dhcp_xid = 0x12345678U; memset(&msg, 0, sizeof(msg)); + msg.op = BOOT_REPLY; msg.magic = ee32(DHCP_MAGIC); msg.xid = ee32(0x87654321U); msg.yiaddr = ee32(0x0A000064U); @@ -774,6 +787,7 @@ START_TEST(test_dhcp_parse_ack_rejects_mismatched_xid) s.dhcp_state = DHCP_REQUEST_SENT; memset(&msg, 0, sizeof(msg)); + msg.op = BOOT_REPLY; msg.magic = ee32(DHCP_MAGIC); msg.xid = ee32(0x87654321U); opt = (struct dhcp_option *)msg.options; @@ -790,6 +804,58 @@ START_TEST(test_dhcp_parse_ack_rejects_mismatched_xid) } END_TEST +START_TEST(test_dhcp_parse_offer_rejects_boot_request_op) +{ + struct wolfIP s; + struct dhcp_msg msg; + struct dhcp_option *opt; + struct ipconf *primary; + + wolfIP_init(&s); + primary = wolfIP_primary_ipconf(&s); + ck_assert_ptr_nonnull(primary); + + memset(&msg, 0, sizeof(msg)); + msg.op = BOOT_REQUEST; + msg.magic = ee32(DHCP_MAGIC); + msg.yiaddr = ee32(0x0A000064U); + opt = (struct dhcp_option *)msg.options; + opt->code = DHCP_OPTION_MSG_TYPE; + opt->len = 1; + opt->data[0] = DHCP_OFFER; + opt = (struct dhcp_option *)((uint8_t *)opt + 3); + opt->code = DHCP_OPTION_END; + opt->len = 0; + + ck_assert_int_eq(dhcp_parse_offer(&s, &msg, sizeof(msg)), -1); + ck_assert_uint_eq(s.dhcp_ip, 0U); + ck_assert_uint_eq(primary->ip, 0U); + ck_assert_int_eq(s.dhcp_state, DHCP_OFF); +} +END_TEST + +START_TEST(test_dhcp_parse_ack_rejects_boot_request_op) +{ + struct wolfIP s; + struct dhcp_msg msg; + struct ipconf *primary; + + wolfIP_init(&s); + primary = wolfIP_primary_ipconf(&s); + ck_assert_ptr_nonnull(primary); + s.dhcp_ip = 0x0A000064U; + s.dhcp_server_ip = 0x0A000001U; + s.dhcp_state = DHCP_REQUEST_SENT; + + build_dhcp_ack_msg(&msg, 0x0A000001U, 0xFFFFFF00U, 0x0A000002U, 0x08080808U); + msg.op = BOOT_REQUEST; + + ck_assert_int_eq(dhcp_parse_ack(&s, &msg, sizeof(msg)), -1); + ck_assert_int_eq(s.dhcp_state, DHCP_REQUEST_SENT); + ck_assert_uint_eq(primary->ip, 0U); +} +END_TEST + START_TEST(test_dhcp_parse_offer_bad_magic_rejected) { struct wolfIP s; @@ -798,6 +864,7 @@ START_TEST(test_dhcp_parse_offer_bad_magic_rejected) wolfIP_init(&s); memset(&msg, 0, sizeof(msg)); + msg.op = BOOT_REPLY; msg.magic = 0; opt = (struct dhcp_option *)msg.options; opt->code = DHCP_OPTION_MSG_TYPE; @@ -819,6 +886,7 @@ START_TEST(test_dhcp_parse_ack_bad_magic_rejected) wolfIP_init(&s); memset(&msg, 0, sizeof(msg)); + msg.op = BOOT_REPLY; msg.magic = 0; opt = (struct dhcp_option *)msg.options; opt->code = DHCP_OPTION_MSG_TYPE; @@ -840,6 +908,7 @@ START_TEST(test_dhcp_parse_offer_zero_len_option_rejected) wolfIP_init(&s); memset(&msg, 0, sizeof(msg)); + msg.op = BOOT_REPLY; msg.magic = ee32(DHCP_MAGIC); opt = (struct dhcp_option *)msg.options; opt->code = DHCP_OPTION_MSG_TYPE; @@ -861,6 +930,7 @@ START_TEST(test_dhcp_parse_ack_zero_len_option_rejected) wolfIP_init(&s); memset(&msg, 0, sizeof(msg)); + msg.op = BOOT_REPLY; msg.magic = ee32(DHCP_MAGIC); opt = (struct dhcp_option *)msg.options; opt->code = DHCP_OPTION_MSG_TYPE; diff --git a/src/wolfip.c b/src/wolfip.c index 4606293..de3781a 100644 --- a/src/wolfip.c +++ b/src/wolfip.c @@ -5508,6 +5508,8 @@ static int dhcp_parse_offer(struct wolfIP *s, struct dhcp_msg *msg, uint32_t msg struct ipconf *primary = wolfIP_primary_ipconf(s); if (msg_len < DHCP_HEADER_LEN) return -1; + if (msg->op != BOOT_REPLY) + return -1; if (ee32(msg->magic) != DHCP_MAGIC) return -1; if (ee32(msg->xid) != s->dhcp_xid) @@ -5644,6 +5646,8 @@ static int dhcp_parse_ack(struct wolfIP *s, struct dhcp_msg *msg, uint32_t msg_l uint32_t rebind_s = 0; if (msg_len < DHCP_HEADER_LEN) return -1; + if (msg->op != BOOT_REPLY) + return -1; if (ee32(msg->magic) != DHCP_MAGIC) return -1; if (ee32(msg->xid) != s->dhcp_xid) From 5a1ec92ea470997070e2a28a9d86238c8138cbdd Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Mon, 30 Mar 2026 11:42:57 +0200 Subject: [PATCH 12/20] Reject BOOT_REQUEST DHCP NAKs F/1590 --- src/test/unit/unit.c | 1 + src/test/unit/unit_tests_proto.c | 42 ++++++++++++++++++++++++++++++++ src/wolfip.c | 2 ++ 3 files changed, 45 insertions(+) diff --git a/src/test/unit/unit.c b/src/test/unit/unit.c index 6366b9d..b6c57ab 100644 --- a/src/test/unit/unit.c +++ b/src/test/unit/unit.c @@ -728,6 +728,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_proto, test_regression_paws_rejects_stale_timestamp); tcase_add_test(tc_proto, test_regression_paws_accepts_wrapped_newer_timestamp); tcase_add_test(tc_proto, test_regression_dhcp_nak_restarts_configuration); + tcase_add_test(tc_proto, test_regression_dhcp_boot_request_nak_ignored); tcase_add_test(tc_proto, test_regression_dns_rcode_error_aborts_query); tcase_add_test(tc_proto, test_regression_udp_checksum_zero_substituted_with_ffff); tcase_add_test(tc_proto, test_regression_last_ack_rejects_out_of_window_segment); diff --git a/src/test/unit/unit_tests_proto.c b/src/test/unit/unit_tests_proto.c index a866ffc..16e7a78 100644 --- a/src/test/unit/unit_tests_proto.c +++ b/src/test/unit/unit_tests_proto.c @@ -5020,6 +5020,48 @@ START_TEST(test_regression_dhcp_nak_restarts_configuration) } END_TEST +/* RFC 2131 s2: server-to-client DHCP messages use BOOT_REPLY. + * A reflected or malformed BOOT_REQUEST must not be treated as a DHCPNAK. */ +START_TEST(test_regression_dhcp_boot_request_nak_ignored) +{ + struct wolfIP s; + struct dhcp_msg msg; + struct dhcp_option *opt; + struct tsocket *ts; + struct ipconf *primary; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000064U, 0xFFFFFF00U, 0); + + s.dhcp_udp_sd = wolfIP_sock_socket(&s, AF_INET, IPSTACK_SOCK_DGRAM, WI_IPPROTO_UDP); + ck_assert_int_gt(s.dhcp_udp_sd, 0); + ts = &s.udpsockets[SOCKET_UNMARK(s.dhcp_udp_sd)]; + + s.dhcp_state = DHCP_RENEWING; + s.dhcp_xid = 0x12345678U; + primary = wolfIP_primary_ipconf(&s); + ck_assert_ptr_nonnull(primary); + + memset(&msg, 0, sizeof(msg)); + msg.op = BOOT_REQUEST; + msg.magic = ee32(DHCP_MAGIC); + msg.xid = ee32(0x12345678U); + opt = (struct dhcp_option *)msg.options; + opt->code = DHCP_OPTION_MSG_TYPE; + opt->len = 1; + opt->data[0] = DHCP_NAK; + opt = (struct dhcp_option *)((uint8_t *)opt + 3); + opt->code = DHCP_OPTION_END; + + enqueue_udp_rx(ts, &msg, sizeof(msg), DHCP_SERVER_PORT); + (void)dhcp_poll(&s); + + ck_assert_int_eq(s.dhcp_state, DHCP_RENEWING); + ck_assert_uint_eq(primary->ip, 0x0A000064U); +} +END_TEST + /* DNS response parser does not check the RCODE field. An error response * such as NXDOMAIN (RCODE=3) passes the QR+RD check, the empty answer * section is silently skipped, and the query stays active until the diff --git a/src/wolfip.c b/src/wolfip.c index de3781a..298ca7e 100644 --- a/src/wolfip.c +++ b/src/wolfip.c @@ -5610,6 +5610,8 @@ static int dhcp_msg_type(struct wolfIP *s, struct dhcp_msg *msg, uint32_t msg_le return -1; if (ee32(msg->xid) != s->dhcp_xid) return -1; + if (msg->op != BOOT_REPLY) + return -1; if (msg_len - DHCP_HEADER_LEN > sizeof(msg->options)) opt_end = (uint8_t *)msg->options + sizeof(msg->options); else From c33a48c2136351d1c7214514bf4640a28ee6bffe Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Mon, 30 Mar 2026 11:45:25 +0200 Subject: [PATCH 13/20] Reject DHCP OFFER without server ID F/1598 --- src/test/unit/unit.c | 1 + src/test/unit/unit_tests_tcp_ack.c | 33 ++++++++++++++++++++++++++++++ src/wolfip.c | 4 +++- 3 files changed, 37 insertions(+), 1 deletion(-) diff --git a/src/test/unit/unit.c b/src/test/unit/unit.c index b6c57ab..d9af408 100644 --- a/src/test/unit/unit.c +++ b/src/test/unit/unit.c @@ -352,6 +352,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_utils, test_dhcp_parse_offer_len_lt_four_rejected); tcase_add_test(tc_utils, test_dhcp_parse_offer_ignores_short_unknown_option); tcase_add_test(tc_utils, test_dhcp_parse_offer_ignores_zero_len_unknown_option); + tcase_add_test(tc_utils, test_dhcp_parse_offer_missing_server_id_rejected); tcase_add_test(tc_utils, test_dhcp_parse_offer_missing_end_rejected); tcase_add_test(tc_utils, test_dhcp_parse_offer_msg_type_len_ne_1_rejected); tcase_add_test(tc_utils, test_dhcp_parse_ack_truncated_option_rejected); diff --git a/src/test/unit/unit_tests_tcp_ack.c b/src/test/unit/unit_tests_tcp_ack.c index 002154d..eaa878b 100644 --- a/src/test/unit/unit_tests_tcp_ack.c +++ b/src/test/unit/unit_tests_tcp_ack.c @@ -519,6 +519,39 @@ START_TEST(test_dhcp_parse_offer_ignores_zero_len_unknown_option) } END_TEST +START_TEST(test_dhcp_parse_offer_missing_server_id_rejected) +{ + struct wolfIP s; + struct dhcp_msg msg; + struct dhcp_option *opt; + struct ipconf *primary; + uint32_t offer_ip = 0x0A000064U; + + wolfIP_init(&s); + primary = wolfIP_primary_ipconf(&s); + ck_assert_ptr_nonnull(primary); + + s.dhcp_server_ip = 0x0A000001U; + + memset(&msg, 0, sizeof(msg)); + msg.op = BOOT_REPLY; + msg.magic = ee32(DHCP_MAGIC); + msg.yiaddr = ee32(offer_ip); + opt = (struct dhcp_option *)msg.options; + opt->code = DHCP_OPTION_MSG_TYPE; + opt->len = 1; + opt->data[0] = DHCP_OFFER; + opt = (struct dhcp_option *)((uint8_t *)opt + 3); + opt->code = DHCP_OPTION_END; + opt->len = 0; + + ck_assert_int_eq(dhcp_parse_offer(&s, &msg, DHCP_HEADER_LEN + 4), -1); + ck_assert_uint_eq(s.dhcp_server_ip, 0x0A000001U); + ck_assert_uint_eq(s.dhcp_ip, 0U); + ck_assert_int_ne(s.dhcp_state, DHCP_REQUEST_SENT); +} +END_TEST + START_TEST(test_dhcp_parse_offer_missing_end_rejected) { struct wolfIP s; diff --git a/src/wolfip.c b/src/wolfip.c index 298ca7e..3158012 100644 --- a/src/wolfip.c +++ b/src/wolfip.c @@ -5503,6 +5503,7 @@ static int dhcp_parse_offer(struct wolfIP *s, struct dhcp_msg *msg, uint32_t msg uint8_t *opt = (uint8_t *)msg->options; uint8_t *opt_end; int saw_end = 0; + int saw_server_id = 0; uint32_t ip; uint32_t netmask = DHCP_DEFAULT_24BIT_NETMASK; struct ipconf *primary = wolfIP_primary_ipconf(s); @@ -5566,6 +5567,7 @@ static int dhcp_parse_offer(struct wolfIP *s, struct dhcp_msg *msg, uint32_t msg if (len < 4) return -1; s->dhcp_server_ip = DHCP_OPT_data_to_u32(inner); + saw_server_id = 1; } if (code == DHCP_OPTION_SUBNET_MASK) { if (len < 4) @@ -5574,7 +5576,7 @@ static int dhcp_parse_offer(struct wolfIP *s, struct dhcp_msg *msg, uint32_t msg } opt += 2 + len; } - if (!saw_end) + if (!saw_end || !saw_server_id) return -1; ip = ee32(msg->yiaddr); if (primary) { From e1234410732e17a872d628fd3348ab6e3a4b1694 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Mon, 30 Mar 2026 11:48:58 +0200 Subject: [PATCH 14/20] Reject DHCPACK packets without server ID F/1600 --- src/test/unit/unit.c | 1 + src/test/unit/unit_tests_tcp_ack.c | 65 +++++++++++++++++++++++++++++- src/wolfip.c | 5 ++- 3 files changed, 68 insertions(+), 3 deletions(-) diff --git a/src/test/unit/unit.c b/src/test/unit/unit.c index d9af408..c9831d9 100644 --- a/src/test/unit/unit.c +++ b/src/test/unit/unit.c @@ -360,6 +360,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_utils, test_dhcp_parse_ack_msg_type_len_ne_1_rejected); tcase_add_test(tc_utils, test_dhcp_parse_ack_len_lt_four_rejected); tcase_add_test(tc_utils, test_dhcp_parse_ack_ignores_zero_len_unknown_option); + tcase_add_test(tc_utils, test_dhcp_parse_ack_missing_server_id_rejected); tcase_add_test(tc_utils, test_dhcp_parse_ack_missing_end_rejected); tcase_add_test(tc_utils, test_dhcp_parse_offer_bad_magic_rejected); tcase_add_test(tc_utils, test_dhcp_parse_ack_bad_magic_rejected); diff --git a/src/test/unit/unit_tests_tcp_ack.c b/src/test/unit/unit_tests_tcp_ack.c index eaa878b..a27833a 100644 --- a/src/test/unit/unit_tests_tcp_ack.c +++ b/src/test/unit/unit_tests_tcp_ack.c @@ -665,6 +665,7 @@ START_TEST(test_dhcp_parse_ack_ignores_short_unknown_option) struct dhcp_option *opt; struct ipconf *primary; uint32_t offer_ip = 0x0A000064U; + uint32_t server_ip = 0x0A000001U; uint32_t mask = 0xFFFFFF00U; wolfIP_init(&s); @@ -683,6 +684,13 @@ START_TEST(test_dhcp_parse_ack_ignores_short_unknown_option) opt->len = 1; opt->data[0] = 0x01; opt = (struct dhcp_option *)((uint8_t *)opt + 3); + opt->code = DHCP_OPTION_SERVER_ID; + opt->len = 4; + opt->data[0] = (server_ip >> 24) & 0xFF; + opt->data[1] = (server_ip >> 16) & 0xFF; + opt->data[2] = (server_ip >> 8) & 0xFF; + opt->data[3] = (server_ip >> 0) & 0xFF; + opt = (struct dhcp_option *)((uint8_t *)opt + 6); opt->code = DHCP_OPTION_SUBNET_MASK; opt->len = 4; opt->data[0] = (mask >> 24) & 0xFF; @@ -700,9 +708,10 @@ START_TEST(test_dhcp_parse_ack_ignores_short_unknown_option) opt->code = DHCP_OPTION_END; opt->len = 0; - ck_assert_int_eq(dhcp_parse_ack(&s, &msg, DHCP_HEADER_LEN + 20), 0); + ck_assert_int_eq(dhcp_parse_ack(&s, &msg, DHCP_HEADER_LEN + 26), 0); ck_assert_uint_eq(primary->ip, offer_ip); ck_assert_uint_eq(primary->mask, mask); + ck_assert_uint_eq(s.dhcp_server_ip, server_ip); } END_TEST @@ -713,6 +722,7 @@ START_TEST(test_dhcp_parse_ack_ignores_zero_len_unknown_option) struct dhcp_option *opt; struct ipconf *primary; uint32_t offer_ip = 0x0A000064U; + uint32_t server_ip = 0x0A000001U; uint32_t mask = 0xFFFFFF00U; wolfIP_init(&s); @@ -730,6 +740,13 @@ START_TEST(test_dhcp_parse_ack_ignores_zero_len_unknown_option) opt->code = 61; /* Client identifier (unused by parser) */ opt->len = 0; opt = (struct dhcp_option *)((uint8_t *)opt + 2); + opt->code = DHCP_OPTION_SERVER_ID; + opt->len = 4; + opt->data[0] = (server_ip >> 24) & 0xFF; + opt->data[1] = (server_ip >> 16) & 0xFF; + opt->data[2] = (server_ip >> 8) & 0xFF; + opt->data[3] = (server_ip >> 0) & 0xFF; + opt = (struct dhcp_option *)((uint8_t *)opt + 6); opt->code = DHCP_OPTION_SUBNET_MASK; opt->len = 4; opt->data[0] = (mask >> 24) & 0xFF; @@ -747,9 +764,53 @@ START_TEST(test_dhcp_parse_ack_ignores_zero_len_unknown_option) opt->code = DHCP_OPTION_END; opt->len = 0; - ck_assert_int_eq(dhcp_parse_ack(&s, &msg, DHCP_HEADER_LEN + 19), 0); + ck_assert_int_eq(dhcp_parse_ack(&s, &msg, DHCP_HEADER_LEN + 25), 0); ck_assert_uint_eq(primary->ip, offer_ip); ck_assert_uint_eq(primary->mask, mask); + ck_assert_uint_eq(s.dhcp_server_ip, server_ip); +} +END_TEST + +START_TEST(test_dhcp_parse_ack_missing_server_id_rejected) +{ + struct wolfIP s; + struct dhcp_msg msg; + struct dhcp_option *opt; + struct ipconf *primary; + uint32_t offer_ip = 0x0A000064U; + uint32_t mask = 0xFFFFFF00U; + + wolfIP_init(&s); + primary = wolfIP_primary_ipconf(&s); + ck_assert_ptr_nonnull(primary); + s.dhcp_server_ip = 0x0A000001U; + + memset(&msg, 0, sizeof(msg)); + msg.op = BOOT_REPLY; + msg.magic = ee32(DHCP_MAGIC); + opt = (struct dhcp_option *)msg.options; + opt->code = DHCP_OPTION_MSG_TYPE; + opt->len = 1; + opt->data[0] = DHCP_ACK; + opt = (struct dhcp_option *)((uint8_t *)opt + 3); + opt->code = DHCP_OPTION_SUBNET_MASK; + opt->len = 4; + opt->data[0] = (mask >> 24) & 0xFF; + opt->data[1] = (mask >> 16) & 0xFF; + opt->data[2] = (mask >> 8) & 0xFF; + opt->data[3] = (mask >> 0) & 0xFF; + opt = (struct dhcp_option *)((uint8_t *)opt + 6); + opt->code = DHCP_OPTION_OFFER_IP; + opt->len = 4; + opt->data[0] = (offer_ip >> 24) & 0xFF; + opt->data[1] = (offer_ip >> 16) & 0xFF; + opt->data[2] = (offer_ip >> 8) & 0xFF; + opt->data[3] = (offer_ip >> 0) & 0xFF; + opt = (struct dhcp_option *)((uint8_t *)opt + 6); + opt->code = DHCP_OPTION_END; + opt->len = 0; + + ck_assert_int_eq(dhcp_parse_ack(&s, &msg, DHCP_HEADER_LEN + 16), -1); } END_TEST diff --git a/src/wolfip.c b/src/wolfip.c index 3158012..6c89a4f 100644 --- a/src/wolfip.c +++ b/src/wolfip.c @@ -5644,6 +5644,7 @@ static int dhcp_parse_ack(struct wolfIP *s, struct dhcp_msg *msg, uint32_t msg_l uint8_t *opt = (uint8_t *)msg->options; uint8_t *opt_end; int saw_end = 0; + int saw_server_id = 0; struct ipconf *primary = wolfIP_primary_ipconf(s); uint32_t lease_s = 0; uint32_t renew_s = 0; @@ -5710,6 +5711,7 @@ static int dhcp_parse_ack(struct wolfIP *s, struct dhcp_msg *msg, uint32_t msg_l return -1; data = DHCP_OPT_data_to_u32(inner); s->dhcp_server_ip = data; + saw_server_id = 1; } else if (primary && code == DHCP_OPTION_OFFER_IP) { if (len < 4) return -1; @@ -5747,7 +5749,8 @@ static int dhcp_parse_ack(struct wolfIP *s, struct dhcp_msg *msg, uint32_t msg_l } if (!saw_end) return -1; - if (primary && (primary->ip != 0) && (primary->mask != 0)) { + if (primary && saw_server_id && + (primary->ip != 0) && (primary->mask != 0)) { dhcp_cancel_timer(s); s->dhcp_state = DHCP_BOUND; dhcp_schedule_lease_timer(s, lease_s, renew_s, rebind_s); From 9547338b31639e5c128c463db5ffe1bd6000e1c4 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Mon, 30 Mar 2026 11:51:45 +0200 Subject: [PATCH 15/20] reject forward DNS compression pointers F/1599 --- src/test/unit/unit_tests_api.c | 10 ++++++++++ src/wolfip.c | 15 +++++++++------ 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/src/test/unit/unit_tests_api.c b/src/test/unit/unit_tests_api.c index 084889c..c9694a1 100644 --- a/src/test/unit/unit_tests_api.c +++ b/src/test/unit/unit_tests_api.c @@ -853,6 +853,7 @@ START_TEST(test_dns_skip_and_copy_name) { uint8_t buf[64]; int pos = 0; + int ptr_pos; int ret; char out[64]; @@ -875,6 +876,15 @@ START_TEST(test_dns_skip_and_copy_name) ret = dns_copy_name(buf, sizeof(buf), pos - 2, out, sizeof(out)); ck_assert_int_eq(ret, 0); ck_assert_str_eq(out, "www.example.com"); + + /* forward pointer must be rejected */ + ptr_pos = pos; + buf[pos++] = 0xC0; + buf[pos++] = (uint8_t)(ptr_pos + 2); + buf[pos++] = 3; memcpy(&buf[pos], "bad", 3); pos += 3; + buf[pos++] = 0; + ret = dns_copy_name(buf, pos, ptr_pos, out, sizeof(out)); + ck_assert_int_eq(ret, -1); } END_TEST diff --git a/src/wolfip.c b/src/wolfip.c index 6c89a4f..ca22940 100644 --- a/src/wolfip.c +++ b/src/wolfip.c @@ -6759,13 +6759,16 @@ static int dns_copy_name(const uint8_t *buf, int len, int offset, char *out, out[o] = '\0'; return 0; } - if ((c & 0xC0) == 0xC0) { - if (pos + 1 >= len) + if ((c & 0xC0) == 0xC0) { + int ptr_pos = pos; + if (pos + 1 >= len) + return -1; + { + uint16_t ptr = ((c & 0x3F) << 8) | buf[pos + 1]; + if (ptr >= ptr_pos) return -1; - { - uint16_t ptr = ((c & 0x3F) << 8) | buf[pos + 1]; - pos = ptr; - } + pos = ptr; + } jumped = 1; continue; } From f98e46b34e8b3b78b63be5be648ec8639cd54526 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Mon, 30 Mar 2026 11:56:48 +0200 Subject: [PATCH 16/20] Replace magic numbers in DNS implementation --- src/wolfip.c | 70 +++++++++++++++++++++++++++++++++------------------- 1 file changed, 44 insertions(+), 26 deletions(-) diff --git a/src/wolfip.c b/src/wolfip.c index ca22940..47acaa3 100644 --- a/src/wolfip.c +++ b/src/wolfip.c @@ -6633,11 +6633,25 @@ void wolfIP_recv_ex(struct wolfIP *s, unsigned int if_idx, void *buf, uint32_t l #define DNS_RESPONSE 0x80 #define DNS_A 0x01 /* A record only */ #define DNS_PTR 0x0C +#define DNS_CLASS_IN 0x01 #define DNS_RD 0x0100 /* Recursion desired */ #define DNS_TC 0x0200 /* Truncated response */ +#define DNS_RCODE_MASK 0x000F +#define DNS_FLAGS_RESPONSE_RD (DNS_RD | ((uint16_t)DNS_RESPONSE << 8)) +#define DNS_ID_NONE 0 +#define DNS_QUESTION_COUNT 1 +#define DNS_MIN_ID 1 #define DNS_QUERY_TYPE_NONE 0 #define DNS_QUERY_TYPE_A 1 #define DNS_QUERY_TYPE_PTR 2 +#define DNS_NAME_TERMINATOR 0x00 +#define DNS_LABEL_SEPARATOR '.' +#define DNS_COMPRESSION_PTR_MASK 0xC0 +#define DNS_COMPRESSION_PTR_VALUE 0xC0 +#define DNS_COMPRESSION_OFFSET_MASK 0x3F +#define DNS_IPV4_RDATA_LEN 4 +#define DNS_PTR_OCTET_COUNT 4 +#define DNS_PTR_NAME_BUF_LEN 128 #define MAX_DNS_NAME_LEN 255 #define MAX_DNS_LABEL_LEN 63 #define DNS_QUERY_TIMEOUT 2000U @@ -6688,7 +6702,7 @@ static size_t dns_write_u8(char *dst, uint8_t val) static int dns_format_ptr_name(char *dst, size_t len, uint32_t ip) { - uint8_t octets[4] = { + uint8_t octets[DNS_PTR_OCTET_COUNT] = { (uint8_t)(ip & 0xFF), (uint8_t)((ip >> 8) & 0xFF), (uint8_t)((ip >> 16) & 0xFF), @@ -6697,7 +6711,7 @@ static int dns_format_ptr_name(char *dst, size_t len, uint32_t ip) size_t pos = 0; size_t i; static const char suffix[] = "in-addr.arpa"; - for (i = 0; i < 4; i++) { + for (i = 0; i < DNS_PTR_OCTET_COUNT; i++) { uint8_t val = octets[i]; size_t written; if (pos + 3 >= len) @@ -6706,7 +6720,7 @@ static int dns_format_ptr_name(char *dst, size_t len, uint32_t ip) pos += written; if (pos + 1 >= len) return -1; - dst[pos++] = '.'; + dst[pos++] = DNS_LABEL_SEPARATOR; } { size_t suffix_len = sizeof(suffix); @@ -6714,7 +6728,7 @@ static int dns_format_ptr_name(char *dst, size_t len, uint32_t ip) return -1; memcpy(dst + pos, suffix, suffix_len); pos += suffix_len - 1; - dst[pos] = '\0'; + dst[pos] = DNS_NAME_TERMINATOR; } return 0; } @@ -6725,9 +6739,9 @@ static int dns_skip_name(const uint8_t *buf, int len, int offset) int loop = 0; while (pos < len && loop++ < len) { uint8_t c = buf[pos++]; - if (c == 0) + if (c == DNS_NAME_TERMINATOR) break; - if ((c & 0xC0) == 0xC0) { + if ((c & DNS_COMPRESSION_PTR_MASK) == DNS_COMPRESSION_PTR_VALUE) { if (pos >= len) return -1; pos++; @@ -6751,20 +6765,21 @@ static int dns_copy_name(const uint8_t *buf, int len, int offset, char *out, int jumped = 0; while (pos < len && loop++ < len) { uint8_t c = buf[pos]; - if (c == 0) { + if (c == DNS_NAME_TERMINATOR) { if (!jumped) pos++; if (o >= out_len) return -1; - out[o] = '\0'; + out[o] = DNS_NAME_TERMINATOR; return 0; } - if ((c & 0xC0) == 0xC0) { + if ((c & DNS_COMPRESSION_PTR_MASK) == DNS_COMPRESSION_PTR_VALUE) { int ptr_pos = pos; if (pos + 1 >= len) return -1; { - uint16_t ptr = ((c & 0x3F) << 8) | buf[pos + 1]; + uint16_t ptr = ((c & DNS_COMPRESSION_OFFSET_MASK) << 8) | + buf[pos + 1]; if (ptr >= ptr_pos) return -1; pos = ptr; @@ -6778,7 +6793,7 @@ static int dns_copy_name(const uint8_t *buf, int len, int offset, char *out, if (o != 0) { if (o + 1 >= out_len) return -1; - out[o++] = '.'; + out[o++] = DNS_LABEL_SEPARATOR; } if (o + c >= out_len) return -1; @@ -6903,13 +6918,13 @@ void dns_callback(int dns_sd, uint16_t ev, void *arg) return; flags = ee16(hdr->flags); /* Parse DNS response */ - if ((flags & 0x8100) == 0x8100) { + if ((flags & DNS_FLAGS_RESPONSE_RD) == DNS_FLAGS_RESPONSE_RD) { if ((flags & DNS_TC) != 0) { dns_abort_query(s); return; } /* RFC 1035 s4.1.1: RCODE != 0 is an error; abort query. */ - if ((flags & 0x000F) != 0) { + if ((flags & DNS_RCODE_MASK) != 0) { dns_abort_query(s); return; } @@ -6939,9 +6954,11 @@ void dns_callback(int dns_sd, uint16_t ev, void *arg) dns_abort_query(s); return; } - if (s->dns_query_type == DNS_QUERY_TYPE_A && ee16(rr->type) == DNS_A && rdlen >= 4) { + if (s->dns_query_type == DNS_QUERY_TYPE_A && + ee16(rr->type) == DNS_A && + rdlen >= DNS_IPV4_RDATA_LEN) { uint32_t ip; - if (pos + 4 > dns_len) { + if (pos + DNS_IPV4_RDATA_LEN > dns_len) { dns_abort_query(s); return; } @@ -6971,7 +6988,7 @@ void dns_callback(int dns_sd, uint16_t ev, void *arg) static int dns_send_query(struct wolfIP *s, const char *dname, uint16_t *id, uint16_t qtype) { - uint8_t buf[512]; + uint8_t buf[MAX_DNS_RESPONSE]; struct dns_header *hdr; struct dns_question *q; char *q_name, *tok_start, *tok_end; @@ -6991,20 +7008,21 @@ static int dns_send_query(struct wolfIP *s, const char *dname, uint16_t *id, } s->dns_id = (uint16_t)(wolfIP_getrandom() & 0xFFFF); if (s->dns_id == 0) - s->dns_id = 1; + s->dns_id = DNS_MIN_ID; *id = s->dns_id; - memset(buf, 0, 512); + memset(buf, 0, sizeof(buf)); s->dns_query_type = (qtype == DNS_PTR) ? DNS_QUERY_TYPE_PTR : DNS_QUERY_TYPE_A; hdr = (struct dns_header *)buf; hdr->id = ee16(s->dns_id); - hdr->qdcount = ee16(1); + hdr->qdcount = ee16(DNS_QUESTION_COUNT); hdr->flags = ee16(DNS_QUERY | DNS_RD); /* Prepare the DNS query name */ q_name = (char *)(buf + sizeof(struct dns_header)); tok_start = (char *)dname; while(*tok_start) { tok_end = tok_start; - while ((*tok_end != '.') && (*tok_end != 0)) { + while ((*tok_end != DNS_LABEL_SEPARATOR) && + (*tok_end != DNS_NAME_TERMINATOR)) { tok_end++; } label_len = (uint32_t)(tok_end - tok_start); @@ -7015,15 +7033,15 @@ static int dns_send_query(struct wolfIP *s, const char *dname, uint16_t *id, memcpy(q_name, tok_start, label_len); q_name += label_len; tok_len += label_len + 1; - if (*tok_end == 0) + if (*tok_end == DNS_NAME_TERMINATOR) break; tok_start = tok_end + 1; } - *q_name = 0; + *q_name = DNS_NAME_TERMINATOR; tok_len++; q = (struct dns_question *)(buf + sizeof(struct dns_header) + tok_len); q->qtype = ee16(qtype); - q->qclass = ee16(1); + q->qclass = ee16(DNS_CLASS_IN); s->dns_query_len = (uint16_t)(sizeof(struct dns_header) + tok_len + sizeof(struct dns_question)); memcpy(s->dns_query_buf, buf, s->dns_query_len); s->dns_retry_count = 0; @@ -7036,7 +7054,7 @@ static int dns_send_query(struct wolfIP *s, const char *dname, uint16_t *id, if (ret < 0) { /* Roll back the outstanding query state when the first send never queues. */ dns_abort_query(s); - *id = 0; + *id = DNS_ID_NONE; return ret; } dns_schedule_timer(s); @@ -7055,14 +7073,14 @@ int nslookup(struct wolfIP *s, const char *dname, uint16_t *id, void (*lookup_cb int wolfIP_dns_ptr_lookup(struct wolfIP *s, uint32_t ip, uint16_t *id, void (*lookup_cb)(const char *name)) { - char ptr_name[128]; + char ptr_name[DNS_PTR_NAME_BUF_LEN]; if (!s || !id || !lookup_cb) return -22; if (dns_format_ptr_name(ptr_name, sizeof(ptr_name), ip) < 0) return -22; s->dns_ptr_cb = lookup_cb; s->dns_lookup_cb = NULL; - s->dns_ptr_name[0] = '\0'; + s->dns_ptr_name[0] = DNS_NAME_TERMINATOR; s->dns_query_type = DNS_QUERY_TYPE_PTR; return dns_send_query(s, ptr_name, id, DNS_PTR); } From 0f9024d3a4d0dc7c64af6d99d4797b443baba05a Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Mon, 30 Mar 2026 12:47:57 +0200 Subject: [PATCH 17/20] Fix overflow in test + add correct structures for encapsulation of packets in ICMP --- src/test/unit/unit_tests_dns_dhcp.c | 16 +++++++--------- src/wolfip.c | 24 ++++++++++++++++++++---- 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/src/test/unit/unit_tests_dns_dhcp.c b/src/test/unit/unit_tests_dns_dhcp.c index 456e330..3aa9531 100644 --- a/src/test/unit/unit_tests_dns_dhcp.c +++ b/src/test/unit/unit_tests_dns_dhcp.c @@ -2039,7 +2039,7 @@ START_TEST(test_icmp_input_dest_unreach_port_unreachable_closes_matching_tcp_soc struct wolfIP s; struct tsocket *ts; struct wolfIP_icmp_dest_unreachable_packet icmp; - struct wolfIP_tcp_seg *orig; + struct wolfIP_tcp_wire_prefix *orig; uint32_t frame_len; wolfIP_init(&s); @@ -2065,15 +2065,14 @@ START_TEST(test_icmp_input_dest_unreach_port_unreachable_closes_matching_tcp_soc icmp.type = ICMP_DEST_UNREACH; icmp.code = ICMP_PORT_UNREACH; - orig = (struct wolfIP_tcp_seg *)icmp.orig_packet; + orig = (struct wolfIP_tcp_wire_prefix *)icmp.orig_packet; orig->ip.ver_ihl = 0x45; orig->ip.proto = WI_IPPROTO_TCP; orig->ip.src = ee32(ts->local_ip); orig->ip.dst = ee32(ts->remote_ip); - orig->ip.len = ee16(IP_HEADER_LEN + TCP_HEADER_LEN); + orig->ip.len = ee16(IP_HEADER_LEN + 8U); orig->src_port = ee16(ts->src_port); orig->dst_port = ee16(ts->dst_port); - orig->hlen = TCP_HEADER_LEN << 2; icmp.csum = ee16(icmp_checksum((struct wolfIP_icmp_packet *)&icmp, ICMP_DEST_UNREACH_SIZE)); @@ -2090,7 +2089,7 @@ START_TEST(test_icmp_input_dest_unreach_frag_needed_reduces_tcp_peer_mss) struct wolfIP s; struct tsocket *ts; struct wolfIP_icmp_dest_unreachable_packet icmp; - struct wolfIP_tcp_seg *orig; + struct wolfIP_tcp_wire_prefix *orig; uint32_t frame_len; uint16_t next_hop_mtu; @@ -2116,19 +2115,18 @@ START_TEST(test_icmp_input_dest_unreach_frag_needed_reduces_tcp_peer_mss) icmp.ip.proto = WI_IPPROTO_ICMP; icmp.ip.len = ee16(IP_HEADER_LEN + ICMP_DEST_UNREACH_SIZE); icmp.type = ICMP_DEST_UNREACH; - icmp.code = 4; + icmp.code = ICMP_FRAG_NEEDED; next_hop_mtu = ee16(576U); memcpy(&icmp.unused[2], &next_hop_mtu, sizeof(next_hop_mtu)); - orig = (struct wolfIP_tcp_seg *)icmp.orig_packet; + orig = (struct wolfIP_tcp_wire_prefix *)icmp.orig_packet; orig->ip.ver_ihl = 0x45; orig->ip.proto = WI_IPPROTO_TCP; orig->ip.src = ee32(ts->local_ip); orig->ip.dst = ee32(ts->remote_ip); - orig->ip.len = ee16(IP_HEADER_LEN + TCP_HEADER_LEN); + orig->ip.len = ee16(IP_HEADER_LEN + 8U); orig->src_port = ee16(ts->src_port); orig->dst_port = ee16(ts->dst_port); - orig->hlen = TCP_HEADER_LEN << 2; icmp.csum = ee16(icmp_checksum((struct wolfIP_icmp_packet *)&icmp, ICMP_DEST_UNREACH_SIZE)); diff --git a/src/wolfip.c b/src/wolfip.c index 47acaa3..039ef66 100644 --- a/src/wolfip.c +++ b/src/wolfip.c @@ -655,6 +655,16 @@ struct PACKED wolfIP_ip_packet { uint8_t data[0]; }; +/* ICMP quotes the original IP packet without the link-layer header. */ +struct PACKED wolfIP_ip_wire { + uint8_t ver_ihl, tos; + uint16_t len, id, flags_fo; + uint8_t ttl, proto; + uint16_t csum; + ip4 src, dst; + uint8_t data[0]; +}; + /* Describe a TCP segment down to the datalink layer */ struct PACKED wolfIP_tcp_seg { struct wolfIP_ip_packet ip; @@ -665,6 +675,12 @@ struct PACKED wolfIP_tcp_seg { uint8_t data[0]; }; +struct PACKED wolfIP_tcp_wire_prefix { + struct wolfIP_ip_wire ip; + uint16_t src_port, dst_port; + uint32_t seq; +}; + struct PACKED tcp_opt_ts { /* Timestamp option (10 extra bytes) */ uint8_t opt, len; @@ -1967,8 +1983,8 @@ static void icmp_try_recv(struct wolfIP *s, unsigned int if_idx, static void icmp_try_deliver_tcp_error(struct wolfIP *s, const struct wolfIP_icmp_packet *icmp) { - const struct wolfIP_ip_packet *orig_ip; - const struct wolfIP_tcp_seg *orig_tcp; + const struct wolfIP_ip_wire *orig_ip; + const struct wolfIP_tcp_wire_prefix *orig_tcp; uint32_t icmp_len; uint32_t avail; uint32_t orig_hlen; @@ -1986,7 +2002,7 @@ static void icmp_try_deliver_tcp_error(struct wolfIP *s, if (avail < IP_HEADER_LEN) return; - orig_ip = (const struct wolfIP_ip_packet *)((const uint8_t *)icmp + + orig_ip = (const struct wolfIP_ip_wire *)((const uint8_t *)icmp + sizeof(struct wolfIP_icmp_packet)); orig_hlen = (uint32_t)((orig_ip->ver_ihl & 0x0FU) << 2); if (orig_hlen < IP_HEADER_LEN || orig_hlen > avail) @@ -1996,7 +2012,7 @@ static void icmp_try_deliver_tcp_error(struct wolfIP *s, if (avail < (orig_hlen + 8U)) return; - orig_tcp = (const struct wolfIP_tcp_seg *)orig_ip; + orig_tcp = (const struct wolfIP_tcp_wire_prefix *)orig_ip; for (i = 0; i < MAX_TCPSOCKETS; i++) { struct tsocket *t = &s->tcpsockets[i]; From cb3e80c8dc86237c4b3c8eca677eb22c47d2bf1c Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Mon, 30 Mar 2026 12:55:12 +0200 Subject: [PATCH 18/20] Fixed overflows in dns-dhcp test --- src/test/unit/unit_tests_dns_dhcp.c | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/test/unit/unit_tests_dns_dhcp.c b/src/test/unit/unit_tests_dns_dhcp.c index 3aa9531..4f8bc2c 100644 --- a/src/test/unit/unit_tests_dns_dhcp.c +++ b/src/test/unit/unit_tests_dns_dhcp.c @@ -56,19 +56,23 @@ static void build_dhcp_ack_msg(struct dhcp_msg *msg, uint32_t server_ip, uint32_ START_TEST(test_dhcp_option_u32_macros_round_trip_wire_order) { - struct dhcp_option opt; + struct PACKED { + struct dhcp_option opt; + uint8_t data[4]; + } opt_buf; + struct dhcp_option *opt = &opt_buf.opt; uint32_t value = 0x0A000001U; - memset(&opt, 0, sizeof(opt)); - opt.len = 4; + memset(&opt_buf, 0, sizeof(opt_buf)); + opt->len = 4; - DHCP_OPT_u32_to_data(&opt, value); + DHCP_OPT_u32_to_data(opt, value); - ck_assert_uint_eq(opt.data[0], 0x0AU); - ck_assert_uint_eq(opt.data[1], 0x00U); - ck_assert_uint_eq(opt.data[2], 0x00U); - ck_assert_uint_eq(opt.data[3], 0x01U); - ck_assert_uint_eq(DHCP_OPT_data_to_u32(&opt), value); + ck_assert_uint_eq(opt->data[0], 0x0AU); + ck_assert_uint_eq(opt->data[1], 0x00U); + ck_assert_uint_eq(opt->data[2], 0x00U); + ck_assert_uint_eq(opt->data[3], 0x01U); + ck_assert_uint_eq(DHCP_OPT_data_to_u32(opt), value); } END_TEST From 1a795828491dbc497a15d1f101b8f14c1cb9d93f Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Mon, 30 Mar 2026 13:40:03 +0200 Subject: [PATCH 19/20] Fixed null-deref (cppcheck) + addressed copilot comment --- src/test/unit/unit.c | 1 + src/test/unit/unit_tests_dns_dhcp.c | 59 +++++++++++++++++++++++++++++ src/wolfip.c | 18 ++++++--- 3 files changed, 72 insertions(+), 6 deletions(-) diff --git a/src/test/unit/unit.c b/src/test/unit/unit.c index c9831d9..7aef66b 100644 --- a/src/test/unit/unit.c +++ b/src/test/unit/unit.c @@ -698,6 +698,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_proto, test_icmp_input_filter_drop_receiving); tcase_add_test(tc_proto, test_icmp_input_dest_unreach_port_unreachable_closes_matching_tcp_socket); tcase_add_test(tc_proto, test_icmp_input_dest_unreach_frag_needed_reduces_tcp_peer_mss); + tcase_add_test(tc_proto, test_icmp_input_dest_unreach_port_unreachable_quoted_ip_options_match_tcp_socket); tcase_add_test(tc_proto, test_udp_sendto_and_recvfrom); tcase_add_test(tc_proto, test_udp_sendto_respects_mtu_api); tcase_add_test(tc_proto, test_udp_recvfrom_sets_remote_ip); diff --git a/src/test/unit/unit_tests_dns_dhcp.c b/src/test/unit/unit_tests_dns_dhcp.c index 4f8bc2c..8812aaa 100644 --- a/src/test/unit/unit_tests_dns_dhcp.c +++ b/src/test/unit/unit_tests_dns_dhcp.c @@ -2142,6 +2142,65 @@ START_TEST(test_icmp_input_dest_unreach_frag_needed_reduces_tcp_peer_mss) } END_TEST +START_TEST(test_icmp_input_dest_unreach_port_unreachable_quoted_ip_options_match_tcp_socket) +{ + struct wolfIP s; + struct tsocket *ts; + uint8_t packet[sizeof(struct wolfIP_icmp_dest_unreachable_packet) + 4]; + struct wolfIP_icmp_packet *icmp = (struct wolfIP_icmp_packet *)packet; + struct wolfIP_ip_wire *orig_ip; + uint8_t *orig_tcp; + uint16_t port; + uint32_t icmp_body_len; + uint32_t frame_len; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + ts = &s.tcpsockets[0]; + memset(ts, 0, sizeof(*ts)); + ts->proto = WI_IPPROTO_TCP; + ts->S = &s; + ts->sock.tcp.state = TCP_ESTABLISHED; + ts->local_ip = 0x0A000001U; + ts->remote_ip = 0x0A000002U; + ts->src_port = 1234; + ts->dst_port = 4321; + + memset(packet, 0, sizeof(packet)); + icmp_body_len = ICMP_HEADER_LEN + 24U + 8U; + icmp->ip.src = ee32(0x0A0000FEU); + icmp->ip.dst = ee32(ts->local_ip); + icmp->ip.ttl = 64; + icmp->ip.proto = WI_IPPROTO_ICMP; + icmp->ip.len = ee16(IP_HEADER_LEN + icmp_body_len); + icmp->type = ICMP_DEST_UNREACH; + icmp->code = ICMP_PORT_UNREACH; + + orig_ip = (struct wolfIP_ip_wire *)(packet + sizeof(struct wolfIP_icmp_packet)); + orig_ip->ver_ihl = 0x46; + orig_ip->proto = WI_IPPROTO_TCP; + orig_ip->src = ee32(ts->local_ip); + orig_ip->dst = ee32(ts->remote_ip); + orig_ip->len = ee16(24U + 8U); + memset(orig_ip->data, 0xAB, 4); + + orig_tcp = ((uint8_t *)orig_ip) + 24U; + port = ee16(ts->src_port); + memcpy(orig_tcp, &port, sizeof(port)); + port = ee16(ts->dst_port); + memcpy(orig_tcp + sizeof(port), &port, sizeof(port)); + + icmp->csum = ee16(icmp_checksum(icmp, icmp_body_len)); + frame_len = (uint32_t)(ETH_HEADER_LEN + IP_HEADER_LEN + icmp_body_len); + + icmp_input(&s, TEST_PRIMARY_IF, (struct wolfIP_ip_packet *)icmp, frame_len); + + ck_assert_uint_eq(ts->proto, 0U); +} +END_TEST + START_TEST(test_dns_send_query_errors) { struct wolfIP s; diff --git a/src/wolfip.c b/src/wolfip.c index 039ef66..32fa622 100644 --- a/src/wolfip.c +++ b/src/wolfip.c @@ -1984,7 +1984,8 @@ static void icmp_try_deliver_tcp_error(struct wolfIP *s, const struct wolfIP_icmp_packet *icmp) { const struct wolfIP_ip_wire *orig_ip; - const struct wolfIP_tcp_wire_prefix *orig_tcp; + const uint8_t *orig_tcp; + uint16_t src_port, dst_port; uint32_t icmp_len; uint32_t avail; uint32_t orig_hlen; @@ -2012,7 +2013,9 @@ static void icmp_try_deliver_tcp_error(struct wolfIP *s, if (avail < (orig_hlen + 8U)) return; - orig_tcp = (const struct wolfIP_tcp_wire_prefix *)orig_ip; + orig_tcp = ((const uint8_t *)orig_ip) + orig_hlen; + memcpy(&src_port, orig_tcp, sizeof(src_port)); + memcpy(&dst_port, orig_tcp + sizeof(src_port), sizeof(dst_port)); for (i = 0; i < MAX_TCPSOCKETS; i++) { struct tsocket *t = &s->tcpsockets[i]; @@ -2022,8 +2025,7 @@ static void icmp_try_deliver_tcp_error(struct wolfIP *s, continue; if (t->local_ip != ee32(orig_ip->src) || t->remote_ip != ee32(orig_ip->dst)) continue; - if (t->src_port != ee16(orig_tcp->src_port) || - t->dst_port != ee16(orig_tcp->dst_port)) + if (t->src_port != ee16(src_port) || t->dst_port != ee16(dst_port)) continue; if (icmp->type == ICMP_DEST_UNREACH) { @@ -5923,11 +5925,15 @@ static int dhcp_send_discover(struct wolfIP *s) struct dhcp_msg disc; struct dhcp_option *opt = (struct dhcp_option *)(disc.options); struct wolfIP_sockaddr_in sin; - uint64_t retry_at = s ? (s->last_tick + 1U) : 0; + uint64_t retry_at; int ret; uint32_t opt_sz = 0; - if (s && s->dhcp_state == DHCP_OFF) + if (!s) + return -1; + + retry_at = s->last_tick + 1U; + if (s->dhcp_state == DHCP_OFF) s->dhcp_start_tick = s->last_tick; /* Prepare DHCP discover */ From a77fe9d3d06af3424ac2b27cbdc29ba53281e946 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Mon, 30 Mar 2026 18:27:15 +0200 Subject: [PATCH 20/20] Addressed review ICMP hard errors were closing TCP established connections. Also fix/split test case. --- src/test/unit/unit.c | 5 ++- src/test/unit/unit_tests_dns_dhcp.c | 60 +++++++++++++++++++++++++++-- src/wolfip.c | 4 +- 3 files changed, 62 insertions(+), 7 deletions(-) diff --git a/src/test/unit/unit.c b/src/test/unit/unit.c index 7aef66b..7c8ea50 100644 --- a/src/test/unit/unit.c +++ b/src/test/unit/unit.c @@ -696,9 +696,10 @@ Suite *wolf_suite(void) tcase_add_test(tc_proto, test_icmp_input_echo_request_ip_filter_drop); tcase_add_test(tc_proto, test_icmp_input_echo_request_eth_filter_drop); tcase_add_test(tc_proto, test_icmp_input_filter_drop_receiving); - tcase_add_test(tc_proto, test_icmp_input_dest_unreach_port_unreachable_closes_matching_tcp_socket); + tcase_add_test(tc_proto, test_icmp_input_dest_unreach_port_unreachable_keeps_established_tcp_socket); tcase_add_test(tc_proto, test_icmp_input_dest_unreach_frag_needed_reduces_tcp_peer_mss); - tcase_add_test(tc_proto, test_icmp_input_dest_unreach_port_unreachable_quoted_ip_options_match_tcp_socket); + tcase_add_test(tc_proto, test_icmp_input_dest_unreach_port_unreachable_closes_syn_sent_tcp_socket); + tcase_add_test(tc_proto, test_icmp_input_dest_unreach_port_unreachable_quoted_ip_options_keep_established_tcp_socket); tcase_add_test(tc_proto, test_udp_sendto_and_recvfrom); tcase_add_test(tc_proto, test_udp_sendto_respects_mtu_api); tcase_add_test(tc_proto, test_udp_recvfrom_sets_remote_ip); diff --git a/src/test/unit/unit_tests_dns_dhcp.c b/src/test/unit/unit_tests_dns_dhcp.c index 8812aaa..46315d6 100644 --- a/src/test/unit/unit_tests_dns_dhcp.c +++ b/src/test/unit/unit_tests_dns_dhcp.c @@ -2038,7 +2038,7 @@ START_TEST(test_icmp_input_filter_drop_receiving) } END_TEST -START_TEST(test_icmp_input_dest_unreach_port_unreachable_closes_matching_tcp_socket) +START_TEST(test_icmp_input_dest_unreach_port_unreachable_keeps_established_tcp_socket) { struct wolfIP s; struct tsocket *ts; @@ -2084,7 +2084,8 @@ START_TEST(test_icmp_input_dest_unreach_port_unreachable_closes_matching_tcp_soc icmp_input(&s, TEST_PRIMARY_IF, (struct wolfIP_ip_packet *)&icmp, frame_len); - ck_assert_uint_eq(ts->proto, 0U); + ck_assert_uint_eq(ts->proto, WI_IPPROTO_TCP); + ck_assert_int_eq(ts->sock.tcp.state, TCP_ESTABLISHED); } END_TEST @@ -2142,7 +2143,57 @@ START_TEST(test_icmp_input_dest_unreach_frag_needed_reduces_tcp_peer_mss) } END_TEST -START_TEST(test_icmp_input_dest_unreach_port_unreachable_quoted_ip_options_match_tcp_socket) +START_TEST(test_icmp_input_dest_unreach_port_unreachable_closes_syn_sent_tcp_socket) +{ + struct wolfIP s; + struct tsocket *ts; + struct wolfIP_icmp_dest_unreachable_packet icmp; + struct wolfIP_tcp_wire_prefix *orig; + uint32_t frame_len; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 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_SENT; + ts->local_ip = 0x0A000001U; + ts->remote_ip = 0x0A000002U; + ts->src_port = 1234; + ts->dst_port = 4321; + + memset(&icmp, 0, sizeof(icmp)); + icmp.ip.src = ee32(0x0A0000FEU); + icmp.ip.dst = ee32(ts->local_ip); + icmp.ip.ttl = 64; + icmp.ip.proto = WI_IPPROTO_ICMP; + icmp.ip.len = ee16(IP_HEADER_LEN + ICMP_DEST_UNREACH_SIZE); + icmp.type = ICMP_DEST_UNREACH; + icmp.code = ICMP_PORT_UNREACH; + + orig = (struct wolfIP_tcp_wire_prefix *)icmp.orig_packet; + orig->ip.ver_ihl = 0x45; + orig->ip.proto = WI_IPPROTO_TCP; + orig->ip.src = ee32(ts->local_ip); + orig->ip.dst = ee32(ts->remote_ip); + orig->ip.len = ee16(IP_HEADER_LEN + 8U); + orig->src_port = ee16(ts->src_port); + orig->dst_port = ee16(ts->dst_port); + + icmp.csum = ee16(icmp_checksum((struct wolfIP_icmp_packet *)&icmp, + ICMP_DEST_UNREACH_SIZE)); + frame_len = (uint32_t)(ETH_HEADER_LEN + IP_HEADER_LEN + ICMP_DEST_UNREACH_SIZE); + + icmp_input(&s, TEST_PRIMARY_IF, (struct wolfIP_ip_packet *)&icmp, frame_len); + + ck_assert_uint_eq(ts->proto, 0U); +} +END_TEST + +START_TEST(test_icmp_input_dest_unreach_port_unreachable_quoted_ip_options_keep_established_tcp_socket) { struct wolfIP s; struct tsocket *ts; @@ -2197,7 +2248,8 @@ START_TEST(test_icmp_input_dest_unreach_port_unreachable_quoted_ip_options_match icmp_input(&s, TEST_PRIMARY_IF, (struct wolfIP_ip_packet *)icmp, frame_len); - ck_assert_uint_eq(ts->proto, 0U); + ck_assert_uint_eq(ts->proto, WI_IPPROTO_TCP); + ck_assert_int_eq(ts->sock.tcp.state, TCP_ESTABLISHED); } END_TEST diff --git a/src/wolfip.c b/src/wolfip.c index 32fa622..98942fd 100644 --- a/src/wolfip.c +++ b/src/wolfip.c @@ -2043,7 +2043,9 @@ static void icmp_try_deliver_tcp_error(struct wolfIP *s, } } else if (icmp->code == ICMP_PROT_UNREACH || icmp->code == ICMP_PORT_UNREACH) { - close_socket(t); + if (t->sock.tcp.state == TCP_SYN_SENT || + t->sock.tcp.state == TCP_SYN_RCVD) + close_socket(t); } } break;