[PATCH] udhcpc6: bound DHCPv6 packet construction

Ali Ahmet Memis via busybox <[email protected]>
Newsgroups gmane.linux.busybox
Message-ID <[email protected]>
The DHCPv6 client builds several outgoing messages in a fixed-size
stack buffer, but did not check the remaining space when appending
retained DHCPv6 options or client-specified options.

A malicious DHCPv6 server can provide oversized retained options
which are later copied during Renew/Release processing. Local -x
options can also consume the remaining packet space.

Add bounded option appends and reject packet construction when an
option does not fit in the destination buffer.

Signed-off-by: Ali Ahmet Memis <[email protected]>
---
 networking/udhcp/d6_dhcpc.c | 139 +++++++++++++++++++++++++-----------
 1 file changed, 99 insertions(+), 40 deletions(-)

diff --git a/networking/udhcp/d6_dhcpc.c b/networking/udhcp/d6_dhcpc.c
index 7233c5fbe..ab3994250 100644
--- a/networking/udhcp/d6_dhcpc.c
+++ b/networking/udhcp/d6_dhcpc.c
@@ -528,38 +528,53 @@ static uint8_t *init_d6_packet(struct d6_packet *packet, char type)
 	return ptr;
 }
 
-static uint8_t *add_d6_client_options(uint8_t *ptr)
+static uint8_t *d6_append(uint8_t *ptr, const void *src, unsigned len, uint8_t *end)
+{
+	if (len > (unsigned)(end - ptr))
+		return NULL;
+	return mempcpy(ptr, src, len);
+}
+
+static uint8_t *add_d6_client_options(uint8_t *ptr, uint8_t *end)
 {
 	struct option_set *curr;
-	uint8_t *start = ptr;
 	unsigned option;
 	uint16_t len;
+	unsigned oro_len = 0;
 
-	ptr += 4;
 	for (option = 1; option < 256; option++) {
-		if (client_data.opt_mask[option >> 3] & (1 << (option & 7))) {
-			ptr[0] = (option >> 8);
-			ptr[1] = option;
-			ptr += 2;
-		}
+		if (client_data.opt_mask[option >> 3] & (1 << (option & 7)))
+			oro_len += 2;
 	}
 
-	if ((ptr - start - 4) != 0) {
-		start[0] = (D6_OPT_ORO >> 8);
-		start[1] = D6_OPT_ORO;
-		start[2] = ((ptr - start - 4) >> 8);
-		start[3] = (ptr - start - 4);
-	} else
-		ptr = start;
+	if (oro_len) {
+		if (oro_len + 4 > (unsigned)(end - ptr))
+			return NULL;
+		ptr[0] = (D6_OPT_ORO >> 8);
+		ptr[1] = D6_OPT_ORO;
+		ptr[2] = (oro_len >> 8);
+		ptr[3] = oro_len;
+		ptr += 4;
+		for (option = 1; option < 256; option++) {
+			if (client_data.opt_mask[option >> 3] & (1 << (option & 7))) {
+				*ptr++ = (option >> 8);
+				*ptr++ = option;
+			}
+		}
+	}
 
 #if ENABLE_FEATURE_UDHCPC6_RFC4704
-	ptr = mempcpy(ptr, &opt_fqdn_req, sizeof(opt_fqdn_req));
+	ptr = d6_append(ptr, (const uint8_t *)&opt_fqdn_req, sizeof(opt_fqdn_req), end);
+	if (!ptr)
+		return NULL;
 #endif
 	/* Add -x options if any */
 	curr = client_data.options;
 	while (curr) {
 		len = (curr->data[D6_OPT_LEN] << 8) | curr->data[D6_OPT_LEN + 1];
-		ptr = mempcpy(ptr, curr->data, D6_OPT_DATA + len);
+		ptr = d6_append(ptr, curr->data, D6_OPT_DATA + len, end);
+		if (!ptr)
+			return NULL;
 		curr = curr->next;
 	}
 
@@ -612,6 +627,7 @@ static NOINLINE int send_d6_info_request(void)
 {
 	struct d6_packet packet;
 	uint8_t *opt_ptr;
+	uint8_t *end = (uint8_t *)&packet + sizeof(packet);
 
 	/* Fill in: msg type, xid, ELAPSED_TIME */
 	opt_ptr = init_d6_packet(&packet, D6_MSG_INFORMATION_REQUEST);
@@ -619,7 +635,9 @@ static NOINLINE int send_d6_info_request(void)
 	/* Add options: client-id,
 	 * "param req" option according to -O, options specified with -x
 	 */
-	opt_ptr = add_d6_client_options(opt_ptr);
+	opt_ptr = add_d6_client_options(opt_ptr, end);
+	if (!opt_ptr)
+		return -1;
 
 	bb_error_msg("sending %s", "info request");
 	return d6_mcast_from_client_data_ifindex(&packet, opt_ptr);
@@ -736,6 +754,7 @@ static NOINLINE int send_d6_discover(struct in6_addr *requested_ipv6)
 {
 	struct d6_packet packet;
 	uint8_t *opt_ptr;
+	uint8_t *end = (uint8_t *)&packet + sizeof(packet);
 	unsigned len;
 
 	/* Fill in: msg type, xid, ELAPSED_TIME */
@@ -756,7 +775,9 @@ static NOINLINE int send_d6_discover(struct in6_addr *requested_ipv6)
 			iaaddr->len = 16+4+4;
 			memcpy(iaaddr->data, requested_ipv6, 16);
 		}
-		opt_ptr = mempcpy(opt_ptr, client6_data.ia_na, len);
+		opt_ptr = d6_append(opt_ptr, client6_data.ia_na, len, end);
+		if (!opt_ptr)
+			return -1;
 	}
 
 	/* IA_PD */
@@ -768,13 +789,17 @@ static NOINLINE int send_d6_discover(struct in6_addr *requested_ipv6)
 		client6_data.ia_pd->code = D6_OPT_IA_PD;
 		client6_data.ia_pd->len = len - 4;
 		generate_iaid(client6_data.ia_pd->data); /* IAID */
-		opt_ptr = mempcpy(opt_ptr, client6_data.ia_pd, len);
+		opt_ptr = d6_append(opt_ptr, client6_data.ia_pd, len, end);
+		if (!opt_ptr)
+			return -1;
 	}
 
 	/* Add options: client-id,
 	 * "param req" option according to -O, options specified with -x
 	 */
