Security fix, CVE-2017-14491, DNS heap buffer overflow.(1) 75/168275/1
authorSeonah Moon <seonah1.moon@samsung.com>
Mon, 16 Oct 2017 09:27:03 +0000 (18:27 +0900)
committerSeonah Moon <seonah1.moon@samsung.com>
Thu, 25 Jan 2018 08:59:13 +0000 (17:59 +0900)
Fix heap overflow in DNS code. This is a potentially serious
security hole. It allows an attacker who can make DNS
requests to dnsmasq, and who controls the contents of
a domain, which is thereby queried, to overflow
(by 2 bytes) a heap buffer and either crash, or
even take control of, dnsmasq.

http://thekelleys.org.uk/gitweb/?p=dnsmasq.git;a=commit;h=0549c73b7ea6b22a3c49beb4d432f185a81efcbc

Change-Id: I3cc432632f51e89b888f3a5d999ba422c134847a
Signed-off-by: Seonah Moon <seonah1.moon@samsung.com>
src/dnsmasq.h
src/dnssec.c
src/option.c
src/rfc1035.c
src/rfc2131.c
src/rfc3315.c
src/util.c

index cf1a782..ef15e6d 100644 (file)
@@ -1169,7 +1169,7 @@ u32 rand32(void);
 u64 rand64(void);
 int legal_hostname(char *c);
 char *canonicalise(char *s, int *nomem);
-unsigned char *do_rfc1035_name(unsigned char *p, char *sval);
+unsigned char *do_rfc1035_name(unsigned char *p, char *sval, char *limit);
 void *safe_malloc(size_t size);
 void safe_pipe(int *fd, int read_noblock);
 void *whine_malloc(size_t size);
index 4deda24..830f304 100644 (file)
@@ -2264,7 +2264,7 @@ size_t dnssec_generate_query(struct dns_header *header, char *end, char *name, i
 
   p = (unsigned char *)(header+1);
        
-  p = do_rfc1035_name(p, name);
+  p = do_rfc1035_name(p, name, NULL);
   *p++ = 0;
   PUTSHORT(type, p);
   PUTSHORT(class, p);
index ecc2619..ed204fb 100644 (file)
@@ -1348,7 +1348,7 @@ static int parse_dhcp_opt(char *errstr, char *arg, int flags)
                    }
                  
                  p = newp;
-                 end = do_rfc1035_name(p + len, dom);
+                 end = do_rfc1035_name(p + len, dom, NULL);
                  *end++ = 0;
                  len = end - p;
                  free(dom);
