diff options
| author | Tom Rini <trini@konsulko.com> | 2026-09-15 08:55:15 -0600 |
|---|---|---|
| committer | Tom Rini <trini@konsulko.com> | 2026-09-15 08:55:15 -0600 |
| commit | 2c7eaee7b3422ce7c84e892abfe16e1afce42e83 (patch) | |
| tree | ab5542b9674c0d8a7d5189ea693900a3d80c78bb | |
| parent | a44f46af0aa17e48572e0d46c0d082886ee353c3 (diff) | |
| parent | a3afed6c8483357b094be6def40fd8d73617d72a (diff) | |
Merge tag 'net-20260915' of https://git.u-boot-project.org/u-boot/custodians/u-boot-net
Pull request net-20260915.
net
- test: dm: nfs: Add regression tests for the NFS reply-length checks
- net: nfs: Bound the length of an NFS readlink reply
- net: nfs: Bound the length of an NFS read reply
- fix error check in eth init
- airoha: fix PCS calibration retry limit
net-legacy
- net6: validate IPv6 payload and transport lengths on receive
- test: dm: eth: add DHCPv6 oversized option regression tests
- net: dhcp6: bound received DUID option lengths
net-lwip
- lwip: allow DHCP to stop without releasing its lease
- lwip: add an HTTP client abort operation
- lwip: allow DNS callbacks to be canceled
- lwip: return ERR_ABRT after aborting wget receive
| -rw-r--r-- | configs/sandbox_defconfig | 1 | ||||
| -rw-r--r-- | drivers/net/airoha/pcs-an7581.c | 2 | ||||
| -rw-r--r-- | lib/lwip/lwip/src/apps/http/http_client.c | 71 | ||||
| -rw-r--r-- | lib/lwip/lwip/src/apps/sntp/sntp.c | 3 | ||||
| -rw-r--r-- | lib/lwip/lwip/src/core/dns.c | 26 | ||||
| -rw-r--r-- | lib/lwip/lwip/src/core/ipv4/dhcp.c | 39 | ||||
| -rw-r--r-- | lib/lwip/lwip/src/include/lwip/apps/http_client.h | 1 | ||||
| -rw-r--r-- | lib/lwip/lwip/src/include/lwip/dhcp.h | 1 | ||||
| -rw-r--r-- | lib/lwip/lwip/src/include/lwip/dns.h | 1 | ||||
| -rw-r--r-- | net/dhcpv6.c | 12 | ||||
| -rw-r--r-- | net/dhcpv6.h | 3 | ||||
| -rw-r--r-- | net/eth-uclass.c | 4 | ||||
| -rw-r--r-- | net/lwip/wget.c | 2 | ||||
| -rw-r--r-- | net/net6.c | 18 | ||||
| -rw-r--r-- | net/nfs-common.c | 42 | ||||
| -rw-r--r-- | test/dm/Makefile | 1 | ||||
| -rw-r--r-- | test/dm/eth.c | 151 | ||||
| -rw-r--r-- | test/dm/nfs.c | 91 |
18 files changed, 426 insertions, 43 deletions
diff --git a/configs/sandbox_defconfig b/configs/sandbox_defconfig index 79f46317e45..ca73080b06d 100644 --- a/configs/sandbox_defconfig +++ b/configs/sandbox_defconfig @@ -123,6 +123,7 @@ CONFIG_CMD_LINK_LOCAL=y CONFIG_IPV6_ROUTER_DISCOVERY=y CONFIG_CMD_ETHSW=y CONFIG_CMD_DNS=y +CONFIG_CMD_NFS=y CONFIG_CMD_SNTP=y CONFIG_CMD_2048=y CONFIG_CMD_BMP=y diff --git a/drivers/net/airoha/pcs-an7581.c b/drivers/net/airoha/pcs-an7581.c index 746ff55d72f..bc15d9d07c4 100644 --- a/drivers/net/airoha/pcs-an7581.c +++ b/drivers/net/airoha/pcs-an7581.c @@ -1321,7 +1321,7 @@ retry_calibration: */ regmap_read(priv->xfi_pma, AIROHA_PCS_PMA_RX_FREQDET, &val); if (!(val & AIROHA_PCS_PMA_FBCK_LOCK)) { - if (calibration_try > AIROHA_PCS_MAX_CALIBRATION_TRY) { + if (calibration_try >= AIROHA_PCS_MAX_CALIBRATION_TRY) { dev_err(priv->dev, "No FBCK Lock from FreqDet module after %d calibration try. PCS won't work.\n", AIROHA_PCS_MAX_CALIBRATION_TRY); return -EIO; diff --git a/lib/lwip/lwip/src/apps/http/http_client.c b/lib/lwip/lwip/src/apps/http/http_client.c index 1973e79e723..2bc2a0db97e 100644 --- a/lib/lwip/lwip/src/apps/http/http_client.c +++ b/lib/lwip/lwip/src/apps/http/http_client.c @@ -146,15 +146,23 @@ typedef struct _httpc_state u32_t rx_content_len; u32_t hdr_content_len; httpc_parse_state_t parse_state; +#if LWIP_DNS + u8_t dns_pending; +#endif #if HTTPC_DEBUG_REQUEST char* server_name; char* uri; #endif } httpc_state_t; -/** Free http client state and deallocate all resources within */ -static err_t -httpc_free_state(httpc_state_t* req) +#if LWIP_DNS +static void httpc_dns_found(const char* hostname, const ip_addr_t *ipaddr, + void *arg); +#endif + +/** Free http client state and detach its connection callbacks */ +static struct altcp_pcb * +httpc_detach_state(httpc_state_t* req) { struct altcp_pcb* tpcb; @@ -168,16 +176,26 @@ httpc_free_state(httpc_state_t* req) } tpcb = req->pcb; - mem_free(req); - req = NULL; - if (tpcb != NULL) { - err_t r; altcp_arg(tpcb, NULL); altcp_recv(tpcb, NULL); altcp_err(tpcb, NULL); altcp_poll(tpcb, NULL, 0); altcp_sent(tpcb, NULL); + } + mem_free(req); + return tpcb; +} + +/** Free http client state and deallocate all resources within */ +static err_t +httpc_free_state(httpc_state_t* req) +{ + struct altcp_pcb* tpcb; + + tpcb = httpc_detach_state(req); + if (tpcb != NULL) { + err_t r; r = altcp_close(tpcb); if (r != ERR_OK) { altcp_abort(tpcb); @@ -202,6 +220,38 @@ httpc_close(httpc_state_t* req, httpc_result_t result, u32_t server_response, er return ERR_OK; } +/** Abort an HTTP client request. */ +err_t +httpc_abort(httpc_state_t *req) +{ + struct altcp_pcb *tpcb; + + if (req == NULL) { + return ERR_ARG; + } + +#if LWIP_DNS + if (req->dns_pending) { + dns_cancel(httpc_dns_found, req); + req->dns_pending = 0; + } +#endif + + if (req->conn_settings != NULL) { + if (req->conn_settings->result_fn != NULL) { + req->conn_settings->result_fn(req->callback_arg, + HTTPC_RESULT_LOCAL_ABORT, req->rx_content_len, 0, ERR_ABRT); + } + } + + tpcb = httpc_detach_state(req); + if (tpcb != NULL) { + altcp_abort(tpcb); + } + + return ERR_OK; +} + /** Parse http header response line 1 */ static err_t http_parse_response_status(struct pbuf *p, u16_t *http_version, u16_t *http_status, u16_t *http_status_str_offset) @@ -447,6 +497,7 @@ httpc_dns_found(const char* hostname, const ip_addr_t *ipaddr, void *arg) LWIP_UNUSED_ARG(hostname); + req->dns_pending = 0; if (ipaddr != NULL) { err = httpc_get_internal_addr(req, ipaddr); if (err == ERR_OK) { @@ -479,9 +530,13 @@ httpc_get_internal_dns(httpc_state_t* req, const char* server_name) if (err == ERR_OK) { /* cached or IP-string */ err = httpc_get_internal_addr(req, &req->remote_addr); - } else if (err == ERR_INPROGRESS) { + } +#if LWIP_DNS + else if (err == ERR_INPROGRESS) { + req->dns_pending = 1; return ERR_OK; } +#endif return err; } diff --git a/lib/lwip/lwip/src/apps/sntp/sntp.c b/lib/lwip/lwip/src/apps/sntp/sntp.c index 0e7f36520fa..a37699f65b7 100644 --- a/lib/lwip/lwip/src/apps/sntp/sntp.c +++ b/lib/lwip/lwip/src/apps/sntp/sntp.c @@ -711,6 +711,9 @@ void sntp_stop(void) { LWIP_ASSERT_CORE_LOCKED(); +#if SNTP_SERVER_DNS + dns_cancel(sntp_dns_found, NULL); +#endif if (sntp_pcb != NULL) { #if SNTP_MONITOR_SERVER_REACHABILITY u8_t i; diff --git a/lib/lwip/lwip/src/core/dns.c b/lib/lwip/lwip/src/core/dns.c index 6540f143bac..5762647ee27 100644 --- a/lib/lwip/lwip/src/core/dns.c +++ b/lib/lwip/lwip/src/core/dns.c @@ -388,6 +388,32 @@ dns_getserver(u8_t numdns) } /** + * @ingroup dns + * Cancel pending callbacks registered by dns_gethostbyname(). + * + * DNS queries shared with other callers continue so their result can still be + * cached and delivered. Only callbacks matching both arguments are removed. + * + * @param found callback passed to dns_gethostbyname() + * @param callback_arg callback argument passed to dns_gethostbyname() + */ +void +dns_cancel(dns_found_callback found, void *callback_arg) +{ + u8_t i; + + LWIP_ASSERT_CORE_LOCKED(); + + for (i = 0; i < DNS_MAX_REQUESTS; i++) { + if ((dns_requests[i].found == found) && + (dns_requests[i].arg == callback_arg)) { + dns_requests[i].found = NULL; + dns_requests[i].arg = NULL; + } + } +} + +/** * The DNS resolver client timer - handle retries and timeouts and should * be called every DNS_TMR_INTERVAL milliseconds (every second by default). */ diff --git a/lib/lwip/lwip/src/core/ipv4/dhcp.c b/lib/lwip/lwip/src/core/ipv4/dhcp.c index d9ed8b0f37a..bfc6cfe488e 100644 --- a/lib/lwip/lwip/src/core/ipv4/dhcp.c +++ b/lib/lwip/lwip/src/core/ipv4/dhcp.c @@ -1315,20 +1315,15 @@ dhcp_reboot(struct netif *netif) return result; } -/** - * @ingroup dhcp4 - * Release a DHCP lease and stop DHCP statemachine (and AUTOIP if LWIP_DHCP_AUTOIP_COOP). - * - * @param netif network interface - */ -void -dhcp_release_and_stop(struct netif *netif) +/** Stop the DHCP state machine, optionally releasing its lease. */ +static void +dhcp_stop_internal(struct netif *netif, int release) { struct dhcp *dhcp = netif_dhcp_data(netif); ip_addr_t server_ip_addr; LWIP_ASSERT_CORE_LOCKED(); - LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE, ("dhcp_release_and_stop()\n")); + LWIP_DEBUGF(DHCP_DEBUG | LWIP_DBG_TRACE, ("dhcp_stop_internal()\n")); if (dhcp == NULL) { return; } @@ -1352,7 +1347,7 @@ dhcp_release_and_stop(struct netif *netif) dhcp->t1_renew_time = dhcp->t2_rebind_time = dhcp->lease_used = dhcp->t0_timeout = 0; /* send release message when current IP was assigned via DHCP */ - if (dhcp_supplied_address(netif)) { + if (release && dhcp_supplied_address(netif)) { /* create and initialize the DHCP message header */ struct pbuf *p_out; u16_t options_out_len; @@ -1393,6 +1388,30 @@ dhcp_release_and_stop(struct netif *netif) /** * @ingroup dhcp4 + * Stop DHCP without sending DHCPRELEASE or removing the configured address. + * + * @param netif network interface + */ +void +dhcp_stop_without_release(struct netif *netif) +{ + dhcp_stop_internal(netif, 0); +} + +/** + * @ingroup dhcp4 + * Release a DHCP lease and stop DHCP statemachine (and AUTOIP if LWIP_DHCP_AUTOIP_COOP). + * + * @param netif network interface + */ +void +dhcp_release_and_stop(struct netif *netif) +{ + dhcp_stop_internal(netif, 1); +} + +/** + * @ingroup dhcp4 * This function calls dhcp_release_and_stop() internally. * @deprecated Use dhcp_release_and_stop() instead. */ diff --git a/lib/lwip/lwip/src/include/lwip/apps/http_client.h b/lib/lwip/lwip/src/include/lwip/apps/http_client.h index d39e1bf829a..3f62150dca1 100644 --- a/lib/lwip/lwip/src/include/lwip/apps/http_client.h +++ b/lib/lwip/lwip/src/include/lwip/apps/http_client.h @@ -143,6 +143,7 @@ err_t httpc_get_file(const ip_addr_t* server_addr, u16_t port, const char* uri, altcp_recv_fn recv_fn, void* callback_arg, httpc_state_t **connection); err_t httpc_get_file_dns(const char* server_name, u16_t port, const char* uri, const httpc_connection_t *settings, altcp_recv_fn recv_fn, void* callback_arg, httpc_state_t **connection); +err_t httpc_abort(httpc_state_t *connection); #if LWIP_HTTPC_HAVE_FILE_IO err_t httpc_get_file_to_disk(const ip_addr_t* server_addr, u16_t port, const char* uri, const httpc_connection_t *settings, diff --git a/lib/lwip/lwip/src/include/lwip/dhcp.h b/lib/lwip/lwip/src/include/lwip/dhcp.h index b413fa63f96..e7a79174f54 100644 --- a/lib/lwip/lwip/src/include/lwip/dhcp.h +++ b/lib/lwip/lwip/src/include/lwip/dhcp.h @@ -127,6 +127,7 @@ err_t dhcp_start(struct netif *netif); err_t dhcp_renew(struct netif *netif); err_t dhcp_release(struct netif *netif); void dhcp_stop(struct netif *netif); +void dhcp_stop_without_release(struct netif *netif); void dhcp_release_and_stop(struct netif *netif); void dhcp_inform(struct netif *netif); void dhcp_network_changed_link_up(struct netif *netif); diff --git a/lib/lwip/lwip/src/include/lwip/dns.h b/lib/lwip/lwip/src/include/lwip/dns.h index 091341544f3..82579f09f35 100644 --- a/lib/lwip/lwip/src/include/lwip/dns.h +++ b/lib/lwip/lwip/src/include/lwip/dns.h @@ -111,6 +111,7 @@ err_t dns_gethostbyname(const char *hostname, ip_addr_t *addr, err_t dns_gethostbyname_addrtype(const char *hostname, ip_addr_t *addr, dns_found_callback found, void *callback_arg, u8_t dns_addrtype); +void dns_cancel(dns_found_callback found, void *callback_arg); #if DNS_LOCAL_HOSTLIST diff --git a/net/dhcpv6.c b/net/dhcpv6.c index 640f089a2e1..a18e358137c 100644 --- a/net/dhcpv6.c +++ b/net/dhcpv6.c @@ -322,15 +322,19 @@ static void dhcp6_parse_options(uchar *rx_pkt, unsigned int len) switch (ntohs(option_hdr->option_id)) { case DHCP6_OPTION_CLIENTID: - if (memcmp(option_ptr, sm_params.duid, option_len) - != 0) { - debug("CLIENT ID DOESN'T MATCH\n"); - } else { + if (option_len == sizeof(sm_params.duid) && + !memcmp(option_ptr, sm_params.duid, option_len)) { debug("CLIENT ID FOUND and MATCHES\n"); sm_params.rx_status.client_id_match = true; + } else { + debug("CLIENT ID DOESN'T MATCH\n"); } break; case DHCP6_OPTION_SERVERID: + if (option_len > DHCP6_DUID_MAX_LEN) { + debug("SERVER ID too long\n"); + break; + } sm_params.rx_status.server_id_found = true; sm_params.rx_status.server_uid_ptr = (uchar *)option_hdr; sm_params.rx_status.server_uid_size = option_len + diff --git a/net/dhcpv6.h b/net/dhcpv6.h index d41a3c30615..b0207a9f2c9 100644 --- a/net/dhcpv6.h +++ b/net/dhcpv6.h @@ -37,6 +37,9 @@ #define DUID_LL_SIZE (sizeof(struct dhcp6_option_duid_ll) + ETH_ALEN) #define DUID_MAX_SIZE DUID_LL_SIZE /* only supports DUID-LL currently */ +/* RFC 8415 sec 11.1: a DUID is a 2-octet type plus at most 128 octets */ +#define DHCP6_DUID_MAX_LEN 130 + /* vendor-class-data to send in vendor clas option */ #define DHCP6_VCI_STRING "U-Boot" diff --git a/net/eth-uclass.c b/net/eth-uclass.c index 5c437143a30..07ec59b97f0 100644 --- a/net/eth-uclass.c +++ b/net/eth-uclass.c @@ -289,8 +289,10 @@ int eth_start_udev(struct udevice *dev) if (priv->running) return 0; - if (!device_active(dev)) + if (!device_active(dev)) { + eth_errno = -EINVAL; return -EINVAL; + } ret = eth_get_ops(dev)->start(dev); if (ret < 0) diff --git a/net/lwip/wget.c b/net/lwip/wget.c index 247ece18e2b..e8930da410b 100644 --- a/net/lwip/wget.c +++ b/net/lwip/wget.c @@ -206,7 +206,7 @@ static err_t httpc_recv_cb(void *arg, struct altcp_pcb *pcb, struct pbuf *pbuf, for (buf = pbuf; buf; buf = buf->next) { if (store_block(ctx, buf->payload, buf->len) < 0) { altcp_abort(pcb); - ret = ERR_BUF; + ret = ERR_ABRT; goto out; } } diff --git a/net/net6.c b/net/net6.c index 4cff98df15c..e1a455748e3 100644 --- a/net/net6.c +++ b/net/net6.c @@ -392,11 +392,19 @@ int net_ip6_handler(struct ethernet_hdr *et, struct ip6_hdr *ip6, int len) if (ip6->version != 6) return -EINVAL; + hlen = ntohs(ip6->payload_len); + + if (len < IP6_HDR_SIZE + hlen) + return -EINVAL; + len = IP6_HDR_SIZE + hlen; + switch (ip6->nexthdr) { case PROT_ICMPV6: + if (hlen < sizeof(struct icmp6hdr)) + return -EINVAL; + icmp = (struct icmp6hdr *)(((uchar *)ip6) + IP6_HDR_SIZE); csum = icmp->icmp6_cksum; - hlen = ntohs(ip6->payload_len); icmp->icmp6_cksum = 0; /* checksum */ csum_p = csum_partial((u8 *)icmp, hlen, 0); @@ -421,9 +429,15 @@ int net_ip6_handler(struct ethernet_hdr *et, struct ip6_hdr *ip6, int len) } break; case IPPROTO_UDP: + if (hlen < UDP_HDR_SIZE) + return -EINVAL; + udp = (struct udp_hdr *)(((uchar *)ip6) + IP6_HDR_SIZE); + if (ntohs(udp->udp_len) < UDP_HDR_SIZE || + ntohs(udp->udp_len) > hlen) + return -EINVAL; + csum = udp->udp_xsum; - hlen = ntohs(ip6->payload_len); udp->udp_xsum = 0; /* checksum */ csum_p = csum_partial((u8 *)udp, hlen, 0); diff --git a/net/nfs-common.c b/net/nfs-common.c index 637fcfd9bb8..869ba6b9ef9 100644 --- a/net/nfs-common.c +++ b/net/nfs-common.c @@ -642,7 +642,8 @@ static int nfs3_get_attributes_offset(uint32_t *data) static int nfs_readlink_reply(uchar *pkt, unsigned int len) { struct rpc_t rpc_pkt; - int rlen; + u32 rlen; + size_t data_offset; int nfsv3_data_offset = 0; memcpy((unsigned char *)&rpc_pkt, pkt, len); @@ -666,27 +667,33 @@ static int nfs_readlink_reply(uchar *pkt, unsigned int len) /* new path length */ rlen = ntohl(rpc_pkt.u.reply.data[1 + nfsv3_data_offset]); - if (((uchar *)&rpc_pkt.u.reply.data[0] - (uchar *)&rpc_pkt + rlen) > len) + data_offset = (uchar *)&rpc_pkt.u.reply.data[2 + nfsv3_data_offset] - + (uchar *)&rpc_pkt; + + /* reject a length that runs past the received packet */ + if (data_offset > len || rlen > len - data_offset) return -NFS_RPC_DROP; if (*((char *)&rpc_pkt.u.reply.data[2 + nfsv3_data_offset]) != '/') { - int pathlen; - int new_len; + size_t pathlen; + size_t new_len; strcat(nfs_path, "/"); pathlen = strlen(nfs_path); - new_len = pathlen + rlen; - if (new_len >= sizeof(nfs_path_buff)) { - printf("NFS: symlink too long (%d bytes)\n", new_len); + if (pathlen >= sizeof(nfs_path_buff) || + rlen >= sizeof(nfs_path_buff) - pathlen) { + printf("NFS: symlink too long (%zu + %u bytes)\n", + pathlen, rlen); return -NFS_RPC_DROP; } + new_len = pathlen + rlen; memcpy(nfs_path + pathlen, (uchar *)&rpc_pkt.u.reply.data[2 + nfsv3_data_offset], rlen); nfs_path[new_len] = 0; } else { if (rlen >= sizeof(nfs_path_buff)) { - printf("NFS: symlink too long (%d bytes)\n", rlen); + printf("NFS: symlink too long (%u bytes)\n", rlen); return -NFS_RPC_DROP; } memcpy(nfs_path, @@ -700,7 +707,8 @@ static int nfs_readlink_reply(uchar *pkt, unsigned int len) static int nfs_read_reply(uchar *pkt, unsigned int len) { struct rpc_t rpc_pkt; - int rlen; + u32 rlen; + size_t data_offset; uchar *data_ptr; memcpy(&rpc_pkt.u.data[0], pkt, sizeof(rpc_pkt.u.reply)); @@ -734,17 +742,19 @@ static int nfs_read_reply(uchar *pkt, unsigned int len) int nfsv3_data_offset = nfs3_get_attributes_offset(rpc_pkt.u.reply.data); - /* count value */ - rlen = ntohl(rpc_pkt.u.reply.data[1 + nfsv3_data_offset]); - /* Skip unused values : - * EOF: 32 bits value, - * data_size: 32 bits value, - */ + /* Skip count and EOF, read data_size from opaque data */ + rlen = ntohl(rpc_pkt.u.reply.data[3 + nfsv3_data_offset]); data_ptr = (uchar *) &rpc_pkt.u.reply.data[4 + nfsv3_data_offset]; } - if (((uchar *)&rpc_pkt.u.reply.data[0] - (uchar *)&rpc_pkt + rlen) > len) + /* reject a length larger than a read requests */ + if (rlen > NFS_READ_SIZE) + return -9999; + + /* reject a length that runs past the received packet */ + data_offset = data_ptr - (uchar *)&rpc_pkt; + if (data_offset > len || rlen > len - data_offset) return -9999; if (store_block(data_ptr, nfs_offset, rlen)) diff --git a/test/dm/Makefile b/test/dm/Makefile index fb3e6a7008f..cc51fd33079 100644 --- a/test/dm/Makefile +++ b/test/dm/Makefile @@ -78,6 +78,7 @@ obj-$(CONFIG_MUX_MMIO) += mux-mmio.o obj-y += fdtdec.o obj-$(CONFIG_MTD_RAW_NAND) += nand.o obj-$(CONFIG_IP_DEFRAG) += net_defrag.o +obj-$(CONFIG_CMD_NFS) += nfs.o obj-$(CONFIG_UT_DM) += nop.o obj-y += ofnode.o obj-y += ofread.o diff --git a/test/dm/eth.c b/test/dm/eth.c index ed0b57d8861..f3a972c5c0a 100644 --- a/test/dm/eth.c +++ b/test/dm/eth.c @@ -621,6 +621,157 @@ static int dm_test_eth_async_ping_reply(struct unit_test_state *uts) return 0; } DM_TEST(dm_test_eth_async_ping_reply, UTF_SCAN_FDT); + +#if IS_ENABLED(CONFIG_CMD_DHCP6) && IS_ENABLED(CONFIG_IPV6) +#define DHCP6_DUID_LL_LEN 10 /* type(2) + hw_type(2) + MAC(6) */ +static bool dhcp6_request_seen; +static bool dhcp6_advertise_sent; +static int dhcp6_clientid_len; +static int dhcp6_serverid_len; + +/* + * Answer a DHCPv6 SOLICIT with an ADVERTISE whose SERVERID option is longer + * than any valid DUID. A correct client rejects it and never sends a REQUEST; + * a client that trusts the length copies it out of bounds while building the + * REQUEST. + */ +static int sb_dhcp6_advertise_handler(struct udevice *dev, void *packet, + unsigned int len) +{ + struct eth_sandbox_priv *priv = dev_get_priv(dev); + struct ethernet_hdr *seth = packet; + struct ethernet_hdr *eth; + struct ip6_hdr *sip6, *ip6; + struct udp_hdr *sudp, *udp; + uchar *sdhcp6, *d, *opt, *rx; + int msglen; + u16 udptot; + + if (ntohs(seth->et_protlen) != PROT_IP6) + return 0; + sip6 = (struct ip6_hdr *)((uchar *)packet + ETHER_HDR_SIZE); + if (sip6->nexthdr != IPPROTO_UDP) + return 0; + sudp = (struct udp_hdr *)((uchar *)sip6 + IP6_HDR_SIZE); + if (ntohs(sudp->udp_dst) != 547 || ntohs(sudp->udp_src) != 546) + return 0; + sdhcp6 = (uchar *)sudp + UDP_HDR_SIZE; + + /* a REQUEST means the client accepted the over-long SERVERID */ + if (sdhcp6[0] == 3) { /* DHCP6_MSG_REQUEST */ + dhcp6_request_seen = true; + net_set_state(NETLOOP_FAIL); + return 0; + } + if (sdhcp6[0] != 1) /* DHCP6_MSG_SOLICIT */ + return 0; + if (dhcp6_advertise_sent) { + /* the client re-solicited, so it rejected the ADVERTISE */ + net_set_state(NETLOOP_FAIL); + return 0; + } + dhcp6_advertise_sent = true; + if (priv->recv_packets >= PKTBUFSRX) + return 0; + + rx = priv->recv_packet_buffer[priv->recv_packets]; + memset(rx, 0, PKTSIZE); + + eth = (struct ethernet_hdr *)rx; + memcpy(eth->et_dest, seth->et_src, ARP_HLEN); + memcpy(eth->et_src, priv->fake_host_hwaddr, ARP_HLEN); + eth->et_protlen = htons(PROT_IP6); + + ip6 = (struct ip6_hdr *)(rx + ETHER_HDR_SIZE); + ip6->version = 6; + ip6->nexthdr = IPPROTO_UDP; + ip6->hop_limit = 255; + memcpy(&ip6->saddr, &sip6->daddr, sizeof(struct in6_addr)); + memcpy(&ip6->daddr, &sip6->saddr, sizeof(struct in6_addr)); + + udp = (struct udp_hdr *)((uchar *)ip6 + IP6_HDR_SIZE); + udp->udp_src = htons(547); + udp->udp_dst = htons(546); + + d = (uchar *)udp + UDP_HDR_SIZE; + opt = d; + /* dhcp6 header: reuse the SOLICIT trans_id, msg_type = ADVERTISE */ + memcpy(opt, sdhcp6, 4); + opt[0] = 2; /* DHCP6_MSG_ADVERTISE */ + opt += 4; + /* CLIENTID: the client DUID from the SOLICIT, padded to the test size */ + opt[0] = 0; opt[1] = 1; + opt[2] = dhcp6_clientid_len >> 8; + opt[3] = dhcp6_clientid_len & 0xff; + memcpy(opt + 4, sdhcp6 + 8, DHCP6_DUID_LL_LEN); + opt += 4 + dhcp6_clientid_len; + /* echo the client's IA_NA (hdr 4 + iaid/t1/t2 12) */ + memcpy(opt, sdhcp6 + 4 + 14 + 6, 16); + opt += 16; + /* PREFERENCE = 255 so the client acts on this ADVERTISE at once */ + opt[0] = 0; opt[1] = 7; opt[2] = 0; opt[3] = 1; opt[4] = 255; + opt += 5; + /* SERVERID of the test size */ + opt[0] = 0; opt[1] = 2; + opt[2] = dhcp6_serverid_len >> 8; + opt[3] = dhcp6_serverid_len & 0xff; + memset(opt + 4, 0x41, dhcp6_serverid_len); + opt += 4 + dhcp6_serverid_len; + + msglen = opt - d; + udptot = UDP_HDR_SIZE + msglen; + ip6->payload_len = htons(udptot); + udp->udp_len = htons(udptot); + udp->udp_xsum = 0; + udp->udp_xsum = csum_ipv6_magic(&ip6->saddr, &ip6->daddr, udptot, + IPPROTO_UDP, + csum_partial((u8 *)udp, udptot, 0)); + + priv->recv_packet_length[priv->recv_packets] = + ETHER_HDR_SIZE + IP6_HDR_SIZE + udptot; + priv->recv_packets++; + + return 0; +} + +static int dhcp6_run_advertise(struct unit_test_state *uts) +{ + dhcp6_request_seen = false; + dhcp6_advertise_sent = false; + sandbox_eth_set_tx_handler(0, sb_dhcp6_advertise_handler); + sandbox_eth_skip_timeout(); + + env_set("ethact", "eth@10002000"); + net_loop(DHCP6); + + sandbox_eth_set_tx_handler(0, NULL); + + /* the malformed ADVERTISE must be rejected: no REQUEST is sent */ + ut_assert(!dhcp6_request_seen); + + return 0; +} + +/* Check the DHCPv6 client rejects an over-long SERVERID option */ +static int dm_test_dhcp6_serverid_reject(struct unit_test_state *uts) +{ + dhcp6_clientid_len = DHCP6_DUID_LL_LEN; + dhcp6_serverid_len = 200; + + return dhcp6_run_advertise(uts); +} +DM_TEST(dm_test_dhcp6_serverid_reject, UTF_SCAN_FDT); + +/* Check the DHCPv6 client rejects an over-long CLIENTID option */ +static int dm_test_dhcp6_clientid_reject(struct unit_test_state *uts) +{ + dhcp6_clientid_len = 20; + dhcp6_serverid_len = DHCP6_DUID_LL_LEN; + + return dhcp6_run_advertise(uts); +} +DM_TEST(dm_test_dhcp6_clientid_reject, UTF_SCAN_FDT); +#endif #endif #if IS_ENABLED(CONFIG_IPV6_ROUTER_DISCOVERY) diff --git a/test/dm/nfs.c b/test/dm/nfs.c new file mode 100644 index 00000000000..e18e5812440 --- /dev/null +++ b/test/dm/nfs.c @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Regression tests for the NFS reply-length checks. + */ + +#include <net.h> +#include <string.h> +#include <test/ut.h> +#include <dm/test.h> +#include "../../net/nfs-common.h" + +static int dm_test_nfs_read_oob(struct unit_test_state *uts) +{ + int saved_state = nfs_state; + unsigned long saved_id = rpc_id; + int saved_offset = nfs_offset; + enum nfs_version saved_version = choosen_nfs_version; + u32 saved_size = net_boot_file_size; + struct rpc_t reply; + + /* Pretend a READ request is outstanding (NFSv3). */ + choosen_nfs_version = NFS_V3; + nfs_state = STATE_READ_REQ; + nfs_offset = 0; + rpc_id = 0x11223344; + net_boot_file_size = 0; + + /* Accepted reply, matching xid, READ status OK, no attributes, then a + * count and data length with the top bit set. + */ + memset(&reply, 0, sizeof(reply)); + reply.u.reply.id = htonl((u32)rpc_id); + reply.u.reply.data[0] = 0; /* nfsstat3: OK */ + reply.u.reply.data[1] = 0; /* attributes_follow: no */ + reply.u.reply.data[2] = htonl(0x80000000); /* count */ + reply.u.reply.data[4] = htonl(0x80000000); /* data length */ + + nfs_pkt_recv((uchar *)&reply.u.reply, sizeof(reply.u.reply)); + + /* Rejected: nothing stored. */ + ut_asserteq(0, net_boot_file_size); + + nfs_state = saved_state; + rpc_id = saved_id; + nfs_offset = saved_offset; + choosen_nfs_version = saved_version; + net_boot_file_size = saved_size; + + return 0; +} +DM_TEST(dm_test_nfs_read_oob, 0); + +static int dm_test_nfs_readlink_oob(struct unit_test_state *uts) +{ + int saved_state = nfs_state; + unsigned long saved_id = rpc_id; + enum nfs_version saved_version = choosen_nfs_version; + char *saved_path = nfs_path; + struct rpc_t reply; + + /* Pretend a READLINK request is outstanding (NFSv3). */ + choosen_nfs_version = NFS_V3; + nfs_state = STATE_READLINK_REQ; + rpc_id = 0x11223344; + nfs_path = nfs_path_buff; + strcpy(nfs_path_buff, "dir"); + + /* Accepted reply, matching xid, READLINK status OK, no attributes, a + * length of -1 that slips past the destination bound as pathlen - 1, + * then a relative (non-'/') target. + */ + memset(&reply, 0, sizeof(reply)); + reply.u.reply.id = htonl((u32)rpc_id); + reply.u.reply.data[0] = 0; /* nfsstat3: OK */ + reply.u.reply.data[1] = 0; /* attributes_follow: no */ + reply.u.reply.data[2] = htonl(0xffffffff); /* symlink length -1 */ + reply.u.reply.data[3] = htonl(0x61616161); /* target, not '/' */ + + nfs_pkt_recv((uchar *)&reply.u.reply, sizeof(reply.u.reply)); + + /* Rejected: the path buffer is untouched. */ + ut_asserteq_str("dir", nfs_path_buff); + + nfs_state = saved_state; + rpc_id = saved_id; + choosen_nfs_version = saved_version; + nfs_path = saved_path; + + return 0; +} +DM_TEST(dm_test_nfs_readlink_oob, 0); |
