diff options
Diffstat (limited to 'package/network')
14 files changed, 2280 insertions, 3 deletions
diff --git a/package/network/services/dnsmasq/Makefile b/package/network/services/dnsmasq/Makefile index d31f4c7e63..832246cf72 100644 --- a/package/network/services/dnsmasq/Makefile +++ b/package/network/services/dnsmasq/Makefile @@ -10,7 +10,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=dnsmasq PKG_UPSTREAM_VERSION:=2.80 PKG_VERSION:=$(subst test,~~test,$(subst rc,~rc,$(PKG_UPSTREAM_VERSION))) -PKG_RELEASE:=16.1 +PKG_RELEASE:=16.2 PKG_SOURCE:=$(PKG_NAME)-$(PKG_UPSTREAM_VERSION).tar.xz PKG_SOURCE_URL:=http://thekelleys.org.uk/dnsmasq diff --git a/package/network/services/dnsmasq/patches/0102-Fix-remote-buffer-overflow-CERT-VU-434904.patch b/package/network/services/dnsmasq/patches/0102-Fix-remote-buffer-overflow-CERT-VU-434904.patch new file mode 100644 index 0000000000..2f7457c12e --- /dev/null +++ b/package/network/services/dnsmasq/patches/0102-Fix-remote-buffer-overflow-CERT-VU-434904.patch @@ -0,0 +1,375 @@ +From 4e96a4be685c9e4445f6ee79ad0b36b9119b502a Mon Sep 17 00:00:00 2001 +From: Simon Kelley <simon@thekelleys.org.uk> +Date: Wed, 11 Nov 2020 23:25:04 +0000 +Subject: 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. +--- + CHANGELOG | 7 +- + src/dnssec.c | 273 ++++++++++++++++++++++++++++----------------------- + 2 files changed, 158 insertions(+), 122 deletions(-) + +--- a/CHANGELOG ++++ b/CHANGELOG +@@ -1,3 +1,9 @@ ++ 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. ++ ++ ++>>>>>>> Fix remote buffer overflow CERT VU#434904 + version 2.81 + Impove cache behaviour for TCP connections. For ease of + implementaion, dnsmasq has always forked a new process to handle +--- a/src/dnssec.c ++++ b/src/dnssec.c +@@ -222,138 +222,147 @@ static int check_date_range(u32 date_sta + && 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); +- +- p1 += 8; /* skip class, type, ttl */ +- GETSHORT(rdlen1, p1); +- end1 = p1 + 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;) ++ 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; ++ ++ 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; ++ ++ 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); +- +- 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; ++ int ok1, ok2; + +- 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 (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 (rc < 0) +- quit = 1; ++ 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); +@@ -549,15 +558,18 @@ static int validate_rrset(time_t now, st + 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; + +@@ -566,12 +578,11 @@ static int validate_rrset(time_t now, st + /* 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++; + } + +@@ -592,24 +603,44 @@ static int validate_rrset(time_t now, st + 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. +- 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); ++ /* 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; ++ ++ 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); diff --git a/package/network/services/dnsmasq/patches/0103-Check-destination-of-DNS-UDP-query-replies.patch b/package/network/services/dnsmasq/patches/0103-Check-destination-of-DNS-UDP-query-replies.patch new file mode 100644 index 0000000000..b13ba2d38f --- /dev/null +++ b/package/network/services/dnsmasq/patches/0103-Check-destination-of-DNS-UDP-query-replies.patch @@ -0,0 +1,106 @@ +From 257ac0c5f7732cbc6aa96fdd3b06602234593aca Mon Sep 17 00:00:00 2001 +From: Simon Kelley <simon@thekelleys.org.uk> +Date: Thu, 12 Nov 2020 18:49:23 +0000 +Subject: Check destination of DNS UDP query replies. + +At any time, dnsmasq will have a set of sockets open, bound to +random ports, on which it sends queries to upstream nameservers. +This patch fixes the existing problem that a reply for ANY in-flight +query would be accepted via ANY open port, which increases the +chances of an attacker flooding answers "in the blind" in an +attempt to poison the DNS cache. CERT VU#434904 refers. +--- + CHANGELOG | 6 +++++- + src/forward.c | 37 ++++++++++++++++++++++++++++--------- + 2 files changed, 33 insertions(+), 10 deletions(-) + +--- a/CHANGELOG ++++ b/CHANGELOG +@@ -2,8 +2,12 @@ + dnsmasq with DNSSEC compiled in and enabled is vulnerable to this, + referenced by CERT VU#434904. + ++ Be sure to only accept UDP DNS query replies at the address ++ from which the query was originated. This keeps as much entropy ++ in the {query-ID, random-port} tuple as possible, help defeat ++ cache poisoning attacks. Refer: CERT VU#434904. ++ + +->>>>>>> Fix remote buffer overflow CERT VU#434904 + version 2.81 + Impove cache behaviour for TCP connections. For ease of + implementaion, dnsmasq has always forked a new process to handle +--- a/src/forward.c ++++ b/src/forward.c +@@ -16,7 +16,7 @@ + + #include "dnsmasq.h" + +-static struct frec *lookup_frec(unsigned short id, void *hash); ++static struct frec *lookup_frec(unsigned short id, int fd, int family, void *hash); + static struct frec *lookup_frec_by_sender(unsigned short id, + union mysockaddr *addr, + void *hash); +@@ -797,7 +797,7 @@ void reply_query(int fd, int family, tim + crc = questions_crc(header, n, daemon->namebuff); + #endif + +- if (!(forward = lookup_frec(ntohs(header->id), hash))) ++ if (!(forward = lookup_frec(ntohs(header->id), fd, family, hash))) + return; + + #ifdef HAVE_DUMPFILE +@@ -2289,14 +2289,25 @@ struct frec *get_new_frec(time_t now, in + } + + /* crc is all-ones if not known. */ +-static struct frec *lookup_frec(unsigned short id, void *hash) ++static struct frec *lookup_frec(unsigned short id, int fd, int family, void *hash) + { + struct frec *f; + + for(f = daemon->frec_list; f; f = f->next) + if (f->sentto && f->new_id == id && + (!hash || memcmp(hash, f->hash, HASH_SIZE) == 0)) +- return f; ++ { ++ /* sent from random port */ ++ if (family == AF_INET && f->rfd4 && f->rfd4->fd == fd) ++ return f; ++ ++ if (family == AF_INET6 && f->rfd6 && f->rfd6->fd == fd) ++ return f; ++ ++ /* sent to upstream from bound socket. */ ++ if (f->sentto->sfd && f->sentto->sfd->fd == fd) ++ return f; ++ } + + return NULL; + } +@@ -2357,12 +2368,20 @@ void server_gone(struct server *server) + static unsigned short get_id(void) + { + unsigned short ret = 0; ++ struct frec *f; + +- do +- ret = rand16(); +- while (lookup_frec(ret, NULL)); +- +- return ret; ++ while (1) ++ { ++ ret = rand16(); ++ ++ /* ensure id is unique. */ ++ for (f = daemon->frec_list; f; f = f->next) ++ if (f->sentto && f->new_id == ret) ++ break; ++ ++ if (!f) ++ return ret; ++ } + } + + diff --git a/package/network/services/dnsmasq/patches/0104-Use-SHA-256-to-provide-security-against-DNS-cache-po.patch b/package/network/services/dnsmasq/patches/0104-Use-SHA-256-to-provide-security-against-DNS-cache-po.patch new file mode 100644 index 0000000000..bcf6815697 --- /dev/null +++ b/package/network/services/dnsmasq/patches/0104-Use-SHA-256-to-provide-security-against-DNS-cache-po.patch @@ -0,0 +1,581 @@ +From 2d765867c597db18be9d876c9c17e2c0fe1953cd Mon Sep 17 00:00:00 2001 +From: Simon Kelley <simon@thekelleys.org.uk> +Date: Thu, 12 Nov 2020 22:06:07 +0000 +Subject: Use SHA-256 to provide security against DNS cache poisoning. + +Use the SHA-256 hash function to verify that DNS answers +received are for the questions originally asked. This replaces +the slightly insecure SHA-1 (when compiled with DNSSEC) or +the very insecure CRC32 (otherwise). Refer: CERT VU#434904. +--- + CHANGELOG | 5 + + Makefile | 3 +- + bld/Android.mk | 2 +- + src/dnsmasq.h | 11 +- + src/dnssec.c | 31 ----- + src/forward.c | 43 ++----- + src/hash_questions.c | 281 +++++++++++++++++++++++++++++++++++++++++++ + src/rfc1035.c | 49 -------- + 8 files changed, 301 insertions(+), 124 deletions(-) + create mode 100644 src/hash_questions.c + +--- a/CHANGELOG ++++ b/CHANGELOG +@@ -7,6 +7,11 @@ + in the {query-ID, random-port} tuple as possible, help defeat + cache poisoning attacks. Refer: CERT VU#434904. + ++ Use the SHA-256 hash function to verify that DNS answers ++ received are for the questions originally asked. This replaces ++ the slightly insecure SHA-1 (when compiled with DNSSEC) or ++ the very insecure CRC32 (otherwise). Refer: CERT VU#434904. ++ + + version 2.81 + Impove cache behaviour for TCP connections. For ease of +--- a/Makefile ++++ b/Makefile +@@ -77,7 +77,8 @@ objs = cache.o rfc1035.o util.o option.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 rrfilter.o edns0.o arp.o crypto.o dump.o ubus.o metrics.o ++ poll.o rrfilter.o edns0.o arp.o crypto.o dump.o ubus.o \ ++ metrics.o hash_questions.o + + hdrs = dnsmasq.h config.h dhcp-protocol.h dhcp6-protocol.h \ + dns-protocol.h radv-protocol.h ip6addr.h metrics.h +--- a/bld/Android.mk ++++ b/bld/Android.mk +@@ -11,7 +11,7 @@ LOCAL_SRC_FILES := bpf.c cache.c dbus.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 rrfilter.c edns0.c arp.c \ +- crypto.c dump.c ubus.c ++ crypto.c dump.c ubus.c metrics.c hash_questions.c + + LOCAL_MODULE := dnsmasq + +--- a/src/dnsmasq.h ++++ b/src/dnsmasq.h +@@ -644,11 +644,7 @@ struct hostsfile { + #define FREC_TEST_PKTSZ 256 + #define FREC_HAS_EXTRADATA 512 + +-#ifdef HAVE_DNSSEC +-#define HASH_SIZE 20 /* SHA-1 digest size */ +-#else +-#define HASH_SIZE sizeof(int) +-#endif ++#define HASH_SIZE 32 /* SHA-256 digest size */ + + struct frec { + union mysockaddr source; +@@ -1199,7 +1195,6 @@ int check_for_bogus_wildcard(struct dns_ + struct bogus_addr *baddr, time_t now); + int check_for_ignored_address(struct dns_header *header, size_t qlen, struct bogus_addr *baddr); + int check_for_local_domain(char *name, time_t now); +-unsigned int questions_crc(struct dns_header *header, size_t plen, char *name); + size_t resize_packet(struct dns_header *header, size_t plen, + unsigned char *pheader, size_t hlen); + int add_resource_record(struct dns_header *header, char *limit, int *truncp, +@@ -1227,9 +1222,11 @@ int dnssec_validate_reply(time_t now, st + int check_unsigned, int *neganswer, int *nons); + int dnskey_keytag(int alg, int flags, unsigned char *key, int keylen); + size_t filter_rrsigs(struct dns_header *header, size_t plen); +-unsigned char* hash_questions(struct dns_header *header, size_t plen, char *name); + int setup_timestamp(void); + ++/* hash_questions.c */ ++unsigned char *hash_questions(struct dns_header *header, size_t plen, char *name); ++ + /* crypto.c */ + const struct nettle_hash *hash_find(char *name); + int hash_init(const struct nettle_hash *hash, void **ctxp, unsigned char **digestp); +--- a/src/dnssec.c ++++ b/src/dnssec.c +@@ -2082,35 +2082,4 @@ size_t dnssec_generate_query(struct dns_ + return ret; + } + +-unsigned char* hash_questions(struct dns_header *header, size_t plen, char *name) +-{ +- int q; +- unsigned int len; +- unsigned char *p = (unsigned char *)(header+1); +- const struct nettle_hash *hash; +- void *ctx; +- unsigned char *digest; +- +- if (!(hash = hash_find("sha1")) || !hash_init(hash, &ctx, &digest)) +- return NULL; +- +- for (q = ntohs(header->qdcount); q != 0; q--) +- { +- if (!extract_name(header, plen, &p, name, 1, 4)) +- break; /* bad packet */ +- +- len = to_wire(name); +- hash->update(ctx, len, (unsigned char *)name); +- /* CRC the class and type as well */ +- hash->update(ctx, 4, p); +- +- p += 4; +- if (!CHECK_LEN(header, p, plen, 0)) +- break; /* bad packet */ +- } +- +- hash->digest(ctx, hash->digest_size, digest); +- return digest; +-} +- + #endif /* HAVE_DNSSEC */ +--- a/src/forward.c ++++ b/src/forward.c +@@ -248,19 +248,16 @@ static int forward_query(int udpfd, unio + union all_addr *addrp = NULL; + unsigned int flags = 0; + struct server *start = NULL; +-#ifdef HAVE_DNSSEC + void *hash = hash_questions(header, plen, daemon->namebuff); ++#ifdef HAVE_DNSSEC + int do_dnssec = 0; +-#else +- unsigned int crc = questions_crc(header, plen, daemon->namebuff); +- void *hash = &crc; + #endif + unsigned int gotname = extract_request(header, plen, daemon->namebuff, NULL); + unsigned char *oph = find_pseudoheader(header, plen, NULL, NULL, NULL, NULL); + (void)do_bit; + + /* may be no servers available. */ +- if (forward || (hash && (forward = lookup_frec_by_sender(ntohs(header->id), udpaddr, hash)))) ++ if (forward || (forward = lookup_frec_by_sender(ntohs(header->id), udpaddr, hash))) + { + /* If we didn't get an answer advertising a maximal packet in EDNS, + fall back to 1280, which should work everywhere on IPv6. +@@ -761,9 +758,6 @@ void reply_query(int fd, int family, tim + size_t nn; + struct server *server; + void *hash; +-#ifndef HAVE_DNSSEC +- unsigned int crc; +-#endif + + /* packet buffer overwritten */ + daemon->srv_save = NULL; +@@ -790,12 +784,7 @@ void reply_query(int fd, int family, tim + if (difftime(now, server->pktsz_reduced) > UDP_TEST_TIME) + server->edns_pktsz = daemon->edns_pktsz; + +-#ifdef HAVE_DNSSEC + hash = hash_questions(header, n, daemon->namebuff); +-#else +- hash = &crc; +- crc = questions_crc(header, n, daemon->namebuff); +-#endif + + if (!(forward = lookup_frec(ntohs(header->id), fd, family, hash))) + return; +@@ -1100,8 +1089,7 @@ void reply_query(int fd, int family, tim + log_query(F_NOEXTRA | F_DNSSEC | F_IPV6, daemon->keyname, (union all_addr *)&(server->addr.in6.sin6_addr), + querystr("dnssec-query", querytype)); + +- if ((hash = hash_questions(header, nn, daemon->namebuff))) +- memcpy(new->hash, hash, HASH_SIZE); ++ memcpy(new->hash, hash_questions(header, nn, daemon->namebuff), HASH_SIZE); + new->new_id = get_id(); + header->id = htons(new->new_id); + /* Save query for retransmission */ +@@ -1937,15 +1925,9 @@ unsigned char *tcp_request(int confd, ti + if (!flags && last_server) + { + struct server *firstsendto = NULL; +-#ifdef HAVE_DNSSEC +- unsigned char *newhash, hash[HASH_SIZE]; +- if ((newhash = hash_questions(header, (unsigned int)size, daemon->namebuff))) +- memcpy(hash, newhash, HASH_SIZE); +- else +- memset(hash, 0, HASH_SIZE); +-#else +- unsigned int crc = questions_crc(header, (unsigned int)size, daemon->namebuff); +-#endif ++ unsigned char hash[HASH_SIZE]; ++ memcpy(hash, hash_questions(header, (unsigned int)size, daemon->namebuff), HASH_SIZE); ++ + /* Loop round available servers until we succeed in connecting to one. + Note that this code subtly ensures that consecutive queries on this connection + which can go to the same server, do so. */ +@@ -2068,20 +2050,11 @@ unsigned char *tcp_request(int confd, ti + /* If the crc of the question section doesn't match the crc we sent, then + someone might be attempting to insert bogus values into the cache by + sending replies containing questions and bogus answers. */ +-#ifdef HAVE_DNSSEC +- newhash = hash_questions(header, (unsigned int)m, daemon->namebuff); +- if (!newhash || memcmp(hash, newhash, HASH_SIZE) != 0) ++ if (memcmp(hash, hash_questions(header, (unsigned int)m, daemon->namebuff), HASH_SIZE) != 0) + { + m = 0; + break; + } +-#else +- if (crc != questions_crc(header, (unsigned int)m, daemon->namebuff)) +- { +- m = 0; +- break; +- } +-#endif + + m = process_reply(header, now, last_server, (unsigned int)m, + option_bool(OPT_NO_REBIND) && !norebind, no_cache_dnssec, cache_secure, bogusanswer, +@@ -2295,7 +2268,7 @@ static struct frec *lookup_frec(unsigned + + for(f = daemon->frec_list; f; f = f->next) + if (f->sentto && f->new_id == id && +- (!hash || memcmp(hash, f->hash, HASH_SIZE) == 0)) ++ (memcmp(hash, f->hash, HASH_SIZE) == 0)) + { + /* sent from random port */ + if (family == AF_INET && f->rfd4 && f->rfd4->fd == fd) +--- /dev/null ++++ b/src/hash_questions.c +@@ -0,0 +1,281 @@ ++/* Copyright (c) 2012-2020 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 <http://www.gnu.org/licenses/>. ++*/ ++ ++ ++/* Hash the question section. This is used to safely detect query ++ retransmission and to detect answers to questions we didn't ask, which ++ might be poisoning attacks. Note that we decode the name rather ++ than CRC the raw bytes, since replies might be compressed differently. ++ We ignore case in the names for the same reason. ++ ++ The hash used is SHA-256. If we're building with DNSSEC support, ++ we use the Nettle cypto library. If not, we prefer not to ++ add a dependency on Nettle, and use a stand-alone implementaion. ++*/ ++ ++#include "dnsmasq.h" ++ ++#ifdef HAVE_DNSSEC ++unsigned char *hash_questions(struct dns_header *header, size_t plen, char *name) ++{ ++ int q; ++ unsigned char *p = (unsigned char *)(header+1); ++ const struct nettle_hash *hash; ++ void *ctx; ++ unsigned char *digest; ++ ++ if (!(hash = hash_find("sha256")) || !hash_init(hash, &ctx, &digest)) ++ { ++ /* don't think this can ever happen. */ ++ static unsigned char dummy[HASH_SIZE]; ++ static int warned = 0; ++ ++ if (warned) ++ my_syslog(LOG_ERR, _("Failed to create SHA-256 hash object")); ++ warned = 1; ++ ++ return dummy; ++ } ++ ++ for (q = ntohs(header->qdcount); q != 0; q--) ++ { ++ char *cp, c; ++ ++ if (!extract_name(header, plen, &p, name, 1, 4)) ++ break; /* bad packet */ ++ ++ for (cp = name; (c = *cp); cp++) ++ if (c >= 'A' && c <= 'Z') ++ *cp += 'a' - 'A'; ++ ++ hash->update(ctx, cp - name, (unsigned char *)name); ++ /* CRC the class and type as well */ ++ hash->update(ctx, 4, p); ++ ++ p += 4; ++ if (!CHECK_LEN(header, p, plen, 0)) ++ break; /* bad packet */ ++ } ++ ++ hash->digest(ctx, hash->digest_size, digest); ++ return digest; ++} ++ ++#else /* HAVE_DNSSEC */ ++ ++#define SHA256_BLOCK_SIZE 32 // SHA256 outputs a 32 byte digest ++typedef unsigned char BYTE; // 8-bit byte ++typedef unsigned int WORD; // 32-bit word, change to "long" for 16-bit machines ++ ++typedef struct { ++ BYTE data[64]; ++ WORD datalen; ++ unsigned long long bitlen; ++ WORD state[8]; ++} SHA256_CTX; ++ ++static void sha256_init(SHA256_CTX *ctx); ++static void sha256_update(SHA256_CTX *ctx, const BYTE data[], size_t len); ++static void sha256_final(SHA256_CTX *ctx, BYTE hash[]); ++ ++ ++unsigned char *hash_questions(struct dns_header *header, size_t plen, char *name) ++{ ++ int q; ++ unsigned char *p = (unsigned char *)(header+1); ++ SHA256_CTX ctx; ++ static BYTE digest[SHA256_BLOCK_SIZE]; ++ ++ sha256_init(&ctx); ++ ++ for (q = ntohs(header->qdcount); q != 0; q--) ++ { ++ char *cp, c; ++ ++ if (!extract_name(header, plen, &p, name, 1, 4)) ++ break; /* bad packet */ ++ ++ for (cp = name; (c = *cp); cp++) ++ if (c >= 'A' && c <= 'Z') ++ *cp += 'a' - 'A'; ++ ++ sha256_update(&ctx, (BYTE *)name, cp - name); ++ /* CRC the class and type as well */ ++ sha256_update(&ctx, (BYTE *)p, 4); ++ ++ p += 4; ++ if (!CHECK_LEN(header, p, plen, 0)) ++ break; /* bad packet */ ++ } ++ ++ sha256_final(&ctx, digest); ++ return (unsigned char *)digest; ++} ++ ++/* Code from here onwards comes from https://github.com/B-Con/crypto-algorithms ++ and was written by Brad Conte (brad@bradconte.com), to whom all credit is given. ++ ++ This code is in the public domain, and the copyright notice at the head of this ++ file does not apply to it. ++*/ ++ ++ ++/****************************** MACROS ******************************/ ++#define ROTLEFT(a,b) (((a) << (b)) | ((a) >> (32-(b)))) ++#define ROTRIGHT(a,b) (((a) >> (b)) | ((a) << (32-(b)))) ++ ++#define CH(x,y,z) (((x) & (y)) ^ (~(x) & (z))) ++#define MAJ(x,y,z) (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z))) ++#define EP0(x) (ROTRIGHT(x,2) ^ ROTRIGHT(x,13) ^ ROTRIGHT(x,22)) ++#define EP1(x) (ROTRIGHT(x,6) ^ ROTRIGHT(x,11) ^ ROTRIGHT(x,25)) ++#define SIG0(x) (ROTRIGHT(x,7) ^ ROTRIGHT(x,18) ^ ((x) >> 3)) ++#define SIG1(x) (ROTRIGHT(x,17) ^ ROTRIGHT(x,19) ^ ((x) >> 10)) ++ ++/**************************** VARIABLES *****************************/ ++static const WORD k[64] = { ++ 0x428a2f98,0x71374491,0xb5c0fbcf,0xe9b5dba5,0x3956c25b,0x59f111f1,0x923f82a4,0xab1c5ed5, ++ 0xd807aa98,0x12835b01,0x243185be,0x550c7dc3,0x72be5d74,0x80deb1fe,0x9bdc06a7,0xc19bf174, ++ 0xe49b69c1,0xefbe4786,0x0fc19dc6,0x240ca1cc,0x2de92c6f,0x4a7484aa,0x5cb0a9dc,0x76f988da, ++ 0x983e5152,0xa831c66d,0xb00327c8,0xbf597fc7,0xc6e00bf3,0xd5a79147,0x06ca6351,0x14292967, ++ 0x27b70a85,0x2e1b2138,0x4d2c6dfc,0x53380d13,0x650a7354,0x766a0abb,0x81c2c92e,0x92722c85, ++ 0xa2bfe8a1,0xa81a664b,0xc24b8b70,0xc76c51a3,0xd192e819,0xd6990624,0xf40e3585,0x106aa070, ++ 0x19a4c116,0x1e376c08,0x2748774c,0x34b0bcb5,0x391c0cb3,0x4ed8aa4a,0x5b9cca4f,0x682e6ff3, ++ 0x748f82ee,0x78a5636f,0x84c87814,0x8cc70208,0x90befffa,0xa4506ceb,0xbef9a3f7,0xc67178f2 ++}; ++ ++/*********************** FUNCTION DEFINITIONS ***********************/ ++static void sha256_transform(SHA256_CTX *ctx, const BYTE data[]) ++{ ++ WORD a, b, c, d, e, f, g, h, i, j, t1, t2, m[64]; ++ ++ for (i = 0, j = 0; i < 16; ++i, j += 4) ++ m[i] = (data[j] << 24) | (data[j + 1] << 16) | (data[j + 2] << 8) | (data[j + 3]); ++ for ( ; i < 64; ++i) ++ m[i] = SIG1(m[i - 2]) + m[i - 7] + SIG0(m[i - 15]) + m[i - 16]; ++ ++ a = ctx->state[0]; ++ b = ctx->state[1]; ++ c = ctx->state[2]; ++ d = ctx->state[3]; ++ e = ctx->state[4]; ++ f = ctx->state[5]; ++ g = ctx->state[6]; ++ h = ctx->state[7]; ++ ++ for (i = 0; i < 64; ++i) ++ { ++ t1 = h + EP1(e) + CH(e,f,g) + k[i] + m[i]; ++ t2 = EP0(a) + MAJ(a,b,c); ++ h = g; ++ g = f; ++ f = e; ++ e = d + t1; ++ d = c; ++ c = b; ++ b = a; ++ a = t1 + t2; ++ } ++ ++ ctx->state[0] += a; ++ ctx->state[1] += b; ++ ctx->state[2] += c; ++ ctx->state[3] += d; ++ ctx->state[4] += e; ++ ctx->state[5] += f; ++ ctx->state[6] += g; ++ ctx->state[7] += h; ++} ++ ++static void sha256_init(SHA256_CTX *ctx) ++{ ++ ctx->datalen = 0; ++ ctx->bitlen = 0; ++ ctx->state[0] = 0x6a09e667; ++ ctx->state[1] = 0xbb67ae85; ++ ctx->state[2] = 0x3c6ef372; ++ ctx->state[3] = 0xa54ff53a; ++ ctx->state[4] = 0x510e527f; ++ ctx->state[5] = 0x9b05688c; ++ ctx->state[6] = 0x1f83d9ab; ++ ctx->state[7] = 0x5be0cd19; ++} ++ ++static void sha256_update(SHA256_CTX *ctx, const BYTE data[], size_t len) ++{ ++ WORD i; ++ ++ for (i = 0; i < len; ++i) ++ { ++ ctx->data[ctx->datalen] = data[i]; ++ ctx->datalen++; ++ if (ctx->datalen == 64) { ++ sha256_transform(ctx, ctx->data); ++ ctx->bitlen += 512; ++ ctx->datalen = 0; ++ } ++ } ++} ++ ++static void sha256_final(SHA256_CTX *ctx, BYTE hash[]) ++{ ++ WORD i; ++ ++ i = ctx->datalen; ++ ++ // Pad whatever data is left in the buffer. ++ if (ctx->datalen < 56) ++ { ++ ctx->data[i++] = 0x80; ++ while (i < 56) ++ ctx->data[i++] = 0x00; ++ } ++ else ++ { ++ ctx->data[i++] = 0x80; ++ while (i < 64) ++ ctx->data[i++] = 0x00; ++ sha256_transform(ctx, ctx->data); ++ memset(ctx->data, 0, 56); ++ } ++ ++ // Append to the padding the total message's length in bits and transform. ++ ctx->bitlen += ctx->datalen * 8; ++ ctx->data[63] = ctx->bitlen; ++ ctx->data[62] = ctx->bitlen >> 8; ++ ctx->data[61] = ctx->bitlen >> 16; ++ ctx->data[60] = ctx->bitlen >> 24; ++ ctx->data[59] = ctx->bitlen >> 32; ++ ctx->data[58] = ctx->bitlen >> 40; ++ ctx->data[57] = ctx->bitlen >> 48; ++ ctx->data[56] = ctx->bitlen >> 56; ++ sha256_transform(ctx, ctx->data); ++ ++ // Since this implementation uses little endian byte ordering and SHA uses big endian, ++ // reverse all the bytes when copying the final state to the output hash. ++ for (i = 0; i < 4; ++i) ++ { ++ hash[i] = (ctx->state[0] >> (24 - i * 8)) & 0x000000ff; ++ hash[i + 4] = (ctx->state[1] >> (24 - i * 8)) & 0x000000ff; ++ hash[i + 8] = (ctx->state[2] >> (24 - i * 8)) & 0x000000ff; ++ hash[i + 12] = (ctx->state[3] >> (24 - i * 8)) & 0x000000ff; ++ hash[i + 16] = (ctx->state[4] >> (24 - i * 8)) & 0x000000ff; ++ hash[i + 20] = (ctx->state[5] >> (24 - i * 8)) & 0x000000ff; ++ hash[i + 24] = (ctx->state[6] >> (24 - i * 8)) & 0x000000ff; ++ hash[i + 28] = (ctx->state[7] >> (24 - i * 8)) & 0x000000ff; ++ } ++} ++ ++#endif +--- a/src/rfc1035.c ++++ b/src/rfc1035.c +@@ -333,55 +333,6 @@ unsigned char *skip_section(unsigned cha + return ansp; + } + +-/* CRC the question section. This is used to safely detect query +- retransmission and to detect answers to questions we didn't ask, which +- might be poisoning attacks. Note that we decode the name rather +- than CRC the raw bytes, since replies might be compressed differently. +- We ignore case in the names for the same reason. Return all-ones +- if there is not question section. */ +-#ifndef HAVE_DNSSEC +-unsigned int questions_crc(struct dns_header *header, size_t plen, char *name) +-{ +- int q; +- unsigned int crc = 0xffffffff; +- unsigned char *p1, *p = (unsigned char *)(header+1); +- +- for (q = ntohs(header->qdcount); q != 0; q--) +- { +- if (!extract_name(header, plen, &p, name, 1, 4)) +- return crc; /* bad packet */ +- +- for (p1 = (unsigned char *)name; *p1; p1++) +- { +- int i = 8; +- char c = *p1; +- +- if (c >= 'A' && c <= 'Z') +- c += 'a' - 'A'; +- +- crc ^= c << 24; +- while (i--) +- crc = crc & 0x80000000 ? (crc << 1) ^ 0x04c11db7 : crc << 1; +- } +- +- /* CRC the class and type as well */ +- for (p1 = p; p1 < p+4; p1++) +- { +- int i = 8; +- crc ^= *p1 << 24; +- while (i--) +- crc = crc & 0x80000000 ? (crc << 1) ^ 0x04c11db7 : crc << 1; +- } +- +- p += 4; +- if (!CHECK_LEN(header, p, plen, 0)) +- return crc; /* bad packet */ +- } +- +- return crc; +-} +-#endif +- + size_t resize_packet(struct dns_header *header, size_t plen, unsigned char *pheader, size_t hlen) + { + unsigned char *ansp = skip_questions(header, plen); diff --git a/package/network/services/dnsmasq/patches/0105-Optimse-RR-digest-calculation-in-DNSSEC.patch b/package/network/services/dnsmasq/patches/0105-Optimse-RR-digest-calculation-in-DNSSEC.patch new file mode 100644 index 0000000000..10f966237b --- /dev/null +++ b/package/network/services/dnsmasq/patches/0105-Optimse-RR-digest-calculation-in-DNSSEC.patch @@ -0,0 +1,122 @@ +From 059aded0700309308dafd9720b0313ce52f6e189 Mon Sep 17 00:00:00 2001 +From: Simon Kelley <simon@thekelleys.org.uk> +Date: Thu, 12 Nov 2020 23:09:15 +0000 +Subject: Optimse RR digest calculation in DNSSEC. + +If an RR is of a type which doesn't need canonicalisation, +bypass the relatively slow canonicalisation code, and insert +it direct into the digest. +--- + src/dnssec.c | 82 +++++++++++++++++++++++++++++++--------------------- + 1 file changed, 49 insertions(+), 33 deletions(-) + +--- a/src/dnssec.c ++++ b/src/dnssec.c +@@ -559,7 +559,7 @@ static int validate_rrset(time_t now, st + hash->update(ctx, (unsigned int)wire_len, (unsigned char*)keyname); + from_wire(keyname); + +-#define RRBUFLEN 300 /* Most RRs are smaller than this. */ ++#define RRBUFLEN 128 /* Most RRs are smaller than this. */ + + for (i = 0; i < rrsetidx; ++i) + { +@@ -597,50 +597,66 @@ static int validate_rrset(time_t now, st + hash->update(ctx, (unsigned int)wire_len, (unsigned char *)name_start); + hash->update(ctx, 4, p); /* class and type */ + hash->update(ctx, 4, (unsigned char *)&nsigttl); +- +- p += 8; /* skip class, type, ttl */ ++ ++ p += 8; /* skip type, class, ttl */ + GETSHORT(rdlen, p); + if (!CHECK_LEN(header, p, plen, rdlen)) + return STAT_BOGUS; +- +- /* 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; +- +- 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) ++ /* Optimisation for RR types which need no cannonicalisation. ++ This includes DNSKEY DS NSEC and NSEC3, which are also long, so ++ it saves lots of calls to get_rdata, and avoids the pessimal ++ segmented insertion, even with a small rrbuf[]. ++ ++ If canonicalisation is not needed, a simple insertion into the hash works. ++ */ ++ if (*rr_desc == (u16)-1) ++ { ++ len = htons(rdlen); ++ hash->update(ctx, 2, (unsigned char *)&len); ++ hash->update(ctx, rdlen, p); ++ } ++ else + { ++ /* 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; ++ + 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) + { +- rrbuf[j] = *state.op; +- +- if (j == RRBUFLEN - 1) +- { +- hash->update(ctx, RRBUFLEN, rrbuf); +- j = -1; +- } ++ 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; ++ } ++ } + } ++ ++ if (j != 0) ++ hash->update(ctx, j, rrbuf); + } +- +- if (j != 0) +- hash->update(ctx, j, rrbuf); + } + + hash->digest(ctx, hash->digest_size, digest); diff --git a/package/network/services/dnsmasq/patches/0107-Add-missing-check-for-NULL-return-from-allocate_rfd.patch b/package/network/services/dnsmasq/patches/0107-Add-missing-check-for-NULL-return-from-allocate_rfd.patch new file mode 100644 index 0000000000..f9b4b5c500 --- /dev/null +++ b/package/network/services/dnsmasq/patches/0107-Add-missing-check-for-NULL-return-from-allocate_rfd.patch @@ -0,0 +1,64 @@ +From 824461192ca5098043f9ca4ddeba7df1f65b30ba Mon Sep 17 00:00:00 2001 +From: Simon Kelley <simon@thekelleys.org.uk> +Date: Sun, 15 Nov 2020 22:13:25 +0000 +Subject: Add missing check for NULL return from allocate_rfd(). + +--- + src/forward.c | 18 ++++++++++-------- + 1 file changed, 10 insertions(+), 8 deletions(-) + +--- a/src/forward.c ++++ b/src/forward.c +@@ -815,7 +815,6 @@ void reply_query(int fd, int family, tim + int is_sign; + + #ifdef HAVE_DNSSEC +- /* For DNSSEC originated queries, just retry the query to the same server. */ + if (forward->flags & (FREC_DNSKEY_QUERY | FREC_DS_QUERY)) + { + struct server *start; +@@ -841,6 +840,8 @@ void reply_query(int fd, int family, tim + } + + ++ fd = -1; ++ + if (start->sfd) + fd = start->sfd->fd; + else +@@ -848,19 +849,21 @@ void reply_query(int fd, int family, tim + if (start->addr.sa.sa_family == AF_INET6) + { + /* may have changed family */ +- if (!forward->rfd6) +- forward->rfd6 = allocate_rfd(AF_INET6); +- fd = forward->rfd6->fd; ++ if (forward->rfd6 || (forward->rfd6 = allocate_rfd(AF_INET6))) ++ fd = forward->rfd6->fd; + } + else + { + /* may have changed family */ +- if (!forward->rfd4) +- forward->rfd4 = allocate_rfd(AF_INET); +- fd = forward->rfd4->fd; ++ if (forward->rfd4 || (forward->rfd4 = allocate_rfd(AF_INET))) ++ fd = forward->rfd4->fd; + } + } + ++ /* Can't get socket. */ ++ if (fd == -1) ++ return; ++ + while (retry_send(sendto(fd, (char *)header, plen, 0, + &start->addr.sa, + sa_len(&start->addr)))); +@@ -2261,7 +2264,6 @@ struct frec *get_new_frec(time_t now, in + return f; /* OK if malloc fails and this is NULL */ + } + +-/* crc is all-ones if not known. */ + static struct frec *lookup_frec(unsigned short id, int fd, int family, void *hash) + { + struct frec *f; diff --git a/package/network/services/dnsmasq/patches/0108-Handle-multiple-identical-near-simultaneous-DNS-quer.patch b/package/network/services/dnsmasq/patches/0108-Handle-multiple-identical-near-simultaneous-DNS-quer.patch new file mode 100644 index 0000000000..c4beb6e46c --- /dev/null +++ b/package/network/services/dnsmasq/patches/0108-Handle-multiple-identical-near-simultaneous-DNS-quer.patch @@ -0,0 +1,352 @@ +From 15b60ddf935a531269bb8c68198de012a4967156 Mon Sep 17 00:00:00 2001 +From: Simon Kelley <simon@thekelleys.org.uk> +Date: Wed, 18 Nov 2020 18:34:55 +0000 +Subject: Handle multiple identical near simultaneous DNS queries better. + +Previously, such queries would all be forwarded +independently. This is, in theory, inefficent but in practise +not a problem, _except_ that is means that an answer for any +of the forwarded queries will be accepted and cached. +An attacker can send a query multiple times, and for each repeat, +another {port, ID} becomes capable of accepting the answer he is +sending in the blind, to random IDs and ports. The chance of a +succesful attack is therefore multiplied by the number of repeats +of the query. The new behaviour detects repeated queries and +merely stores the clients sending repeats so that when the +first query completes, the answer can be sent to all the +clients who asked. Refer: CERT VU#434904. +--- + CHANGELOG | 16 +++++- + src/dnsmasq.h | 19 ++++--- + src/forward.c | 142 ++++++++++++++++++++++++++++++++++++++++++-------- + 3 files changed, 147 insertions(+), 30 deletions(-) + +--- a/CHANGELOG ++++ b/CHANGELOG +@@ -4,13 +4,27 @@ + + Be sure to only accept UDP DNS query replies at the address + from which the query was originated. This keeps as much entropy +- in the {query-ID, random-port} tuple as possible, help defeat ++ in the {query-ID, random-port} tuple as possible, to help defeat + cache poisoning attacks. Refer: CERT VU#434904. + + Use the SHA-256 hash function to verify that DNS answers + received are for the questions originally asked. This replaces + the slightly insecure SHA-1 (when compiled with DNSSEC) or + the very insecure CRC32 (otherwise). Refer: CERT VU#434904. ++ ++ Handle multiple identical near simultaneous DNS queries better. ++ Previously, such queries would all be forwarded ++ independently. This is, in theory, inefficent but in practise ++ not a problem, _except_ that is means that an answer for any ++ of the forwarded queries will be accepted and cached. ++ An attacker can send a query multiple times, and for each repeat, ++ another {port, ID} becomes capable of accepting the answer he is ++ sending in the blind, to random IDs and ports. The chance of a ++ succesful attack is therefore multiplied by the number of repeats ++ of the query. The new behaviour detects repeated queries and ++ merely stores the clients sending repeats so that when the ++ first query completes, the answer can be sent to all the ++ clients who asked. Refer: CERT VU#434904. + + + version 2.81 +--- a/src/dnsmasq.h ++++ b/src/dnsmasq.h +@@ -642,19 +642,24 @@ struct hostsfile { + #define FREC_DO_QUESTION 64 + #define FREC_ADDED_PHEADER 128 + #define FREC_TEST_PKTSZ 256 +-#define FREC_HAS_EXTRADATA 512 ++#define FREC_HAS_EXTRADATA 512 ++#define FREC_HAS_PHEADER 1024 + + #define HASH_SIZE 32 /* SHA-256 digest size */ + + struct frec { +- union mysockaddr source; +- union all_addr dest; ++ struct frec_src { ++ union mysockaddr source; ++ union all_addr dest; ++ unsigned int iface, log_id; ++ unsigned short orig_id; ++ struct frec_src *next; ++ } frec_src; + struct server *sentto; /* NULL means free */ + struct randfd *rfd4; + struct randfd *rfd6; +- unsigned int iface; +- unsigned short orig_id, new_id; +- int log_id, fd, forwardall, flags; ++ unsigned short new_id; ++ int fd, forwardall, flags; + time_t time; + unsigned char *hash[HASH_SIZE]; + #ifdef HAVE_DNSSEC +@@ -1069,6 +1074,8 @@ extern struct daemon { + int back_to_the_future; + #endif + struct frec *frec_list; ++ struct frec_src *free_frec_src; ++ int frec_src_count; + struct serverfd *sfds; + struct irec *interfaces; + struct listener *listeners; +--- a/src/forward.c ++++ b/src/forward.c +@@ -20,6 +20,8 @@ static struct frec *lookup_frec(unsigned + static struct frec *lookup_frec_by_sender(unsigned short id, + union mysockaddr *addr, + void *hash); ++static struct frec *lookup_frec_by_query(void *hash, unsigned int flags); ++ + static unsigned short get_id(void); + static void free_frec(struct frec *f); + +@@ -247,6 +249,7 @@ static int forward_query(int udpfd, unio + int type = SERV_DO_DNSSEC, norebind = 0; + union all_addr *addrp = NULL; + unsigned int flags = 0; ++ unsigned int fwd_flags = 0; + struct server *start = NULL; + void *hash = hash_questions(header, plen, daemon->namebuff); + #ifdef HAVE_DNSSEC +@@ -255,7 +258,18 @@ static int forward_query(int udpfd, unio + unsigned int gotname = extract_request(header, plen, daemon->namebuff, NULL); + unsigned char *oph = find_pseudoheader(header, plen, NULL, NULL, NULL, NULL); + (void)do_bit; +- ++ ++ if (header->hb4 & HB4_CD) ++ fwd_flags |= FREC_CHECKING_DISABLED; ++ if (ad_reqd) ++ fwd_flags |= FREC_AD_QUESTION; ++ if (oph) ++ fwd_flags |= FREC_HAS_PHEADER; ++#ifdef HAVE_DNSSEC ++ if (do_bit) ++ fwd_flags |= FREC_DO_QUESTION; ++#endif ++ + /* may be no servers available. */ + if (forward || (forward = lookup_frec_by_sender(ntohs(header->id), udpaddr, hash))) + { +@@ -328,6 +342,39 @@ static int forward_query(int udpfd, unio + } + else + { ++ /* Query from new source, but the same query may be in progress ++ from another source. If so, just add this client to the ++ list that will get the reply. ++ ++ Note that is the EDNS client subnet option is in use, we can't do this, ++ as the clients (and therefore query EDNS options) will be different ++ for each query. The EDNS subnet code has checks to avoid ++ attacks in this case. */ ++ if (!option_bool(OPT_CLIENT_SUBNET) && (forward = lookup_frec_by_query(hash, fwd_flags))) ++ { ++ /* Note whine_malloc() zeros memory. */ ++ if (!daemon->free_frec_src && ++ daemon->frec_src_count < daemon->ftabsize && ++ (daemon->free_frec_src = whine_malloc(sizeof(struct frec_src)))) ++ daemon->frec_src_count++; ++ ++ /* If we've been spammed with many duplicates, just drop the query. */ ++ if (daemon->free_frec_src) ++ { ++ struct frec_src *new = daemon->free_frec_src; ++ daemon->free_frec_src = new->next; ++ new->next = forward->frec_src.next; ++ forward->frec_src.next = new; ++ new->orig_id = ntohs(header->id); ++ new->source = *udpaddr; ++ new->dest = *dst_addr; ++ new->log_id = daemon->log_id; ++ new->iface = dst_iface; ++ } ++ ++ return 1; ++ } ++ + if (gotname) + flags = search_servers(now, &addrp, gotname, daemon->namebuff, &type, &domain, &norebind); + +@@ -335,22 +382,22 @@ static int forward_query(int udpfd, unio + do_dnssec = type & SERV_DO_DNSSEC; + #endif + type &= ~SERV_DO_DNSSEC; +- ++ + if (daemon->servers && !flags) + forward = get_new_frec(now, NULL, 0); + /* table full - flags == 0, return REFUSED */ + + if (forward) + { +- forward->source = *udpaddr; +- forward->dest = *dst_addr; +- forward->iface = dst_iface; +- forward->orig_id = ntohs(header->id); ++ forward->frec_src.source = *udpaddr; ++ forward->frec_src.orig_id = ntohs(header->id); ++ forward->frec_src.dest = *dst_addr; ++ forward->frec_src.iface = dst_iface; + forward->new_id = get_id(); + forward->fd = udpfd; + memcpy(forward->hash, hash, HASH_SIZE); + forward->forwardall = 0; +- forward->flags = 0; ++ forward->flags = fwd_flags; + if (norebind) + forward->flags |= FREC_NOREBIND; + if (header->hb4 & HB4_CD) +@@ -405,9 +452,9 @@ static int forward_query(int udpfd, unio + unsigned char *pheader; + + /* If a query is retried, use the log_id for the retry when logging the answer. */ +- forward->log_id = daemon->log_id; ++ forward->frec_src.log_id = daemon->log_id; + +- plen = add_edns0_config(header, plen, ((unsigned char *)header) + PACKETSZ, &forward->source, now, &subnet); ++ plen = add_edns0_config(header, plen, ((unsigned char *)header) + PACKETSZ, &forward->frec_src.source, now, &subnet); + + if (subnet) + forward->flags |= FREC_HAS_SUBNET; +@@ -544,7 +591,7 @@ static int forward_query(int udpfd, unio + return 1; + + /* could not send on, prepare to return */ +- header->id = htons(forward->orig_id); ++ header->id = htons(forward->frec_src.orig_id); + free_frec(forward); /* cancel */ + } + +@@ -796,8 +843,8 @@ void reply_query(int fd, int family, tim + + /* log_query gets called indirectly all over the place, so + pass these in global variables - sorry. */ +- daemon->log_display_id = forward->log_id; +- daemon->log_source_addr = &forward->source; ++ daemon->log_display_id = forward->frec_src.log_id; ++ daemon->log_source_addr = &forward->frec_src.source; + + if (daemon->ignore_addr && RCODE(header) == NOERROR && + check_for_ignored_address(header, n, daemon->ignore_addr)) +@@ -1065,6 +1112,7 @@ void reply_query(int fd, int family, tim + new->sentto = server; + new->rfd4 = NULL; + new->rfd6 = NULL; ++ new->frec_src.next = NULL; + new->flags &= ~(FREC_DNSKEY_QUERY | FREC_DS_QUERY | FREC_HAS_EXTRADATA); + new->forwardall = 0; + +@@ -1199,9 +1247,11 @@ void reply_query(int fd, int family, tim + + if ((nn = process_reply(header, now, forward->sentto, (size_t)n, check_rebind, no_cache_dnssec, cache_secure, bogusanswer, + forward->flags & FREC_AD_QUESTION, forward->flags & FREC_DO_QUESTION, +- forward->flags & FREC_ADDED_PHEADER, forward->flags & FREC_HAS_SUBNET, &forward->source))) ++ forward->flags & FREC_ADDED_PHEADER, forward->flags & FREC_HAS_SUBNET, &forward->frec_src.source))) + { +- header->id = htons(forward->orig_id); ++ struct frec_src *src; ++ ++ header->id = htons(forward->frec_src.orig_id); + header->hb4 |= HB4_RA; /* recursion if available */ + #ifdef HAVE_DNSSEC + /* We added an EDNSO header for the purpose of getting DNSSEC RRs, and set the value of the UDP payload size +@@ -1217,13 +1267,26 @@ void reply_query(int fd, int family, tim + } + #endif + ++ for (src = &forward->frec_src; src; src = src->next) ++ { ++ header->id = htons(src->orig_id); ++ + #ifdef HAVE_DUMPFILE +- dump_packet(DUMP_REPLY, daemon->packet, (size_t)nn, NULL, &forward->source); ++ dump_packet(DUMP_REPLY, daemon->packet, (size_t)nn, NULL, &src->source); + #endif +- +- send_from(forward->fd, option_bool(OPT_NOWILD) || option_bool (OPT_CLEVERBIND), daemon->packet, nn, +- &forward->source, &forward->dest, forward->iface); ++ ++ send_from(forward->fd, option_bool(OPT_NOWILD) || option_bool (OPT_CLEVERBIND), daemon->packet, nn, ++ &src->source, &src->dest, src->iface); ++ ++ if (option_bool(OPT_EXTRALOG) && src != &forward->frec_src) ++ { ++ daemon->log_display_id = src->log_id; ++ daemon->log_source_addr = &src->source; ++ log_query(F_UPSTREAM, "query", NULL, "duplicate"); ++ } ++ } + } ++ + free_frec(forward); /* cancel */ + } + } +@@ -2153,6 +2216,17 @@ void free_rfd(struct randfd *rfd) + + static void free_frec(struct frec *f) + { ++ struct frec_src *src, *tmp; ++ ++ /* add back to freelist of not the record builtin to every frec. */ ++ for (src = f->frec_src.next; src; src = tmp) ++ { ++ tmp = src->next; ++ src->next = daemon->free_frec_src; ++ daemon->free_frec_src = src; ++ } ++ ++ f->frec_src.next = NULL; + free_rfd(f->rfd4); + f->rfd4 = NULL; + f->sentto = NULL; +@@ -2292,17 +2366,39 @@ static struct frec *lookup_frec_by_sende + void *hash) + { + struct frec *f; ++ struct frec_src *src; ++ ++ for (f = daemon->frec_list; f; f = f->next) ++ if (f->sentto && ++ !(f->flags & (FREC_DNSKEY_QUERY | FREC_DS_QUERY)) && ++ memcmp(hash, f->hash, HASH_SIZE) == 0) ++ for (src = &f->frec_src; src; src = src->next) ++ if (src->orig_id == id && ++ sockaddr_isequal(&src->source, addr)) ++ return f; ++ ++ return NULL; ++} ++ ++static struct frec *lookup_frec_by_query(void *hash, unsigned int flags) ++{ ++ struct frec *f; ++ ++ /* FREC_DNSKEY and FREC_DS_QUERY are never set in flags, so the test below ++ ensures that no frec created for internal DNSSEC query can be returned here. */ ++ ++#define FLAGMASK (FREC_CHECKING_DISABLED | FREC_AD_QUESTION | FREC_DO_QUESTION \ ++ | FREC_HAS_PHEADER | FREC_DNSKEY_QUERY | FREC_DS_QUERY) + + for(f = daemon->frec_list; f; f = f->next) + if (f->sentto && +- f->orig_id == id && +- memcmp(hash, f->hash, HASH_SIZE) == 0 && +- sockaddr_isequal(&f->source, addr)) ++ (f->flags & FLAGMASK) == flags && ++ memcmp(hash, f->hash, HASH_SIZE) == 0) + return f; +- ++ + return NULL; + } +- ++ + /* Send query packet again, if we can. */ + void resend_query() + { diff --git a/package/network/services/dnsmasq/patches/0109-Handle-caching-with-EDNS-options-better.patch b/package/network/services/dnsmasq/patches/0109-Handle-caching-with-EDNS-options-better.patch new file mode 100644 index 0000000000..64fb0dcf70 --- /dev/null +++ b/package/network/services/dnsmasq/patches/0109-Handle-caching-with-EDNS-options-better.patch @@ -0,0 +1,350 @@ +From 25e63f1e56f5acdcf91893a1b92ad1e0f2f552d8 Mon Sep 17 00:00:00 2001 +From: Simon Kelley <simon@thekelleys.org.uk> +Date: Wed, 25 Nov 2020 21:17:52 +0000 +Subject: Handle caching with EDNS options better. + +If we add the EDNS client subnet option, or the client's +MAC address, then the reply we get back may very depending on +that. Since the cache is ignorant of such things, it's not safe to +cache such replies. This patch determines when a dangerous EDNS +option is being added and disables caching. + +Note that for much the same reason, we can't combine multiple +queries for the same question when dangerous EDNS options are +being added, and the code now handles that in the same way. This +query combining is required for security against cache poisoning, +so disabling the cache has a security function as well as a +correctness one. +--- + man/dnsmasq.8 | 4 +-- + src/dnsmasq.h | 3 ++- + src/edns0.c | 75 ++++++++++++++++++++++++++++++++------------------- + src/forward.c | 41 ++++++++++++++++++---------- + 4 files changed, 78 insertions(+), 45 deletions(-) + +--- a/man/dnsmasq.8 ++++ b/man/dnsmasq.8 +@@ -690,8 +690,8 @@ still marks the request so that no upstr + address information either. The default is zero for both IPv4 and + IPv6. Note that upstream nameservers may be configured to return + different results based on this information, but the dnsmasq cache +-does not take account. If a dnsmasq instance is configured such that +-different results may be encountered, caching should be disabled. ++does not take account. Caching is therefore disabled for such replies, ++unless the subnet address being added is constant. + + For example, + .B --add-subnet=24,96 +--- a/src/dnsmasq.h ++++ b/src/dnsmasq.h +@@ -644,6 +644,7 @@ struct hostsfile { + #define FREC_TEST_PKTSZ 256 + #define FREC_HAS_EXTRADATA 512 + #define FREC_HAS_PHEADER 1024 ++#define FREC_NO_CACHE 2048 + + #define HASH_SIZE 32 /* SHA-256 digest size */ + +@@ -1628,7 +1629,7 @@ size_t add_pseudoheader(struct dns_heade + unsigned short udp_sz, int optno, unsigned char *opt, size_t optlen, int set_do, int replace); + size_t add_do_bit(struct dns_header *header, size_t plen, unsigned char *limit); + size_t add_edns0_config(struct dns_header *header, size_t plen, unsigned char *limit, +- union mysockaddr *source, time_t now, int *check_subnet); ++ union mysockaddr *source, time_t now, int *check_subnet, int *cacheable); + int check_source(struct dns_header *header, size_t plen, unsigned char *pseudoheader, union mysockaddr *peer); + + /* arp.c */ +--- a/src/edns0.c ++++ b/src/edns0.c +@@ -264,7 +264,8 @@ static void encoder(unsigned char *in, c + out[3] = char64(in[2]); + } + +-static size_t add_dns_client(struct dns_header *header, size_t plen, unsigned char *limit, union mysockaddr *l3, time_t now) ++static size_t add_dns_client(struct dns_header *header, size_t plen, unsigned char *limit, ++ union mysockaddr *l3, time_t now, int *cacheablep) + { + int maclen, replace = 2; /* can't get mac address, just delete any incoming. */ + unsigned char mac[DHCP_CHADDR_MAX]; +@@ -273,6 +274,7 @@ static size_t add_dns_client(struct dns_ + if ((maclen = find_mac(l3, mac, 1, now)) == 6) + { + replace = 1; ++ *cacheablep = 0; + + if (option_bool(OPT_MAC_HEX)) + print_mac(encode, mac, maclen); +@@ -288,14 +290,18 @@ static size_t add_dns_client(struct dns_ + } + + +-static size_t add_mac(struct dns_header *header, size_t plen, unsigned char *limit, union mysockaddr *l3, time_t now) ++static size_t add_mac(struct dns_header *header, size_t plen, unsigned char *limit, ++ union mysockaddr *l3, time_t now, int *cacheablep) + { + int maclen; + unsigned char mac[DHCP_CHADDR_MAX]; + + if ((maclen = find_mac(l3, mac, 1, now)) != 0) +- plen = add_pseudoheader(header, plen, limit, PACKETSZ, EDNS0_OPTION_MAC, mac, maclen, 0, 0); +- ++ { ++ *cacheablep = 0; ++ plen = add_pseudoheader(header, plen, limit, PACKETSZ, EDNS0_OPTION_MAC, mac, maclen, 0, 0); ++ } ++ + return plen; + } + +@@ -313,17 +319,18 @@ static void *get_addrp(union mysockaddr + return &addr->in.sin_addr; + } + +-static size_t calc_subnet_opt(struct subnet_opt *opt, union mysockaddr *source) ++static size_t calc_subnet_opt(struct subnet_opt *opt, union mysockaddr *source, int *cacheablep) + { + /* http://tools.ietf.org/html/draft-vandergaast-edns-client-subnet-02 */ + + int len; + void *addrp = NULL; + int sa_family = source->sa.sa_family; +- ++ int cacheable = 0; ++ + opt->source_netmask = 0; + opt->scope_netmask = 0; +- ++ + if (source->sa.sa_family == AF_INET6 && daemon->add_subnet6) + { + opt->source_netmask = daemon->add_subnet6->mask; +@@ -331,6 +338,7 @@ static size_t calc_subnet_opt(struct sub + { + sa_family = daemon->add_subnet6->addr.sa.sa_family; + addrp = get_addrp(&daemon->add_subnet6->addr, sa_family); ++ cacheable = 1; + } + else + addrp = &source->in6.sin6_addr; +@@ -343,6 +351,7 @@ static size_t calc_subnet_opt(struct sub + { + sa_family = daemon->add_subnet4->addr.sa.sa_family; + addrp = get_addrp(&daemon->add_subnet4->addr, sa_family); ++ cacheable = 1; /* Address is constant */ + } + else + addrp = &source->in.sin_addr; +@@ -350,8 +359,6 @@ static size_t calc_subnet_opt(struct sub + + opt->family = htons(sa_family == AF_INET6 ? 2 : 1); + +- len = 0; +- + if (addrp && opt->source_netmask != 0) + { + len = ((opt->source_netmask - 1) >> 3) + 1; +@@ -359,18 +366,26 @@ static size_t calc_subnet_opt(struct sub + if (opt->source_netmask & 7) + opt->addr[len-1] &= 0xff << (8 - (opt->source_netmask & 7)); + } ++ else ++ { ++ cacheable = 1; /* No address ever supplied. */ ++ len = 0; ++ } ++ ++ if (cacheablep) ++ *cacheablep = cacheable; + + return len + 4; + } + +-static size_t add_source_addr(struct dns_header *header, size_t plen, unsigned char *limit, union mysockaddr *source) ++static size_t add_source_addr(struct dns_header *header, size_t plen, unsigned char *limit, union mysockaddr *source, int *cacheable) + { + /* http://tools.ietf.org/html/draft-vandergaast-edns-client-subnet-02 */ + + int len; + struct subnet_opt opt; + +- len = calc_subnet_opt(&opt, source); ++ len = calc_subnet_opt(&opt, source, cacheable); + return add_pseudoheader(header, plen, (unsigned char *)limit, PACKETSZ, EDNS0_OPTION_CLIENT_SUBNET, (unsigned char *)&opt, len, 0, 0); + } + +@@ -383,18 +398,18 @@ int check_source(struct dns_header *head + unsigned char *p; + int code, i, rdlen; + +- calc_len = calc_subnet_opt(&opt, peer); +- +- if (!(p = skip_name(pseudoheader, header, plen, 10))) +- return 1; +- +- p += 8; /* skip UDP length and RCODE */ ++ calc_len = calc_subnet_opt(&opt, peer, NULL); + +- GETSHORT(rdlen, p); +- if (!CHECK_LEN(header, p, plen, rdlen)) +- return 1; /* bad packet */ +- +- /* check if option there */ ++ if (!(p = skip_name(pseudoheader, header, plen, 10))) ++ return 1; ++ ++ p += 8; /* skip UDP length and RCODE */ ++ ++ GETSHORT(rdlen, p); ++ if (!CHECK_LEN(header, p, plen, rdlen)) ++ return 1; /* bad packet */ ++ ++ /* check if option there */ + for (i = 0; i + 4 < rdlen; i += len + 4) + { + GETSHORT(code, p); +@@ -412,24 +427,28 @@ int check_source(struct dns_header *head + return 1; + } + ++/* Set *check_subnet if we add a client subnet option, which needs to checked ++ in the reply. Set *cacheable to zero if we add an option which the answer ++ may depend on. */ + size_t add_edns0_config(struct dns_header *header, size_t plen, unsigned char *limit, +- union mysockaddr *source, time_t now, int *check_subnet) ++ union mysockaddr *source, time_t now, int *check_subnet, int *cacheable) + { + *check_subnet = 0; +- ++ *cacheable = 1; ++ + if (option_bool(OPT_ADD_MAC)) +- plen = add_mac(header, plen, limit, source, now); ++ plen = add_mac(header, plen, limit, source, now, cacheable); + + if (option_bool(OPT_MAC_B64) || option_bool(OPT_MAC_HEX)) +- plen = add_dns_client(header, plen, limit, source, now); +- ++ plen = add_dns_client(header, plen, limit, source, now, cacheable); ++ + if (daemon->dns_client_id) + plen = add_pseudoheader(header, plen, limit, PACKETSZ, EDNS0_OPTION_NOMCPEID, + (unsigned char *)daemon->dns_client_id, strlen(daemon->dns_client_id), 0, 1); + + if (option_bool(OPT_CLIENT_SUBNET)) + { +- plen = add_source_addr(header, plen, limit, source); ++ plen = add_source_addr(header, plen, limit, source, cacheable); + *check_subnet = 1; + } + +--- a/src/forward.c ++++ b/src/forward.c +@@ -344,13 +344,10 @@ static int forward_query(int udpfd, unio + { + /* Query from new source, but the same query may be in progress + from another source. If so, just add this client to the +- list that will get the reply. ++ list that will get the reply.*/ + +- Note that is the EDNS client subnet option is in use, we can't do this, +- as the clients (and therefore query EDNS options) will be different +- for each query. The EDNS subnet code has checks to avoid +- attacks in this case. */ +- if (!option_bool(OPT_CLIENT_SUBNET) && (forward = lookup_frec_by_query(hash, fwd_flags))) ++ if (!option_bool(OPT_ADD_MAC) && !option_bool(OPT_MAC_B64) && ++ (forward = lookup_frec_by_query(hash, fwd_flags))) + { + /* Note whine_malloc() zeros memory. */ + if (!daemon->free_frec_src && +@@ -447,18 +444,21 @@ static int forward_query(int udpfd, unio + if (!flags && forward) + { + struct server *firstsentto = start; +- int subnet, forwarded = 0; ++ int subnet, cacheable, forwarded = 0; + size_t edns0_len; + unsigned char *pheader; + + /* If a query is retried, use the log_id for the retry when logging the answer. */ + forward->frec_src.log_id = daemon->log_id; + +- plen = add_edns0_config(header, plen, ((unsigned char *)header) + PACKETSZ, &forward->frec_src.source, now, &subnet); ++ plen = add_edns0_config(header, plen, ((unsigned char *)header) + PACKETSZ, &forward->frec_src.source, now, &subnet, &cacheable); + + if (subnet) + forward->flags |= FREC_HAS_SUBNET; +- ++ ++ if (!cacheable) ++ forward->flags |= FREC_NO_CACHE; ++ + #ifdef HAVE_DNSSEC + if (option_bool(OPT_DNSSEC_VALID) && do_dnssec) + { +@@ -642,7 +642,7 @@ static size_t process_reply(struct dns_h + } + } + #endif +- ++ + if ((pheader = find_pseudoheader(header, n, &plen, &sizep, &is_sign, NULL))) + { + /* Get extended RCODE. */ +@@ -1244,6 +1244,11 @@ void reply_query(int fd, int family, tim + header->hb4 |= HB4_CD; + else + header->hb4 &= ~HB4_CD; ++ ++ /* Never cache answers which are contingent on the source or MAC address EDSN0 option, ++ since the cache is ignorant of such things. */ ++ if (forward->flags & FREC_NO_CACHE) ++ no_cache_dnssec = 1; + + if ((nn = process_reply(header, now, forward->sentto, (size_t)n, check_rebind, no_cache_dnssec, cache_secure, bogusanswer, + forward->flags & FREC_AD_QUESTION, forward->flags & FREC_DO_QUESTION, +@@ -1788,7 +1793,7 @@ unsigned char *tcp_request(int confd, ti + int local_auth = 0; + #endif + int checking_disabled, do_bit, added_pheader = 0, have_pseudoheader = 0; +- int check_subnet, no_cache_dnssec = 0, cache_secure = 0, bogusanswer = 0; ++ int check_subnet, cacheable, no_cache_dnssec = 0, cache_secure = 0, bogusanswer = 0; + size_t m; + unsigned short qtype; + unsigned int gotname; +@@ -1959,7 +1964,7 @@ unsigned char *tcp_request(int confd, ti + char *domain = NULL; + unsigned char *oph = find_pseudoheader(header, size, NULL, NULL, NULL, NULL); + +- size = add_edns0_config(header, size, ((unsigned char *) header) + 65536, &peer_addr, now, &check_subnet); ++ size = add_edns0_config(header, size, ((unsigned char *) header) + 65536, &peer_addr, now, &check_subnet, &cacheable); + + if (gotname) + flags = search_servers(now, &addrp, gotname, daemon->namebuff, &type, &domain, &norebind); +@@ -2122,6 +2127,11 @@ unsigned char *tcp_request(int confd, ti + break; + } + ++ /* Never cache answers which are contingent on the source or MAC address EDSN0 option, ++ since the cache is ignorant of such things. */ ++ if (!cacheable) ++ no_cache_dnssec = 1; ++ + m = process_reply(header, now, last_server, (unsigned int)m, + option_bool(OPT_NO_REBIND) && !norebind, no_cache_dnssec, cache_secure, bogusanswer, + ad_reqd, do_bit, added_pheader, check_subnet, &peer_addr); +@@ -2385,10 +2395,13 @@ static struct frec *lookup_frec_by_query + struct frec *f; + + /* FREC_DNSKEY and FREC_DS_QUERY are never set in flags, so the test below +- ensures that no frec created for internal DNSSEC query can be returned here. */ ++ ensures that no frec created for internal DNSSEC query can be returned here. ++ ++ Similarly FREC_NO_CACHE is never set in flags, so a query which is ++ contigent on a particular source address EDNS0 option will never be matched. */ + + #define FLAGMASK (FREC_CHECKING_DISABLED | FREC_AD_QUESTION | FREC_DO_QUESTION \ +- | FREC_HAS_PHEADER | FREC_DNSKEY_QUERY | FREC_DS_QUERY) ++ | FREC_HAS_PHEADER | FREC_DNSKEY_QUERY | FREC_DS_QUERY | FREC_NO_CACHE) + + for(f = daemon->frec_list; f; f = f->next) + if (f->sentto && diff --git a/package/network/services/dnsmasq/patches/0110-Support-hash-function-from-nettle-only.patch b/package/network/services/dnsmasq/patches/0110-Support-hash-function-from-nettle-only.patch new file mode 100644 index 0000000000..d671af2906 --- /dev/null +++ b/package/network/services/dnsmasq/patches/0110-Support-hash-function-from-nettle-only.patch @@ -0,0 +1,181 @@ +From 2024f9729713fd657d65e64c2e4e471baa0a3e5b Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Petr=20Men=C5=A1=C3=ADk?= <pemensik@redhat.com> +Date: Wed, 25 Nov 2020 17:18:55 +0100 +Subject: Support hash function from nettle (only) + +Unlike COPTS=-DHAVE_DNSSEC, allow usage of just sha256 function from +nettle, but keep DNSSEC disabled at build time. Skips use of internal +hash implementation without support for validation built-in. +--- + Makefile | 8 +++++--- + bld/pkg-wrapper | 41 ++++++++++++++++++++++------------------- + src/config.h | 8 ++++++++ + src/crypto.c | 7 +++++++ + src/dnsmasq.h | 2 +- + src/hash_questions.c | 2 +- + 6 files changed, 44 insertions(+), 24 deletions(-) + +--- a/Makefile ++++ b/Makefile +@@ -53,7 +53,7 @@ top?=$(CURDIR) + + dbus_cflags = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_DBUS $(PKG_CONFIG) --cflags dbus-1` + dbus_libs = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_DBUS $(PKG_CONFIG) --libs dbus-1` +-ubus_libs = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_UBUS "" --copy -lubox -lubus` ++ubus_libs = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_UBUS "" --copy '-lubox -lubus'` + idn_cflags = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_IDN $(PKG_CONFIG) --cflags libidn` + idn_libs = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_IDN $(PKG_CONFIG) --libs libidn` + idn2_cflags = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_LIBIDN2 $(PKG_CONFIG) --cflags libidn2` +@@ -62,8 +62,10 @@ ct_cflags = `echo $(COPTS) | $(top)/ + ct_libs = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_CONNTRACK $(PKG_CONFIG) --libs libnetfilter_conntrack` + lua_cflags = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_LUASCRIPT $(PKG_CONFIG) --cflags lua5.2` + lua_libs = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_LUASCRIPT $(PKG_CONFIG) --libs lua5.2` +-nettle_cflags = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_DNSSEC $(PKG_CONFIG) --cflags nettle hogweed` +-nettle_libs = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_DNSSEC $(PKG_CONFIG) --libs nettle hogweed` ++nettle_cflags = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_DNSSEC $(PKG_CONFIG) --cflags 'nettle hogweed' \ ++ HAVE_NETTLEHASH $(PKG_CONFIG) --cflags nettle` ++nettle_libs = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_DNSSEC $(PKG_CONFIG) --libs 'nettle hogweed' \ ++ HAVE_NETTLEHASH $(PKG_CONFIG) --libs nettle` + gmp_libs = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_DNSSEC NO_GMP --copy -lgmp` + sunos_libs = `if uname | grep SunOS >/dev/null 2>&1; then echo -lsocket -lnsl -lposix4; fi` + version = -DVERSION='\"`$(top)/bld/get-version $(top)`\"' +--- a/bld/pkg-wrapper ++++ b/bld/pkg-wrapper +@@ -1,35 +1,37 @@ + #!/bin/sh + +-search=$1 +-shift +-pkg=$1 +-shift +-op=$1 +-shift +- + in=`cat` + +-if grep "^\#[[:space:]]*define[[:space:]]*$search" config.h >/dev/null 2>&1 || \ +- echo $in | grep $search >/dev/null 2>&1; then ++search() ++{ ++ grep "^\#[[:space:]]*define[[:space:]]*$1" config.h >/dev/null 2>&1 || \ ++ echo $in | grep $1 >/dev/null 2>&1 ++} ++ ++while [ "$#" -gt 0 ]; do ++ search=$1 ++ pkg=$2 ++ op=$3 ++ lib=$4 ++ shift 4 ++if search "$search"; then ++ + # Nasty, nasty, in --copy, arg 2 (if non-empty) is another config to search for, used with NO_GMP + if [ $op = "--copy" ]; then + if [ -z "$pkg" ]; then +- pkg="$*" +- elif grep "^\#[[:space:]]*define[[:space:]]*$pkg" config.h >/dev/null 2>&1 || \ +- echo $in | grep $pkg >/dev/null 2>&1; then ++ pkg="$lib" ++ elif search "$pkg"; then + pkg="" + else +- pkg="$*" ++ pkg="$lib" + fi +- elif grep "^\#[[:space:]]*define[[:space:]]*${search}_STATIC" config.h >/dev/null 2>&1 || \ +- echo $in | grep ${search}_STATIC >/dev/null 2>&1; then +- pkg=`$pkg --static $op $*` ++ elif search "${search}_STATIC"; then ++ pkg=`$pkg --static $op $lib` + else +- pkg=`$pkg $op $*` ++ pkg=`$pkg $op $lib` + fi + +- if grep "^\#[[:space:]]*define[[:space:]]*${search}_STATIC" config.h >/dev/null 2>&1 || \ +- echo $in | grep ${search}_STATIC >/dev/null 2>&1; then ++ if search "${search}_STATIC"; then + if [ $op = "--libs" ] || [ $op = "--copy" ]; then + echo "-Wl,-Bstatic $pkg -Wl,-Bdynamic" + else +@@ -40,3 +42,4 @@ if grep "^\#[[:space:]]*define[[:space:] + fi + fi + ++done +--- a/src/config.h ++++ b/src/config.h +@@ -117,6 +117,9 @@ HAVE_AUTH + define this to include the facility to act as an authoritative DNS + server for one or more zones. + ++HAVE_NETTLEHASH ++ include just hash function from nettle, but no DNSSEC. ++ + HAVE_DNSSEC + include DNSSEC validator. + +@@ -184,6 +187,7 @@ RESOLVFILE + /* #define HAVE_IDN */ + /* #define HAVE_LIBIDN2 */ + /* #define HAVE_CONNTRACK */ ++/* #define HAVE_NETTLEHASH */ + /* #define HAVE_DNSSEC */ + + +@@ -408,6 +412,10 @@ static char *compile_opts = + "no-" + #endif + "auth " ++#if !defined(HAVE_NETTLEHASH) && !defined(HAVE_DNSSEC) ++"no-" ++#endif ++"nettlehash " + #ifndef HAVE_DNSSEC + "no-" + #endif +--- a/src/crypto.c ++++ b/src/crypto.c +@@ -23,6 +23,9 @@ + #include <nettle/ecdsa.h> + #include <nettle/ecc-curve.h> + #include <nettle/eddsa.h> ++#endif ++ ++#if defined(HAVE_DNSSEC) || defined(HAVE_NETTLEHASH) + #include <nettle/nettle-meta.h> + #include <nettle/bignum.h> + +@@ -165,6 +168,10 @@ int hash_init(const struct nettle_hash * + + return 1; + } ++ ++#endif ++ ++#ifdef HAVE_DNSSEC + + static int dnsmasq_rsa_verify(struct blockdata *key_data, unsigned int key_len, unsigned char *sig, size_t sig_len, + unsigned char *digest, size_t digest_len, int algo) +--- a/src/dnsmasq.h ++++ b/src/dnsmasq.h +@@ -150,7 +150,7 @@ extern int capget(cap_user_header_t head + #include <priv.h> + #endif + +-#ifdef HAVE_DNSSEC ++#if defined(HAVE_DNSSEC) || defined(HAVE_NETTLEHASH) + # include <nettle/nettle-meta.h> + #endif + +--- a/src/hash_questions.c ++++ b/src/hash_questions.c +@@ -28,7 +28,7 @@ + + #include "dnsmasq.h" + +-#ifdef HAVE_DNSSEC ++#if defined(HAVE_DNSSEC) || defined(HAVE_NETTLEHASH) + unsigned char *hash_questions(struct dns_header *header, size_t plen, char *name) + { + int q; diff --git a/package/network/services/dnsmasq/patches/0111-Small-cleanups-in-frec_src-datastucture-handling.patch b/package/network/services/dnsmasq/patches/0111-Small-cleanups-in-frec_src-datastucture-handling.patch new file mode 100644 index 0000000000..45e04bd1c7 --- /dev/null +++ b/package/network/services/dnsmasq/patches/0111-Small-cleanups-in-frec_src-datastucture-handling.patch @@ -0,0 +1,56 @@ +From 6a6e06fbb0d4690507ceaf2bb6f0d8910f3d4914 Mon Sep 17 00:00:00 2001 +From: Simon Kelley <simon@thekelleys.org.uk> +Date: Fri, 4 Dec 2020 18:35:11 +0000 +Subject: Small cleanups in frec_src datastucture handling. + +--- + src/forward.c | 22 +++++++++++++--------- + 1 file changed, 13 insertions(+), 9 deletions(-) + +--- a/src/forward.c ++++ b/src/forward.c +@@ -353,7 +353,10 @@ static int forward_query(int udpfd, unio + if (!daemon->free_frec_src && + daemon->frec_src_count < daemon->ftabsize && + (daemon->free_frec_src = whine_malloc(sizeof(struct frec_src)))) +- daemon->frec_src_count++; ++ { ++ daemon->frec_src_count++; ++ daemon->free_frec_src->next = NULL; ++ } + + /* If we've been spammed with many duplicates, just drop the query. */ + if (daemon->free_frec_src) +@@ -390,6 +393,7 @@ static int forward_query(int udpfd, unio + forward->frec_src.orig_id = ntohs(header->id); + forward->frec_src.dest = *dst_addr; + forward->frec_src.iface = dst_iface; ++ forward->frec_src.next = NULL; + forward->new_id = get_id(); + forward->fd = udpfd; + memcpy(forward->hash, hash, HASH_SIZE); +@@ -2226,16 +2230,16 @@ void free_rfd(struct randfd *rfd) + + static void free_frec(struct frec *f) + { +- struct frec_src *src, *tmp; +- +- /* add back to freelist of not the record builtin to every frec. */ +- for (src = f->frec_src.next; src; src = tmp) ++ struct frec_src *last; ++ ++ /* add back to freelist if not the record builtin to every frec. */ ++ for (last = f->frec_src.next; last && last->next; last = last->next) ; ++ if (last) + { +- tmp = src->next; +- src->next = daemon->free_frec_src; +- daemon->free_frec_src = src; ++ last->next = daemon->free_frec_src; ++ daemon->free_frec_src = f->frec_src.next; + } +- ++ + f->frec_src.next = NULL; + free_rfd(f->rfd4); + f->rfd4 = NULL; diff --git a/package/network/services/dnsmasq/patches/0112-Add-CVE-numbers-to-security-update-descriptions-in-C.patch b/package/network/services/dnsmasq/patches/0112-Add-CVE-numbers-to-security-update-descriptions-in-C.patch new file mode 100644 index 0000000000..1d7d3a7dae --- /dev/null +++ b/package/network/services/dnsmasq/patches/0112-Add-CVE-numbers-to-security-update-descriptions-in-C.patch @@ -0,0 +1,41 @@ +From e01e09c7125b40646aff4a582672e711a18a69a4 Mon Sep 17 00:00:00 2001 +From: Simon Kelley <simon@thekelleys.org.uk> +Date: Fri, 8 Jan 2021 22:50:03 +0000 +Subject: Add CVE numbers to security update descriptions in CHANGELOG + +--- + CHANGELOG | 9 +++++---- + 1 file changed, 5 insertions(+), 4 deletions(-) + +--- a/CHANGELOG ++++ b/CHANGELOG +@@ -1,16 +1,17 @@ + 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. ++ referenced by CVE-2020-25681, CVE-2020-25682, CVE-2020-25683 ++ CVE-2020-25687. + + Be sure to only accept UDP DNS query replies at the address + from which the query was originated. This keeps as much entropy + in the {query-ID, random-port} tuple as possible, to help defeat +- cache poisoning attacks. Refer: CERT VU#434904. ++ cache poisoning attacks. Refer: CVE-2020-25684. + + Use the SHA-256 hash function to verify that DNS answers + received are for the questions originally asked. This replaces + the slightly insecure SHA-1 (when compiled with DNSSEC) or +- the very insecure CRC32 (otherwise). Refer: CERT VU#434904. ++ the very insecure CRC32 (otherwise). Refer: CVE-2020-25685. + + Handle multiple identical near simultaneous DNS queries better. + Previously, such queries would all be forwarded +@@ -24,7 +25,7 @@ + of the query. The new behaviour detects repeated queries and + merely stores the clients sending repeats so that when the + first query completes, the answer can be sent to all the +- clients who asked. Refer: CERT VU#434904. ++ clients who asked. Refer: CVE-2020-25686. + + + version 2.81 diff --git a/package/network/services/dnsmasq/patches/0113-Fix-warning-message-logic.patch b/package/network/services/dnsmasq/patches/0113-Fix-warning-message-logic.patch new file mode 100644 index 0000000000..667aea1b20 --- /dev/null +++ b/package/network/services/dnsmasq/patches/0113-Fix-warning-message-logic.patch @@ -0,0 +1,20 @@ +From 503f68dbc437df20a45aab440e6fad92062af229 Mon Sep 17 00:00:00 2001 +From: Simon Kelley <simon@thekelleys.org.uk> +Date: Fri, 15 Jan 2021 21:53:29 +0000 +Subject: Fix warning message logic. + +--- + src/hash_questions.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +--- a/src/hash_questions.c ++++ b/src/hash_questions.c +@@ -43,7 +43,7 @@ unsigned char *hash_questions(struct dns + static unsigned char dummy[HASH_SIZE]; + static int warned = 0; + +- if (warned) ++ if (!warned) + my_syslog(LOG_ERR, _("Failed to create SHA-256 hash object")); + warned = 1; + diff --git a/package/network/services/dnsmasq/patches/0115-Update-to-new-struct-frec-fields-in-conntrack-code.patch b/package/network/services/dnsmasq/patches/0115-Update-to-new-struct-frec-fields-in-conntrack-code.patch new file mode 100644 index 0000000000..49648dcb58 --- /dev/null +++ b/package/network/services/dnsmasq/patches/0115-Update-to-new-struct-frec-fields-in-conntrack-code.patch @@ -0,0 +1,29 @@ +From cc0b4489c782f6b90ca118abb18e716a7a831289 Mon Sep 17 00:00:00 2001 +From: Simon Kelley <simon@thekelleys.org.uk> +Date: Fri, 15 Jan 2021 22:21:52 +0000 +Subject: Update to new struct frec fields in conntrack code. + +--- + src/forward.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +--- a/src/forward.c ++++ b/src/forward.c +@@ -530,7 +530,7 @@ static int forward_query(int udpfd, unio + if (option_bool(OPT_CONNTRACK)) + { + unsigned int mark; +- if (get_incoming_mark(&forward->source, &forward->dest, 0, &mark)) ++ if (get_incoming_mark(&forward->frec_src.source, &forward->frec_src.dest, 0, &mark)) + setsockopt(fd, SOL_SOCKET, SO_MARK, &mark, sizeof(unsigned int)); + } + #endif +@@ -1178,7 +1178,7 @@ void reply_query(int fd, int family, tim + if (option_bool(OPT_CONNTRACK)) + { + unsigned int mark; +- if (get_incoming_mark(&orig->source, &orig->dest, 0, &mark)) ++ if (get_incoming_mark(&orig->frec_src.source, &orig->frec_src.dest, 0, &mark)) + setsockopt(fd, SOL_SOCKET, SO_MARK, &mark, sizeof(unsigned int)); + } + #endif diff --git a/package/network/services/dnsmasq/patches/050-crypto-use-nettle-ecc_curve-access-functions.patch b/package/network/services/dnsmasq/patches/050-crypto-use-nettle-ecc_curve-access-functions.patch index c52a6bc44a..df1aaaebce 100644 --- a/package/network/services/dnsmasq/patches/050-crypto-use-nettle-ecc_curve-access-functions.patch +++ b/package/network/services/dnsmasq/patches/050-crypto-use-nettle-ecc_curve-access-functions.patch @@ -15,7 +15,7 @@ Signed-off-by: Hans Dedecker <dedeckeh@gmail.com> --- a/src/crypto.c +++ b/src/crypto.c -@@ -294,7 +294,7 @@ static int dnsmasq_ecdsa_verify(struct b +@@ -301,7 +301,7 @@ static int dnsmasq_ecdsa_verify(struct b if (!(key_256 = whine_malloc(sizeof(struct ecc_point)))) return 0; @@ -24,7 +24,7 @@ Signed-off-by: Hans Dedecker <dedeckeh@gmail.com> } key = key_256; -@@ -307,7 +307,7 @@ static int dnsmasq_ecdsa_verify(struct b +@@ -314,7 +314,7 @@ static int dnsmasq_ecdsa_verify(struct b if (!(key_384 = whine_malloc(sizeof(struct ecc_point)))) return 0; |