U-Boot Archive on lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 0/2] net: dhcp6: fix out-of-bounds access in the option parser
@ 2026-08-06 18:37 Shahriyar Jalayeri
  2026-08-06 18:37 ` [PATCH 1/2] net: dhcp6: bound received DUID option lengths Shahriyar Jalayeri
                   ` (2 more replies)
  0 siblings, 3 replies; 5+ messages in thread
From: Shahriyar Jalayeri @ 2026-08-06 18:37 UTC (permalink / raw)
  To: Sean Edmond, Ramon Fried, u-boot
  Cc: Argus, Jerome Forissier, Tom Rini, Francois Berder,
	Shahriyar Jalayeri

The DHCPv6 client takes the SERVERID and CLIENTID option lengths from a
received ADVERTISE/REPLY without bounding them. The SERVERID length is
later copied unbounded into the fixed net_tx_packet buffer when the
REQUEST is built (an out-of-bounds write), and the CLIENTID length
drives a memcmp against the fixed client-DUID buffer (an out-of-bounds
read).
Both are reachable by any on-link attacker that answers a DHCPv6 SOLICIT
during netboot.

Patch 1 bounds both lengths at parse time. Patch 2 adds sandbox DM
regression tests that inject an ADVERTISE with an over-long SERVERID or
CLIENTID and check the client rejects it.

Based on master (baa64b2f892). A reproducer is available on request.

Signed-off-by: Shahriyar Jalayeri <shahriyar@byteray.co.uk>
---
Shahriyar Jalayeri (2):
      net: dhcp6: bound received DUID option lengths
      test: dm: eth: add DHCPv6 oversized option regression tests

 net/dhcpv6.c  |  12 +++--
 net/dhcpv6.h  |   3 ++
 test/dm/eth.c | 151 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 162 insertions(+), 4 deletions(-)
---
base-commit: baa64b2f892890f00a377eac4a3e685472bb56b5
change-id: 20260806-b4-dhcp6-serverid-oob-06d0eb283aac

Best regards,
--  
Shahriyar Jalayeri <shahriyar@byteray.co.uk>


^ permalink raw reply	[flat|nested] 5+ messages in thread

* [PATCH 1/2] net: dhcp6: bound received DUID option lengths
  2026-08-06 18:37 [PATCH 0/2] net: dhcp6: fix out-of-bounds access in the option parser Shahriyar Jalayeri
@ 2026-08-06 18:37 ` Shahriyar Jalayeri
  2026-08-06 18:37 ` [PATCH 2/2] test: dm: eth: add DHCPv6 oversized option regression tests Shahriyar Jalayeri
  2026-08-14 15:42 ` [PATCH 0/2] net: dhcp6: fix out-of-bounds access in the option parser Jerome Forissier
  2 siblings, 0 replies; 5+ messages in thread
From: Shahriyar Jalayeri @ 2026-08-06 18:37 UTC (permalink / raw)
  To: Sean Edmond, Ramon Fried, u-boot
  Cc: Argus, Jerome Forissier, Tom Rini, Francois Berder,
	Shahriyar Jalayeri

dhcp6_parse_options() takes the SERVERID and CLIENTID option lengths
from the received ADVERTISE/REPLY and uses them unchecked:

- the SERVERID length is stored as server_uid_size and later copied into
  the fixed net_tx_packet buffer in dhcp6_send_request_packet() with no
  capacity check, so a large SERVERID overruns net_tx_packet (an
  out-of-bounds write);
- the CLIENTID length is passed straight to memcmp() against the fixed
  sm_params.duid buffer, so a length larger than it reads past the end
  (an out-of-bounds read).

Both are reachable by any on-link attacker that answers a DHCPv6 SOLICIT
during netboot; neither length is bounded against anything but the
received packet size.

Bound the SERVERID length to the RFC 8415 DUID maximum at parse time,
which also bounds the malloc() and the copy into the REQUEST for every
caller, and only compare a CLIENTID that is exactly the size of the
client DUID.

Fixes: a0245818f7f8 ("net: dhcp6: Add DHCPv6 (DHCP for IPv6)")
Signed-off-by: Shahriyar Jalayeri <shahriyar@byteray.co.uk>
---
 net/dhcpv6.c | 12 ++++++++----
 net/dhcpv6.h |  3 +++
 2 files changed, 11 insertions(+), 4 deletions(-)

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"
 

-- 
2.43.0


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* [PATCH 2/2] test: dm: eth: add DHCPv6 oversized option regression tests
  2026-08-06 18:37 [PATCH 0/2] net: dhcp6: fix out-of-bounds access in the option parser Shahriyar Jalayeri
  2026-08-06 18:37 ` [PATCH 1/2] net: dhcp6: bound received DUID option lengths Shahriyar Jalayeri
