summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSimon Kelley <simon@thekelleys.org.uk>2014-02-25 23:02:28 +0000
committerSimon Kelley <simon@thekelleys.org.uk>2014-02-25 23:02:28 +0000
commit613ad15d02154f39e5335697035136b4dbe92b4d (patch)
tree9abd71c29a5a24f9cb9e57ecc483592c5dca5541
parent24187530fb3cfc5e2eaa6352e7ea9b27f644e213 (diff)
downloaddnsmasq-613ad15d02154f39e5335697035136b4dbe92b4d.tar.gz
Strip DNSSEC RRs when query doesn't have DO bit set.
-rw-r--r--src/dnsmasq.h5
-rw-r--r--src/dnssec.c352
-rw-r--r--src/forward.c52
-rw-r--r--src/rfc1035.c12
4 files changed, 327 insertions, 94 deletions
diff --git a/src/dnsmasq.h b/src/dnsmasq.h
index d52adfa..f984f3b 100644
--- a/src/dnsmasq.h
+++ b/src/dnsmasq.h
@@ -542,6 +542,8 @@ struct hostsfile {
#define FREC_DNSKEY_QUERY 8
#define FREC_DS_QUERY 16
#define FREC_AD_QUESTION 32
+#define FREC_DO_QUESTION 64
+#define FREC_ADDED_PHEADER 128
#ifdef HAVE_DNSSEC
#define HASH_SIZE 20 /* SHA-1 digest size */
@@ -1048,7 +1050,7 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *namebuff,
int no_cache, int secure, int *doctored);
size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
struct in_addr local_addr, struct in_addr local_netmask,
- time_t now, int *ad_reqd);
+ time_t now, int *ad_reqd, int *do_bit);
int check_for_bogus_wildcard(struct dns_header *header, size_t qlen, char *name,
struct bogus_addr *addr, time_t now);
unsigned char *find_pseudoheader(struct dns_header *header, size_t plen,
@@ -1085,6 +1087,7 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t n, char
int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class);
int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int *class);
int dnskey_keytag(int alg, int flags, unsigned char *rdata, int rdlen);
+size_t filter_rrsigs(struct dns_header *header, size_t plen);
unsigned char* hash_questions(struct dns_header *header, size_t plen, char *name);
/* util.c */
diff --git a/src/dnssec.c b/src/dnssec.c
index be1b101..a902ded 100644
--- a/src/dnssec.c
+++ b/src/dnssec.c
@@ -472,6 +472,32 @@ static int get_rdata(struct dns_header *header, size_t plen, unsigned char *end,
}
}
+static int expand_workspace(unsigned char ***wkspc, int *sz, int new)
+{
+ unsigned char **p;
+ int new_sz = *sz;
+
+ if (new_sz > new)
+ return 1;
+
+ if (new >= 100)
+ return 0;
+
+ new_sz += 5;
+
+ if (!(p = whine_malloc((new_sz) * sizeof(unsigned char **))))
+ return 0;
+
+ if (*wkspc)
+ {
+ memcpy(p, *wkspc, *sz * sizeof(unsigned char **));
+ free(*wkspc);
+ }
+
+ *wkspc = p;
+ *sz = new_sz;
+}
+
/* 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.
@@ -615,64 +641,31 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
{
if (stype == type)
{
- if (rrsetidx == rrset_sz)
- {
- unsigned char **new;
-
- /* Protect against insane/maliciuos queries which bloat the workspace
- and eat CPU in the sort */
- if (rrsetidx >= 100)
- return STAT_INSECURE;
-
- /* expand */
- if (!(new = whine_malloc((rrset_sz + 5) * sizeof(unsigned char **))))
- return STAT_INSECURE;
-
- if (rrset)
- {
- memcpy(new, rrset, rrset_sz * sizeof(unsigned char **));
- free(rrset);
- }
-
- rrset = new;
- rrset_sz += 5;
- }
+ if (!expand_workspace(&rrset, &rrset_sz, rrsetidx))
+ return STAT_INSECURE;
+
rrset[rrsetidx++] = pstart;
}
if (stype == T_RRSIG)
{
- if (rdlen < 18)
- return STAT_INSECURE; /* bad packet */
-
- GETSHORT(type_covered, p);
-
- if (type_covered == type)
- {
- if (sigidx == sig_sz)
- {
- unsigned char **new;
-
- /* expand */
- if (!(new = whine_malloc((sig_sz + 5) * sizeof(unsigned char **))))
- return STAT_INSECURE;
-
- if (sigs)
- {
- memcpy(new, sigs, sig_sz * sizeof(unsigned char **));
- free(sigs);
- }
-
- sigs = new;
- sig_sz += 5;
- }
-
- sigs[sigidx++] = pdata;
- }
- p = pdata + 2; /* restore for ADD_RDLEN */
+ if (rdlen < 18)
+ return STAT_INSECURE; /* bad packet */
+
+ GETSHORT(type_covered, p);
+
+ if (type_covered == type)
+ {
+ if (!expand_workspace(&sigs, &sig_sz, sigidx))
+ return STAT_INSECURE;
+
+ sigs[sigidx++] = pdata;
+ }
+
+ p = pdata + 2; /* restore for ADD_RDLEN */
}
}
-
+
if (!ADD_RDLEN(header, p, plen, rdlen))
return STAT_INSECURE;
}
@@ -1195,32 +1188,12 @@ static int find_nsec_records(struct dns_header *header, size_t plen, unsigned ch
type_found = type;
- if (nsecs_found == nsecset_sz)
- {
- unsigned char **new;
-
- /* Protect against insane/malicious queries which bloat the workspace
- and eat CPU in the sort */
- if (nsecs_found >= 100)
- return 0;
-
- /* expand */
- if (!(new = whine_malloc((nsecset_sz + 5) * sizeof(unsigned char **))))
- return 0;
-
- if (nsecset)
- {
- memcpy(new, nsecset, nsecset_sz * sizeof(unsigned char **));
- free(nsecset);
- }
-
- nsecset = new;
- nsecset_sz += 5;
- }
-
+ if (!expand_workspace(&nsecset, &nsecset_sz, nsecs_found))
+ return 0;
+
nsecset[nsecs_found++] = pstart;
}
-
+
if (!ADD_RDLEN(header, p, plen, rdlen))
return 0;
}
@@ -1908,6 +1881,235 @@ size_t dnssec_generate_query(struct dns_header *header, char *end, char *name, i
return add_do_bit(header, p - (unsigned char *)header, end);
}
+/* Go through a domain name, find "pointers" and fix them up based on how many bytes
+ we've chopped out of the packet, or check they don't point into an elided part. */
+static int check_name(unsigned char **namep, struct dns_header *header, size_t plen, int fixup, unsigned char **rrs, int rr_count)
+{
+ unsigned char *ansp = *namep;
+
+ while(1)
+ {
+ unsigned int label_type;
+
+ if (!CHECK_LEN(header, ansp, plen, 1))
+ return 0;
+
+ label_type = (*ansp) & 0xc0;
+
+ if (label_type == 0xc0)
+ {
+ /* pointer for compression. */
+ unsigned int offset, i;
+ unsigned char *p;
+
+ if (!CHECK_LEN(header, ansp, plen, 2))
+ return 0;
+
+ offset = ((*ansp++) & 0x3f) << 8;
+ offset |= *ansp++;
+
+ p = offset + (unsigned char *)header;
+
+ for (i = 0; i < rr_count; i++)
+ if (p < rrs[i])
+ break;
+ else
+ if (i & 1)
+ offset -= rrs[i] - rrs[i-1];
+
+ /* does the pointer end up in an elided RR? */
+ if (i & 1)
+ return -1;
+
+ /* No, scale the pointer */
+ if (fixup)
+ {
+ ansp -= 2;
+ *ansp++ = (offset >> 8) | 0xc0;
+ *ansp++ = offset & 0xff;
+ }
+ break;
+ }
+ else if (label_type == 0x80)
+ return 0; /* reserved */
+ else if (label_type == 0x40)
+ {
+ /* Extended label type */
+ unsigned int count;
+
+ if (!CHECK_LEN(header, ansp, plen, 2))
+ return 0;
+
+ if (((*ansp++) & 0x3f) != 1)
+ return 0; /* we only understand bitstrings */
+
+ count = *(ansp++); /* Bits in bitstring */
+
+ if (count == 0) /* count == 0 means 256 bits */
+ ansp += 32;
+ else
+ ansp += ((count-1)>>3)+1;
+ }
+ else
+ { /* label type == 0 Bottom six bits is length */
+ unsigned int len = (*ansp++) & 0x3f;
+
+ if (!ADD_RDLEN(header, ansp, plen, len))
+ return 0;
+
+ if (len == 0)
+ break; /* zero length label marks the end. */
+ }
+ }
+
+ *namep = ansp;
+
+ return 1;
+}
+
+/* Go through RRs and check or fixup the domain names contained within */
+static int check_rrs(unsigned char *p, struct dns_header *header, size_t plen, int fixup, unsigned char **rrs, int rr_count)
+{
+ int i, type, class, rdlen;
+
+ for (i = 0; i < ntohs(header->ancount) + ntohs(header->nscount); i++)
+ {
+ if (type != T_NSEC && type != T_NSEC3 && type != T_RRSIG)
+ {
+ if (!check_name(&p, header, plen, fixup, rrs, rr_count))
+ return 0;
+ }
+ else
+ {
+ if (!(p = skip_name(p, header, plen, 10)))
+ return 0;
+ }
+
+ GETSHORT(type, p);
+ GETSHORT(class, p);
+ p += 4; /* TTL */
+ GETSHORT(rdlen, p);
+
+ if (type != T_NSEC && type != T_NSEC3 && type != T_RRSIG)
+ {
+ if (class == C_IN)
+ {
+ u16 *d;
+ unsigned char *pp = p;
+
+ for (d = get_desc(type); *d != (u16)-1; d++)
+ {
+ if (*d != 0)
+ pp += *d;
+ else if (!check_name(&pp, header, plen, fixup, rrs, rr_count))
+ return 0;
+ }
+ }
+ }
+
+ if (!ADD_RDLEN(header, p, plen, rdlen))
+ return 0;
+ }
+
+ return 1;
+}
+
+
+size_t filter_rrsigs(struct dns_header *header, size_t plen)
+{
+ static unsigned char **rrs;
+ static int rr_sz = 0;
+
+ unsigned char *p = (unsigned char *)(header+1);
+ int i, rdlen, qtype, qclass, rr_found, chop_an, chop_ns;
+
+ if (ntohs(header->qdcount) != 1 ||
+ !(p = skip_name(p, header, plen, 4)))
+ return plen;
+
+ GETSHORT(qtype, p);
+ GETSHORT(qclass, p);
+
+ /* First pass, find pointers to start and end of all the records we wish to elide:
+ records added for DNSSEC, unless explicity queried for */
+ for (rr_found = 0, chop_ns = 0, chop_an = 0, i = 0; i < ntohs(header->ancount) + ntohs(header->nscount); i++)
+ {
+ unsigned char *pstart = p;
+ int type, class;
+
+ if (!(p = skip_name(p, header, plen, 10)))
+ return plen;
+
+ GETSHORT(type, p);
+ GETSHORT(class, p);
+ p += 4; /* TTL */
+ GETSHORT(rdlen, p);
+
+ if ((type == T_NSEC || type == T_NSEC3 || type == T_RRSIG) &&
+ (type != qtype || class != qclass))
+ {
+ if (!expand_workspace(&rrs, &rr_sz, rr_found + 1))
+ return plen;
+
+ rrs[rr_found++] = pstart;
+
+ if (!ADD_RDLEN(header, p, plen, rdlen))
+ return plen;
+
+ rrs[rr_found++] = p;
+
+ if (i < ntohs(header->ancount))
+ chop_an++;
+ else
+ chop_ns++;
+ }
+ else if (!ADD_RDLEN(header, p, plen, rdlen))
+ return plen;
+ }
+
+ /* Nothing to do. */
+ if (rr_found == 0)
+ return plen;
+
+ /* Second pass, look for pointers in names in the records we're keeping and make sure they don't
+ point to records we're going to elide. This is theoretically possible, but unlikely. If
+ it happens, we give up and leave the answer unchanged. */
+ p = (unsigned char *)(header+1);
+
+ /* question first */
+ if (!check_name(&p, header, plen, 0, rrs, rr_found))
+ return plen;
+ p += 4; /* qclass, qtype */
+
+ /* Now answers and NS */
+ if (!check_rrs(p, header, plen, 0, rrs, rr_found))
+ return plen;
+
+ /* Third pass, elide records */
+ for (p = rrs[0], i = 1; i < rr_found; i += 2)
+ {
+ unsigned char *start = rrs[i];
+ unsigned char *end = (i != rr_found - 1) ? rrs[i+1] : ((unsigned char *)(header+1)) + plen;
+
+ memmove(p, start, end-start);
+ p += end-start;
+ }
+
+ plen = p - (unsigned char *)header;
+ header->ancount = htons(ntohs(header->ancount) - chop_an);
+ header->nscount = htons(ntohs(header->nscount) - chop_ns);
+
+ /* Fourth pass, fix up pointers in the remaining records */
+ p = (unsigned char *)(header+1);
+
+ check_name(&p, header, plen, 1, rrs, rr_found);
+ p += 4; /* qclass, qtype */
+
+ check_rrs(p, header, plen, 1, rrs, rr_found);
+
+ return plen;
+}
+
unsigned char* hash_questions(struct dns_header *header, size_t plen, char *name)
{
int q;
diff --git a/src/forward.c b/src/forward.c
index 5b9fa1e..c05efe4 100644
--- a/src/forward.c
+++ b/src/forward.c
@@ -235,7 +235,7 @@ static unsigned int search_servers(time_t now, struct all_addr **addrpp,
static int forward_query(int udpfd, union mysockaddr *udpaddr,
struct all_addr *dst_addr, unsigned int dst_iface,
struct dns_header *header, size_t plen, time_t now,
- struct frec *forward, int ad_reqd)
+ struct frec *forward, int ad_reqd, int do_bit)
{
char *domain = NULL;
int type = 0, norebind = 0;
@@ -336,8 +336,10 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
forward->flags |= FREC_AD_QUESTION;
#ifdef HAVE_DNSSEC
forward->work_counter = DNSSEC_WORK;
+ if (do_bit)
+ forward->flags |= FREC_DO_QUESTION;
#endif
-
+
header->id = htons(forward->new_id);
/* In strict_order mode, always try servers in the order
@@ -393,11 +395,17 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
#ifdef HAVE_DNSSEC
if (option_bool(OPT_DNSSEC_VALID))
{
- plen = add_do_bit(header, plen, ((char *) header) + daemon->packet_buff_sz);
+ size_t new_plen = add_do_bit(header, plen, ((char *) header) + daemon->packet_buff_sz);
+
/* For debugging, set Checking Disabled, otherwise, have the upstream check too,
this allows it to select auth servers when one is returning bad data. */
if (option_bool(OPT_DNSSEC_DEBUG))
header->hb4 |= HB4_CD;
+
+ if (new_plen != plen)
+ forward->flags |= FREC_ADDED_PHEADER;
+
+ plen = new_plen;
}
#endif
@@ -506,7 +514,7 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
}
static size_t process_reply(struct dns_header *header, time_t now, struct server *server, size_t n, int check_rebind,
- int no_cache, int cache_secure, int ad_reqd, int check_subnet, union mysockaddr *query_source)
+ int no_cache, int cache_secure, int ad_reqd, int do_bit, int added_pheader, int check_subnet, union mysockaddr *query_source)
{
unsigned char *pheader, *sizep;
char **sets = 0;
@@ -553,6 +561,12 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server
my_syslog(LOG_WARNING, _("discarding DNS reply: subnet option mismatch"));
return 0;
}
+
+ if (added_pheader)
+ {
+ pheader = 0;
+ header->arcount = htons(0);
+ }
}
/* RFC 4035 sect 4.6 para 3 */
@@ -624,6 +638,10 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server
if (!(header->hb4 & HB4_CD) && ad_reqd && cache_secure)
header->hb4 |= HB4_AD;
+
+ /* If the requestor didn't set the DO bit, don't return DNSSEC info. */
+ if (!do_bit)
+ n = filter_rrsigs(header, n);
#endif
/* do this after extract_addresses. Ensure NODATA reply and remove
@@ -708,7 +726,7 @@ void reply_query(int fd, int family, time_t now)
if ((nn = resize_packet(header, (size_t)n, pheader, plen)))
{
header->hb3 &= ~(HB3_QR | HB3_TC);
- forward_query(-1, NULL, NULL, 0, header, nn, now, forward, 0);
+ forward_query(-1, NULL, NULL, 0, header, nn, now, forward, 0, 0);
return;
}
}
@@ -927,7 +945,8 @@ void reply_query(int fd, int family, time_t now)
header->hb4 &= ~HB4_CD;
if ((nn = process_reply(header, now, server, (size_t)n, check_rebind, no_cache_dnssec, cache_secure,
- forward->flags & FREC_AD_QUESTION, forward->flags & FREC_HAS_SUBNET, &forward->source)))
+ forward->flags & FREC_AD_QUESTION, forward->flags & FREC_DO_QUESTION,
+ forward->flags & FREC_ADDED_PHEADER, forward->flags & FREC_HAS_SUBNET, &forward->source)))
{
header->id = htons(forward->orig_id);
header->hb4 |= HB4_RA; /* recursion if available */
@@ -1169,9 +1188,9 @@ void receive_query(struct listener *listen, time_t now)
else
#endif
{
- int ad_reqd;
+ int ad_reqd, do_bit;
m = answer_request(header, ((char *) header) + daemon->packet_buff_sz, (size_t)n,
- dst_addr_4, netmask, now, &ad_reqd);
+ dst_addr_4, netmask, now, &ad_reqd, &do_bit);
if (m >= 1)
{
@@ -1180,7 +1199,7 @@ void receive_query(struct listener *listen, time_t now)
daemon->local_answer++;
}
else if (forward_query(listen->fd, &source_addr, &dst_addr, if_index,
- header, (size_t)n, now, NULL, ad_reqd))
+ header, (size_t)n, now, NULL, ad_reqd, do_bit))
daemon->queries_forwarded++;
else
daemon->local_answer++;
@@ -1272,7 +1291,8 @@ unsigned char *tcp_request(int confd, time_t now,
#ifdef HAVE_AUTH
int local_auth = 0;
#endif
- int checking_disabled, ad_question, check_subnet, no_cache_dnssec = 0, cache_secure = 0;
+ int checking_disabled, ad_question, do_bit, added_pheader = 0;
+ int check_subnet, no_cache_dnssec = 0, cache_secure = 0;
size_t m;
unsigned short qtype;
unsigned int gotname;
@@ -1350,7 +1370,7 @@ unsigned char *tcp_request(int confd, time_t now,
{
/* m > 0 if answered from cache */
m = answer_request(header, ((char *) header) + 65536, (size_t)size,
- dst_addr_4, netmask, now, &ad_question);
+ dst_addr_4, netmask, now, &ad_question, &do_bit);
/* Do this by steam now we're not in the select() loop */
check_log_writer(NULL);
@@ -1430,11 +1450,17 @@ unsigned char *tcp_request(int confd, time_t now,
#ifdef HAVE_DNSSEC
if (option_bool(OPT_DNSSEC_VALID))
{
- size = add_do_bit(header, size, ((char *) header) + 65536);
+ size_t new_size = add_do_bit(header, size, ((char *) header) + 65536);
+
/* For debugging, set Checking Disabled, otherwise, have the upstream check too,
this allows it to select auth servers when one is returning bad data. */
if (option_bool(OPT_DNSSEC_DEBUG))
header->hb4 |= HB4_CD;
+
+ if (size != new_size)
+ added_pheader = 1;
+
+ size = new_size;
}
#endif
@@ -1533,7 +1559,7 @@ unsigned char *tcp_request(int confd, time_t now,
m = process_reply(header, now, last_server, (unsigned int)m,
option_bool(OPT_NO_REBIND) && !norebind, no_cache_dnssec,
- cache_secure, ad_question, check_subnet, &peer_addr);
+ cache_secure, ad_question, do_bit, added_pheader, check_subnet, &peer_addr);
break;
}
diff --git a/src/rfc1035.c b/src/rfc1035.c
index b9152e1..5693ef9 100644
--- a/src/rfc1035.c
+++ b/src/rfc1035.c
@@ -601,11 +601,11 @@ static int filter_mac(int family, char *addrp, char *mac, size_t maclen, void *p
if (family == parm->l3->sa.sa_family)
{
- if (family == AF_INET && memcmp (&parm->l3->in.sin_addr, addrp, INADDRSZ) == 0)
+ if (family == AF_INET && memcmp(&parm->l3->in.sin_addr, addrp, INADDRSZ) == 0)
match = 1;
#ifdef HAVE_IPV6
else
- if (family == AF_INET6 && memcmp (&parm->l3->in6.sin6_addr, addrp, IN6ADDRSZ) == 0)
+ if (family == AF_INET6 && memcmp(&parm->l3->in6.sin6_addr, addrp, IN6ADDRSZ) == 0)
match = 1;
#endif
}
@@ -1453,7 +1453,7 @@ static unsigned long crec_ttl(struct crec *crecp, time_t now)
/* return zero if we can't answer from cache, or packet size if we can */
size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
struct in_addr local_addr, struct in_addr local_netmask,
- time_t now, int *ad_reqd)
+ time_t now, int *ad_reqd, int *do_bit)
{
char *name = daemon->namebuff;
unsigned char *p, *ansp, *pheader;
@@ -1475,7 +1475,8 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
/* RFC 6840 5.7 */
*ad_reqd = header->hb4 & HB4_AD;
-
+ *do_bit = 0;
+
/* If there is an RFC2671 pseudoheader then it will be overwritten by
partial replies, so we have to do a dry run to see if we can answer
the query. We check to see if the do bit is set, if so we always
@@ -1493,7 +1494,8 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
pheader += 2; /* ext_rcode */
GETSHORT(flags, pheader);
- sec_reqd = flags & 0x8000; /* do bit */
+ if ((sec_reqd = flags & 0x8000))
+ *do_bit = 1;/* do bit */
*ad_reqd = 1;
/* If our client is advertising a larger UDP packet size