From f6aa529b0e5220d3e65beefd3abf9442571eef0d Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Fri, 13 Mar 2026 08:47:33 +0100 Subject: [PATCH 01/10] Correctly parse ACK in TCP_CLOSING F/689 --- src/test/unit/unit.c | 78 ++++++++++++++++++++++++++++++++++++++++++++ src/wolfip.c | 3 +- 2 files changed, 80 insertions(+), 1 deletion(-) diff --git a/src/test/unit/unit.c b/src/test/unit/unit.c index 1c152d0..37cafa5 100644 --- a/src/test/unit/unit.c +++ b/src/test/unit/unit.c @@ -15554,6 +15554,83 @@ START_TEST(test_tcp_input_close_wait_processes_ack) } END_TEST +START_TEST(test_tcp_input_closing_processes_ack) +{ + struct wolfIP s; + struct tsocket *ts; + struct tcp_seg_buf segbuf; + struct wolfIP_tcp_seg *queued; + struct wolfIP_tcp_seg ackseg; + struct pkt_desc *desc; + struct wolfIP_timer tmr; + + 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_CLOSING; + ts->src_port = 1234; + ts->dst_port = 4321; + ts->local_ip = 0x0A000001U; + ts->remote_ip = 0x0A000002U; + ts->sock.tcp.last = 100; + ts->sock.tcp.snd_una = 100; + ts->sock.tcp.seq = 101; + ts->sock.tcp.bytes_in_flight = 1; + ts->sock.tcp.rto = 100; + ts->sock.tcp.ctrl_rto_active = 1; + ts->sock.tcp.ctrl_rto_retries = 2; + fifo_init(&ts->sock.tcp.txbuf, ts->txmem, TXBUF_SIZE); + + memset(&segbuf, 0, sizeof(segbuf)); + queued = &segbuf.seg; + queued->ip.len = ee16(IP_HEADER_LEN + TCP_HEADER_LEN + 1); + queued->hlen = TCP_HEADER_LEN << 2; + queued->seq = ee32(100); + ck_assert_int_eq(fifo_push(&ts->sock.tcp.txbuf, &segbuf, sizeof(segbuf)), 0); + desc = fifo_peek(&ts->sock.tcp.txbuf); + ck_assert_ptr_nonnull(desc); + desc->flags |= PKT_FLAG_SENT; + + memset(&tmr, 0, sizeof(tmr)); + tmr.cb = test_timer_cb; + tmr.expires = 200; + tmr.arg = ts; + ts->sock.tcp.tmr_rto = timers_binheap_insert(&s.timers, tmr); + ck_assert_int_ne(ts->sock.tcp.tmr_rto, NO_TIMER); + + memset(&ackseg, 0, sizeof(ackseg)); + ackseg.ip.ver_ihl = 0x45; + ackseg.ip.ttl = 64; + ackseg.ip.proto = WI_IPPROTO_TCP; + ackseg.ip.len = ee16(IP_HEADER_LEN + TCP_HEADER_LEN); + ackseg.ip.src = ee32(ts->remote_ip); + ackseg.ip.dst = ee32(ts->local_ip); + ackseg.src_port = ee16(ts->dst_port); + ackseg.dst_port = ee16(ts->src_port); + ackseg.hlen = TCP_HEADER_LEN << 2; + ackseg.flags = TCP_FLAG_ACK; + ackseg.ack = ee32(101); + ackseg.win = ee16(32); + fix_tcp_checksums(&ackseg); + + tcp_input(&s, TEST_PRIMARY_IF, &ackseg, + (uint32_t)(ETH_HEADER_LEN + IP_HEADER_LEN + TCP_HEADER_LEN)); + + ck_assert_int_eq(ts->sock.tcp.state, TCP_TIME_WAIT); + ck_assert_uint_eq(ts->sock.tcp.snd_una, 101U); + ck_assert_uint_eq(ts->sock.tcp.bytes_in_flight, 0U); + ck_assert_ptr_eq(fifo_peek(&ts->sock.tcp.txbuf), NULL); + ck_assert_int_eq(ts->sock.tcp.tmr_rto, NO_TIMER); + ck_assert_uint_eq(ts->sock.tcp.ctrl_rto_active, 0U); + ck_assert_uint_eq(ts->sock.tcp.ctrl_rto_retries, 0U); +} +END_TEST + START_TEST(test_tcp_sock_close_state_transitions) { struct wolfIP s; @@ -19015,6 +19092,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_utils, test_tcp_input_syn_bound_ip_mismatch); tcase_add_test(tc_utils, test_tcp_input_syn_rcvd_ack_wrong_flags); tcase_add_test(tc_utils, test_tcp_input_close_wait_processes_ack); + tcase_add_test(tc_utils, test_tcp_input_closing_processes_ack); tcase_add_test(tc_utils, test_tcp_input_established_ack_only_returns); tcase_add_test(tc_utils, test_tcp_input_syn_dst_not_local); tcase_add_test(tc_utils, test_tcp_input_syn_dst_outside_subnet); diff --git a/src/wolfip.c b/src/wolfip.c index e59bef8..7d5f1c3 100644 --- a/src/wolfip.c +++ b/src/wolfip.c @@ -3417,7 +3417,8 @@ static void tcp_input(struct wolfIP *S, unsigned int if_idx, else if ((t->sock.tcp.state == TCP_ESTABLISHED) || (t->sock.tcp.state == TCP_CLOSE_WAIT) || (t->sock.tcp.state == TCP_FIN_WAIT_1) || - (t->sock.tcp.state == TCP_FIN_WAIT_2)) { + (t->sock.tcp.state == TCP_FIN_WAIT_2) || + (t->sock.tcp.state == TCP_CLOSING)) { if (tcp->flags & TCP_FLAG_ACK) { tcp_ack(t, tcp); From 7f908bfb3986bf03df2cd79827a69b2ccdc6e064 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Fri, 13 Mar 2026 08:53:13 +0100 Subject: [PATCH 02/10] Send RST for unmatched segments (RFC9293) F/690 --- src/test/unit/unit.c | 75 ++++++++++++++++++++++++++++++++++++++++++++ src/wolfip.c | 64 +++++++++++++++++++++++++++++++++++++ 2 files changed, 139 insertions(+) diff --git a/src/test/unit/unit.c b/src/test/unit/unit.c index 37cafa5..487e237 100644 --- a/src/test/unit/unit.c +++ b/src/test/unit/unit.c @@ -14720,6 +14720,78 @@ START_TEST(test_tcp_input_port_mismatch_skips_socket) } END_TEST +START_TEST(test_tcp_input_unmatched_ack_sends_rst) +{ + struct wolfIP s; + struct wolfIP_tcp_seg *rst; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + last_frame_sent_size = 0; + memset(last_frame_sent, 0, sizeof(last_frame_sent)); + + inject_tcp_segment(&s, TEST_PRIMARY_IF, 0x0A000002U, 0x0A000001U, + 4321, 1234, 77, 101, TCP_FLAG_ACK); + + ck_assert_uint_eq(last_frame_sent_size, (uint32_t)sizeof(struct wolfIP_tcp_seg)); + rst = (struct wolfIP_tcp_seg *)last_frame_sent; + ck_assert_uint_eq(ee32(rst->ip.src), 0x0A000001U); + ck_assert_uint_eq(ee32(rst->ip.dst), 0x0A000002U); + ck_assert_uint_eq(ee16(rst->src_port), 1234); + ck_assert_uint_eq(ee16(rst->dst_port), 4321); + ck_assert_uint_eq(rst->flags, TCP_FLAG_RST); + ck_assert_uint_eq(ee32(rst->seq), 101U); + ck_assert_uint_eq(ee32(rst->ack), 0U); +} +END_TEST + +START_TEST(test_tcp_input_unmatched_syn_sends_rst_ack) +{ + struct wolfIP s; + struct wolfIP_tcp_seg *rst; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + last_frame_sent_size = 0; + memset(last_frame_sent, 0, sizeof(last_frame_sent)); + + inject_tcp_segment(&s, TEST_PRIMARY_IF, 0x0A000002U, 0x0A000001U, + 4321, 1234, 77, 0, TCP_FLAG_SYN); + + ck_assert_uint_eq(last_frame_sent_size, (uint32_t)sizeof(struct wolfIP_tcp_seg)); + rst = (struct wolfIP_tcp_seg *)last_frame_sent; + ck_assert_uint_eq(ee32(rst->ip.src), 0x0A000001U); + ck_assert_uint_eq(ee32(rst->ip.dst), 0x0A000002U); + ck_assert_uint_eq(ee16(rst->src_port), 1234); + ck_assert_uint_eq(ee16(rst->dst_port), 4321); + ck_assert_uint_eq(rst->flags, (uint8_t)(TCP_FLAG_RST | TCP_FLAG_ACK)); + ck_assert_uint_eq(ee32(rst->seq), 0U); + ck_assert_uint_eq(ee32(rst->ack), 78U); +} +END_TEST + +START_TEST(test_tcp_input_unmatched_rst_is_discarded) +{ + struct wolfIP s; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + last_frame_sent_size = 0; + memset(last_frame_sent, 0, sizeof(last_frame_sent)); + + inject_tcp_segment(&s, TEST_PRIMARY_IF, 0x0A000002U, 0x0A000001U, + 4321, 1234, 77, 0, TCP_FLAG_RST); + + ck_assert_uint_eq(last_frame_sent_size, 0U); +} +END_TEST + START_TEST(test_tcp_input_syn_bound_ip_mismatch) { struct wolfIP s; @@ -19089,6 +19161,9 @@ Suite *wolf_suite(void) tcase_add_test(tc_utils, test_tcp_input_syn_rcvd_ack_invalid_seq_rejected); tcase_add_test(tc_utils, test_tcp_input_filter_drop); tcase_add_test(tc_utils, test_tcp_input_port_mismatch_skips_socket); + tcase_add_test(tc_utils, test_tcp_input_unmatched_ack_sends_rst); + tcase_add_test(tc_utils, test_tcp_input_unmatched_syn_sends_rst_ack); + tcase_add_test(tc_utils, test_tcp_input_unmatched_rst_is_discarded); tcase_add_test(tc_utils, test_tcp_input_syn_bound_ip_mismatch); tcase_add_test(tc_utils, test_tcp_input_syn_rcvd_ack_wrong_flags); tcase_add_test(tc_utils, test_tcp_input_close_wait_processes_ack); diff --git a/src/wolfip.c b/src/wolfip.c index 7d5f1c3..cda7011 100644 --- a/src/wolfip.c +++ b/src/wolfip.c @@ -2172,6 +2172,69 @@ static void tcp_send_ack(struct tsocket *t) return tcp_send_empty(t, TCP_FLAG_ACK); } +static void tcp_send_reset_reply(struct wolfIP *s, unsigned int if_idx, + const struct wolfIP_tcp_seg *in) +{ + struct wolfIP_tcp_seg out; + union transport_pseudo_header ph; + uint16_t ip_len; + uint32_t tcp_hlen; + uint32_t seg_ack; + + if (in->flags & TCP_FLAG_RST) + return; + + ip_len = ee16(in->ip.len); + tcp_hlen = (uint32_t)(in->hlen >> 2); + if (tcp_hlen < TCP_HEADER_LEN) + return; + if (ip_len < (uint16_t)(IP_HEADER_LEN + tcp_hlen)) + return; + + memset(&out, 0, sizeof(out)); + out.src_port = in->dst_port; + out.dst_port = in->src_port; + out.hlen = TCP_HEADER_LEN << 2; + + if (in->flags & TCP_FLAG_ACK) { + out.seq = in->ack; + out.flags = TCP_FLAG_RST; + } else { + seg_ack = ee32(in->seq); + seg_ack = tcp_seq_inc(seg_ack, ip_len - (uint16_t)(IP_HEADER_LEN + tcp_hlen)); + if (in->flags & TCP_FLAG_SYN) + seg_ack = tcp_seq_inc(seg_ack, 1); + if (in->flags & TCP_FLAG_FIN) + seg_ack = tcp_seq_inc(seg_ack, 1); + out.ack = ee32(seg_ack); + out.flags = TCP_FLAG_RST | TCP_FLAG_ACK; + } + + out.ip.src = in->ip.dst; + out.ip.dst = in->ip.src; + out.ip.ver_ihl = 0x45; + out.ip.len = ee16(IP_HEADER_LEN + TCP_HEADER_LEN); + out.ip.ttl = 64; + out.ip.proto = WI_IPPROTO_TCP; + out.ip.id = ee16(s->ipcounter); + s->ipcounter = (uint16_t)(s->ipcounter + 1); + iphdr_set_checksum(&out.ip); + + memset(&ph, 0, sizeof(ph)); + ph.ph.src = out.ip.src; + ph.ph.dst = out.ip.dst; + ph.ph.proto = WI_IPPROTO_TCP; + ph.ph.len = ee16(TCP_HEADER_LEN); + out.csum = ee16(transport_checksum(&ph, &out.src_port)); + +#ifdef ETHERNET + if (!wolfIP_ll_is_non_ethernet(s, if_idx)) + wolfIP_forward_packet(s, if_idx, &out.ip, sizeof(out), in->ip.eth.src, 0); + else +#endif + wolfIP_forward_packet(s, if_idx, &out.ip, sizeof(out), NULL, 0); +} + static void tcp_send_finack(struct tsocket *t) { tcp_send_empty(t, TCP_FLAG_FIN | TCP_FLAG_ACK); @@ -3467,6 +3530,7 @@ static void tcp_input(struct wolfIP *S, unsigned int if_idx, } } } + tcp_send_reset_reply(S, if_idx, tcp); } static void tcp_rto_cb(void *arg) From 9366582109de0f5fe4b871149bdceb663588708d Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Fri, 13 Mar 2026 08:58:03 +0100 Subject: [PATCH 03/10] UDP: send ICMP port unreach for closed UDP port F/694 --- src/test/unit/unit.c | 52 +++++++++++++++++++++++++++++++++++++ src/wolfip.c | 62 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 114 insertions(+) diff --git a/src/test/unit/unit.c b/src/test/unit/unit.c index 487e237..b979aef 100644 --- a/src/test/unit/unit.c +++ b/src/test/unit/unit.c @@ -7712,6 +7712,57 @@ START_TEST(test_udp_try_recv_short_expected_len) } END_TEST +START_TEST(test_udp_try_recv_unmatched_port_sends_icmp_unreachable) +{ + struct wolfIP s; + uint8_t udp_buf[sizeof(struct wolfIP_udp_datagram) + 4]; + struct wolfIP_udp_datagram *udp = (struct wolfIP_udp_datagram *)udp_buf; + struct wolfIP_icmp_ttl_exceeded_packet *icmp; + uint32_t local_ip = 0x0A000001U; + uint32_t remote_ip = 0x0A000002U; + uint8_t src_mac[6] = {0x20, 0x21, 0x22, 0x23, 0x24, 0x25}; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, local_ip, 0xFFFFFF00U, 0); + + memset(udp_buf, 0, sizeof(udp_buf)); + memcpy(udp->ip.eth.src, src_mac, sizeof(src_mac)); + memcpy(udp->ip.eth.dst, s.ll_dev[TEST_PRIMARY_IF].mac, 6); + udp->ip.eth.type = ee16(ETH_TYPE_IP); + udp->ip.ver_ihl = 0x45; + udp->ip.ttl = 64; + udp->ip.proto = WI_IPPROTO_UDP; + udp->ip.len = ee16(IP_HEADER_LEN + UDP_HEADER_LEN + 4); + udp->ip.src = ee32(remote_ip); + udp->ip.dst = ee32(local_ip); + udp->src_port = ee16(4321); + udp->dst_port = ee16(1234); + udp->len = ee16(UDP_HEADER_LEN + 4); + memcpy(udp->data, "test", 4); + fix_udp_checksums(udp); + + memset(last_frame_sent, 0, sizeof(last_frame_sent)); + last_frame_sent_size = 0; + + udp_try_recv(&s, TEST_PRIMARY_IF, udp, + (uint32_t)(ETH_HEADER_LEN + IP_HEADER_LEN + UDP_HEADER_LEN + 4)); + + ck_assert_uint_eq(last_frame_sent_size, + sizeof(struct wolfIP_icmp_ttl_exceeded_packet)); + icmp = (struct wolfIP_icmp_ttl_exceeded_packet *)last_frame_sent; + ck_assert_uint_eq(icmp->type, 3U); + ck_assert_uint_eq(icmp->code, 3U); + ck_assert_mem_eq(icmp->unused, "\x00\x00\x00\x00", sizeof(icmp->unused)); + ck_assert_mem_eq(icmp->ip.eth.dst, src_mac, 6); + ck_assert_mem_eq(icmp->ip.eth.src, s.ll_dev[TEST_PRIMARY_IF].mac, 6); + ck_assert_uint_eq(ee32(icmp->ip.src), local_ip); + ck_assert_uint_eq(ee32(icmp->ip.dst), remote_ip); + ck_assert_mem_eq(icmp->orig_packet, ((uint8_t *)udp) + ETH_HEADER_LEN, + TTL_EXCEEDED_ORIG_PACKET_SIZE); +} +END_TEST + START_TEST(test_dns_callback_bad_flags) { struct wolfIP s; @@ -19071,6 +19122,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_utils, test_udp_try_recv_short_expected_len); tcase_add_test(tc_utils, test_udp_try_recv_conf_null); tcase_add_test(tc_utils, test_udp_try_recv_remote_ip_matches_local_ip); + tcase_add_test(tc_utils, test_udp_try_recv_unmatched_port_sends_icmp_unreachable); tcase_add_test(tc_utils, test_dns_callback_bad_flags); tcase_add_test(tc_utils, test_dns_callback_bad_name); tcase_add_test(tc_utils, test_dns_callback_short_header_ignored); diff --git a/src/wolfip.c b/src/wolfip.c index cda7011..5a6c3b5 100644 --- a/src/wolfip.c +++ b/src/wolfip.c @@ -72,6 +72,8 @@ struct wolfIP_icmp_packet; #define ICMP_ECHO_REPLY 0 #define ICMP_ECHO_REQUEST 8 #define ICMP_TTL_EXCEEDED 11 +#define ICMP_DEST_UNREACH 3 +#define ICMP_PORT_UNREACH 3 #define WI_IPPROTO_ICMP 0x01 #define WI_IPPROTO_TCP 0x06 @@ -717,6 +719,14 @@ struct PACKED wolfIP_icmp_ttl_exceeded_packet { uint8_t orig_packet[TTL_EXCEEDED_ORIG_PACKET_SIZE]; }; +struct PACKED wolfIP_icmp_dest_unreachable_packet { + struct wolfIP_ip_packet ip; + uint8_t type, code; + uint16_t csum; + uint8_t unused[4]; + uint8_t orig_packet[TTL_EXCEEDED_ORIG_PACKET_SIZE]; +}; + static uint16_t icmp_echo_id(const struct wolfIP_icmp_packet *icmp) { uint16_t net = 0; @@ -1508,6 +1518,46 @@ static void wolfIP_send_ttl_exceeded(struct wolfIP *s, unsigned int if_idx, } wolfIP_ll_send_frame(s, if_idx, &icmp, sizeof(icmp)); } + +static void wolfIP_send_port_unreachable(struct wolfIP *s, unsigned int if_idx, + struct wolfIP_ip_packet *orig) +{ + struct wolfIP_ll_dev *ll = wolfIP_ll_at(s, if_idx); + struct wolfIP_icmp_dest_unreachable_packet icmp = {0}; + struct wolfIP_icmp_packet *icmp_pkt = (struct wolfIP_icmp_packet *)&icmp; +#if !CONFIG_IPFILTER + (void)icmp_pkt; +#endif + if (!ll || !ll->send) + return; + icmp.type = ICMP_DEST_UNREACH; + icmp.code = ICMP_PORT_UNREACH; + memcpy(icmp.orig_packet, ((uint8_t *)orig) + ETH_HEADER_LEN, + TTL_EXCEEDED_ORIG_PACKET_SIZE); + icmp.csum = ee16(icmp_checksum((struct wolfIP_icmp_packet *)&icmp, + ICMP_TTL_EXCEEDED_SIZE)); + icmp.ip.ver_ihl = 0x45; + icmp.ip.ttl = 64; + icmp.ip.proto = WI_IPPROTO_ICMP; + icmp.ip.id = ipcounter_next(s); + icmp.ip.len = ee16(IP_HEADER_LEN + ICMP_TTL_EXCEEDED_SIZE); + icmp.ip.src = ee32(wolfIP_ipconf_at(s, if_idx)->ip); + icmp.ip.dst = orig->src; + icmp.ip.csum = 0; + iphdr_set_checksum(&icmp.ip); + if (!wolfIP_ll_is_non_ethernet(s, if_idx)) { + eth_output_add_header(s, if_idx, orig->eth.src, &icmp.ip.eth, ETH_TYPE_IP); + } + if (wolfIP_filter_notify_icmp(WOLFIP_FILT_SENDING, s, if_idx, icmp_pkt, sizeof(icmp)) != 0) + return; + if (wolfIP_filter_notify_ip(WOLFIP_FILT_SENDING, s, if_idx, &icmp.ip, sizeof(icmp)) != 0) + return; + if (!wolfIP_ll_is_non_ethernet(s, if_idx)) { + if (wolfIP_filter_notify_eth(WOLFIP_FILT_SENDING, s, if_idx, &icmp.ip.eth, sizeof(icmp)) != 0) + return; + } + wolfIP_ll_send_frame(s, if_idx, &icmp, sizeof(icmp)); +} #else static void wolfIP_send_ttl_exceeded(struct wolfIP *s, unsigned int if_idx, struct wolfIP_ip_packet *orig) @@ -1516,6 +1566,14 @@ static void wolfIP_send_ttl_exceeded(struct wolfIP *s, unsigned int if_idx, (void)if_idx; (void)orig; } + +static void wolfIP_send_port_unreachable(struct wolfIP *s, unsigned int if_idx, + struct wolfIP_ip_packet *orig) +{ + (void)s; + (void)if_idx; + (void)orig; +} #endif /* User Callbacks */ @@ -1647,6 +1705,7 @@ static void udp_try_recv(struct wolfIP *s, unsigned int if_idx, struct wolfIP_udp_datagram *udp, uint32_t frame_len) { int i; + int matched = 0; ip4 dst_ip; ip4 src_ip; @@ -1697,8 +1756,11 @@ static void udp_try_recv(struct wolfIP *s, unsigned int if_idx, /* Insert into socket buffer */ fifo_push(&t->sock.udp.rxbuf, udp, frame_len); t->events |= CB_EVENT_READABLE; + matched = 1; } } + if (!matched) + wolfIP_send_port_unreachable(s, if_idx, &udp->ip); } /* ICMP sockets reuse the UDP fifo bookkeeping */ From 649cfb5499efae3e5e4156bf048160f67fcb9e60 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Fri, 13 Mar 2026 11:16:02 +0100 Subject: [PATCH 04/10] IHL > 5: discard options but correctly receive packet F/695 --- src/test/unit/unit.c | 82 +++++++++++++++++++++++++++++++++++++++++++ src/wolfip.c | 83 ++++++++++++++++++++++++++++++++++---------- 2 files changed, 147 insertions(+), 18 deletions(-) diff --git a/src/test/unit/unit.c b/src/test/unit/unit.c index b979aef..b92da66 100644 --- a/src/test/unit/unit.c +++ b/src/test/unit/unit.c @@ -474,6 +474,37 @@ static void fix_udp_checksums(struct wolfIP_udp_datagram *udp) fix_udp_checksum(udp, udp_len); } +static void fix_ip_checksum_with_hlen(struct wolfIP_ip_packet *ip, uint16_t ip_hlen) +{ + uint32_t sum = 0; + uint32_t i; + const uint8_t *ptr = (const uint8_t *)(&ip->ver_ihl); + + ip->csum = 0; + for (i = 0; i < ip_hlen; i += 2) { + uint16_t word; + memcpy(&word, ptr + i, sizeof(word)); + sum += ee16(word); + } + while (sum >> 16) + sum = (sum & 0xffff) + (sum >> 16); + ip->csum = ee16((uint16_t)~sum); +} + +static void fix_udp_checksum_raw(struct wolfIP_ip_packet *ip, void *udp_hdr, uint16_t udp_len) +{ + union transport_pseudo_header ph; + uint16_t *udp_csum = (uint16_t *)((uint8_t *)udp_hdr + 6); + + memset(&ph, 0, sizeof(ph)); + ph.ph.src = ip->src; + ph.ph.dst = ip->dst; + ph.ph.proto = WI_IPPROTO_UDP; + ph.ph.len = ee16(udp_len); + *udp_csum = 0; + *udp_csum = ee16(transport_checksum(&ph, udp_hdr)); +} + static void inject_udp_datagram(struct wolfIP *s, unsigned int if_idx, ip4 src_ip, ip4 dst_ip, uint16_t src_port, uint16_t dst_port, const uint8_t *payload, uint16_t payload_len) { @@ -9091,6 +9122,56 @@ START_TEST(test_ip_recv_forward_ttl_exceeded) } END_TEST +START_TEST(test_ip_recv_udp_with_ip_options_delivers_payload) +{ + struct wolfIP s; + struct tsocket *ts; + uint8_t frame[ETH_HEADER_LEN + IP_HEADER_LEN + 4 + UDP_HEADER_LEN + 4]; + struct wolfIP_ip_packet *ip = (struct wolfIP_ip_packet *)frame; + uint8_t *udp_hdr = frame + ETH_HEADER_LEN + IP_HEADER_LEN + 4; + uint16_t udp_len = UDP_HEADER_LEN + 4; + uint32_t local_ip = 0x0A000001U; + uint32_t remote_ip = 0x0A000002U; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, local_ip, 0xFFFFFF00U, 0); + + ts = udp_new_socket(&s); + ck_assert_ptr_nonnull(ts); + ts->src_port = 1234; + ts->local_ip = local_ip; + + memset(frame, 0, sizeof(frame)); + memcpy(ip->eth.dst, s.ll_dev[TEST_PRIMARY_IF].mac, 6); + memcpy(ip->eth.src, "\x01\x02\x03\x04\x05\x06", 6); + ip->eth.type = ee16(ETH_TYPE_IP); + ip->ver_ihl = 0x46; + ip->ttl = 64; + ip->proto = WI_IPPROTO_UDP; + ip->len = ee16(IP_HEADER_LEN + 4 + udp_len); + ip->src = ee32(remote_ip); + ip->dst = ee32(local_ip); + ip->data[0] = 1; + ip->data[1] = 1; + ip->data[2] = 1; + ip->data[3] = 0; + + ((uint16_t *)udp_hdr)[0] = ee16(4321); + ((uint16_t *)udp_hdr)[1] = ee16(1234); + ((uint16_t *)udp_hdr)[2] = ee16(udp_len); + memcpy(udp_hdr + UDP_HEADER_LEN, "opt!", 4); + + fix_udp_checksum_raw(ip, udp_hdr, udp_len); + fix_ip_checksum_with_hlen(ip, (uint16_t)(IP_HEADER_LEN + 4)); + + ip_recv(&s, TEST_PRIMARY_IF, ip, (uint32_t)sizeof(frame)); + + ck_assert_ptr_nonnull(fifo_peek(&ts->sock.udp.rxbuf)); + ck_assert_int_ne((ts->events & CB_EVENT_READABLE), 0); +} +END_TEST + START_TEST(test_ip_recv_forward_arp_queue_and_flush) { struct wolfIP s; @@ -19415,6 +19496,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_proto, test_forward_interface_dest_is_local_ip); tcase_add_test(tc_proto, test_forward_interface_short_circuit_cases); tcase_add_test(tc_proto, test_ip_recv_forward_ttl_exceeded); + tcase_add_test(tc_proto, test_ip_recv_udp_with_ip_options_delivers_payload); tcase_add_test(tc_proto, test_ip_recv_forward_arp_queue_and_flush); tcase_add_test(tc_proto, test_arp_flush_pending_ttl_expired); tcase_add_test(tc_proto, test_wolfip_forwarding_basic); diff --git a/src/wolfip.c b/src/wolfip.c index 5a6c3b5..f8716fa 100644 --- a/src/wolfip.c +++ b/src/wolfip.c @@ -2712,8 +2712,13 @@ static void iphdr_set_checksum(struct wolfIP_ip_packet *ip) { uint32_t sum = 0; uint32_t i = 0; + uint32_t ip_hlen = (uint32_t)(ip->ver_ihl & 0x0fU) << 2; const uint8_t *ptr = (const uint8_t *)(&ip->ver_ihl); - for (i = 0; i < IP_HEADER_LEN; i += 2) { + + if (ip_hlen < IP_HEADER_LEN) + ip_hlen = IP_HEADER_LEN; + + for (i = 0; i < ip_hlen; i += 2) { uint16_t word; memcpy(&word, ptr + i, sizeof(word)); sum += ee16(word); @@ -2728,8 +2733,16 @@ static int iphdr_verify_checksum(struct wolfIP_ip_packet *ip) { uint32_t sum = 0; uint32_t i; + uint32_t ip_hlen; const uint8_t *ptr = (const uint8_t *)(&ip->ver_ihl); - for (i = 0; i < IP_HEADER_LEN; i += 2) { + + if ((ip->ver_ihl >> 4) != 4) + return -1; + ip_hlen = (uint32_t)(ip->ver_ihl & 0x0fU) << 2; + if (ip_hlen < IP_HEADER_LEN) + return -1; + + for (i = 0; i < ip_hlen; i += 2) { uint16_t word; memcpy(&word, ptr + i, sizeof(word)); sum += ee16(word); @@ -5669,6 +5682,8 @@ size_t wolfIP_instance_size(void) static inline void ip_recv(struct wolfIP *s, unsigned int if_idx, struct wolfIP_ip_packet *ip, uint32_t len) { + uint8_t version; + uint32_t ip_hlen; #if WOLFIP_ENABLE_FORWARDING unsigned int i; #endif @@ -5676,6 +5691,14 @@ static inline void ip_recv(struct wolfIP *s, unsigned int if_idx, * (ethernet header + ip header, with no options) */ if (len < sizeof(struct wolfIP_ip_packet)) return; + version = ip->ver_ihl >> 4; + ip_hlen = (uint32_t)(ip->ver_ihl & 0x0fU) << 2; + if (version != 4 || ip_hlen < IP_HEADER_LEN) + return; + if (len < (uint32_t)(ETH_HEADER_LEN + ip_hlen)) + return; + if (ee16(ip->len) < ip_hlen) + return; /* validate IP header checksum per RFC 1122 */ if (iphdr_verify_checksum(ip) != 0) return; @@ -5690,7 +5713,7 @@ static inline void ip_recv(struct wolfIP *s, unsigned int if_idx, if (wolfIP_filter_notify_ip(WOLFIP_FILT_RECEIVING, s, if_idx, ip, len) != 0) return; #if WOLFIP_ENABLE_FORWARDING - if (ip->ver_ihl == 0x45) { + if (version == 4 && ip_hlen >= IP_HEADER_LEN) { ip4 dest = ee32(ip->dst); int is_local = 0; if (dest == IPADDR_ANY || IS_IP_BCAST(dest)) { @@ -5753,25 +5776,49 @@ static inline void ip_recv(struct wolfIP *s, unsigned int if_idx, } #endif /* WOLFIP_ESP */ - if (ip->ver_ihl == 0x45 && ip->proto == 0x06) { - struct wolfIP_tcp_seg *tcp = (struct wolfIP_tcp_seg *)ip; - tcp_input(s, if_idx, tcp, len); - } - else if (ip->ver_ihl == 0x45 && ip->proto == 0x11) { - struct wolfIP_udp_datagram *udp = (struct wolfIP_udp_datagram *)ip; + { + struct wolfIP_ip_packet *dispatch_ip = ip; + uint32_t dispatch_len = len; + uint8_t frame[LINK_MTU]; + + if (ip_hlen > IP_HEADER_LEN) { + uint32_t opt_len = ip_hlen - IP_HEADER_LEN; + uint16_t total_ip_len = ee16(ip->len); + + if (len > LINK_MTU) + return; + memcpy(frame, ip, len); + dispatch_ip = (struct wolfIP_ip_packet *)frame; + memmove(((uint8_t *)dispatch_ip) + ETH_HEADER_LEN + IP_HEADER_LEN, + ((uint8_t *)dispatch_ip) + ETH_HEADER_LEN + ip_hlen, + len - (ETH_HEADER_LEN + ip_hlen)); + dispatch_len -= opt_len; + dispatch_ip->ver_ihl = (uint8_t)((dispatch_ip->ver_ihl & 0xf0U) | 0x05U); + dispatch_ip->len = ee16(total_ip_len - (uint16_t)opt_len); + dispatch_ip->csum = 0; + iphdr_set_checksum(dispatch_ip); + } + + if (dispatch_ip->proto == 0x06) { + struct wolfIP_tcp_seg *tcp = (struct wolfIP_tcp_seg *)dispatch_ip; + tcp_input(s, if_idx, tcp, dispatch_len); + } + else if (dispatch_ip->proto == 0x11) { + struct wolfIP_udp_datagram *udp = (struct wolfIP_udp_datagram *)dispatch_ip; #ifdef DEBUG_UDP - wolfIP_print_udp(udp); + wolfIP_print_udp(udp); #endif /* DEBUG_UDP */ - udp_try_recv(s, if_idx, udp, len); - } - else if (ip->ver_ihl == 0x45 && ip->proto == 0x01) { - icmp_input(s, if_idx, ip, len); - } + udp_try_recv(s, if_idx, udp, dispatch_len); + } + else if (dispatch_ip->proto == 0x01) { + icmp_input(s, if_idx, dispatch_ip, dispatch_len); + } #ifdef DEBUG_IP - else { - LOG("info: dropping ip packet: 0x%02x\n", ip->proto); - } + else { + LOG("info: dropping ip packet: 0x%02x\n", dispatch_ip->proto); + } #endif + } } static void wolfIP_recv_on(struct wolfIP *s, unsigned int if_idx, void *buf, uint32_t len) From 4502d00a5e9fd6177230dbe96b07a27671160439 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Fri, 13 Mar 2026 11:19:09 +0100 Subject: [PATCH 05/10] icmp_input: discard packets with bad checksum F/693 --- src/test/unit/unit.c | 37 +++++++++++++++++++++++++++++++++++++ src/wolfip.c | 3 +++ 2 files changed, 40 insertions(+) diff --git a/src/test/unit/unit.c b/src/test/unit/unit.c index b92da66..f146616 100644 --- a/src/test/unit/unit.c +++ b/src/test/unit/unit.c @@ -5774,6 +5774,7 @@ START_TEST(test_icmp_input_echo_reply_queues) icmp.ip.len = ee16(IP_HEADER_LEN + ICMP_HEADER_LEN); icmp.type = ICMP_ECHO_REPLY; icmp_set_echo_id(&icmp, ts->src_port); + icmp.csum = ee16(icmp_checksum(&icmp, ICMP_HEADER_LEN)); frame_len = (uint32_t)(ETH_HEADER_LEN + IP_HEADER_LEN + ICMP_HEADER_LEN); icmp_input(&s, TEST_PRIMARY_IF, (struct wolfIP_ip_packet *)&icmp, frame_len); @@ -5800,6 +5801,7 @@ START_TEST(test_icmp_input_echo_request_reply_sent) icmp.ip.ttl = 64; icmp.ip.len = ee16(IP_HEADER_LEN + ICMP_HEADER_LEN); icmp.type = ICMP_ECHO_REQUEST; + icmp.csum = ee16(icmp_checksum(&icmp, ICMP_HEADER_LEN)); frame_len = (uint32_t)(ETH_HEADER_LEN + IP_HEADER_LEN + ICMP_HEADER_LEN); icmp_input(&s, TEST_PRIMARY_IF, (struct wolfIP_ip_packet *)&icmp, frame_len); @@ -5808,6 +5810,34 @@ START_TEST(test_icmp_input_echo_request_reply_sent) } END_TEST +START_TEST(test_icmp_input_echo_request_bad_checksum_dropped) +{ + struct wolfIP s; + struct wolfIP_icmp_packet icmp; + uint32_t frame_len; + + wolfIP_init(&s); + mock_link_init(&s); + s.dhcp_state = DHCP_OFF; + wolfIP_filter_set_callback(NULL, NULL); + last_frame_sent_size = 0; + + memset(&icmp, 0, sizeof(icmp)); + icmp.ip.src = ee32(0x0A000002U); + icmp.ip.dst = ee32(0x0A000001U); + icmp.ip.ttl = 64; + icmp.ip.len = ee16(IP_HEADER_LEN + ICMP_HEADER_LEN); + icmp.type = ICMP_ECHO_REQUEST; + icmp.csum = ee16(icmp_checksum(&icmp, ICMP_HEADER_LEN)); + icmp.csum ^= ee16(0x0001); + frame_len = (uint32_t)(ETH_HEADER_LEN + IP_HEADER_LEN + ICMP_HEADER_LEN); + + icmp_input(&s, TEST_PRIMARY_IF, (struct wolfIP_ip_packet *)&icmp, frame_len); + + ck_assert_uint_eq(last_frame_sent_size, 0U); +} +END_TEST + START_TEST(test_icmp_input_echo_request_odd_len_reply_checksum) { struct wolfIP s; @@ -5842,6 +5872,7 @@ START_TEST(test_icmp_input_echo_request_odd_len_reply_checksum) icmp->code = 0; icmp->csum = 0; ((uint8_t *)&icmp->type)[ICMP_HEADER_LEN] = 0xAB; + icmp->csum = ee16(icmp_checksum(icmp, icmp_len)); frame_len = (uint32_t)(ETH_HEADER_LEN + IP_HEADER_LEN + icmp_len); icmp_input(&s, TEST_PRIMARY_IF, ip, frame_len); @@ -5883,6 +5914,7 @@ START_TEST(test_icmp_input_echo_request_dhcp_running_no_reply) icmp.ip.dst = ee32(0x0A000001U); icmp.ip.len = ee16(IP_HEADER_LEN + ICMP_HEADER_LEN); icmp.type = ICMP_ECHO_REQUEST; + icmp.csum = ee16(icmp_checksum(&icmp, ICMP_HEADER_LEN)); frame_len = (uint32_t)(ETH_HEADER_LEN + IP_HEADER_LEN + ICMP_HEADER_LEN); icmp_input(&s, TEST_PRIMARY_IF, (struct wolfIP_ip_packet *)&icmp, frame_len); @@ -5909,6 +5941,7 @@ START_TEST(test_icmp_input_echo_request_filter_drop) icmp.ip.dst = ee32(0x0A000001U); icmp.ip.len = ee16(IP_HEADER_LEN + ICMP_HEADER_LEN); icmp.type = ICMP_ECHO_REQUEST; + icmp.csum = ee16(icmp_checksum(&icmp, ICMP_HEADER_LEN)); frame_len = (uint32_t)(ETH_HEADER_LEN + IP_HEADER_LEN + ICMP_HEADER_LEN); icmp_input(&s, TEST_PRIMARY_IF, (struct wolfIP_ip_packet *)&icmp, frame_len); @@ -5938,6 +5971,7 @@ START_TEST(test_icmp_input_echo_request_ip_filter_drop) icmp.ip.dst = ee32(0x0A000001U); icmp.ip.len = ee16(IP_HEADER_LEN + ICMP_HEADER_LEN); icmp.type = ICMP_ECHO_REQUEST; + icmp.csum = ee16(icmp_checksum(&icmp, ICMP_HEADER_LEN)); frame_len = (uint32_t)(ETH_HEADER_LEN + IP_HEADER_LEN + ICMP_HEADER_LEN); icmp_input(&s, TEST_PRIMARY_IF, (struct wolfIP_ip_packet *)&icmp, frame_len); @@ -5967,6 +6001,7 @@ START_TEST(test_icmp_input_echo_request_eth_filter_drop) icmp.ip.dst = ee32(0x0A000001U); icmp.ip.len = ee16(IP_HEADER_LEN + ICMP_HEADER_LEN); icmp.type = ICMP_ECHO_REQUEST; + icmp.csum = ee16(icmp_checksum(&icmp, ICMP_HEADER_LEN)); frame_len = (uint32_t)(ETH_HEADER_LEN + IP_HEADER_LEN + ICMP_HEADER_LEN); icmp_input(&s, TEST_PRIMARY_IF, (struct wolfIP_ip_packet *)&icmp, frame_len); @@ -5995,6 +6030,7 @@ START_TEST(test_icmp_input_filter_drop_receiving) icmp.ip.dst = ee32(0x0A000001U); icmp.ip.len = ee16(IP_HEADER_LEN + ICMP_HEADER_LEN); icmp.type = ICMP_ECHO_REQUEST; + icmp.csum = ee16(icmp_checksum(&icmp, ICMP_HEADER_LEN)); frame_len = (uint32_t)(ETH_HEADER_LEN + IP_HEADER_LEN + ICMP_HEADER_LEN); icmp_input(&s, TEST_PRIMARY_IF, (struct wolfIP_ip_packet *)&icmp, frame_len); @@ -19511,6 +19547,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_proto, test_icmp_socket_send_recv); tcase_add_test(tc_proto, test_icmp_input_echo_reply_queues); tcase_add_test(tc_proto, test_icmp_input_echo_request_reply_sent); + tcase_add_test(tc_proto, test_icmp_input_echo_request_bad_checksum_dropped); tcase_add_test(tc_proto, test_icmp_input_echo_request_odd_len_reply_checksum); tcase_add_test(tc_proto, test_icmp_input_echo_request_dhcp_running_no_reply); tcase_add_test(tc_proto, test_icmp_input_echo_request_filter_drop); diff --git a/src/wolfip.c b/src/wolfip.c index f8716fa..45c10db 100644 --- a/src/wolfip.c +++ b/src/wolfip.c @@ -4830,6 +4830,9 @@ static void icmp_input(struct wolfIP *s, unsigned int if_idx, struct wolfIP_ip_p /* validate ip->len doesn't exceed actual received data */ if (len < (uint32_t)(ETH_HEADER_LEN + ee16(ip->len))) return; + /* validate ICMP checksum before processing */ + if (icmp_checksum(icmp, (uint16_t)(ee16(ip->len) - IP_HEADER_LEN)) != 0) + return; if (wolfIP_filter_notify_icmp(WOLFIP_FILT_RECEIVING, s, if_idx, icmp, len) != 0) return; From 37eaa6423ef9c9210f85b7ab9bd14e96ff1f3e09 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Fri, 13 Mar 2026 11:25:22 +0100 Subject: [PATCH 06/10] Do not send ttl_exceeded on locally-destined TCP packets F/697 --- src/test/unit/unit.c | 58 ++++++++++++++++++++++++++++++++------------ src/wolfip.c | 10 +++----- 2 files changed, 46 insertions(+), 22 deletions(-) diff --git a/src/test/unit/unit.c b/src/test/unit/unit.c index f146616..f18859a 100644 --- a/src/test/unit/unit.c +++ b/src/test/unit/unit.c @@ -8041,11 +8041,14 @@ START_TEST(test_dns_abort_query_null_noop) } END_TEST -START_TEST(test_tcp_input_ttl_zero_sends_icmp) +START_TEST(test_tcp_input_ttl_zero_local_ack_still_processes) { struct wolfIP s; struct tsocket *ts; - struct wolfIP_tcp_seg seg; + struct tcp_seg_buf segbuf; + struct wolfIP_tcp_seg *queued; + struct wolfIP_tcp_seg ackseg; + struct pkt_desc *desc; wolfIP_init(&s); mock_link_init(&s); @@ -8061,20 +8064,43 @@ START_TEST(test_tcp_input_ttl_zero_sends_icmp) ts->dst_port = 5678; ts->local_ip = 0x0A000001U; ts->remote_ip = 0x0A000002U; + ts->sock.tcp.snd_una = 100; + ts->sock.tcp.seq = 101; + ts->sock.tcp.bytes_in_flight = 1; + fifo_init(&ts->sock.tcp.txbuf, ts->txmem, TXBUF_SIZE); - memset(&seg, 0, sizeof(seg)); - seg.ip.ver_ihl = 0x45; - seg.ip.ttl = 0; - 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; - 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_gt(last_frame_sent_size, 0); + memset(&segbuf, 0, sizeof(segbuf)); + queued = &segbuf.seg; + queued->ip.len = ee16(IP_HEADER_LEN + TCP_HEADER_LEN + 1); + queued->hlen = TCP_HEADER_LEN << 2; + queued->seq = ee32(100); + ck_assert_int_eq(fifo_push(&ts->sock.tcp.txbuf, &segbuf, sizeof(segbuf)), 0); + desc = fifo_peek(&ts->sock.tcp.txbuf); + ck_assert_ptr_nonnull(desc); + desc->flags |= PKT_FLAG_SENT; + + memset(&ackseg, 0, sizeof(ackseg)); + ackseg.ip.ver_ihl = 0x45; + ackseg.ip.ttl = 0; + ackseg.ip.proto = WI_IPPROTO_TCP; + ackseg.ip.len = ee16(IP_HEADER_LEN + TCP_HEADER_LEN); + ackseg.ip.src = ee32(ts->remote_ip); + ackseg.ip.dst = ee32(ts->local_ip); + ackseg.src_port = ee16(ts->dst_port); + ackseg.dst_port = ee16(ts->src_port); + ackseg.hlen = TCP_HEADER_LEN << 2; + ackseg.flags = TCP_FLAG_ACK; + ackseg.ack = ee32(101); + ackseg.win = ee16(32); + fix_tcp_checksums(&ackseg); + + tcp_input(&s, TEST_PRIMARY_IF, &ackseg, + (uint32_t)(ETH_HEADER_LEN + IP_HEADER_LEN + TCP_HEADER_LEN)); + + ck_assert_uint_eq(last_frame_sent_size, 0U); + ck_assert_uint_eq(ts->sock.tcp.snd_una, 101U); + ck_assert_uint_eq(ts->sock.tcp.bytes_in_flight, 0U); + ck_assert_ptr_eq(fifo_peek(&ts->sock.tcp.txbuf), NULL); } END_TEST @@ -19246,7 +19272,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_utils, test_dns_callback_wrong_id_ignored); tcase_add_test(tc_utils, test_dns_callback_abort_clears_query_state); tcase_add_test(tc_utils, test_dns_abort_query_null_noop); - tcase_add_test(tc_utils, test_tcp_input_ttl_zero_sends_icmp); + tcase_add_test(tc_utils, test_tcp_input_ttl_zero_local_ack_still_processes); tcase_add_test(tc_utils, test_dns_callback_bad_rr_rdlen); tcase_add_test(tc_utils, test_dhcp_parse_offer_no_match); tcase_add_test(tc_utils, test_dhcp_parse_ack_invalid); diff --git a/src/wolfip.c b/src/wolfip.c index 45c10db..93f011a 100644 --- a/src/wolfip.c +++ b/src/wolfip.c @@ -3313,6 +3313,7 @@ static void tcp_input(struct wolfIP *S, unsigned int if_idx, struct wolfIP_tcp_seg *tcp, uint32_t frame_len) { int i; + int matched = 0; /* validate minimum TCP segment length */ if (frame_len < sizeof(struct wolfIP_tcp_seg)) @@ -3363,11 +3364,7 @@ static void tcp_input(struct wolfIP *S, unsigned int if_idx, continue; } } - /* Check IP ttl */ - if (tcp->ip.ttl == 0) { - wolfIP_send_ttl_exceeded(S, if_idx, &tcp->ip); - return; - } + matched = 1; /* Validate minimum TCP header length (data offset). */ if ((tcp->hlen >> 2) < TCP_HEADER_LEN) { return; /* malformed: TCP header below minimum length */ @@ -3605,7 +3602,8 @@ static void tcp_input(struct wolfIP *S, unsigned int if_idx, } } } - tcp_send_reset_reply(S, if_idx, tcp); + if (!matched) + tcp_send_reset_reply(S, if_idx, tcp); } static void tcp_rto_cb(void *arg) From d1307b4baaad311004d3df86d996bc42b5ba2ada Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Fri, 13 Mar 2026 11:27:45 +0100 Subject: [PATCH 07/10] UDP: validate minimum len F/698 --- src/test/unit/unit.c | 31 +++++++++++++++++++++++++++++++ src/wolfip.c | 4 ++++ 2 files changed, 35 insertions(+) diff --git a/src/test/unit/unit.c b/src/test/unit/unit.c index f18859a..3d55483 100644 --- a/src/test/unit/unit.c +++ b/src/test/unit/unit.c @@ -7779,6 +7779,36 @@ START_TEST(test_udp_try_recv_short_expected_len) } END_TEST +START_TEST(test_udp_try_recv_len_below_header_rejected) +{ + struct wolfIP s; + struct tsocket *ts; + struct wolfIP_udp_datagram udp; + uint32_t local_ip = 0x0A000001U; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, local_ip, 0xFFFFFF00U, 0); + + ts = udp_new_socket(&s); + ck_assert_ptr_nonnull(ts); + ts->src_port = 1234; + ts->local_ip = local_ip; + + memset(&udp, 0, sizeof(udp)); + udp.ip.dst = ee32(local_ip); + udp.ip.len = ee16(IP_HEADER_LEN + UDP_HEADER_LEN); + udp.dst_port = ee16(1234); + udp.len = ee16(UDP_HEADER_LEN - 1); + + udp_try_recv(&s, TEST_PRIMARY_IF, &udp, + (uint32_t)(ETH_HEADER_LEN + IP_HEADER_LEN + UDP_HEADER_LEN)); + + ck_assert_ptr_eq(fifo_peek(&ts->sock.udp.rxbuf), NULL); + ck_assert_uint_eq(last_frame_sent_size, 0U); +} +END_TEST + START_TEST(test_udp_try_recv_unmatched_port_sends_icmp_unreachable) { struct wolfIP s; @@ -19263,6 +19293,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_utils, test_udp_try_recv_filter_drop); tcase_add_test(tc_utils, test_udp_try_recv_dhcp_running_local_zero); tcase_add_test(tc_utils, test_udp_try_recv_short_expected_len); + tcase_add_test(tc_utils, test_udp_try_recv_len_below_header_rejected); tcase_add_test(tc_utils, test_udp_try_recv_conf_null); tcase_add_test(tc_utils, test_udp_try_recv_remote_ip_matches_local_ip); tcase_add_test(tc_utils, test_udp_try_recv_unmatched_port_sends_icmp_unreachable); diff --git a/src/wolfip.c b/src/wolfip.c index 93f011a..3357abe 100644 --- a/src/wolfip.c +++ b/src/wolfip.c @@ -1717,6 +1717,10 @@ static void udp_try_recv(struct wolfIP *s, unsigned int if_idx, if (frame_len < (uint32_t)(ETH_HEADER_LEN + ee16(udp->ip.len))) return; + /* validate minimum UDP length per RFC 768 */ + if (ee16(udp->len) < UDP_HEADER_LEN) + return; + /* validate UDP length field fits within the actual received buffer */ if (ee16(udp->len) > frame_len - ETH_HEADER_LEN - IP_HEADER_LEN) return; From f7263c9951976520a6ec021e50b48987bbb7db88 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Fri, 13 Mar 2026 11:30:52 +0100 Subject: [PATCH 08/10] DHCP offer and ACK: validate xid F/692 --- src/test/unit/unit.c | 65 ++++++++++++++++++++++++++++++++++++++++++++ src/wolfip.c | 4 +++ 2 files changed, 69 insertions(+) diff --git a/src/test/unit/unit.c b/src/test/unit/unit.c index 3d55483..c386881 100644 --- a/src/test/unit/unit.c +++ b/src/test/unit/unit.c @@ -8789,6 +8789,69 @@ START_TEST(test_dhcp_parse_ack_missing_end_rejected) } END_TEST +START_TEST(test_dhcp_parse_offer_rejects_mismatched_xid) +{ + 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); + s.dhcp_xid = 0x12345678U; + + memset(&msg, 0, sizeof(msg)); + msg.magic = ee32(DHCP_MAGIC); + msg.xid = ee32(0x87654321U); + 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_mismatched_xid) +{ + 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); + s.dhcp_xid = 0x12345678U; + s.dhcp_ip = 0x0A000064U; + s.dhcp_server_ip = 0x0A000001U; + s.dhcp_state = DHCP_REQUEST_SENT; + + memset(&msg, 0, sizeof(msg)); + msg.magic = ee32(DHCP_MAGIC); + msg.xid = ee32(0x87654321U); + 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_END; + opt->len = 0; + + 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; @@ -19620,6 +19683,8 @@ Suite *wolf_suite(void) tcase_add_test(tc_proto, test_dns_query_and_callback_a); tcase_add_test(tc_proto, test_dhcp_parse_offer_and_ack); tcase_add_test(tc_proto, test_dhcp_parse_offer_defaults_mask_when_missing); + tcase_add_test(tc_proto, test_dhcp_parse_offer_rejects_mismatched_xid); + tcase_add_test(tc_proto, test_dhcp_parse_ack_rejects_mismatched_xid); tcase_add_test(tc_proto, test_tcp_handshake_and_fin_close_wait); tcase_add_test(tc_proto, test_regression_snd_una_initialized_on_syn_rcvd); diff --git a/src/wolfip.c b/src/wolfip.c index 3357abe..e4188da 100644 --- a/src/wolfip.c +++ b/src/wolfip.c @@ -4939,6 +4939,8 @@ static int dhcp_parse_offer(struct wolfIP *s, struct dhcp_msg *msg, uint32_t msg return -1; if (ee32(msg->magic) != DHCP_MAGIC) return -1; + if (ee32(msg->xid) != s->dhcp_xid) + return -1; if (msg_len - DHCP_HEADER_LEN > sizeof(msg->options)) opt_end = (uint8_t *)msg->options + sizeof(msg->options); else @@ -5034,6 +5036,8 @@ static int dhcp_parse_ack(struct wolfIP *s, struct dhcp_msg *msg, uint32_t msg_l return -1; if (ee32(msg->magic) != DHCP_MAGIC) return -1; + if (ee32(msg->xid) != s->dhcp_xid) + return -1; if (msg_len - DHCP_HEADER_LEN > sizeof(msg->options)) opt_end = (uint8_t *)msg->options + sizeof(msg->options); else From e3f6a80590987bb2a649e928463099c95d4105fd Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Fri, 13 Mar 2026 11:57:56 +0100 Subject: [PATCH 09/10] Fix build errors --- src/wolfip.c | 41 ++++++++++++++++++++++++++++------------- 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/src/wolfip.c b/src/wolfip.c index e4188da..4687f27 100644 --- a/src/wolfip.c +++ b/src/wolfip.c @@ -1467,6 +1467,8 @@ static unsigned int wolfIP_if_for_local_ip(struct wolfIP *s, ip4 local_ip, int * static uint16_t transport_checksum(union transport_pseudo_header *ph, void *_data); static int transport_verify_checksum(union transport_pseudo_header *ph, void *data); +static void wolfIP_send_port_unreachable(struct wolfIP *s, unsigned int if_idx, + struct wolfIP_ip_packet *orig); #ifdef ETHERNET static uint16_t icmp_checksum(struct wolfIP_icmp_packet *icmp, uint16_t len); static void iphdr_set_checksum(struct wolfIP_ip_packet *ip); @@ -1479,7 +1481,7 @@ static void arp_request(struct wolfIP *s, unsigned int if_idx, ip4 tip); static int arp_lookup(struct wolfIP *s, unsigned int if_idx, ip4 ip, uint8_t *mac); #endif -#ifdef ETHERNET +#if WOLFIP_ENABLE_FORWARDING && defined(ETHERNET) static void wolfIP_send_ttl_exceeded(struct wolfIP *s, unsigned int if_idx, struct wolfIP_ip_packet *orig) { @@ -1518,7 +1520,17 @@ static void wolfIP_send_ttl_exceeded(struct wolfIP *s, unsigned int if_idx, } wolfIP_ll_send_frame(s, if_idx, &icmp, sizeof(icmp)); } +#elif WOLFIP_ENABLE_FORWARDING +static void wolfIP_send_ttl_exceeded(struct wolfIP *s, unsigned int if_idx, + struct wolfIP_ip_packet *orig) +{ + (void)s; + (void)if_idx; + (void)orig; +} +#endif +#ifdef ETHERNET static void wolfIP_send_port_unreachable(struct wolfIP *s, unsigned int if_idx, struct wolfIP_ip_packet *orig) { @@ -1559,14 +1571,6 @@ static void wolfIP_send_port_unreachable(struct wolfIP *s, unsigned int if_idx, wolfIP_ll_send_frame(s, if_idx, &icmp, sizeof(icmp)); } #else -static void wolfIP_send_ttl_exceeded(struct wolfIP *s, unsigned int if_idx, - struct wolfIP_ip_packet *orig) -{ - (void)s; - (void)if_idx; - (void)orig; -} - static void wolfIP_send_port_unreachable(struct wolfIP *s, unsigned int if_idx, struct wolfIP_ip_packet *orig) { @@ -2294,11 +2298,22 @@ static void tcp_send_reset_reply(struct wolfIP *s, unsigned int if_idx, out.csum = ee16(transport_checksum(&ph, &out.src_port)); #ifdef ETHERNET - if (!wolfIP_ll_is_non_ethernet(s, if_idx)) - wolfIP_forward_packet(s, if_idx, &out.ip, sizeof(out), in->ip.eth.src, 0); - else + if (!wolfIP_ll_is_non_ethernet(s, if_idx)) { + if (eth_output_add_header(s, if_idx, in->ip.eth.src, &out.ip.eth, ETH_TYPE_IP) != 0) + return; + } +#endif + if (wolfIP_filter_notify_tcp(WOLFIP_FILT_SENDING, s, if_idx, &out, sizeof(out)) != 0) + return; + if (wolfIP_filter_notify_ip(WOLFIP_FILT_SENDING, s, if_idx, &out.ip, sizeof(out)) != 0) + return; +#ifdef ETHERNET + if (!wolfIP_ll_is_non_ethernet(s, if_idx)) { + if (wolfIP_filter_notify_eth(WOLFIP_FILT_SENDING, s, if_idx, &out.ip.eth, sizeof(out)) != 0) + return; + } #endif - wolfIP_forward_packet(s, if_idx, &out.ip, sizeof(out), NULL, 0); + wolfIP_ll_send_frame(s, if_idx, &out.ip, sizeof(out)); } static void tcp_send_finack(struct tsocket *t) From 98c2b2b4479a808e4fad3d98e1f50c962c164ecf Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Fri, 13 Mar 2026 12:04:04 +0100 Subject: [PATCH 10/10] Addressed copilot's review --- src/test/unit/unit.c | 61 +++++++++++++++++++++++++++++++++++++++++--- src/wolfip.c | 31 +++++++++++++++++----- 2 files changed, 83 insertions(+), 9 deletions(-) diff --git a/src/test/unit/unit.c b/src/test/unit/unit.c index c386881..fba6473 100644 --- a/src/test/unit/unit.c +++ b/src/test/unit/unit.c @@ -7814,7 +7814,7 @@ START_TEST(test_udp_try_recv_unmatched_port_sends_icmp_unreachable) struct wolfIP s; uint8_t udp_buf[sizeof(struct wolfIP_udp_datagram) + 4]; struct wolfIP_udp_datagram *udp = (struct wolfIP_udp_datagram *)udp_buf; - struct wolfIP_icmp_ttl_exceeded_packet *icmp; + struct wolfIP_icmp_dest_unreachable_packet *icmp; uint32_t local_ip = 0x0A000001U; uint32_t remote_ip = 0x0A000002U; uint8_t src_mac[6] = {0x20, 0x21, 0x22, 0x23, 0x24, 0x25}; @@ -7846,8 +7846,8 @@ START_TEST(test_udp_try_recv_unmatched_port_sends_icmp_unreachable) (uint32_t)(ETH_HEADER_LEN + IP_HEADER_LEN + UDP_HEADER_LEN + 4)); ck_assert_uint_eq(last_frame_sent_size, - sizeof(struct wolfIP_icmp_ttl_exceeded_packet)); - icmp = (struct wolfIP_icmp_ttl_exceeded_packet *)last_frame_sent; + sizeof(struct wolfIP_icmp_dest_unreachable_packet)); + icmp = (struct wolfIP_icmp_dest_unreachable_packet *)last_frame_sent; ck_assert_uint_eq(icmp->type, 3U); ck_assert_uint_eq(icmp->code, 3U); ck_assert_mem_eq(icmp->unused, "\x00\x00\x00\x00", sizeof(icmp->unused)); @@ -7860,6 +7860,41 @@ START_TEST(test_udp_try_recv_unmatched_port_sends_icmp_unreachable) } END_TEST +START_TEST(test_udp_try_recv_unmatched_nonlocal_dst_does_not_send_icmp) +{ + struct wolfIP s; + uint8_t udp_buf[sizeof(struct wolfIP_udp_datagram) + 4]; + struct wolfIP_udp_datagram *udp = (struct wolfIP_udp_datagram *)udp_buf; + uint32_t local_ip = 0x0A000001U; + uint32_t remote_ip = 0x0A000002U; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, local_ip, 0xFFFFFF00U, 0); + + memset(udp_buf, 0, sizeof(udp_buf)); + udp->ip.ver_ihl = 0x45; + udp->ip.ttl = 64; + udp->ip.proto = WI_IPPROTO_UDP; + udp->ip.len = ee16(IP_HEADER_LEN + UDP_HEADER_LEN + 4); + udp->ip.src = ee32(remote_ip); + udp->ip.dst = ee32(0x0A0000FEU); + udp->src_port = ee16(4321); + udp->dst_port = ee16(1234); + udp->len = ee16(UDP_HEADER_LEN + 4); + memcpy(udp->data, "test", 4); + fix_udp_checksums(udp); + + memset(last_frame_sent, 0, sizeof(last_frame_sent)); + last_frame_sent_size = 0; + + udp_try_recv(&s, TEST_PRIMARY_IF, udp, + (uint32_t)(ETH_HEADER_LEN + IP_HEADER_LEN + UDP_HEADER_LEN + 4)); + + ck_assert_uint_eq(last_frame_sent_size, 0U); +} +END_TEST + START_TEST(test_dns_callback_bad_flags) { struct wolfIP s; @@ -15034,6 +15069,24 @@ START_TEST(test_tcp_input_unmatched_ack_sends_rst) } END_TEST +START_TEST(test_tcp_input_unmatched_ack_nonlocal_dst_does_not_send_rst) +{ + struct wolfIP s; + + wolfIP_init(&s); + mock_link_init(&s); + wolfIP_ipconfig_set(&s, 0x0A000001U, 0xFFFFFF00U, 0); + + last_frame_sent_size = 0; + memset(last_frame_sent, 0, sizeof(last_frame_sent)); + + inject_tcp_segment(&s, TEST_PRIMARY_IF, 0x0A000002U, 0x0A0000FEU, + 4321, 1234, 77, 101, TCP_FLAG_ACK); + + ck_assert_uint_eq(last_frame_sent_size, 0U); +} +END_TEST + START_TEST(test_tcp_input_unmatched_syn_sends_rst_ack) { struct wolfIP s; @@ -19360,6 +19413,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_utils, test_udp_try_recv_conf_null); tcase_add_test(tc_utils, test_udp_try_recv_remote_ip_matches_local_ip); 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_bad_name); tcase_add_test(tc_utils, test_dns_callback_short_header_ignored); @@ -19451,6 +19505,7 @@ Suite *wolf_suite(void) tcase_add_test(tc_utils, test_tcp_input_filter_drop); tcase_add_test(tc_utils, test_tcp_input_port_mismatch_skips_socket); tcase_add_test(tc_utils, test_tcp_input_unmatched_ack_sends_rst); + tcase_add_test(tc_utils, test_tcp_input_unmatched_ack_nonlocal_dst_does_not_send_rst); tcase_add_test(tc_utils, test_tcp_input_unmatched_syn_sends_rst_ack); tcase_add_test(tc_utils, test_tcp_input_unmatched_rst_is_discarded); tcase_add_test(tc_utils, test_tcp_input_syn_bound_ip_mismatch); diff --git a/src/wolfip.c b/src/wolfip.c index 4687f27..3a5287e 100644 --- a/src/wolfip.c +++ b/src/wolfip.c @@ -127,6 +127,7 @@ struct wolfIP_icmp_packet; /* Macros */ #define IS_IP_BCAST(ip) ((ip) == 0xFFFFFFFFU) +#define IS_IP_MCAST(ip) (((ip) & 0xF0000000U) == 0xE0000000U) #define PKT_FLAG_SENT 0x01U #define PKT_FLAG_ACKED 0x02U @@ -703,6 +704,7 @@ union transport_pseudo_header { #define TTL_EXCEEDED_ORIG_PACKET_SIZE (28) #define ICMP_TTL_EXCEEDED_SIZE (36) +#define ICMP_DEST_UNREACH_SIZE (36) struct PACKED wolfIP_icmp_packet { struct wolfIP_ip_packet ip; @@ -1497,12 +1499,12 @@ static void wolfIP_send_ttl_exceeded(struct wolfIP *s, unsigned int if_idx, memcpy(icmp.orig_packet, ((uint8_t *)orig) + ETH_HEADER_LEN, TTL_EXCEEDED_ORIG_PACKET_SIZE); icmp.csum = ee16(icmp_checksum((struct wolfIP_icmp_packet *)&icmp, - ICMP_TTL_EXCEEDED_SIZE)); + ICMP_DEST_UNREACH_SIZE)); icmp.ip.ver_ihl = 0x45; icmp.ip.ttl = 64; icmp.ip.proto = WI_IPPROTO_ICMP; icmp.ip.id = ipcounter_next(s); - icmp.ip.len = ee16(IP_HEADER_LEN + ICMP_TTL_EXCEEDED_SIZE); + icmp.ip.len = ee16(IP_HEADER_LEN + ICMP_DEST_UNREACH_SIZE); icmp.ip.src = ee32(wolfIP_ipconf_at(s, if_idx)->ip); icmp.ip.dst = orig->src; icmp.ip.csum = 0; @@ -1767,8 +1769,17 @@ static void udp_try_recv(struct wolfIP *s, unsigned int if_idx, matched = 1; } } - if (!matched) - wolfIP_send_port_unreachable(s, if_idx, &udp->ip); + if (!matched) { + int dst_match = 0; + + if (dst_ip != IPADDR_ANY && src_ip != IPADDR_ANY && + !IS_IP_BCAST(dst_ip) && !IS_IP_BCAST(src_ip) && + !IS_IP_MCAST(dst_ip) && !IS_IP_MCAST(src_ip)) { + (void)wolfIP_if_for_local_ip(s, dst_ip, &dst_match); + if (dst_match) + wolfIP_send_port_unreachable(s, if_idx, &udp->ip); + } + } } /* ICMP sockets reuse the UDP fifo bookkeeping */ @@ -3621,8 +3632,16 @@ static void tcp_input(struct wolfIP *S, unsigned int if_idx, } } } - if (!matched) - tcp_send_reset_reply(S, if_idx, tcp); + if (!matched) { + ip4 dst = ee32(tcp->ip.dst); + int dst_match = 0; + + if (dst != IPADDR_ANY && !IS_IP_BCAST(dst) && !IS_IP_MCAST(dst)) { + (void)wolfIP_if_for_local_ip(S, dst, &dst_match); + if (dst_match) + tcp_send_reset_reply(S, if_idx, tcp); + } + } } static void tcp_rto_cb(void *arg)