diff --git a/include/pico_socket.h b/include/pico_socket.h index 1bd7a1f8..d167bbf5 100644 --- a/include/pico_socket.h +++ b/include/pico_socket.h @@ -234,6 +234,10 @@ int pico_socket_getpeername(struct pico_socket *s, void *remote_addr, uint16_t * int pico_socket_connect(struct pico_socket *s, const void *remote_addr, uint16_t remote_port); int pico_socket_listen(struct pico_socket *s, const int backlog); +/* `orig` must point to a buffer large enough for the peer address of the + * socket's address family: sizeof(struct pico_ip4) for IPv4, sizeof(struct + * pico_ip6) for IPv6. Use `union pico_address` to be safe for either family; + * a smaller buffer is overflown by the family-sized copy (FSCT-2026-0018). */ struct pico_socket *pico_socket_accept(struct pico_socket *s, void *orig, uint16_t *port); int8_t pico_socket_del(struct pico_socket *s); diff --git a/modules/pico_dns_client.c b/modules/pico_dns_client.c index 5e518ade..6a7600b5 100644 --- a/modules/pico_dns_client.c +++ b/modules/pico_dns_client.c @@ -237,14 +237,18 @@ static struct pico_dns_query *pico_dns_client_find_query(struct pico_stack *S, u return NULL; } -/* seek end of string */ -static char *pico_dns_client_seek(char *ptr) +/* seek end of string, without reading past the end of the response buffer */ +static char *pico_dns_client_seek(char *ptr, const char *end) { - if (!ptr) + if (!ptr || !end) return NULL; - while (*ptr != 0) + while ((ptr < end) && (*ptr != 0)) ptr++; + /* No terminator within the response: reject instead of walking past it. */ + if (ptr >= end) + return NULL; + return ptr + 1; } @@ -358,7 +362,7 @@ static char *pico_dns_client_seek_suffix(char *suf, struct pico_dns_header *pre, case PICO_DNS_LABEL: dns_dbg("DNS: label\n"); - suf = pico_dns_client_seek(suf); + suf = pico_dns_client_seek(suf, (const char *)pre + PICO_IP_MRU); break; default: @@ -602,7 +606,7 @@ static void pico_dns_client_callback(uint16_t ev, struct pico_socket *s) /* FIX: what if the query is not a PTR query? */ domain = (char *)header + sizeof(struct pico_dns_header); - qsuffix = (struct pico_dns_question_suffix *)pico_dns_client_seek(domain); + qsuffix = (struct pico_dns_question_suffix *)pico_dns_client_seek(domain, dns_response + sizeof(dns_response)); /* valid asuffix is determined dynamically later on */ if (pico_dns_client_check_qsuffix(qsuffix, q) < 0) diff --git a/modules/pico_fragments.c b/modules/pico_fragments.c index 704ffa7e..be4f4cd0 100644 --- a/modules/pico_fragments.c +++ b/modules/pico_fragments.c @@ -329,6 +329,14 @@ static int pico_fragments_reassemble(struct pico_tree *tree, unsigned int len, u return -3; } + /* A reassembled datagram must fit the 16-bit frame length. Without this + * check the (uint16_t) cast below truncates the allocation while the copy + * loop writes the full length, overflowing the heap buffer. The fragments + * are left for the expiration timer, as on allocation failure below. */ + if (len > (65535U - header_length)) { + return -4; + } + full = pico_frame_alloc((uint16_t)(header_length + len)); if (full) { full->net_hdr = full->buffer; diff --git a/modules/pico_ipv4.c b/modules/pico_ipv4.c index aa46871b..d8f3683a 100644 --- a/modules/pico_ipv4.c +++ b/modules/pico_ipv4.c @@ -675,7 +675,11 @@ static int pico_ipv4_process_in(struct pico_stack *S, struct pico_protocol *self f->frag = short_be(hdr->frag); #endif - if (f->transport_len > max_allowed) { + /* max_allowed measures the space after the fixed IP header, but the transport + * area starts option_len bytes further in; subtract the options so the check + * cannot admit a transport region running past the frame (FSCT-2026-0022). + * The net_len fit-check above guarantees max_allowed >= option_len. */ + if (f->transport_len > (uint16_t)(max_allowed - option_len)) { pico_frame_discard(f); return 0; /* Packet is discarded due to unfeasible length */ } diff --git a/modules/pico_udp.c b/modules/pico_udp.c index f00a421e..006de3c6 100644 --- a/modules/pico_udp.c +++ b/modules/pico_udp.c @@ -199,7 +199,15 @@ uint16_t pico_udp_recv(struct pico_socket *s, void *buf, uint16_t len, void *src if (f) { if (!f->payload_len) { f->payload = f->transport_hdr + sizeof(struct pico_udp_hdr); - f->payload_len = (uint16_t)(f->transport_len - sizeof(struct pico_udp_hdr)); + /* A datagram whose transport length is smaller than the UDP header + * carries no payload; without this guard the subtraction underflows + * to ~65528 and the copy below reads far past the frame + * (FSCT-2026-0019). */ + if (f->transport_len < sizeof(struct pico_udp_hdr)) { + f->payload_len = 0; + } else { + f->payload_len = (uint16_t)(f->transport_len - sizeof(struct pico_udp_hdr)); + } } udp_dbg("expected: %d, got: %d\n", len, f->payload_len); diff --git a/test/examples/tcpecho.c b/test/examples/tcpecho.c index 7a71c7c3..745054ce 100644 --- a/test/examples/tcpecho.c +++ b/test/examples/tcpecho.c @@ -58,7 +58,11 @@ void cb_tcpecho(uint16_t ev, struct pico_socket *s) struct pico_socket *sock_a = { 0 }; - struct pico_ip4 orig = { + /* Family-agnostic buffer: pico_socket_accept() copies the peer address + * sized by the socket's family (16 bytes for IPv6), so a bare + * struct pico_ip4 here would be overflowed by an IPv6 peer + * (FSCT-2026-0018). */ + union pico_address orig = { 0 }; uint16_t port = 0; @@ -68,7 +72,7 @@ void cb_tcpecho(uint16_t ev, struct pico_socket *s) int yes = 1; sock_a = pico_socket_accept(s, &orig, &port); - pico_ipv4_to_string(peer, orig.addr); + pico_ipv4_to_string(peer, orig.ip4.addr); printf("Connection established with %s:%d.\n", peer, short_be(port)); pico_socket_setoption(sock_a, PICO_TCP_NODELAY, &yes); /* Set keepalive options */