|
|
a03026 |
From 8868a04895b27d42d42e364f1a0c0196c1505b04 Mon Sep 17 00:00:00 2001
|
|
|
a03026 |
From: Simon Kelley <simon@thekelleys.org.uk>
|
|
|
a03026 |
Date: Mon, 25 Sep 2017 18:17:11 +0100
|
|
|
a03026 |
Subject: [PATCH 1/9] Security fix, CVE-2017-14491 DNS heap buffer
|
|
|
a03026 |
overflow.
|
|
|
a03026 |
|
|
|
a03026 |
Fix heap overflow in DNS code. This is a potentially serious
|
|
|
a03026 |
security hole. It allows an attacker who can make DNS
|
|
|
a03026 |
requests to dnsmasq, and who controls the contents of
|
|
|
a03026 |
a domain, which is thereby queried, to overflow
|
|
|
a03026 |
(by 2 bytes) a heap buffer and either crash, or
|
|
|
a03026 |
even take control of, dnsmasq.
|
|
|
a03026 |
---
|
|
|
a03026 |
src/dnsmasq.h | 2 +-
|
|
|
a03026 |
src/dnssec.c | 2 +-
|
|
|
a03026 |
src/option.c | 2 +-
|
|
|
a03026 |
src/rfc1035.c | 50 +++++++++++++++++++++++++++++++++++++++++---------
|
|
|
a03026 |
src/rfc2131.c | 4 ++--
|
|
|
a03026 |
src/rfc3315.c | 4 ++--
|
|
|
a03026 |
src/util.c | 7 ++++++-
|
|
|
a03026 |
7 files changed, 54 insertions(+), 17 deletions(-)
|
|
|
a03026 |
|
|
|
a03026 |
diff --git a/src/dnsmasq.h b/src/dnsmasq.h
|
|
|
a03026 |
index 1179492..06e5579 100644
|
|
|
a03026 |
--- a/src/dnsmasq.h
|
|
|
a03026 |
+++ b/src/dnsmasq.h
|
|
|
a03026 |
@@ -1162,7 +1162,7 @@ u32 rand32(void);
|
|
|
a03026 |
u64 rand64(void);
|
|
|
a03026 |
int legal_hostname(char *c);
|
|
|
a03026 |
char *canonicalise(char *s, int *nomem);
|
|
|
a03026 |
-unsigned char *do_rfc1035_name(unsigned char *p, char *sval);
|
|
|
a03026 |
+unsigned char *do_rfc1035_name(unsigned char *p, char *sval, char *limit);
|
|
|
a03026 |
void *safe_malloc(size_t size);
|
|
|
a03026 |
void safe_pipe(int *fd, int read_noblock);
|
|
|
a03026 |
void *whine_malloc(size_t size);
|
|
|
a03026 |
diff --git a/src/dnssec.c b/src/dnssec.c
|
|
|
a03026 |
index 3c77c7d..f45c804 100644
|
|
|
a03026 |
--- a/src/dnssec.c
|
|
|
a03026 |
+++ b/src/dnssec.c
|
|
|
a03026 |
@@ -2227,7 +2227,7 @@ size_t dnssec_generate_query(struct dns_header *header, unsigned char *end, char
|
|
|
a03026 |
|
|
|
a03026 |
p = (unsigned char *)(header+1);
|
|
|
a03026 |
|
|
|
a03026 |
- p = do_rfc1035_name(p, name);
|
|
|
a03026 |
+ p = do_rfc1035_name(p, name, NULL);
|
|
|
a03026 |
*p++ = 0;
|
|
|
a03026 |
PUTSHORT(type, p);
|
|
|
a03026 |
PUTSHORT(class, p);
|
|
|
a03026 |
diff --git a/src/option.c b/src/option.c
|
|
|
a03026 |
index eb78b1a..3469f53 100644
|
|
|
a03026 |
--- a/src/option.c
|
|
|
a03026 |
+++ b/src/option.c
|
|
|
a03026 |
@@ -1378,7 +1378,7 @@ static int parse_dhcp_opt(char *errstr, char *arg, int flags)
|
|
|
a03026 |
}
|
|
|
a03026 |
|
|
|
a03026 |
p = newp;
|
|
|
a03026 |
- end = do_rfc1035_name(p + len, dom);
|
|
|
a03026 |
+ end = do_rfc1035_name(p + len, dom, NULL);
|
|
|
a03026 |
*end++ = 0;
|
|
|
a03026 |
len = end - p;
|
|
|
a03026 |
free(dom);
|
|
|
a03026 |
diff --git a/src/rfc1035.c b/src/rfc1035.c
|
|
|
a03026 |
index 24d08c1..78410d6 100644
|
|
|
a03026 |
--- a/src/rfc1035.c
|
|
|
a03026 |
+++ b/src/rfc1035.c
|
|
|
a03026 |
@@ -1049,6 +1049,7 @@ int check_for_ignored_address(struct dns_header *header, size_t qlen, struct bog
|
|
|
a03026 |
return 0;
|
|
|
a03026 |
}
|
|
|
a03026 |
|
|
|
a03026 |
+
|
|
|
a03026 |
int add_resource_record(struct dns_header *header, char *limit, int *truncp, int nameoffset, unsigned char **pp,
|
|
|
a03026 |
unsigned long ttl, int *offset, unsigned short type, unsigned short class, char *format, ...)
|
|
|
a03026 |
{
|
|
|
a03026 |
@@ -1058,12 +1059,21 @@ int add_resource_record(struct dns_header *header, char *limit, int *truncp, int
|
|
|
a03026 |
unsigned short usval;
|
|
|
a03026 |
long lval;
|
|
|
a03026 |
char *sval;
|
|
|
a03026 |
+#define CHECK_LIMIT(size) \
|
|
|
a03026 |
+ if (limit && p + (size) > (unsigned char*)limit) \
|
|
|
a03026 |
+ { \
|
|
|
a03026 |
+ va_end(ap); \
|
|
|
a03026 |
+ goto truncated; \
|
|
|
a03026 |
+ }
|
|
|
a03026 |
|
|
|
a03026 |
if (truncp && *truncp)
|
|
|
a03026 |
return 0;
|
|
|
a03026 |
-
|
|
|
a03026 |
+
|
|
|
a03026 |
va_start(ap, format); /* make ap point to 1st unamed argument */
|
|
|
a03026 |
-
|
|
|
a03026 |
+
|
|
|
a03026 |
+ /* nameoffset (1 or 2) + type (2) + class (2) + ttl (4) + 0 (2) */
|
|
|
a03026 |
+ CHECK_LIMIT(12);
|
|
|
a03026 |
+
|
|
|
a03026 |
if (nameoffset > 0)
|
|
|
a03026 |
{
|
|
|
a03026 |
PUTSHORT(nameoffset | 0xc000, p);
|
|
|
a03026 |
@@ -1072,7 +1082,13 @@ int add_resource_record(struct dns_header *header, char *limit, int *truncp, int
|
|
|
a03026 |
{
|
|
|
a03026 |
char *name = va_arg(ap, char *);
|
|
|
a03026 |
if (name)
|
|
|
a03026 |
- p = do_rfc1035_name(p, name);
|
|
|
a03026 |
+ p = do_rfc1035_name(p, name, limit);
|
|
|
a03026 |
+ if (!p)
|
|
|
a03026 |
+ {
|
|
|
a03026 |
+ va_end(ap);
|
|
|
a03026 |
+ goto truncated;
|
|
|
a03026 |
+ }
|
|
|
a03026 |
+
|
|
|
a03026 |
if (nameoffset < 0)
|
|
|
a03026 |
{
|
|
|
a03026 |
PUTSHORT(-nameoffset | 0xc000, p);
|
|
|
a03026 |
@@ -1093,6 +1109,7 @@ int add_resource_record(struct dns_header *header, char *limit, int *truncp, int
|
|
|
a03026 |
{
|
|
|
a03026 |
#ifdef HAVE_IPV6
|
|
|
a03026 |
case '6':
|
|
|
a03026 |
+ CHECK_LIMIT(IN6ADDRSZ);
|
|
|
a03026 |
sval = va_arg(ap, char *);
|
|
|
a03026 |
memcpy(p, sval, IN6ADDRSZ);
|
|
|
a03026 |
p += IN6ADDRSZ;
|
|
|
a03026 |
@@ -1100,36 +1117,47 @@ int add_resource_record(struct dns_header *header, char *limit, int *truncp, int
|
|
|
a03026 |
#endif
|
|
|
a03026 |
|
|
|
a03026 |
case '4':
|
|
|
a03026 |
+ CHECK_LIMIT(INADDRSZ);
|
|
|
a03026 |
sval = va_arg(ap, char *);
|
|
|
a03026 |
memcpy(p, sval, INADDRSZ);
|
|
|
a03026 |
p += INADDRSZ;
|
|
|
a03026 |
break;
|
|
|
a03026 |
|
|
|
a03026 |
case 'b':
|
|
|
a03026 |
+ CHECK_LIMIT(1);
|
|
|
a03026 |
usval = va_arg(ap, int);
|
|
|
a03026 |
*p++ = usval;
|
|
|
a03026 |
break;
|
|
|
a03026 |
|
|
|
a03026 |
case 's':
|
|
|
a03026 |
+ CHECK_LIMIT(2);
|
|
|
a03026 |
usval = va_arg(ap, int);
|
|
|
a03026 |
PUTSHORT(usval, p);
|
|
|
a03026 |
break;
|
|
|
a03026 |
|
|
|
a03026 |
case 'l':
|
|
|
a03026 |
+ CHECK_LIMIT(4);
|
|
|
a03026 |
lval = va_arg(ap, long);
|
|
|
a03026 |
PUTLONG(lval, p);
|
|
|
a03026 |
break;
|
|
|
a03026 |
|
|
|
a03026 |
case 'd':
|
|
|
a03026 |
- /* get domain-name answer arg and store it in RDATA field */
|
|
|
a03026 |
- if (offset)
|
|
|
a03026 |
- *offset = p - (unsigned char *)header;
|
|
|
a03026 |
- p = do_rfc1035_name(p, va_arg(ap, char *));
|
|
|
a03026 |
- *p++ = 0;
|
|
|
a03026 |
+ /* get domain-name answer arg and store it in RDATA field */
|
|
|
a03026 |
+ if (offset)
|
|
|
a03026 |
+ *offset = p - (unsigned char *)header;
|
|
|
a03026 |
+ p = do_rfc1035_name(p, va_arg(ap, char *), limit);
|
|
|
a03026 |
+ if (!p)
|
|
|
a03026 |
+ {
|
|
|
a03026 |
+ va_end(ap);
|
|
|
a03026 |
+ goto truncated;
|
|
|
a03026 |
+ }
|
|
|
a03026 |
+ CHECK_LIMIT(1);
|
|
|
a03026 |
+ *p++ = 0;
|
|
|
a03026 |
break;
|
|
|
a03026 |
|
|
|
a03026 |
case 't':
|
|
|
a03026 |
usval = va_arg(ap, int);
|
|
|
a03026 |
+ CHECK_LIMIT(usval);
|
|
|
a03026 |
sval = va_arg(ap, char *);
|
|
|
a03026 |
if (usval != 0)
|
|
|
a03026 |
memcpy(p, sval, usval);
|
|
|
a03026 |
@@ -1141,20 +1169,24 @@ int add_resource_record(struct dns_header *header, char *limit, int *truncp, int
|
|
|
a03026 |
usval = sval ? strlen(sval) : 0;
|
|
|
a03026 |
if (usval > 255)
|
|
|
a03026 |
usval = 255;
|
|
|
a03026 |
+ CHECK_LIMIT(usval + 1);
|
|
|
a03026 |
*p++ = (unsigned char)usval;
|
|
|
a03026 |
memcpy(p, sval, usval);
|
|
|
a03026 |
p += usval;
|
|
|
a03026 |
break;
|
|
|
a03026 |
}
|
|
|
a03026 |
|
|
|
a03026 |
+#undef CHECK_LIMIT
|
|
|
a03026 |
va_end(ap); /* clean up variable argument pointer */
|
|
|
a03026 |
|
|
|
a03026 |
j = p - sav - 2;
|
|
|
a03026 |
- PUTSHORT(j, sav); /* Now, store real RDLength */
|
|
|
a03026 |
+ /* this has already been checked against limit before */
|
|
|
a03026 |
+ PUTSHORT(j, sav); /* Now, store real RDLength */
|
|
|
a03026 |
|
|
|
a03026 |
/* check for overflow of buffer */
|
|
|
a03026 |
if (limit && ((unsigned char *)limit - p) < 0)
|
|
|
a03026 |
{
|
|
|
a03026 |
+truncated:
|
|
|
a03026 |
if (truncp)
|
|
|
a03026 |
*truncp = 1;
|
|
|
a03026 |
return 0;
|
|
|
a03026 |
diff --git a/src/rfc2131.c b/src/rfc2131.c
|
|
|
a03026 |
index 8b99d4b..75893a6 100644
|
|
|
a03026 |
--- a/src/rfc2131.c
|
|
|
a03026 |
+++ b/src/rfc2131.c
|
|
|
a03026 |
@@ -2420,10 +2420,10 @@ static void do_options(struct dhcp_context *context,
|
|
|
a03026 |
|
|
|
a03026 |
if (fqdn_flags & 0x04)
|
|
|
a03026 |
{
|
|
|
a03026 |
- p = do_rfc1035_name(p, hostname);
|
|
|
a03026 |
+ p = do_rfc1035_name(p, hostname, NULL);
|
|
|
a03026 |
if (domain)
|
|
|
a03026 |
{
|
|
|
a03026 |
- p = do_rfc1035_name(p, domain);
|
|
|
a03026 |
+ p = do_rfc1035_name(p, domain, NULL);
|
|
|
a03026 |
*p++ = 0;
|
|
|
a03026 |
}
|
|
|
a03026 |
}
|
|
|
a03026 |
diff --git a/src/rfc3315.c b/src/rfc3315.c
|
|
|
a03026 |
index 3f4d69c..73bdee4 100644
|
|
|
a03026 |
--- a/src/rfc3315.c
|
|
|
a03026 |
+++ b/src/rfc3315.c
|
|
|
a03026 |
@@ -1472,10 +1472,10 @@ static struct dhcp_netid *add_options(struct state *state, int do_refresh)
|
|
|
a03026 |
if ((p = expand(len + 2)))
|
|
|
a03026 |
{
|
|
|
a03026 |
*(p++) = state->fqdn_flags;
|
|
|
a03026 |
- p = do_rfc1035_name(p, state->hostname);
|
|
|
a03026 |
+ p = do_rfc1035_name(p, state->hostname, NULL);
|
|
|
a03026 |
if (state->send_domain)
|
|
|
a03026 |
{
|
|
|
a03026 |
- p = do_rfc1035_name(p, state->send_domain);
|
|
|
a03026 |
+ p = do_rfc1035_name(p, state->send_domain, NULL);
|
|
|
a03026 |
*p = 0;
|
|
|
a03026 |
}
|
|
|
a03026 |
}
|
|
|
a03026 |
diff --git a/src/util.c b/src/util.c
|
|
|
a03026 |
index 1a9f228..be9f8a6 100644
|
|
|
a03026 |
--- a/src/util.c
|
|
|
a03026 |
+++ b/src/util.c
|
|
|
a03026 |
@@ -218,15 +218,20 @@ char *canonicalise(char *in, int *nomem)
|
|
|
a03026 |
return ret;
|
|
|
a03026 |
}
|
|
|
a03026 |
|
|
|
a03026 |
-unsigned char *do_rfc1035_name(unsigned char *p, char *sval)
|
|
|
a03026 |
+unsigned char *do_rfc1035_name(unsigned char *p, char *sval, char *limit)
|
|
|
a03026 |
{
|
|
|
a03026 |
int j;
|
|
|
a03026 |
|
|
|
a03026 |
while (sval && *sval)
|
|
|
a03026 |
{
|
|
|
a03026 |
+ if (limit && p + 1 > (unsigned char*)limit)
|
|
|
a03026 |
+ return p;
|
|
|
a03026 |
+
|
|
|
a03026 |
unsigned char *cp = p++;
|
|
|
a03026 |
for (j = 0; *sval && (*sval != '.'); sval++, j++)
|
|
|
a03026 |
{
|
|
|
a03026 |
+ if (limit && p + 1 > (unsigned char*)limit)
|
|
|
a03026 |
+ return p;
|
|
|
a03026 |
#ifdef HAVE_DNSSEC
|
|
|
a03026 |
if (option_bool(OPT_DNSSEC_VALID) && *sval == NAME_ESCAPE)
|
|
|
a03026 |
*p++ = (*(++sval))-1;
|
|
|
a03026 |
--
|
|
|
a03026 |
2.9.5
|
|
|
a03026 |
|