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 <[email protected]>
---
 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

Reply via email to