index 56647b0..605196a 100644 (file)
@@ -1371,12 +1371,21 @@ int add_resource_record(struct dns_header *header, char *limit, int *truncp, int
   unsigned short usval;
   long lval;
   char *sval;
+#define CHECK_LIMIT(size) \
+  if (limit && p + (size) > (unsigned char*)limit) \
+  { \
+         va_end(ap); \
+         goto truncated; \
+  }
 
   if (truncp && *truncp)
     return 0;
+
   va_start(ap, format);   /* make ap point to 1st unamed argument */
-  
+
+  /* nameoffset (1 or 2) + type (2) + class (2) + ttl (4) + 0 (2) */
+  CHECK_LIMIT(12);
+
   if (nameoffset > 0)
     {
       PUTSHORT(nameoffset | 0xc000, p);
@@ -1384,8 +1393,14 @@ int add_resource_record(struct dns_header *header, char *limit, int *truncp, int
   else
     {
       char *name = va_arg(ap, char *);
-      if (name)
-       p = do_rfc1035_name(p, name);
+         if (name)
+      p = do_rfc1035_name(p, name, limit);
+         if (!p)
+         {
+                 va_end(ap);
+                 goto truncated;
+         }
+
       if (nameoffset < 0)
        {
          PUTSHORT(-nameoffset | 0xc000, p);
@@ -1406,6 +1421,7 @@ int add_resource_record(struct dns_header *header, char *limit, int *truncp, int
       {
 #ifdef HAVE_IPV6
       case '6':
+        CHECK_LIMIT(IN6ADDRSZ);
        sval = va_arg(ap, char *); 
        memcpy(p, sval, IN6ADDRSZ);
        p += IN6ADDRSZ;
@@ -1413,36 +1429,47 @@ int add_resource_record(struct dns_header *header, char *limit, int *truncp, int
 #endif
        
       case '4':
+        CHECK_LIMIT(INADDRSZ);
        sval = va_arg(ap, char *); 
        memcpy(p, sval, INADDRSZ);
        p += INADDRSZ;
        break;
        
       case 'b':
+        CHECK_LIMIT(1);
        usval = va_arg(ap, int);
        *p++ = usval;
        break;
        
       case 's':
+        CHECK_LIMIT(2);
        usval = va_arg(ap, int);
        PUTSHORT(usval, p);
        break;
        
       case 'l':
+        CHECK_LIMIT(4);
        lval = va_arg(ap, long);
        PUTLONG(lval, p);
        break;
-       
+
       case 'd':
-       /* get domain-name answer arg and store it in RDATA field */
-       if (offset)
-         *offset = p - (unsigned char *)header;
-       p = do_rfc1035_name(p, va_arg(ap, char *));
-       *p++ = 0;
+        /* get domain-name answer arg and store it in RDATA field */
+        if (offset)
+        *offset = p - (unsigned char *)header;
+        p = do_rfc1035_name(p, va_arg(ap, char *), limit);
+        if (!p)
+        {
+          va_end(ap);
+          goto truncated;
+        }
+        CHECK_LIMIT(1);
+        *p++ = 0;
        break;
        
       case 't':
        usval = va_arg(ap, int);
+      CHECK_LIMIT(usval);
        sval = va_arg(ap, char *);
        if (usval != 0)
          memcpy(p, sval, usval);
@@ -1454,20 +1481,24 @@ int add_resource_record(struct dns_header *header, char *limit, int *truncp, int
        usval = sval ? strlen(sval) : 0;
        if (usval > 255)
          usval = 255;
+        CHECK_LIMIT(usval + 1);
        *p++ = (unsigned char)usval;
        memcpy(p, sval, usval);
        p += usval;
        break;
       }
 
+#undef CHECK_LIMIT
   va_end(ap);  /* clean up variable argument pointer */
   
   j = p - sav - 2;
+  /* this has already been checked against limit before */
   PUTSHORT(j, sav);     /* Now, store real RDLength */
   
   /* check for overflow of buffer */
   if (limit && ((unsigned char *)limit - p) < 0)
     {
+truncated:
       if (truncp)
        *truncp = 1;
       return 0;
index 9f69ed5..bcfa5d6 100644 (file)
@@ -2352,10 +2352,10 @@ static void do_options(struct dhcp_context *context,
 
              if (fqdn_flags & 0x04)
                {
-                 p = do_rfc1035_name(p, hostname);
+                 p = do_rfc1035_name(p, hostname, NULL);
                  if (domain)
                    {
-                     p = do_rfc1035_name(p, domain);
+                     p = do_rfc1035_name(p, domain, NULL);
                      *p++ = 0;
                    }
                }
index 2665d0d..62818d4 100644 (file)
@@ -1472,10 +1472,10 @@ static struct dhcp_netid *add_options(struct state *state, int do_refresh)
       if ((p = expand(len + 2)))
        {
          *(p++) = state->fqdn_flags;
-         p = do_rfc1035_name(p, state->hostname);
+         p = do_rfc1035_name(p, state->hostname, NULL);
          if (state->send_domain)
            {
-             p = do_rfc1035_name(p, state->send_domain);
+             p = do_rfc1035_name(p, state->send_domain, NULL);
              *p = 0;
            }
        }
index 469eaed..66fa103 100644 (file)
@@ -218,15 +218,20 @@ char *canonicalise(char *in, int *nomem)
   return ret;
 }
 
-unsigned char *do_rfc1035_name(unsigned char *p, char *sval)
+unsigned char *do_rfc1035_name(unsigned char *p, char *sval, char *limit)
 {
   int j;
   
   while (sval && *sval)
     {
+      if (limit && p + 1 > (unsigned char*)limit)
+        return p;
+
       unsigned char *cp = p++;
       for (j = 0; *sval && (*sval != '.'); sval++, j++)
        {
+      if (limit && p + 1 > (unsigned char*)limit)
+        return p;
 #ifdef HAVE_DNSSEC
          if (option_bool(OPT_DNSSEC_VALID) && *sval == NAME_ESCAPE)
            *p++ = (*(++sval))-1;