[ntpsec commit] Rewrite decodenetnum()

Daniel Franke dfranke at ntpsec.org
Wed Oct 21 18:36:33 UTC 2015


Module:    ntpsec
Branch:    master
Commit:    f08eaec887218e5b99e7dd26ac624167e1c14e90
Changeset: http://git.ntpsec.org/ntpsec/commit/?id=f08eaec887218e5b99e7dd26ac624167e1c14e90

Author:    Daniel Fox Franke <dfoxfranke at gmail.com>
Date:      Fri Sep 25 17:39:02 2015 -0400

Rewrite decodenetnum()

Improve readability, avoid crashing on overlong input, and reject more
malformed input.

---

 libntp/decodenetnum.c | 154 +++++++++++++++++++++++++++++++++++---------------
 1 file changed, 109 insertions(+), 45 deletions(-)

diff --git a/libntp/decodenetnum.c b/libntp/decodenetnum.c
index c7c2563..aa2f3ac 100644
--- a/libntp/decodenetnum.c
+++ b/libntp/decodenetnum.c
@@ -1,13 +1,15 @@
 /*
- * decodenetnum - return a net number (this is crude, but careful)
+ * decodenetnum - convert text IP address and port to sockaddr_u
  */
 #include <config.h>
+#include <stddef.h>
+#include <stdbool.h>
+#include <string.h>
 #include <sys/types.h>
-#include <ctype.h>
 #include <sys/socket.h>
+#include <netdb.h>
 #include <netinet/in.h>
 
-#include "ntp.h"
 #include "ntp_stdlib.h"
 #include "ntp_assert.h"
 
@@ -15,6 +17,15 @@
  * decodenetnum		convert text IP address and port to sockaddr_u
  *
  * Returns false for failure, true for success.
+ *
+ * We accept:
+ * IPv4
+ * IPv6
+ * [IPv6]
+ * IPv4:port
+ * [IPv6]:port
+ *
+ * The IP must be numeric but the port can be symbolic.
  */
 bool
 decodenetnum(
@@ -23,57 +34,110 @@ decodenetnum(
 	)
 {
 	struct addrinfo hints, *ai = NULL;
-	int err;
-	u_short port;
-	const char *cp;
-	const char *port_str;
-	char *pp;
-	char *np;
-	char name[80];
+	const char *ip_start, *ip_end, *port_start, *temp;
+	const size_t numlen = strlen(num);
+	bool have_brackets;
+
+#ifdef INCLUDE_IPV6_SUPPORT
+	char ip[INET6_ADDRSTRLEN];
+#else
+	char ip[INET_ADDRSTRLEN];
+#endif
 
 	NTP_REQUIRE(num != NULL);
-	NTP_REQUIRE(strlen(num) < sizeof(name));
+	/* Quickly reject empty or impossibly long inputs. */
+	if(numlen == 0 ||
+	   numlen > (sizeof ip - 1) + (NI_MAXSERV - 1) + (3 /* "[]:" */)) {
+		return false;
+	}
 
-	port_str = NULL;
-	if ('[' != num[0]) {
-		/*
-		 * to distinguish IPv6 embedded colons from a port
-		 * specification on an IPv4 address, assume all 
-		 * legal IPv6 addresses have at least two colons.
-		 */
-		pp = strchr(num, ':');
-		if (NULL == pp)
-			cp = num;	/* no colons */
-		else if (NULL != strchr(pp + 1, ':'))
-			cp = num;	/* two or more colons */
-		else {			/* one colon */
-			strlcpy(name, num, sizeof(name));
-			cp = name;
-			pp = strchr(cp, ':');
-			*pp = '\0';
-			port_str = pp + 1;
+	/* Is this a bracketed IPv6 address? */
+	have_brackets = ('[' == num[0]);
+	if(have_brackets) {
+		/* If it's formatted like [IPv6]:port, the port part
+		   comes after the "]:". */
+		if((temp = strstr(num, "]:")) != NULL) {
+			ip_start = num + 1;
+			ip_end = temp;
+			port_start = temp + 2;
 		}
+		else if(num[numlen-1] == ']') {
+			/* It's just [IPv6]. */
+			ip_start = num + 1;
+			ip_end = ip_start + numlen - 1;
+			port_start = NULL;
+		}
+		else {
+			/* Anything else must be invalid. */
+			return false;
+		}
+	}
+	/* No brackets. Searching backward, see if there's at least one
+	 * colon... */
+	else if((temp = strrchr(num, ':')) != NULL) {
+		/* ...and then look for a second one, searching forward. */
+		if(strchr(num, ':') == temp) {
+			/* Searching from both directions gave us the same
+			   result, so there's only one colon. What's after
+			   it is the port. */
+			ip_start = num;
+			ip_end = temp;
+			port_start = temp + 1;
+		} else {
+			/* Two colons and no brackets, so it has to be a bare
+			   IPv6 address */
+			ip_start = num;
+			ip_end = ip_start + numlen;
+			port_start = NULL;
+		}
+	} else {
+		/* No colon, no brackets. */
+		ip_start = num;
+		ip_end = ip_start + numlen;
+		port_start = NULL;
+	}
+
+	/* Now we have ip_start pointing to the start of the IP,
+	   ip_end pointing past the end of the IP, and port_start
+	   either NULL or pointing to the start of the port. Check
+	   whether the IP is short enough to possibly be valid and
+	   if so copy it into ip. */
+	if(ip_end - ip_start + 1 > sizeof ip) {
+		return false;
 	} else {
-		cp = num + 1;
-		np = name; 
-		while (*cp && ']' != *cp)
-			*np++ = *cp++;
-		*np = 0;
-		if (']' == cp[0] && ':' == cp[1] && '\0' != cp[2])
-			port_str = &cp[2];
-		cp = name; 
+		memcpy(ip, ip_start, ip_end - ip_start);
+		ip[ip_end - ip_start] = '\0';
 	}
+
 	ZERO(hints);
-	hints.ai_flags = Z_AI_NUMERICHOST;
-	err = getaddrinfo(cp, "ntp", &hints, &ai);
-	if (err != 0)
+	hints.ai_socktype = SOCK_DGRAM;
+	hints.ai_flags = AI_NUMERICHOST;
+	hints.ai_protocol = IPPROTO_UDP;
+	/* One final validity check: only IPv6 addresses are allowed to
+	 * have brackets. */
+#ifdef INCLUDE_IPV6_SUPPORT
+	hints.ai_family = have_brackets ? AF_INET6 : AF_UNSPEC;
+#else
+	if(have_brackets) {
+		return false;
+	}
+	hints.ai_family = AF_INET;
+#endif
+
+	/* If we've gotten this far, then we still don't know that
+	   either the IP address or the port is well-formed, but at
+	   least they're unambiguously delimited from each other.
+	   Let getaddrinfo() perform all further validation. */
+	if(getaddrinfo(ip, port_start == NULL ? "ntp" : port_start,
+		       &hints, &ai) != 0) {
 		return false;
+	}
+
 	NTP_INSIST(ai->ai_addrlen <= sizeof(*netnum));
-	ZERO(*netnum);
-	memcpy(netnum, ai->ai_addr, ai->ai_addrlen);
+	if(netnum) {
+		ZERO(*netnum);
+		memcpy(netnum, ai->ai_addr, ai->ai_addrlen);
+	}
 	freeaddrinfo(ai);
-	if (NULL == port_str || 1 != sscanf(port_str, "%hu", &port))
-		port = NTP_PORT;
-	SET_PORT(netnum, port);
 	return true;
 }



More information about the vc mailing list