b6a670
From 445282d13b90712ef90d2c2141d0e19bb1d896d2 Mon Sep 17 00:00:00 2001
b6a670
From: Simon Kelley <simon@thekelleys.org.uk>
b6a670
Date: Mon, 25 Sep 2017 18:17:11 +0100
b6a670
Subject: [PATCH] Security fix, CVE-2017-14491 DNS heap buffer overflow.
b6a670
b6a670
Fix heap overflow in DNS code. This is a potentially serious
b6a670
security hole. It allows an attacker who can make DNS
b6a670
requests to dnsmasq, and who controls the contents of
b6a670
a domain, which is thereby queried, to overflow
b6a670
(by 2 bytes) a heap buffer and either crash, or
b6a670
even take control of, dnsmasq.
b6a670
b6a670
(original commit 0549c73b7ea6b22a3c49beb4d432f185a81efcbc)
b6a670
---
b6a670
 src/dnsmasq.h |  2 +-
b6a670
 src/dnssec.c  |  2 +-
b6a670
 src/option.c  |  2 +-
b6a670
 src/rfc1035.c | 50 +++++++++++++++++++++++++++++++++++++++++---------
b6a670
 src/rfc2131.c |  4 ++--
b6a670
 src/rfc3315.c |  4 ++--
b6a670
 src/util.c    |  7 ++++++-
b6a670
 7 files changed, 54 insertions(+), 17 deletions(-)
b6a670
b6a670
diff --git a/src/dnsmasq.h b/src/dnsmasq.h
b6a670
index 06fae35..7a18898 100644
b6a670
--- a/src/dnsmasq.h
b6a670
+++ b/src/dnsmasq.h
b6a670
@@ -1179,7 +1179,7 @@ u32 rand32(void);
b6a670
 u64 rand64(void);
b6a670
 int legal_hostname(char *c);
b6a670
 char *canonicalise(char *s, int *nomem);
b6a670
-unsigned char *do_rfc1035_name(unsigned char *p, char *sval);
b6a670
+unsigned char *do_rfc1035_name(unsigned char *p, char *sval, char *limit);
b6a670
 void *safe_malloc(size_t size);
b6a670
 void safe_pipe(int *fd, int read_noblock);
b6a670
 void *whine_malloc(size_t size);
b6a670
diff --git a/src/dnssec.c b/src/dnssec.c
b6a670
index 3330eef..b6cb55f 100644
b6a670
--- a/src/dnssec.c
b6a670
+++ b/src/dnssec.c
b6a670
@@ -2230,7 +2230,7 @@ size_t dnssec_generate_query(struct dns_header *header, unsigned char *end, char
b6a670
 
b6a670
   p = (unsigned char *)(header+1);
b6a670
 	
b6a670
-  p = do_rfc1035_name(p, name);
b6a670
+  p = do_rfc1035_name(p, name, NULL);
b6a670
   *p++ = 0;
b6a670
   PUTSHORT(type, p);
b6a670
   PUTSHORT(class, p);
b6a670
diff --git a/src/option.c b/src/option.c
b6a670
index 064ef62..22bd19a 100644
b6a670
--- a/src/option.c
b6a670
+++ b/src/option.c
b6a670
@@ -1415,7 +1415,7 @@ static int parse_dhcp_opt(char *errstr, char *arg, int flags)
b6a670
 		    }
b6a670
 		  
b6a670
 		  p = newp;
b6a670
-		  end = do_rfc1035_name(p + len, dom);
b6a670
+		  end = do_rfc1035_name(p + len, dom, NULL);
b6a670
 		  *end++ = 0;
b6a670
 		  len = end - p;
b6a670
 		  free(dom);
b6a670
diff --git a/src/rfc1035.c b/src/rfc1035.c
b6a670
index 1671883..3397a26 100644
b6a670
--- a/src/rfc1035.c
b6a670
+++ b/src/rfc1035.c
b6a670
@@ -1062,6 +1062,7 @@ int check_for_ignored_address(struct dns_header *header, size_t qlen, struct bog
b6a670
   return 0;
b6a670
 }
b6a670
 
b6a670
+
b6a670
 int add_resource_record(struct dns_header *header, char *limit, int *truncp, int nameoffset, unsigned char **pp, 
b6a670
 			unsigned long ttl, int *offset, unsigned short type, unsigned short class, char *format, ...)
b6a670
 {
b6a670
@@ -1071,12 +1072,21 @@ int add_resource_record(struct dns_header *header, char *limit, int *truncp, int
b6a670
   unsigned short usval;
b6a670
   long lval;
b6a670
   char *sval;
b6a670
+#define CHECK_LIMIT(size) \
b6a670
+  if (limit && p + (size) > (unsigned char*)limit) \
b6a670
+    { \
b6a670
+      va_end(ap); \
b6a670
+      goto truncated; \
b6a670
+    }
b6a670
 
b6a670
   if (truncp && *truncp)
b6a670
     return 0;
b6a670
- 
b6a670
+
b6a670
   va_start(ap, format);   /* make ap point to 1st unamed argument */
b6a670
-  
b6a670
+
b6a670
+  /* nameoffset (1 or 2) + type (2) + class (2) + ttl (4) + 0 (2) */
b6a670
+  CHECK_LIMIT(12);
b6a670
+
b6a670
   if (nameoffset > 0)
b6a670
     {
b6a670
       PUTSHORT(nameoffset | 0xc000, p);
b6a670
@@ -1085,7 +1095,13 @@ int add_resource_record(struct dns_header *header, char *limit, int *truncp, int
b6a670
     {
b6a670
       char *name = va_arg(ap, char *);
b6a670
       if (name)
b6a670
-	p = do_rfc1035_name(p, name);
b6a670
+	p = do_rfc1035_name(p, name, limit);
b6a670
+        if (!p)
b6a670
+          {
b6a670
+            va_end(ap);
b6a670
+            goto truncated;
b6a670
+          }
b6a670
+
b6a670
       if (nameoffset < 0)
b6a670
 	{
b6a670
 	  PUTSHORT(-nameoffset | 0xc000, p);
b6a670
@@ -1106,6 +1122,7 @@ int add_resource_record(struct dns_header *header, char *limit, int *truncp, int
b6a670
       {
b6a670
 #ifdef HAVE_IPV6
b6a670
       case '6':
b6a670
+        CHECK_LIMIT(IN6ADDRSZ);
b6a670
 	sval = va_arg(ap, char *); 
b6a670
 	memcpy(p, sval, IN6ADDRSZ);
b6a670
 	p += IN6ADDRSZ;
b6a670
@@ -1113,36 +1130,47 @@ int add_resource_record(struct dns_header *header, char *limit, int *truncp, int
b6a670
 #endif
b6a670
 	
b6a670
       case '4':
b6a670
+        CHECK_LIMIT(INADDRSZ);
b6a670
 	sval = va_arg(ap, char *); 
b6a670
 	memcpy(p, sval, INADDRSZ);
b6a670
 	p += INADDRSZ;
b6a670
 	break;
b6a670
 	
b6a670
       case 'b':
b6a670
+        CHECK_LIMIT(1);
b6a670
 	usval = va_arg(ap, int);
b6a670
 	*p++ = usval;
b6a670
 	break;
b6a670
 	
b6a670
       case 's':
b6a670
+        CHECK_LIMIT(2);
b6a670
 	usval = va_arg(ap, int);
b6a670
 	PUTSHORT(usval, p);
b6a670
 	break;
b6a670
 	
b6a670
       case 'l':
b6a670
+        CHECK_LIMIT(4);
b6a670
 	lval = va_arg(ap, long);
b6a670
 	PUTLONG(lval, p);
b6a670
 	break;
b6a670
 	
b6a670
       case 'd':
b6a670
-	/* get domain-name answer arg and store it in RDATA field */
b6a670
-	if (offset)
b6a670
-	  *offset = p - (unsigned char *)header;
b6a670
-	p = do_rfc1035_name(p, va_arg(ap, char *));
b6a670
-	*p++ = 0;
b6a670
+        /* get domain-name answer arg and store it in RDATA field */
b6a670
+        if (offset)
b6a670
+          *offset = p - (unsigned char *)header;
b6a670
+        p = do_rfc1035_name(p, va_arg(ap, char *), limit);
b6a670
+        if (!p)
b6a670
+          {
b6a670
+            va_end(ap);
b6a670
+            goto truncated;
b6a670
+          }
b6a670
+        CHECK_LIMIT(1);
b6a670
+        *p++ = 0;
b6a670
 	break;
b6a670
 	
b6a670
       case 't':
b6a670
 	usval = va_arg(ap, int);
b6a670
+        CHECK_LIMIT(usval);
b6a670
 	sval = va_arg(ap, char *);
b6a670
 	if (usval != 0)
b6a670
 	  memcpy(p, sval, usval);
b6a670
@@ -1154,20 +1182,24 @@ int add_resource_record(struct dns_header *header, char *limit, int *truncp, int
b6a670
 	usval = sval ? strlen(sval) : 0;
b6a670
 	if (usval > 255)
b6a670
 	  usval = 255;
b6a670
+        CHECK_LIMIT(usval + 1);
b6a670
 	*p++ = (unsigned char)usval;
b6a670
 	memcpy(p, sval, usval);
b6a670
 	p += usval;
b6a670
 	break;
b6a670
       }
b6a670
 
b6a670
+#undef CHECK_LIMIT
b6a670
   va_end(ap);	/* clean up variable argument pointer */
b6a670
   
b6a670
   j = p - sav - 2;
b6a670
-  PUTSHORT(j, sav);     /* Now, store real RDLength */
b6a670
+ /* this has already been checked against limit before */
b6a670
+ PUTSHORT(j, sav);     /* Now, store real RDLength */
b6a670
   
b6a670
   /* check for overflow of buffer */
b6a670
   if (limit && ((unsigned char *)limit - p) < 0)
b6a670
     {
b6a670
+truncated:
b6a670
       if (truncp)
b6a670
 	*truncp = 1;
b6a670
       return 0;
b6a670
diff --git a/src/rfc2131.c b/src/rfc2131.c
b6a670
index a679470..052498c 100644
b6a670
--- a/src/rfc2131.c
b6a670
+++ b/src/rfc2131.c
b6a670
@@ -2454,10 +2454,10 @@ static void do_options(struct dhcp_context *context,
b6a670
 
b6a670
 	      if (fqdn_flags & 0x04)
b6a670
 		{
b6a670
-		  p = do_rfc1035_name(p, hostname);
b6a670
+		  p = do_rfc1035_name(p, hostname, NULL);
b6a670
 		  if (domain)
b6a670
 		    {
b6a670
-		      p = do_rfc1035_name(p, domain);
b6a670
+		      p = do_rfc1035_name(p, domain, NULL);
b6a670
 		      *p++ = 0;
b6a670
 		    }
b6a670
 		}
b6a670
diff --git a/src/rfc3315.c b/src/rfc3315.c
b6a670
index 054ecd0..715b6db 100644
b6a670
--- a/src/rfc3315.c
b6a670
+++ b/src/rfc3315.c
b6a670
@@ -1479,10 +1479,10 @@ static struct dhcp_netid *add_options(struct state *state, int do_refresh)
b6a670
       if ((p = expand(len + 2)))
b6a670
 	{
b6a670
 	  *(p++) = state->fqdn_flags;
b6a670
-	  p = do_rfc1035_name(p, state->hostname);
b6a670
+	  p = do_rfc1035_name(p, state->hostname, NULL);
b6a670
 	  if (state->send_domain)
b6a670
 	    {
b6a670
-	      p = do_rfc1035_name(p, state->send_domain);
b6a670
+	      p = do_rfc1035_name(p, state->send_domain, NULL);
b6a670
 	      *p = 0;
b6a670
 	    }
b6a670
 	}
b6a670
diff --git a/src/util.c b/src/util.c
b6a670
index 145e53a..471faa9 100644
b6a670
--- a/src/util.c
b6a670
+++ b/src/util.c
b6a670
@@ -227,15 +227,20 @@ char *canonicalise(char *in, int *nomem)
b6a670
   return ret;
b6a670
 }
b6a670
 
b6a670
-unsigned char *do_rfc1035_name(unsigned char *p, char *sval)
b6a670
+unsigned char *do_rfc1035_name(unsigned char *p, char *sval, char *limit)
b6a670
 {
b6a670
   int j;
b6a670
   
b6a670
   while (sval && *sval)
b6a670
     {
b6a670
+      if (limit && p + 1 > (unsigned char*)limit)
b6a670
+        return p;
b6a670
+
b6a670
       unsigned char *cp = p++;
b6a670
       for (j = 0; *sval && (*sval != '.'); sval++, j++)
b6a670
 	{
b6a670
+          if (limit && p + 1 > (unsigned char*)limit)
b6a670
+            return p;
b6a670
 #ifdef HAVE_DNSSEC
b6a670
 	  if (option_bool(OPT_DNSSEC_VALID) && *sval == NAME_ESCAPE)
b6a670
 	    *p++ = (*(++sval))-1;
b6a670
-- 
b6a670
2.9.5
b6a670