Commit 0549c73b authored by Simon Kelley's avatar Simon Kelley

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.
parent b697fbb7
...@@ -24,6 +24,18 @@ version 2.78 ...@@ -24,6 +24,18 @@ version 2.78
Juan Manuel Fernandez and Kevin Darbyshire-Bryant for Juan Manuel Fernandez and Kevin Darbyshire-Bryant for
chasing this one down. CVE-2017-13704 applies. chasing this one down. CVE-2017-13704 applies.
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.77 version 2.77
Generate an error when configured with a CNAME loop, Generate an error when configured with a CNAME loop,
......
...@@ -1185,7 +1185,7 @@ u32 rand32(void); ...@@ -1185,7 +1185,7 @@ u32 rand32(void);
u64 rand64(void); u64 rand64(void);
int legal_hostname(char *name); int legal_hostname(char *name);
char *canonicalise(char *in, int *nomem); char *canonicalise(char *in, 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_malloc(size_t size);
void safe_pipe(int *fd, int read_noblock); void safe_pipe(int *fd, int read_noblock);
void *whine_malloc(size_t size); void *whine_malloc(size_t size);
......
...@@ -2230,7 +2230,7 @@ size_t dnssec_generate_query(struct dns_header *header, unsigned char *end, char ...@@ -2230,7 +2230,7 @@ size_t dnssec_generate_query(struct dns_header *header, unsigned char *end, char
p = (unsigned char *)(header+1); p = (unsigned char *)(header+1);
p = do_rfc1035_name(p, name); p = do_rfc1035_name(p, name, NULL);
*p++ = 0; *p++ = 0;
PUTSHORT(type, p); PUTSHORT(type, p);
PUTSHORT(class, p); PUTSHORT(class, p);
......
...@@ -1415,7 +1415,7 @@ static int parse_dhcp_opt(char *errstr, char *arg, int flags) ...@@ -1415,7 +1415,7 @@ static int parse_dhcp_opt(char *errstr, char *arg, int flags)
} }
p = newp; p = newp;
end = do_rfc1035_name(p + len, dom); end = do_rfc1035_name(p + len, dom, NULL);
*end++ = 0; *end++ = 0;
len = end - p; len = end - p;
free(dom); free(dom);
......
...@@ -1062,6 +1062,7 @@ int check_for_ignored_address(struct dns_header *header, size_t qlen, struct bog ...@@ -1062,6 +1062,7 @@ int check_for_ignored_address(struct dns_header *header, size_t qlen, struct bog
return 0; return 0;
} }
int add_resource_record(struct dns_header *header, char *limit, int *truncp, int nameoffset, unsigned char **pp, 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, ...) unsigned long ttl, int *offset, unsigned short type, unsigned short class, char *format, ...)
{ {
...@@ -1071,12 +1072,21 @@ int add_resource_record(struct dns_header *header, char *limit, int *truncp, int ...@@ -1071,12 +1072,21 @@ int add_resource_record(struct dns_header *header, char *limit, int *truncp, int
unsigned short usval; unsigned short usval;
long lval; long lval;
char *sval; char *sval;
#define CHECK_LIMIT(size) \
if (limit && p + (size) > (unsigned char*)limit) \
{ \
va_end(ap); \
goto truncated; \
}
if (truncp && *truncp) if (truncp && *truncp)
return 0; return 0;
va_start(ap, format); /* make ap point to 1st unamed argument */ 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) if (nameoffset > 0)
{ {
PUTSHORT(nameoffset | 0xc000, p); PUTSHORT(nameoffset | 0xc000, p);
...@@ -1085,7 +1095,13 @@ int add_resource_record(struct dns_header *header, char *limit, int *truncp, int ...@@ -1085,7 +1095,13 @@ int add_resource_record(struct dns_header *header, char *limit, int *truncp, int
{ {
char *name = va_arg(ap, char *); char *name = va_arg(ap, char *);
if (name) 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) if (nameoffset < 0)
{ {
PUTSHORT(-nameoffset | 0xc000, p); PUTSHORT(-nameoffset | 0xc000, p);
...@@ -1106,6 +1122,7 @@ int add_resource_record(struct dns_header *header, char *limit, int *truncp, int ...@@ -1106,6 +1122,7 @@ int add_resource_record(struct dns_header *header, char *limit, int *truncp, int
{ {
#ifdef HAVE_IPV6 #ifdef HAVE_IPV6
case '6': case '6':
CHECK_LIMIT(IN6ADDRSZ);
sval = va_arg(ap, char *); sval = va_arg(ap, char *);
memcpy(p, sval, IN6ADDRSZ); memcpy(p, sval, IN6ADDRSZ);
p += IN6ADDRSZ; p += IN6ADDRSZ;
...@@ -1113,36 +1130,47 @@ int add_resource_record(struct dns_header *header, char *limit, int *truncp, int ...@@ -1113,36 +1130,47 @@ int add_resource_record(struct dns_header *header, char *limit, int *truncp, int
#endif #endif
case '4': case '4':
CHECK_LIMIT(INADDRSZ);
sval = va_arg(ap, char *); sval = va_arg(ap, char *);
memcpy(p, sval, INADDRSZ); memcpy(p, sval, INADDRSZ);
p += INADDRSZ; p += INADDRSZ;
break; break;
case 'b': case 'b':
CHECK_LIMIT(1);
usval = va_arg(ap, int); usval = va_arg(ap, int);
*p++ = usval; *p++ = usval;
break; break;
case 's': case 's':
CHECK_LIMIT(2);
usval = va_arg(ap, int); usval = va_arg(ap, int);
PUTSHORT(usval, p); PUTSHORT(usval, p);
break; break;
case 'l': case 'l':
CHECK_LIMIT(4);
lval = va_arg(ap, long); lval = va_arg(ap, long);
PUTLONG(lval, p); PUTLONG(lval, p);
break; break;
case 'd': case 'd':
/* get domain-name answer arg and store it in RDATA field */ /* get domain-name answer arg and store it in RDATA field */
if (offset) if (offset)
*offset = p - (unsigned char *)header; *offset = p - (unsigned char *)header;
p = do_rfc1035_name(p, va_arg(ap, char *)); p = do_rfc1035_name(p, va_arg(ap, char *), limit);
*p++ = 0; if (!p)
{
va_end(ap);
goto truncated;
}
CHECK_LIMIT(1);
*p++ = 0;
break; break;
case 't': case 't':
usval = va_arg(ap, int); usval = va_arg(ap, int);
CHECK_LIMIT(usval);
sval = va_arg(ap, char *); sval = va_arg(ap, char *);
if (usval != 0) if (usval != 0)
memcpy(p, sval, usval); memcpy(p, sval, usval);
...@@ -1154,20 +1182,24 @@ int add_resource_record(struct dns_header *header, char *limit, int *truncp, int ...@@ -1154,20 +1182,24 @@ int add_resource_record(struct dns_header *header, char *limit, int *truncp, int
usval = sval ? strlen(sval) : 0; usval = sval ? strlen(sval) : 0;
if (usval > 255) if (usval > 255)
usval = 255; usval = 255;
CHECK_LIMIT(usval + 1);
*p++ = (unsigned char)usval; *p++ = (unsigned char)usval;
memcpy(p, sval, usval); memcpy(p, sval, usval);
p += usval; p += usval;
break; break;
} }
#undef CHECK_LIMIT
va_end(ap); /* clean up variable argument pointer */ va_end(ap); /* clean up variable argument pointer */
j = p - sav - 2; 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 */ /* check for overflow of buffer */
if (limit && ((unsigned char *)limit - p) < 0) if (limit && ((unsigned char *)limit - p) < 0)
{ {
truncated:
if (truncp) if (truncp)
*truncp = 1; *truncp = 1;
return 0; return 0;
......
...@@ -2452,10 +2452,10 @@ static void do_options(struct dhcp_context *context, ...@@ -2452,10 +2452,10 @@ static void do_options(struct dhcp_context *context,
if (fqdn_flags & 0x04) if (fqdn_flags & 0x04)
{ {
p = do_rfc1035_name(p, hostname); p = do_rfc1035_name(p, hostname, NULL);
if (domain) if (domain)
{ {
p = do_rfc1035_name(p, domain); p = do_rfc1035_name(p, domain, NULL);
*p++ = 0; *p++ = 0;
} }
} }
......
...@@ -1479,10 +1479,10 @@ static struct dhcp_netid *add_options(struct state *state, int do_refresh) ...@@ -1479,10 +1479,10 @@ static struct dhcp_netid *add_options(struct state *state, int do_refresh)
if ((p = expand(len + 2))) if ((p = expand(len + 2)))
{ {
*(p++) = state->fqdn_flags; *(p++) = state->fqdn_flags;
p = do_rfc1035_name(p, state->hostname); p = do_rfc1035_name(p, state->hostname, NULL);
if (state->send_domain) if (state->send_domain)
{ {
p = do_rfc1035_name(p, state->send_domain); p = do_rfc1035_name(p, state->send_domain, NULL);
*p = 0; *p = 0;
} }
} }
......
...@@ -240,15 +240,20 @@ char *canonicalise(char *in, int *nomem) ...@@ -240,15 +240,20 @@ char *canonicalise(char *in, int *nomem)
return ret; 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; int j;
while (sval && *sval) while (sval && *sval)
{ {
if (limit && p + 1 > (unsigned char*)limit)
return p;
unsigned char *cp = p++; unsigned char *cp = p++;
for (j = 0; *sval && (*sval != '.'); sval++, j++) for (j = 0; *sval && (*sval != '.'); sval++, j++)
{ {
if (limit && p + 1 > (unsigned char*)limit)
return p;
#ifdef HAVE_DNSSEC #ifdef HAVE_DNSSEC
if (option_bool(OPT_DNSSEC_VALID) && *sval == NAME_ESCAPE) if (option_bool(OPT_DNSSEC_VALID) && *sval == NAME_ESCAPE)
*p++ = (*(++sval))-1; *p++ = (*(++sval))-1;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment