From 0549c73b7ea6b22a3c49beb4d432f185a81efcbc Mon Sep 17 00:00:00 2001 From: Simon Kelley Date: Mon, 25 Sep 2017 18:17:11 +0100 Subject: [PATCH] Security fix, CVE-2017-14491 DNS heap buffer overflow. 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. CVE: CVE-2017-14491 Upstream-Status: Backport [src/dnsmasq.h patch failed, modified manually] Signed-off-by: Sona Sarmadi diff -Nurp a/CHANGELOG b/CHANGELOG --- a/CHANGELOG 2016-05-18 16:51:54.000000000 +0200 +++ b/CHANGELOG 2017-10-04 09:38:20.445498463 +0200 @@ -123,6 +123,18 @@ version 2.75 dhcp-script is configured. Thanks to Adrian Davey for reporting the bug and testing the fix. + 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. + CVE-2017-14491 applies. + Credit to Felix Wilhelm, Fermin J. Serna, Gabriel Campana + and Kevin Hamacher of the Google Security Team for + finding this. + + version 2.74 Fix reversion in 2.73 where --conf-file would attempt to diff -Nurp a/src/dnsmasq.h b/src/dnsmasq.h --- a/src/dnsmasq.h 2016-05-18 16:51:54.000000000 +0200 +++ b/src/dnsmasq.h 2017-10-04 09:39:39.366156718 +0200 @@ -1161,7 +1161,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); diff -Nurp a/src/dnssec.c b/src/dnssec.c --- a/src/dnssec.c 2016-05-18 16:51:54.000000000 +0200 +++ b/src/dnssec.c 2017-10-04 09:38:20.445498463 +0200 @@ -2227,7 +2227,7 @@ size_t dnssec_generate_query(struct dns_ 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); diff -Nurp a/src/option.c b/src/option.c --- a/src/option.c 2016-05-18 16:51:54.000000000 +0200 +++ b/src/option.c 2017-10-04 09:38:20.449498294 +0200 @@ -1378,7 +1378,7 @@ static int parse_dhcp_opt(char *errstr, } p = newp; - end = do_rfc1035_name(p + len, dom); + end = do_rfc1035_name(p + len, dom, NULL); *end++ = 0; len = end - p; free(dom); diff -Nurp a/src/rfc1035.c b/src/rfc1035.c --- a/src/rfc1035.c 2016-05-18 16:51:54.000000000 +0200 +++ b/src/rfc1035.c 2017-10-04 09:38:20.449498294 +0200 @@ -1049,6 +1049,7 @@ int check_for_ignored_address(struct dns return 0; } + int add_resource_record(struct dns_header *header, char *limit, int *truncp, int nameoffset, unsigned char **pp, unsigned long ttl, int *offset, unsigned short type, unsigned short class, char *format, ...) { @@ -1058,12 +1059,21 @@ int add_resource_record(struct dns_heade 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); @@ -1072,7 +1082,13 @@ int add_resource_record(struct dns_heade { char *name = va_arg(ap, char *); if (name) - p = do_rfc1035_name(p, name); + p = do_rfc1035_name(p, name, limit); + if (!p) + { + va_end(ap); + goto truncated; + } + if (nameoffset < 0) { PUTSHORT(-nameoffset | 0xc000, p); @@ -1093,6 +1109,7 @@ int add_resource_record(struct dns_heade { #ifdef HAVE_IPV6 case '6': + CHECK_LIMIT(IN6ADDRSZ); sval = va_arg(ap, char *); memcpy(p, sval, IN6ADDRSZ); p += IN6ADDRSZ; @@ -1100,36 +1117,47 @@ int add_resource_record(struct dns_heade #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); @@ -1141,20 +1169,24 @@ int add_resource_record(struct dns_heade 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; - PUTSHORT(j, sav); /* Now, store real RDLength */ + /* 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; diff -Nurp a/src/rfc2131.c b/src/rfc2131.c --- a/src/rfc2131.c 2016-05-18 16:51:54.000000000 +0200 +++ b/src/rfc2131.c 2017-10-04 09:38:20.449498294 +0200 @@ -2419,10 +2419,10 @@ static void do_options(struct dhcp_conte 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; } } diff -Nurp a/src/rfc3315.c b/src/rfc3315.c --- a/src/rfc3315.c 2016-05-18 16:51:54.000000000 +0200 +++ b/src/rfc3315.c 2017-10-04 09:38:20.449498294 +0200 @@ -1472,10 +1472,10 @@ static struct dhcp_netid *add_options(st 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; } } diff -Nurp a/src/util.c b/src/util.c --- a/src/util.c 2016-05-18 16:51:54.000000000 +0200 +++ b/src/util.c 2017-10-04 09:38:20.453498124 +0200 @@ -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;