Compare commits

...
6 Commits
Author SHA1 Message Date
Simon Kelley 45903b7776 Fix remote buffer overflow CERT VU#434904
The problem is in the sort_rrset() function and allows a remote
attacker to overwrite memory. Any dnsmasq instance with DNSSEC
enabled is vulnerable.
2020-11-11 23:25:04 +00:00
Matthias Andree f60fea1fb0 CHANGELOG: Fix three typoes. 2020-07-19 21:54:44 +01:00
Simon Kelley 4d85e409cd Change default lease time for DHCPv6 to one day.
Also remove floor on valid and preffered times in RA when
no time is specified.
2020-07-12 22:45:46 +01:00
Simon Kelley 2bd02d2f59 Backdated CHANGELOG update. 2020-07-12 21:57:38 +01:00
Simon Kelley 7e194a0a7d Apply floor of 60s to TTL of DNSKEY and DS records in cache.
Short TTLs and specifically zero TTLs can mess up DNSSEC validation.
2020-07-12 17:43:25 +01:00
Simon Kelley 9beb4d9ea2 Fix BNF in man page description for --server. 2020-07-05 17:17:39 +01:00
8 changed files with 216 additions and 133 deletions

No files matched your search

+28 -1
View File
@@ -1,6 +1,33 @@
version 2.83
Fix a remote buffer overflow problem in the DNSSEC code. Any
dnsmasq with DNSSEC compiled in and enabled is vulnerable to this,
referenced by CERT VU#434904.
version 2.82
Improve behaviour in the face of network interfaces which come
and go and change index. Thanks to Petr Mensik for the patch.
Convert hard startup failure on NETLINK_NO_ENOBUFS under qemu-user
to a warning.
Allow IPv6 addresses ofthe form [::ffff:1.2.3.4] in --dhcp-option.
Fix crash under heavy TCP connection load introduced in 2.81.
Thanks to Frank for good work chasing this down.
Change default lease time for DHCPv6 to one day.
Alter calculation of preferred and valid times in router
advertisements, so that these do not have a floor applied
of the lease time in the dhcp-range if this is not explicitly
specified and is merely the default.
Thanks to Martin-Éric Racine for suggestions on this.
version 2.81
Improve cache behaviour for TCP connections. For ease of
implementaion, dnsmasq has always forked a new process to handle
implementation, dnsmasq has always forked a new process to handle
each incoming TCP connection. A side-effect of this is that
any DNS queries answered from TCP connections are not cached:
when TCP connections were rare, this was not a problem.
+2 -2
View File
@@ -428,7 +428,7 @@ Tells dnsmasq to never forward A or AAAA queries for plain names, without dots
or domain parts, to upstream nameservers. If the name is not known
from /etc/hosts or DHCP then a "not found" answer is returned.
.TP
.B \-S, --local, --server=[/[<domain>]/[domain/]][<ipaddr>[#<port>][@<source-ip>|<interface>[#<port>]]
.B \-S, --local, --server=[/[<domain>]/[domain/]][<ipaddr>[#<port>]][@<source-ip>|<interface>[#<port>]]
Specify IP address of upstream servers directly. Setting this flag does
not suppress reading of /etc/resolv.conf, use \fB--no-resolv\fP to do that. If one or more
optional domains are given, that server is used only for those domains
@@ -861,7 +861,7 @@ in
options. If the lease time is given, then leases
will be given for that length of time. The lease time is in seconds,
or minutes (eg 45m) or hours (eg 1h) or "infinite". If not given,
the default lease time is one hour. The
the default lease time is one hour for IPv4 and one day for IPv6. The
minimum lease time is two minutes. For IPv6 ranges, the lease time
maybe "deprecated"; this sets the preferred lifetime sent in a DHCP
lease or router advertisement to zero, which causes clients to use
+17 -4
View File
@@ -472,16 +472,29 @@ void cache_start_insert(void)
struct crec *cache_insert(char *name, union all_addr *addr, unsigned short class,
time_t now, unsigned long ttl, unsigned int flags)
{
/* Don't log DNSSEC records here, done elsewhere */
if (flags & (F_IPV4 | F_IPV6 | F_CNAME | F_SRV))
#ifdef HAVE_DNSSEC
if (flags & (F_DNSKEY | F_DS))
{
/* The DNSSEC validation process works by getting needed records into the
cache, then retrying the validation until they are all in place.
This can be messed up by very short TTLs, and _really_ messed up by
zero TTLs, so we force the TTL to be at least long enough to do a validation.
Ideally, we should use some kind of reference counting so that records are
locked until the validation that asked for them is complete, but this
is much easier, and just as effective. */
if (ttl < DNSSEC_MIN_TTL)
ttl = DNSSEC_MIN_TTL;
}
else
#endif
{
/* Don't log DNSSEC records here, done elsewhere */
log_query(flags | F_UPSTREAM, name, addr, NULL);
/* Don't mess with TTL for DNSSEC records. */
if (daemon->max_cache_ttl != 0 && daemon->max_cache_ttl < ttl)
ttl = daemon->max_cache_ttl;
if (daemon->min_cache_ttl != 0 && daemon->min_cache_ttl > ttl)
ttl = daemon->min_cache_ttl;
}
}
return really_insert(name, addr, class, now, ttl, flags);
}
+3 -1
View File
@@ -40,9 +40,11 @@
#define DHCP_PACKET_MAX 16384 /* hard limit on DHCP packet size */
#define SMALLDNAME 50 /* most domain names are smaller than this */
#define CNAME_CHAIN 10 /* chains longer than this atr dropped for loop protection */
#define DNSSEC_MIN_TTL 60 /* DNSKEY and DS records in cache last at least this long */
#define HOSTSFILE "/etc/hosts"
#define ETHERSFILE "/etc/ethers"
#define DEFLEASE 3600 /* default lease time, 1 hour */
#define DEFLEASE 3600 /* default DHCPv4 lease time, one hour */
#define DEFLEASE6 (3600*24) /* default lease time for DHCPv6. One day. */
#define CHUSER "nobody"
#define CHGRP "dip"
#define TFTP_MAX_CONNECTIONS 50 /* max simultaneous connections */
+1
View File
@@ -942,6 +942,7 @@ struct shared_network {
#define CONTEXT_OLD (1u<<16)
#define CONTEXT_V6 (1u<<17)
#define CONTEXT_RA_OFF_LINK (1u<<18)
#define CONTEXT_SETLEASE (1u<<19)
struct ping_result {
struct in_addr addr;
+152 -121
View File
@@ -223,138 +223,147 @@ static int check_date_range(unsigned long curtime, u32 date_start, u32 date_end)
&& serial_compare_32(curtime, date_end) == SERIAL_LT;
}
/* Return bytes of canonicalised rdata, when the return value is zero, the remaining
data, pointed to by *p, should be used raw. */
static int get_rdata(struct dns_header *header, size_t plen, unsigned char *end, char *buff, int bufflen,
unsigned char **p, u16 **desc)
/* Return bytes of canonicalised rrdata one by one.
Init state->ip with the RR, and state->end with the end of same.
Init state->op to NULL.
Init state->desc to RR descriptor.
Init state->buff with a MAXDNAME * 2 buffer.
After each call which returns 1, state->op points to the next byte of data.
On returning 0, the end has been reached.
*/
struct rdata_state {
u16 *desc;
size_t c;
unsigned char *end, *ip, *op;
char *buff;
};
static int get_rdata(struct dns_header *header, size_t plen, struct rdata_state *state)
{
int d = **desc;
int d;
/* No more data needs mangling */
if (d == (u16)-1)
if (state->op && state->c != 1)
{
/* If there's more data than we have space for, just return what fits,
we'll get called again for more chunks */
if (end - *p > bufflen)
{
memcpy(buff, *p, bufflen);
*p += bufflen;
return bufflen;
}
return 0;
state->op++;
state->c--;
return 1;
}
(*desc)++;
if (d == 0 && extract_name(header, plen, p, buff, 1, 0))
/* domain-name, canonicalise */
return to_wire(buff);
else
{
/* plain data preceding a domain-name, don't run off the end of the data */
if ((end - *p) < d)
d = end - *p;
while (1)
{
d = *(state->desc);
if (d != 0)
if (d == (u16)-1)
{
memcpy(buff, *p, d);
*p += d;
/* all the bytes to the end. */
if ((state->c = state->end - state->ip) != 0)
{
state->op = state->ip;
state->ip = state->end;;
}
else
return 0;
}
else
{
state->desc++;
if (d == (u16)0)
{
/* domain-name, canonicalise */
int len;
if (!extract_name(header, plen, &state->ip, state->buff, 1, 0) ||
(len = to_wire(state->buff)) == 0)
continue;
state->c = len;
state->op = (unsigned char *)state->buff;
}
else
{
/* plain data preceding a domain-name, don't run off the end of the data */
if ((state->end - state->ip) < d)
d = state->end - state->ip;
if (d == 0)
continue;
state->op = state->ip;
state->c = d;
state->ip += d;
}
}
return d;
return 1;
}
}
/* Bubble sort the RRset into the canonical order.
Note that the byte-streams from two RRs may get unsynced: consider
RRs which have two domain-names at the start and then other data.
The domain-names may have different lengths in each RR, but sort equal
------------
|abcde|fghi|
------------
|abcd|efghi|
------------
leaving the following bytes as deciding the order. Hence the nasty left1 and left2 variables.
*/
/* Bubble sort the RRset into the canonical order. */
static int sort_rrset(struct dns_header *header, size_t plen, u16 *rr_desc, int rrsetidx,
unsigned char **rrset, char *buff1, char *buff2)
{
int swap, quit, i, j;
int swap, i, j;
do
{
for (swap = 0, i = 0; i < rrsetidx-1; i++)
{
int rdlen1, rdlen2, left1, left2, len1, len2, len, rc;
u16 *dp1, *dp2;
unsigned char *end1, *end2;
int rdlen1, rdlen2;
struct rdata_state state1, state2;
/* Note that these have been determined to be OK previously,
so we don't need to check for NULL return here. */
unsigned char *p1 = skip_name(rrset[i], header, plen, 10);
unsigned char *p2 = skip_name(rrset[i+1], header, plen, 10);
state1.ip = skip_name(rrset[i], header, plen, 10);
state2.ip = skip_name(rrset[i+1], header, plen, 10);
state1.op = state2.op = NULL;
state1.buff = buff1;
state2.buff = buff2;
state1.desc = state2.desc = rr_desc;
p1 += 8; /* skip class, type, ttl */
GETSHORT(rdlen1, p1);
end1 = p1 + rdlen1;
state1.ip += 8; /* skip class, type, ttl */
GETSHORT(rdlen1, state1.ip);
if (!CHECK_LEN(header, state1.ip, plen, rdlen1))
return rrsetidx; /* short packet */
state1.end = state1.ip + rdlen1;
p2 += 8; /* skip class, type, ttl */
GETSHORT(rdlen2, p2);
end2 = p2 + rdlen2;
dp1 = dp2 = rr_desc;
for (quit = 0, left1 = 0, left2 = 0, len1 = 0, len2 = 0; !quit;)
state2.ip += 8; /* skip class, type, ttl */
GETSHORT(rdlen2, state2.ip);
if (!CHECK_LEN(header, state2.ip, plen, rdlen2))
return rrsetidx; /* short packet */
state2.end = state2.ip + rdlen2;
while (1)
{
if (left1 != 0)
memmove(buff1, buff1 + len1 - left1, left1);
int ok1, ok2;
if ((len1 = get_rdata(header, plen, end1, buff1 + left1, (MAXDNAME * 2) - left1, &p1, &dp1)) == 0)
{
quit = 1;
len1 = end1 - p1;
memcpy(buff1 + left1, p1, len1);
}
len1 += left1;
if (left2 != 0)
memmove(buff2, buff2 + len2 - left2, left2);
if ((len2 = get_rdata(header, plen, end2, buff2 + left2, (MAXDNAME *2) - left2, &p2, &dp2)) == 0)
{
quit = 1;
len2 = end2 - p2;
memcpy(buff2 + left2, p2, len2);
}
len2 += left2;
if (len1 > len2)
left1 = len1 - len2, left2 = 0, len = len2;
else
left2 = len2 - len1, left1 = 0, len = len1;
rc = (len == 0) ? 0 : memcmp(buff1, buff2, len);
if (rc > 0 || (rc == 0 && quit && len1 > len2))
{
unsigned char *tmp = rrset[i+1];
rrset[i+1] = rrset[i];
rrset[i] = tmp;
swap = quit = 1;
}
else if (rc == 0 && quit && len1 == len2)
ok1 = get_rdata(header, plen, &state1);
ok2 = get_rdata(header, plen, &state2);
if (!ok1 && !ok2)
{
/* Two RRs are equal, remove one copy. RFC 4034, para 6.3 */
for (j = i+1; j < rrsetidx-1; j++)
rrset[j] = rrset[j+1];
rrsetidx--;
i--;
break;
}
else if (rc < 0)
quit = 1;
else if (ok1 && (!ok2 || *state1.op > *state2.op))
{
unsigned char *tmp = rrset[i+1];
rrset[i+1] = rrset[i];
rrset[i] = tmp;
swap = 1;
break;
}
else if (ok2 && (!ok1 || *state2.op > *state1.op))
break;
/* arrive here when bytes are equal, go round the loop again
and compare the next ones. */
}
}
} while (swap);
@@ -569,15 +578,18 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
wire_len = to_wire(keyname);
hash->update(ctx, (unsigned int)wire_len, (unsigned char*)keyname);
from_wire(keyname);
#define RRBUFLEN 300 /* Most RRs are smaller than this. */
for (i = 0; i < rrsetidx; ++i)
{
int seg;
unsigned char *end, *cp;
u16 len, *dp;
int j;
struct rdata_state state;
u16 len;
unsigned char rrbuf[RRBUFLEN];
p = rrset[i];
if (!extract_name(header, plen, &p, name, 1, 10))
return STAT_BOGUS;
@@ -586,12 +598,11 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
/* if more labels than in RRsig name, hash *.<no labels in rrsig labels field> 4035 5.3.2 */
if (labels < name_labels)
{
int k;
for (k = name_labels - labels; k != 0; k--)
for (j = name_labels - labels; j != 0; j--)
{
while (*name_start != '.' && *name_start != 0)
name_start++;
if (k != 1 && *name_start == '.')
if (j != 1 && *name_start == '.')
name_start++;
}
@@ -612,24 +623,44 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
if (!CHECK_LEN(header, p, plen, rdlen))
return STAT_BOGUS;
end = p + rdlen;
/* canonicalise rdata and calculate length of same, use
name buffer as workspace for get_rdata. */
state.ip = p;
state.op = NULL;
state.desc = rr_desc;
state.buff = name;
state.end = p + rdlen;
/* canonicalise rdata and calculate length of same, use name buffer as workspace.
Note that name buffer is twice MAXDNAME long in DNSSEC mode. */
cp = p;
dp = rr_desc;
for (len = 0; (seg = get_rdata(header, plen, end, name, MAXDNAME * 2, &cp, &dp)) != 0; len += seg);
len += end - cp;
len = htons(len);
for (j = 0; get_rdata(header, plen, &state); j++)
if (j < RRBUFLEN)
rrbuf[j] = *state.op;
len = htons((u16)j);
hash->update(ctx, 2, (unsigned char *)&len);
/* If the RR is shorter than RRBUFLEN (most of them, in practice)
then we can just digest it now. If it exceeds RRBUFLEN we have to
go back to the start and do it in chunks. */
if (j >= RRBUFLEN)
{
state.ip = p;
state.op = NULL;
state.desc = rr_desc;
for (j = 0; get_rdata(header, plen, &state); j++)
{
rrbuf[j] = *state.op;
if (j == RRBUFLEN - 1)
{
hash->update(ctx, RRBUFLEN, rrbuf);
j = -1;
}
}
}
/* Now canonicalise again and digest. */
cp = p;
dp = rr_desc;
while ((seg = get_rdata(header, plen, end, name, MAXDNAME * 2, &cp, &dp)))
hash->update(ctx, seg, (unsigned char *)name);
if (cp != end)
hash->update(ctx, end - cp, cp);
if (j != 0)
hash->update(ctx, j, rrbuf);
}
hash->digest(ctx, hash->digest_size, digest);
+8 -2
View File
@@ -2991,7 +2991,6 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
struct dhcp_context *new = opt_malloc(sizeof(struct dhcp_context));
memset (new, 0, sizeof(*new));
new->lease_time = DEFLEASE;
while(1)
{
@@ -3041,6 +3040,7 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
if (inet_pton(AF_INET, a[0], &new->start))
{
new->next = daemon->dhcp;
new->lease_time = DEFLEASE;
daemon->dhcp = new;
new->end = new->start;
if (strcmp(a[1], "static") == 0)
@@ -3088,6 +3088,7 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
new->flags |= CONTEXT_V6;
new->prefix = 64; /* default */
new->end6 = new->start6;
new->lease_time = DEFLEASE6;
new->next = daemon->dhcp6;
daemon->dhcp6 = new;
@@ -3187,7 +3188,10 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
}
if (strcmp(a[leasepos], "infinite") == 0)
new->lease_time = 0xffffffff;
{
new->lease_time = 0xffffffff;
new->flags |= CONTEXT_SETLEASE;
}
else if (strcmp(a[leasepos], "deprecated") == 0)
new->flags |= CONTEXT_DEPRECATE;
else
@@ -3226,6 +3230,7 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
ret_err_free(_("bad dhcp-range"), new);
new->lease_time = atoi(a[leasepos]) * fac;
new->flags |= CONTEXT_SETLEASE;
/* Leases of a minute or less confuse
some clients, notably Apple's */
if (new->lease_time < 120)
@@ -3233,6 +3238,7 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
}
}
}
break;
}
+5 -2
View File
@@ -626,8 +626,11 @@ static int add_prefixes(struct in6_addr *local, int prefix,
real_prefix = context->prefix;
}
/* find floor time, don't reduce below 3 * RA interval. */
if (time > context->lease_time)
/* find floor time, don't reduce below 3 * RA interval.
If the lease time has been left as default, don't
use that as a floor. */
if ((context->flags & CONTEXT_SETLEASE) &&
time > context->lease_time)
{
time = context->lease_time;
if (time < ((unsigned int)(3 * param->adv_interval)))