From: Simon Kelley Date: Tue, 15 Dec 2015 17:25:21 +0000 (+0000) Subject: Generalise RR-filtering code, for use with EDNS0. X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=c2bcd1e183bcc5fdd63811c045355fc57e36ecfd;p=people%2Fms%2Fdnsmasq.git Generalise RR-filtering code, for use with EDNS0. --- diff --git a/Makefile b/Makefile index 4c87ea9..b664160 100644 --- a/Makefile +++ b/Makefile @@ -73,7 +73,8 @@ objs = cache.o rfc1035.o util.o option.o forward.o network.o \ dnsmasq.o dhcp.o lease.o rfc2131.o netlink.o dbus.o bpf.o \ helper.o tftp.o log.o conntrack.o dhcp6.o rfc3315.o \ dhcp-common.o outpacket.o radv.o slaac.o auth.o ipset.o \ - domain.o dnssec.o blockdata.o tables.o loop.o inotify.o poll.o + domain.o dnssec.o blockdata.o tables.o loop.o inotify.o \ + poll.o rrfilter.o hdrs = dnsmasq.h config.h dhcp-protocol.h dhcp6-protocol.h \ dns-protocol.h radv-protocol.h ip6addr.h diff --git a/bld/Android.mk b/bld/Android.mk index 5364ee7..67b9c4b 100644 --- a/bld/Android.mk +++ b/bld/Android.mk @@ -10,7 +10,7 @@ LOCAL_SRC_FILES := bpf.c cache.c dbus.c dhcp.c dnsmasq.c \ dhcp6.c rfc3315.c dhcp-common.c outpacket.c \ radv.c slaac.c auth.c ipset.c domain.c \ dnssec.c dnssec-openssl.c blockdata.c tables.c \ - loop.c inotify.c poll.c + loop.c inotify.c poll.c rrfilter.c LOCAL_MODULE := dnsmasq diff --git a/src/dnsmasq.h b/src/dnsmasq.h index 4344cae..39a930c 100644 --- a/src/dnsmasq.h +++ b/src/dnsmasq.h @@ -1513,3 +1513,8 @@ int poll_check(int fd, short event); void poll_listen(int fd, short event); int do_poll(int timeout); +/* rrfilter.c */ +size_t rrfilter(struct dns_header *header, size_t plen, int mode); +u16 *rrfilter_desc(int type); +int expand_workspace(unsigned char ***wkspc, int *szp, int new); + diff --git a/src/dnssec.c b/src/dnssec.c index 359231f..fa3eb81 100644 --- a/src/dnssec.c +++ b/src/dnssec.c @@ -507,50 +507,6 @@ static int check_date_range(unsigned long date_start, unsigned long date_end) && serial_compare_32(curtime, date_end) == SERIAL_LT; } -static u16 *get_desc(int type) -{ - /* List of RRtypes which include domains in the data. - 0 -> domain - integer -> no of plain bytes - -1 -> end - - zero is not a valid RRtype, so the final entry is returned for - anything which needs no mangling. - */ - - static u16 rr_desc[] = - { - T_NS, 0, -1, - T_MD, 0, -1, - T_MF, 0, -1, - T_CNAME, 0, -1, - T_SOA, 0, 0, -1, - T_MB, 0, -1, - T_MG, 0, -1, - T_MR, 0, -1, - T_PTR, 0, -1, - T_MINFO, 0, 0, -1, - T_MX, 2, 0, -1, - T_RP, 0, 0, -1, - T_AFSDB, 2, 0, -1, - T_RT, 2, 0, -1, - T_SIG, 18, 0, -1, - T_PX, 2, 0, 0, -1, - T_NXT, 0, -1, - T_KX, 2, 0, -1, - T_SRV, 6, 0, -1, - T_DNAME, 0, -1, - 0, -1 /* wildcard/catchall */ - }; - - u16 *p = rr_desc; - - while (*p != type && *p != 0) - while (*p++ != (u16)-1); - - return p+1; -} - /* 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, @@ -594,34 +550,6 @@ static int get_rdata(struct dns_header *header, size_t plen, unsigned char *end, } } -static int expand_workspace(unsigned char ***wkspc, int *szp, int new) -{ - unsigned char **p; - int old = *szp; - - if (old >= new+1) - return 1; - - if (new >= 100) - return 0; - - new += 5; - - if (!(p = whine_malloc(new * sizeof(unsigned char **)))) - return 0; - - if (old != 0 && *wkspc) - { - memcpy(p, *wkspc, old * sizeof(unsigned char **)); - free(*wkspc); - } - - *wkspc = p; - *szp = new; - - 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. @@ -849,7 +777,7 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in int rdlen, j, name_labels; struct crec *crecp = NULL; int algo, labels, orig_ttl, key_tag; - u16 *rr_desc = get_desc(type); + u16 *rr_desc = rrfilter_desc(type); if (wildcard_out) *wildcard_out = NULL; @@ -2266,239 +2194,6 @@ size_t dnssec_generate_query(struct dns_header *header, char *end, char *name, i return ret; } -/* 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; - int 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 0; - - /* 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; - unsigned char *pp; - - for (i = 0; i < ntohs(header->ancount) + ntohs(header->nscount) + ntohs(header->arcount); i++) - { - pp = p; - - 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) - { - /* fixup name of RR */ - if (!check_name(&pp, header, plen, fixup, rrs, rr_count)) - return 0; - - if (class == C_IN) - { - u16 *d; - - for (pp = p, 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, chop_ar; - - 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, chop_ar = 0, i = 0; - i < ntohs(header->ancount) + ntohs(header->nscount) + ntohs(header->arcount); - 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 if (i < (ntohs(header->nscount) + ntohs(header->ancount))) - chop_ns++; - else - chop_ar++; - } - 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); - header->arcount = htons(ntohs(header->arcount) - chop_ar); - - /* 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 dd22a62..3e801c8 100644 --- a/src/forward.c +++ b/src/forward.c @@ -662,7 +662,7 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server /* If the requestor didn't set the DO bit, don't return DNSSEC info. */ if (!do_bit) - n = filter_rrsigs(header, n); + n = rrfilter(header, n, 1); #endif /* do this after extract_addresses. Ensure NODATA reply and remove diff --git a/src/rrfilter.c b/src/rrfilter.c new file mode 100644 index 0000000..ae12261 --- /dev/null +++ b/src/rrfilter.c @@ -0,0 +1,339 @@ +/* dnsmasq is Copyright (c) 2000-2015 Simon Kelley + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 dated June, 1991, or + (at your option) version 3 dated 29 June, 2007. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +/* Code to safely remove RRs from an DNS answer */ + +#include "dnsmasq.h" + +/* 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; + int 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 0; + + /* 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, j, type, class, rdlen; + unsigned char *pp; + + for (i = 0; i < ntohs(header->ancount) + ntohs(header->nscount) + ntohs(header->arcount); i++) + { + pp = p; + + if (!(p = skip_name(p, header, plen, 10))) + return 0; + + GETSHORT(type, p); + GETSHORT(class, p); + p += 4; /* TTL */ + GETSHORT(rdlen, p); + + /* If this RR is to be elided, don't fix up its contents */ + for (j = 0; j < rr_count; j += 2) + if (rrs[j] == pp) + break; + + if (j >= rr_count) + { + /* fixup name of RR */ + if (!check_name(&pp, header, plen, fixup, rrs, rr_count)) + return 0; + + if (class == C_IN) + { + u16 *d; + + for (pp = p, d = rrfilter_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; +} + + +/* mode is 0 to remove EDNS0, 1 to filter DNSSEC RRs */ +size_t rrfilter(struct dns_header *header, size_t plen, int mode) +{ + 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, chop_ar; + + 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, chop_ar = 0, i = 0; + i < ntohs(header->ancount) + ntohs(header->nscount) + ntohs(header->arcount); + 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 (!ADD_RDLEN(header, p, plen, rdlen)) + return plen; + + /* Don't remove the answer. */ + if (i < ntohs(header->ancount) && type == qtype && class == qclass) + continue; + + if (mode == 0) /* EDNS */ + { + /* EDNS mode, remove T_OPT from additional section only */ + if (i < (ntohs(header->nscount) + ntohs(header->ancount)) || type != T_OPT) + continue; + } + else if (type != T_NSEC && type != T_NSEC3 && type != T_RRSIG) + /* DNSSEC mode, remove SIGs and NSECs from all three sections. */ + continue; + + + if (!expand_workspace(&rrs, &rr_sz, rr_found + 1)) + return plen; + + rrs[rr_found++] = pstart; + rrs[rr_found++] = p; + + if (i < ntohs(header->ancount)) + chop_an++; + else if (i < (ntohs(header->nscount) + ntohs(header->ancount))) + chop_ns++; + else + chop_ar++; + } + + /* 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); + header->arcount = htons(ntohs(header->arcount) - chop_ar); + + /* 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; +} + +/* This is used in the DNSSEC code too, hence it's exported */ +u16 *rrfilter_desc(int type) +{ + /* List of RRtypes which include domains in the data. + 0 -> domain + integer -> no of plain bytes + -1 -> end + + zero is not a valid RRtype, so the final entry is returned for + anything which needs no mangling. + */ + + static u16 rr_desc[] = + { + T_NS, 0, -1, + T_MD, 0, -1, + T_MF, 0, -1, + T_CNAME, 0, -1, + T_SOA, 0, 0, -1, + T_MB, 0, -1, + T_MG, 0, -1, + T_MR, 0, -1, + T_PTR, 0, -1, + T_MINFO, 0, 0, -1, + T_MX, 2, 0, -1, + T_RP, 0, 0, -1, + T_AFSDB, 2, 0, -1, + T_RT, 2, 0, -1, + T_SIG, 18, 0, -1, + T_PX, 2, 0, 0, -1, + T_NXT, 0, -1, + T_KX, 2, 0, -1, + T_SRV, 6, 0, -1, + T_DNAME, 0, -1, + 0, -1 /* wildcard/catchall */ + }; + + u16 *p = rr_desc; + + while (*p != type && *p != 0) + while (*p++ != (u16)-1); + + return p+1; +} + +int expand_workspace(unsigned char ***wkspc, int *szp, int new) +{ + unsigned char **p; + int old = *szp; + + if (old >= new+1) + return 1; + + if (new >= 100) + return 0; + + new += 5; + + if (!(p = whine_malloc(new * sizeof(unsigned char **)))) + return 0; + + if (old != 0 && *wkspc) + { + memcpy(p, *wkspc, old * sizeof(unsigned char **)); + free(*wkspc); + } + + *wkspc = p; + *szp = new; + + return 1; +}