summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTom Rini <trini@konsulko.com>2026-09-15 08:55:15 -0600
committerTom Rini <trini@konsulko.com>2026-09-15 08:55:15 -0600
commit2c7eaee7b3422ce7c84e892abfe16e1afce42e83 (patch)
treeab5542b9674c0d8a7d5189ea693900a3d80c78bb
parenta44f46af0aa17e48572e0d46c0d082886ee353c3 (diff)
parenta3afed6c8483357b094be6def40fd8d73617d72a (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_defconfig1
-rw-r--r--drivers/net/airoha/pcs-an7581.c2
-rw-r--r--lib/lwip/lwip/src/apps/http/http_client.c71
-rw-r--r--lib/lwip/lwip/src/apps/sntp/sntp.c3
-rw-r--r--lib/lwip/lwip/src/core/dns.c26
-rw-r--r--lib/lwip/lwip/src/core/ipv4/dhcp.c39
-rw-r--r--lib/lwip/lwip/src/include/lwip/apps/http_client.h1
-rw-r--r--lib/lwip/lwip/src/include/lwip/dhcp.h1
-rw-r--r--lib/lwip/lwip/src/include/lwip/dns.h1
-rw-r--r--net/dhcpv6.c12
-rw-r--r--net/dhcpv6.h3
-rw-r--r--net/eth-uclass.c4
-rw-r--r--net/lwip/wget.c2
-rw-r--r--net/net6.c18
-rw-r--r--net/nfs-common.c42
-rw-r--r--test/dm/Makefile1
-rw-r--r--test/dm/eth.c151
-rw-r--r--test/dm/nfs.c91
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);