dhcpcd-discuss

Re: Backporting two dchpcd security patches to 6.0.5

Roy Marples

Fri May 17 13:32:27 2019

Hi Chris

On 17/05/2019 08:39, Chris Lamb wrote:
Hm, I don't quite follow. Would it be possible for you to quickly
mockup what you mean? I would, naturally, be very happy to test.

Untested patch.
Basically I took the latest -6 function and made it work for 6.0.5.

Roy
diff --git a/dhcp.c b/dhcp.c
index 30c1ecd6..fb3734d9 100644
--- a/dhcp.c
+++ b/dhcp.c
@@ -75,6 +75,7 @@ static uint8_t *packet;
  * We ONLY use this when options are split, which for most purposes is
  * practically never. See RFC3396 for details. */
 static uint8_t *opt_buffer;
+static size_t opt_buffer_len;
 
 #define IPV4A		ADDRIPV4 | ARRAY
 #define IPV4R		ADDRIPV4 | REQUEST
@@ -295,65 +296,98 @@ free_option_buffer(void)
 static const uint8_t *
 get_option(const struct dhcp_message *dhcp, uint8_t opt, int *len, int *type)
 {
-	const uint8_t *p = dhcp->options;
-	const uint8_t *e = p + sizeof(dhcp->options);
-	uint8_t l, ol = 0;
-	uint8_t o = 0;
-	uint8_t overl = 0;
-	uint8_t *bp = NULL;
-	const uint8_t *op = NULL;
-	ssize_t bl = 0;
+	const uint8_t *p, *e;
+	uint8_t l, o, ol, overl, *bp;
+	const uint8_t *op;
+	size_t bl;
 
+	p = dhcp->options;
+	e = p + sizeof(dhcp->options);
+	ol = o = overl = 0;
+	bp = NULL;
+	op = NULL;
+	bl = 0;
 	while (p < e) {
 		o = *p++;
-		if (o == opt) {
-			if (op) {
-				if (!opt_buffer) {
-					opt_buffer = malloc(sizeof(*dhcp));
-					if (opt_buffer == NULL)
-						return NULL;
-#ifdef DEBUG_MEMORY
-					atexit(free_option_buffer);
-#endif
-				}
-				if (!bp)
-					bp = opt_buffer;
-				memcpy(bp, op, ol);
-				bp += ol;
-			}
-			ol = *p;
-			op = p + 1;
-			bl += ol;
-		}
 		switch (o) {
 		case DHO_PAD:
+			/* No length to read */
 			continue;
 		case DHO_END:
 			if (overl & 1) {
 				/* bit 1 set means parse boot file */
-				overl &= ~1;
+				overl = (uint8_t)(overl & ~1);
 				p = dhcp->bootfile;
 				e = p + sizeof(dhcp->bootfile);
 			} else if (overl & 2) {
 				/* bit 2 set means parse server name */
-				overl &= ~2;
+				overl = (uint8_t)(overl & ~2);
 				p = dhcp->servername;
 				e = p + sizeof(dhcp->servername);
 			} else
 				goto exit;
-			break;
-		case DHO_OPTIONSOVERLOADED:
-			/* Ensure we only get this option once */
-			if (!overl)
-				overl = p[1];
-			break;
+			/* No length to read */
+			continue;
+		}
+
+		/* Check we can read the length */
+		if (p == e) {
+			errno = EINVAL;
+		return NULL;
 		}
 		l = *p++;
+
+		/* Check we can read the option data, if present */
+		if (p + l > e) {
+			errno = EINVAL;
+			return NULL;
+		}
+
+		if (o == DHO_OPTIONSOVERLOADED) {
+			/* Ensure we only get this option once by setting
+			 * the last bit as well as the value.
+			 * This is valid because only the first two bits
+			 * actually mean anything in RFC2132 Section 9.3 */
+			if (l == 1 && !overl)
+				overl = 0x80 | p[0];
+		}
+
+		if (o == opt) {
+			if (op) {
+				/* We must concatonate the options. */
+				if (bl + l > opt_buffer_len) {
+					size_t pos;
+					uint8_t *nb;
+
+					if (bp)
+						pos = (size_t)
+						    (bp - opt_buffer);
+					else
+						pos = 0;
+					nb = realloc(opt_buffer, bl + l);
+					if (nb == NULL)
+						return NULL;
+#ifdef DEBUG_MEMORY
+					if (opt_buffer == NULL)
+						atexit(free_option_buffer);
+#endif
+					opt_buffer = nb;
+					opt_buffer_len = bl + l;
+					bp = opt_buffer + pos;
+				}
+				if (bp == NULL)
+					bp = opt_buffer;
+				memcpy(bp, op, ol);
+				bp += ol;
+			}
+			ol = l;
+			op = p;
+			bl += ol;
+		}
 		p += l;
 	}
 
 exit:
-
 	bl = validate_length(opt, bl, type);
 	if (bl == -1) {
 		errno = EINVAL;

References:
Backporting two dchpcd security patches to 6.0.5Chris Lamb
Re: Backporting two dchpcd security patches to 6.0.5Roy Marples
Re: Backporting two dchpcd security patches to 6.0.5Chris Lamb
Re: Backporting two dchpcd security patches to 6.0.5Roy Marples
Re: Backporting two dchpcd security patches to 6.0.5Chris Lamb
Archive administrator: postmaster@marples.name