-	opt_ptr = add_d6_client_options(opt_ptr);
+	opt_ptr = add_d6_client_options(opt_ptr, end);
+	if (!opt_ptr)
+		return -1;
 
 	bb_info_msg("sending %s", "discover");
 	return d6_mcast_from_client_data_ifindex(&packet, opt_ptr);
@@ -815,23 +840,34 @@ static NOINLINE int send_d6_select(void)
 {
 	struct d6_packet packet;
 	uint8_t *opt_ptr;
+	uint8_t *end = (uint8_t *)&packet + sizeof(packet);
 
 	/* Fill in: msg type, xid, ELAPSED_TIME */
 	opt_ptr = init_d6_packet(&packet, D6_MSG_REQUEST);
 
 	/* server id */
-	opt_ptr = mempcpy(opt_ptr, client6_data.server_id, client6_data.server_id->len + 2+2);
+	opt_ptr = d6_append(opt_ptr, client6_data.server_id, client6_data.server_id->len + 2+2, end);
+	if (!opt_ptr)
+		return -1;
 	/* IA NA (contains requested IP) */
-	if (client6_data.ia_na)
-		opt_ptr = mempcpy(opt_ptr, client6_data.ia_na, client6_data.ia_na->len + 2+2);
+	if (client6_data.ia_na) {
+		opt_ptr = d6_append(opt_ptr, client6_data.ia_na, client6_data.ia_na->len + 2+2, end);
+		if (!opt_ptr)
+			return -1;
+	}
 	/* IA PD */
-	if (client6_data.ia_pd)
-		opt_ptr = mempcpy(opt_ptr, client6_data.ia_pd, client6_data.ia_pd->len + 2+2);
+	if (client6_data.ia_pd) {
+		opt_ptr = d6_append(opt_ptr, client6_data.ia_pd, client6_data.ia_pd->len + 2+2, end);
+		if (!opt_ptr)
+			return -1;
+	}
 
 	/* Add options: client-id,
 	 * "param req" option according to -O, options specified with -x
 	 */
-	opt_ptr = add_d6_client_options(opt_ptr);
+	opt_ptr = add_d6_client_options(opt_ptr, end);
+	if (!opt_ptr)
+		return -1;
 
 	bb_info_msg("sending %s", "select");
 	return d6_mcast_from_client_data_ifindex(&packet, opt_ptr);
@@ -888,23 +924,34 @@ static NOINLINE int send_d6_renew(struct in6_addr *server_ipv6, struct in6_addr
 {
 	struct d6_packet packet;
 	uint8_t *opt_ptr;
+	uint8_t *end = (uint8_t *)&packet + sizeof(packet);
 
 	/* Fill in: msg type, xid, ELAPSED_TIME */
 	opt_ptr = init_d6_packet(&packet, D6_MSG_RENEW);
 
 	/* server id */
-	opt_ptr = mempcpy(opt_ptr, client6_data.server_id, client6_data.server_id->len + 2+2);
+	opt_ptr = d6_append(opt_ptr, client6_data.server_id, client6_data.server_id->len + 2+2, end);
+	if (!opt_ptr)
+		return -1;
 	/* IA NA (contains requested IP) */
-	if (client6_data.ia_na)
-		opt_ptr = mempcpy(opt_ptr, client6_data.ia_na, client6_data.ia_na->len + 2+2);
+	if (client6_data.ia_na) {
+		opt_ptr = d6_append(opt_ptr, client6_data.ia_na, client6_data.ia_na->len + 2+2, end);
+		if (!opt_ptr)
+			return -1;
+	}
 	/* IA PD */
-	if (client6_data.ia_pd)
-		opt_ptr = mempcpy(opt_ptr, client6_data.ia_pd, client6_data.ia_pd->len + 2+2);
+	if (client6_data.ia_pd) {
+		opt_ptr = d6_append(opt_ptr, client6_data.ia_pd, client6_data.ia_pd->len + 2+2, end);
+		if (!opt_ptr)
+			return -1;
+	}
 
 	/* Add options: client-id,
 	 * "param req" option according to -O, options specified with -x
 	 */
-	opt_ptr = add_d6_client_options(opt_ptr);
+	opt_ptr = add_d6_client_options(opt_ptr, end);
+	if (!opt_ptr)
+		return -1;
 
 	bb_info_msg("sending %s", "renew");
 	if (server_ipv6)
@@ -923,22 +970,34 @@ int send_d6_release(struct in6_addr *server_ipv6, struct in6_addr *our_cur_ipv6)
 {
 	struct d6_packet packet;
 	uint8_t *opt_ptr;
+	uint8_t *end = (uint8_t *)&packet + sizeof(packet);
 	struct option_set *ci;
 
 	/* Fill in: msg type, xid, ELAPSED_TIME */
 	opt_ptr = init_d6_packet(&packet, D6_MSG_RELEASE);
 	/* server id */
-	opt_ptr = mempcpy(opt_ptr, client6_data.server_id, client6_data.server_id->len + 2+2);
+	opt_ptr = d6_append(opt_ptr, client6_data.server_id, client6_data.server_id->len + 2+2, end);
+	if (!opt_ptr)
+		return -1;
 	/* IA NA (contains our current IP) */
-	if (client6_data.ia_na)
-		opt_ptr = mempcpy(opt_ptr, client6_data.ia_na, client6_data.ia_na->len + 2+2);
+	if (client6_data.ia_na) {
+		opt_ptr = d6_append(opt_ptr, client6_data.ia_na, client6_data.ia_na->len + 2+2, end);
+		if (!opt_ptr)
+			return -1;
+	}
 	/* IA PD */
-	if (client6_data.ia_pd)
-		opt_ptr = mempcpy(opt_ptr, client6_data.ia_pd, client6_data.ia_pd->len + 2+2);
+	if (client6_data.ia_pd) {
+		opt_ptr = d6_append(opt_ptr, client6_data.ia_pd, client6_data.ia_pd->len + 2+2, end);
+		if (!opt_ptr)
+			return -1;
+	}
 	/* Client-id */
 	ci = udhcp_find_option(client_data.options, D6_OPT_CLIENTID, /*dhcpv6:*/ 1);
-	if (ci)
-		opt_ptr = mempcpy(opt_ptr, ci->data, D6_OPT_DATA + 2+2 + 6);
+	if (ci) {
+		opt_ptr = d6_append(opt_ptr, ci->data, D6_OPT_DATA + 2+2 + 6, end);
+		if (!opt_ptr)
+			return -1;
+	}
 
 	bb_info_msg("sending %s", "release");
 	return d6_send_kernel_packet_from_client_data_ifindex(
-- 
2.55.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.