@ 2026-08-06 18:37 ` Shahriyar Jalayeri
  2026-08-13 13:21   ` Jerome Forissier
  2026-08-14 15:42 ` [PATCH 0/2] net: dhcp6: fix out-of-bounds access in the option parser Jerome Forissier
  2 siblings, 1 reply; 5+ messages in thread
From: Shahriyar Jalayeri @ 2026-08-06 18:37 UTC (permalink / raw)
  To: Sean Edmond, Ramon Fried, u-boot
  Cc: Argus, Jerome Forissier, Tom Rini, Francois Berder,
	Shahriyar Jalayeri

Answer a DHCPv6 SOLICIT from the sandbox eth tx handler with an
ADVERTISE that carries an option longer than a valid DUID, and check
the client rejects it rather than sending a REQUEST.

Two cases cover the option parser: an over-long SERVERID, which a client
that trusts the length copies out of bounds while building the REQUEST,
and an over-long CLIENTID, which drives a memcmp past the client DUID
buffer.

Signed-off-by: Shahriyar Jalayeri <shahriyar@byteray.co.uk>
---
 test/dm/eth.c | 151 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 151 insertions(+)

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)

-- 
2.43.0


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* Re: [PATCH 2/2] test: dm: eth: add DHCPv6 oversized option regression tests
  2026-08-06 18:37 ` [PATCH 2/2] test: dm: eth: add DHCPv6 oversized option regression tests Shahriyar Jalayeri
@ 2026-08-13 13:21   ` Jerome Forissier
  0 siblings, 0 replies; 5+ messages in thread
From: Jerome Forissier @ 2026-08-13 13:21 UTC (permalink / raw)
  To: Sean Edmond, Ramon Fried, u-boot, Shahriyar Jalayeri
  Cc: Argus, Tom Rini, Francois Berder, nd

On Thu, 06 Aug 2026 20:37:51 +0200, Shahriyar Jalayeri wrote:
> Answer a DHCPv6 SOLICIT from the sandbox eth tx handler with an
> ADVERTISE that carries an option longer than a valid DUID, and check
> the client rejects it rather than sending a REQUEST.
> 
> Two cases cover the option parser: an over-long SERVERID, which a client
> that trusts the length copies out of bounds while building the REQUEST,
> and an over-long CLIENTID, which drives a memcmp past the client DUID
> buffer.
> 
> [...]

Applied to u-boot-net branch for-main, thanks!

[2/2] test: dm: eth: add DHCPv6 oversized option regression tests
      (no commit info)

Best regards,
-- 
Jerome Forissier <jerome.forissier@arm.com>


^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [PATCH 0/2] net: dhcp6: fix out-of-bounds access in the option parser
  2026-08-06 18:37 [PATCH 0/2] net: dhcp6: fix out-of-bounds access in the option parser Shahriyar Jalayeri
  2026-08-06 18:37 ` [PATCH 1/2] net: dhcp6: bound received DUID option lengths Shahriyar Jalayeri
  2026-08-06 18:37 ` [PATCH 2/2] test: dm: eth: add DHCPv6 oversized option regression tests Shahriyar Jalayeri
@ 2026-08-14 15:42 ` Jerome Forissier
  2 siblings, 0 replies; 5+ messages in thread
From: Jerome Forissier @ 2026-08-14 15:42 UTC (permalink / raw)
  To: Sean Edmond, Ramon Fried, u-boot, Shahriyar Jalayeri
  Cc: Argus, Tom Rini, Francois Berder, nd

On Thu, 06 Aug 2026 20:37:49 +0200, Shahriyar Jalayeri wrote:
> The DHCPv6 client takes the SERVERID and CLIENTID option lengths from a
> received ADVERTISE/REPLY without bounding them. The SERVERID length is
> later copied unbounded into the fixed net_tx_packet buffer when the
> REQUEST is built (an out-of-bounds write), and the CLIENTID length
> drives a memcmp against the fixed client-DUID buffer (an out-of-bounds
> read).
> Both are reachable by any on-link attacker that answers a DHCPv6 SOLICIT
> during netboot.
> 
> [...]

Applied to u-boot-net branch for-main, thanks!

[1/2] net: dhcp6: bound received DUID option lengths
      commit: 806a22de450c945db76a1eb4c96aaa2c0e8b2a21
[2/2] test: dm: eth: add DHCPv6 oversized option regression tests
      commit: bee6e6485868342de8ef5de528cf1e2a7b741ce7

Best regards,
-- 
Jerome Forissier <jerome.forissier@arm.com>


^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2026-08-14 15:42 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-06 18:37 [PATCH 0/2] net: dhcp6: fix out-of-bounds access in the option parser Shahriyar Jalayeri
2026-08-06 18:37 ` [PATCH 1/2] net: dhcp6: bound received DUID option lengths Shahriyar Jalayeri
2026-08-06 18:37 ` [PATCH 2/2] test: dm: eth: add DHCPv6 oversized option regression tests Shahriyar Jalayeri
2026-08-13 13:21   ` Jerome Forissier
2026-08-14 15:42 ` [PATCH 0/2] net: dhcp6: fix out-of-bounds access in the option parser Jerome Forissier

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox