git: 368a1bfcd493 - main - dhclient: Avoid out-of-bounds reads in check_classless_option()

From: Mark Johnston <markj_at_FreeBSD.org>
Date: Thu, 15 Oct 2026 15:24:20 UTC
The branch main has been updated by markj:

URL: https://cgit.FreeBSD.org/src/commit/?id=368a1bfcd49315e8dd3e56ca6744ce5d74ef737b

commit 368a1bfcd49315e8dd3e56ca6744ce5d74ef737b
Author:     Mark Johnston <markj@FreeBSD.org>
AuthorDate: 2026-10-14 21:12:42 +0000
Commit:     Mark Johnston <markj@FreeBSD.org>
CommitDate: 2026-10-15 14:50:33 +0000

    dhclient: Avoid out-of-bounds reads in check_classless_option()
    
    The loop did not verify that the option buffer is large enough before
    reading an address.
    
    Reported by:    Andrew Griffiths <andrew@calif.io>
    MFC after:      2 weeks
    Sponsored by:   The FreeBSD Foundation
    Differential Revision:  https://reviews.freebsd.org/D60484
---
 sbin/dhclient/dhclient.c | 64 ++++++++++++++++++++++++++++--------------------
 1 file changed, 38 insertions(+), 26 deletions(-)

diff --git a/sbin/dhclient/dhclient.c b/sbin/dhclient/dhclient.c
index bef956645b85..8fccac182c16 100644
--- a/sbin/dhclient/dhclient.c
+++ b/sbin/dhclient/dhclient.c
@@ -139,7 +139,7 @@ struct sockaddr	*get_ifa(char *, int);
 void		 routehandler(struct protocol *);
 void		 usage(void);
 int		 check_option(struct client_lease *l, int option);
-int		 check_classless_option(unsigned char *data, int len);
+int		 check_classless_option(unsigned char *data, size_t len);
 int		 ipv4addrs(const char * buf);
 int		 res_hnok(const char *dn);
 int		 check_search(const char *srch);
@@ -2846,43 +2846,56 @@ check_option(struct client_lease *l, int option)
 
 /* RFC 3442 The Classless Static Routes option checks */
 int
-check_classless_option(unsigned char *data, int len)
+check_classless_option(unsigned char *data, size_t len)
 {
-	int i = 0;
-	unsigned char width;
+	unsigned int i;
 	in_addr_t addr, mask;
+	unsigned char width;
 
 	if (len < 5) {
-		warning("Too small length: %d", len);
+		warning("Too small length: %zu", len);
 		return (0);
 	}
-	while(i < len) {
+	for (i = 0; i < len; i += 4) {
+		unsigned int octets;
+
 		width = data[i++];
-		if (width == 0) {
-			i += 4;
+		octets = (width + 7) / 8;
+		if (len < i + octets) {
+			warning("Incorrect data length: %zu", len);
+			return (0);
+		}
+
+		switch (octets) {
+		case 0:
 			continue;
-		} else if (width < 9) {
-			addr =  (in_addr_t)(data[i]	<< 24);
+		case 1:
+			addr = ((in_addr_t)data[i] << 24);
 			i += 1;
-		} else if (width < 17) {
-			addr =  (in_addr_t)(data[i]	<< 24) +
-				(in_addr_t)(data[i + 1]	<< 16);
+			break;
+		case 2:
+			addr = ((in_addr_t)data[i] << 24) +
+			    ((in_addr_t)data[i + 1] << 16);
 			i += 2;
-		} else if (width < 25) {
-			addr =  (in_addr_t)(data[i]	<< 24) +
-				(in_addr_t)(data[i + 1]	<< 16) +
-				(in_addr_t)(data[i + 2]	<< 8);
+			break;
+		case 3:
+			addr = ((in_addr_t)data[i] << 24) +
+			    ((in_addr_t)data[i + 1] << 16) +
+			    ((in_addr_t)data[i + 2] << 8);
 			i += 3;
-		} else if (width < 33) {
-			addr =  (in_addr_t)(data[i]	<< 24) +
-				(in_addr_t)(data[i + 1]	<< 16) +
-				(in_addr_t)(data[i + 2]	<< 8)  +
-				data[i + 3];
+			break;
+		case 4:
+			addr = ((in_addr_t)data[i] << 24) +
+			    ((in_addr_t)data[i + 1] << 16) +
+			    ((in_addr_t)data[i + 2] << 8) +
+			    data[i + 3];
 			i += 4;
-		} else {
-			warning("Incorrect subnet width: %d", width);
+			break;
+		default:
+			warning("Incorrect subnet width: %u", width);
 			return (0);
 		}
+
 		mask = (in_addr_t)(~0) << (32 - width);
 		addr = ntohl(addr);
 		mask = ntohl(mask);
@@ -2899,10 +2912,9 @@ check_classless_option(unsigned char *data, int len)
 			data[i - 1] = (unsigned char)(
 				(addr >> (((32 - width)/8)*8)) & 0xFF);
 		}
-		i += 4;
 	}
 	if (i > len) {
-		warning("Incorrect data length: %d (must be %d)", len, i);
+		warning("Incorrect data length: %zu (must be %u)", len, i);
 		return (0);
 	}
 	return (1);