From 2314a73d122b13ae255b3f4e4dec3fdcaf81169d Mon Sep 17 00:00:00 2001 From: CentOS Sources Date: Tue, 30 Mar 2021 14:42:15 -0400 Subject: [PATCH] import dnsmasq-2.79-15.el8 --- SOURCES/dnsmasq-2.79-CVE-2020-25681.patch | 364 ++++++++++++ SOURCES/dnsmasq-2.79-CVE-2020-25684.patch | 94 ++++ SOURCES/dnsmasq-2.79-CVE-2020-25685.patch | 586 ++++++++++++++++++++ SOURCES/dnsmasq-2.79-CVE-2020-25686-2.patch | 61 ++ SOURCES/dnsmasq-2.79-CVE-2020-25686.patch | 327 +++++++++++ SPECS/dnsmasq.spec | 23 +- 6 files changed, 1452 insertions(+), 3 deletions(-) create mode 100644 SOURCES/dnsmasq-2.79-CVE-2020-25681.patch create mode 100644 SOURCES/dnsmasq-2.79-CVE-2020-25684.patch create mode 100644 SOURCES/dnsmasq-2.79-CVE-2020-25685.patch create mode 100644 SOURCES/dnsmasq-2.79-CVE-2020-25686-2.patch create mode 100644 SOURCES/dnsmasq-2.79-CVE-2020-25686.patch diff --git a/SOURCES/dnsmasq-2.79-CVE-2020-25681.patch b/SOURCES/dnsmasq-2.79-CVE-2020-25681.patch new file mode 100644 index 0000000..41328d1 --- /dev/null +++ b/SOURCES/dnsmasq-2.79-CVE-2020-25681.patch @@ -0,0 +1,364 @@ +From 6689e336042d2ca0b075f8db1fe30ed6b47c7d4c Mon Sep 17 00:00:00 2001 +From: Simon Kelley +Date: Wed, 11 Nov 2020 23:25:04 +0000 +Subject: [PATCH 1/4] 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. +--- + src/dnssec.c | 273 ++++++++++++++++++++++++++++----------------------- + 1 file changed, 152 insertions(+), 121 deletions(-) + +diff --git a/src/dnssec.c b/src/dnssec.c +index 8143185..0c703ac 100644 +--- a/src/dnssec.c ++++ b/src/dnssec.c +@@ -222,138 +222,147 @@ static int check_date_range(u32 date_start, u32 date_end) + && serial_compare_32(curtime, date_end) == SERIAL_LT; + } + +-/* Return bytes of canonicalised rdata, when the return value is zero, the remaining +- data, pointed to by *p, should be used raw. */ +-static int get_rdata(struct dns_header *header, size_t plen, unsigned char *end, char *buff, int bufflen, +- unsigned char **p, u16 **desc) ++/* Return bytes of canonicalised rrdata one by one. ++ Init state->ip with the RR, and state->end with the end of same. ++ Init state->op to NULL. ++ Init state->desc to RR descriptor. ++ Init state->buff with a MAXDNAME * 2 buffer. ++ ++ After each call which returns 1, state->op points to the next byte of data. ++ On returning 0, the end has been reached. ++*/ ++struct rdata_state { ++ u16 *desc; ++ size_t c; ++ unsigned char *end, *ip, *op; ++ char *buff; ++}; ++ ++static int get_rdata(struct dns_header *header, size_t plen, struct rdata_state *state) + { +- int d = **desc; ++ int d; + +- /* No more data needs mangling */ +- if (d == (u16)-1) ++ if (state->op && state->c != 1) + { +- /* If there's more data than we have space for, just return what fits, +- we'll get called again for more chunks */ +- if (end - *p > bufflen) +- { +- memcpy(buff, *p, bufflen); +- *p += bufflen; +- return bufflen; +- } +- +- return 0; ++ state->op++; ++ state->c--; ++ return 1; + } +- +- (*desc)++; +- +- if (d == 0 && extract_name(header, plen, p, buff, 1, 0)) +- /* domain-name, canonicalise */ +- return to_wire(buff); +- else +- { +- /* plain data preceding a domain-name, don't run off the end of the data */ +- if ((end - *p) < d) +- d = end - *p; ++ ++ while (1) ++ { ++ d = *(state->desc); + +- if (d != 0) ++ if (d == (u16)-1) + { +- memcpy(buff, *p, d); +- *p += d; ++ /* all the bytes to the end. */ ++ if ((state->c = state->end - state->ip) != 0) ++ { ++ state->op = state->ip; ++ state->ip = state->end;; ++ } ++ else ++ return 0; ++ } ++ else ++ { ++ state->desc++; ++ ++ if (d == (u16)0) ++ { ++ /* domain-name, canonicalise */ ++ int len; ++ ++ if (!extract_name(header, plen, &state->ip, state->buff, 1, 0) || ++ (len = to_wire(state->buff)) == 0) ++ continue; ++ ++ state->c = len; ++ state->op = (unsigned char *)state->buff; ++ } ++ else ++ { ++ /* plain data preceding a domain-name, don't run off the end of the data */ ++ if ((state->end - state->ip) < d) ++ d = state->end - state->ip; ++ ++ if (d == 0) ++ continue; ++ ++ state->op = state->ip; ++ state->c = d; ++ state->ip += d; ++ } + } + +- return d; ++ return 1; + } + } + +-/* Bubble sort the RRset into the canonical order. +- Note that the byte-streams from two RRs may get unsynced: consider +- RRs which have two domain-names at the start and then other data. +- The domain-names may have different lengths in each RR, but sort equal +- +- ------------ +- |abcde|fghi| +- ------------ +- |abcd|efghi| +- ------------ +- +- leaving the following bytes as deciding the order. Hence the nasty left1 and left2 variables. +-*/ ++/* Bubble sort the RRset into the canonical order. */ + + static int sort_rrset(struct dns_header *header, size_t plen, u16 *rr_desc, int rrsetidx, + unsigned char **rrset, char *buff1, char *buff2) + { +- int swap, quit, i, j; ++ int swap, i, j; + + do + { + for (swap = 0, i = 0; i < rrsetidx-1; i++) + { +- int rdlen1, rdlen2, left1, left2, len1, len2, len, rc; +- u16 *dp1, *dp2; +- unsigned char *end1, *end2; ++ int rdlen1, rdlen2; ++ struct rdata_state state1, state2; ++ + /* Note that these have been determined to be OK previously, + so we don't need to check for NULL return here. */ +- unsigned char *p1 = skip_name(rrset[i], header, plen, 10); +- unsigned char *p2 = skip_name(rrset[i+1], header, plen, 10); +- +- p1 += 8; /* skip class, type, ttl */ +- GETSHORT(rdlen1, p1); +- end1 = p1 + rdlen1; +- +- p2 += 8; /* skip class, type, ttl */ +- GETSHORT(rdlen2, p2); +- end2 = p2 + rdlen2; ++ 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; + +- dp1 = dp2 = 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; + +- for (quit = 0, left1 = 0, left2 = 0, len1 = 0, len2 = 0; !quit;) ++ state2.ip += 8; /* skip class, type, ttl */ ++ GETSHORT(rdlen2, state2.ip); ++ if (!CHECK_LEN(header, state2.ip, plen, rdlen2)) ++ return rrsetidx; /* short packet */ ++ state2.end = state2.ip + rdlen2; ++ ++ while (1) + { +- if (left1 != 0) +- memmove(buff1, buff1 + len1 - left1, left1); ++ int ok1, ok2; + +- if ((len1 = get_rdata(header, plen, end1, buff1 + left1, (MAXDNAME * 2) - left1, &p1, &dp1)) == 0) +- { +- quit = 1; +- len1 = end1 - p1; +- memcpy(buff1 + left1, p1, len1); +- } +- len1 += left1; +- +- if (left2 != 0) +- memmove(buff2, buff2 + len2 - left2, left2); +- +- if ((len2 = get_rdata(header, plen, end2, buff2 + left2, (MAXDNAME *2) - left2, &p2, &dp2)) == 0) +- { +- quit = 1; +- len2 = end2 - p2; +- memcpy(buff2 + left2, p2, len2); +- } +- len2 += left2; +- +- if (len1 > len2) +- left1 = len1 - len2, left2 = 0, len = len2; +- else +- left2 = len2 - len1, left1 = 0, len = len1; +- +- rc = (len == 0) ? 0 : memcmp(buff1, buff2, len); +- +- if (rc > 0 || (rc == 0 && quit && len1 > len2)) +- { +- unsigned char *tmp = rrset[i+1]; +- rrset[i+1] = rrset[i]; +- rrset[i] = tmp; +- swap = quit = 1; +- } +- else if (rc == 0 && quit && len1 == len2) ++ ok1 = get_rdata(header, plen, &state1); ++ ok2 = get_rdata(header, plen, &state2); ++ ++ if (!ok1 && !ok2) + { + /* Two RRs are equal, remove one copy. RFC 4034, para 6.3 */ + for (j = i+1; j < rrsetidx-1; j++) + rrset[j] = rrset[j+1]; + rrsetidx--; + i--; ++ break; ++ } ++ else if (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, struct dns_header *header, size_t plen, in + wire_len = to_wire(keyname); + hash->update(ctx, (unsigned int)wire_len, (unsigned char*)keyname); + from_wire(keyname); ++ ++#define RRBUFLEN 300 /* Most RRs are smaller than this. */ + + for (i = 0; i < rrsetidx; ++i) + { +- int seg; +- unsigned char *end, *cp; +- u16 len, *dp; ++ int j; ++ struct rdata_state state; ++ u16 len; ++ unsigned char rrbuf[RRBUFLEN]; + + p = rrset[i]; +- ++ + if (!extract_name(header, plen, &p, name, 1, 10)) + return STAT_BOGUS; + +@@ -566,12 +578,11 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in + /* if more labels than in RRsig name, hash *. 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, struct dns_header *header, size_t plen, in + if (!CHECK_LEN(header, p, plen, rdlen)) + return STAT_BOGUS; + +- end = p + rdlen; ++ /* canonicalise rdata and calculate length of same, use ++ name buffer as workspace for get_rdata. */ ++ state.ip = p; ++ state.op = NULL; ++ state.desc = rr_desc; ++ state.buff = name; ++ state.end = p + rdlen; + +- /* canonicalise rdata and calculate length of same, use name buffer as workspace. +- Note that name buffer is twice MAXDNAME long in DNSSEC mode. */ +- cp = p; +- dp = rr_desc; +- for (len = 0; (seg = get_rdata(header, plen, end, name, MAXDNAME * 2, &cp, &dp)) != 0; len += seg); +- len += end - cp; +- len = htons(len); ++ for (j = 0; get_rdata(header, plen, &state); j++) ++ if (j < RRBUFLEN) ++ rrbuf[j] = *state.op; ++ ++ len = htons((u16)j); + hash->update(ctx, 2, (unsigned char *)&len); ++ ++ /* If the RR is shorter than RRBUFLEN (most of them, in practice) ++ then we can just digest it now. If it exceeds RRBUFLEN we have to ++ go back to the start and do it in chunks. */ ++ if (j >= RRBUFLEN) ++ { ++ state.ip = p; ++ state.op = NULL; ++ state.desc = rr_desc; ++ ++ for (j = 0; get_rdata(header, plen, &state); j++) ++ { ++ rrbuf[j] = *state.op; ++ ++ if (j == RRBUFLEN - 1) ++ { ++ hash->update(ctx, RRBUFLEN, rrbuf); ++ j = -1; ++ } ++ } ++ } + +- /* Now canonicalise again and digest. */ +- cp = p; +- dp = rr_desc; +- while ((seg = get_rdata(header, plen, end, name, MAXDNAME * 2, &cp, &dp))) +- hash->update(ctx, seg, (unsigned char *)name); +- if (cp != end) +- hash->update(ctx, end - cp, cp); ++ if (j != 0) ++ hash->update(ctx, j, rrbuf); + } + + hash->digest(ctx, hash->digest_size, digest); +-- +2.26.2 + diff --git a/SOURCES/dnsmasq-2.79-CVE-2020-25684.patch b/SOURCES/dnsmasq-2.79-CVE-2020-25684.patch new file mode 100644 index 0000000..faee079 --- /dev/null +++ b/SOURCES/dnsmasq-2.79-CVE-2020-25684.patch @@ -0,0 +1,94 @@ +From 04ef96f428beebd07b457b51b5d2f26d3099f1a5 Mon Sep 17 00:00:00 2001 +From: Simon Kelley +Date: Thu, 12 Nov 2020 18:49:23 +0000 +Subject: [PATCH 2/4] 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. +--- + src/forward.c | 37 ++++++++++++++++++++++++++++--------- + 1 file changed, 28 insertions(+), 9 deletions(-) + +diff --git a/src/forward.c b/src/forward.c +index cdd11d3..85eab27 100644 +--- 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); +@@ -780,7 +780,7 @@ void reply_query(int fd, int family, time_t now) + 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; + + /* log_query gets called indirectly all over the place, so +@@ -2195,14 +2195,25 @@ struct frec *get_new_frec(time_t now, int *wait, int force) + } + + /* 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; + } +@@ -2263,12 +2274,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; ++ } + } + + +-- +2.26.2 + diff --git a/SOURCES/dnsmasq-2.79-CVE-2020-25685.patch b/SOURCES/dnsmasq-2.79-CVE-2020-25685.patch new file mode 100644 index 0000000..8cfb4be --- /dev/null +++ b/SOURCES/dnsmasq-2.79-CVE-2020-25685.patch @@ -0,0 +1,586 @@ +From 6cbbae54c9232f182cc76f05962a07244d748b75 Mon Sep 17 00:00:00 2001 +From: Simon Kelley +Date: Thu, 12 Nov 2020 22:06:07 +0000 +Subject: [PATCH 3/4] 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. +--- + Makefile | 3 +- + bld/Android.mk | 3 +- + src/dnsmasq.h | 11 +- + src/dnssec.c | 31 ----- + src/forward.c | 43 ++----- + src/hash_questions.c | 281 +++++++++++++++++++++++++++++++++++++++++++ + src/rfc1035.c | 49 -------- + 7 files changed, 297 insertions(+), 124 deletions(-) + create mode 100644 src/hash_questions.c + +diff --git a/Makefile b/Makefile +index 98ec760..cbbe5d7 100644 +--- a/Makefile ++++ b/Makefile +@@ -76,7 +76,8 @@ objs = cache.o rfc1035.o util.o option.o forward.o network.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 ++ poll.o rrfilter.o edns0.o arp.o crypto.o \ ++ hash_questions.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 80ec842..2db29c1 100644 +--- a/bld/Android.mk ++++ b/bld/Android.mk +@@ -10,7 +10,8 @@ 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 rrfilter.c edns0.c arp.c crypto.c ++ loop.c inotify.c poll.c rrfilter.c edns0.c arp.c \ ++ crypto.c hash_questions.c + + LOCAL_MODULE := dnsmasq + +diff --git a/src/dnsmasq.h b/src/dnsmasq.h +index 6773b69..f31503d 100644 +--- a/src/dnsmasq.h ++++ b/src/dnsmasq.h +@@ -615,11 +615,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; +@@ -1156,7 +1152,6 @@ int check_for_bogus_wildcard(struct dns_header *header, size_t qlen, char *name, + 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, +@@ -1184,9 +1179,11 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch + 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); +diff --git a/src/dnssec.c b/src/dnssec.c +index 0c703ac..b2dda1b 100644 +--- a/src/dnssec.c ++++ b/src/dnssec.c +@@ -2095,35 +2095,4 @@ size_t dnssec_generate_query(struct dns_header *header, unsigned char *end, char + 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 */ +diff --git a/src/forward.c b/src/forward.c +index 85eab27..7ffcaf7 100644 +--- a/src/forward.c ++++ b/src/forward.c +@@ -239,19 +239,16 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, + struct 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); + + (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. +@@ -741,9 +738,6 @@ void reply_query(int fd, int family, time_t now) + size_t nn; + struct server *server; + void *hash; +-#ifndef HAVE_DNSSEC +- unsigned int crc; +-#endif + + /* packet buffer overwritten */ + daemon->srv_save = NULL; +@@ -773,12 +767,7 @@ void reply_query(int fd, int family, time_t now) + 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; +@@ -1006,8 +995,7 @@ void reply_query(int fd, int family, time_t now) + nn = dnssec_generate_query(header,((unsigned char *) header) + server->edns_pktsz, + daemon->keyname, forward->class, T_DS, &server->addr, server->edns_pktsz); + } +- 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 */ +@@ -1840,15 +1828,9 @@ unsigned char *tcp_request(int confd, time_t now, + 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. */ +@@ -1973,20 +1955,11 @@ unsigned char *tcp_request(int confd, time_t now, + /* 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, +@@ -2201,7 +2174,7 @@ static struct frec *lookup_frec(unsigned short id, int fd, int family, void *has + + 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) +diff --git a/src/hash_questions.c b/src/hash_questions.c +new file mode 100644 +index 0000000..ae112ac +--- /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 . ++*/ ++ ++ ++/* 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 +diff --git a/src/rfc1035.c b/src/rfc1035.c +index b078b59..d413f58 100644 +--- a/src/rfc1035.c ++++ b/src/rfc1035.c +@@ -335,55 +335,6 @@ unsigned char *skip_section(unsigned char *ansp, int count, struct dns_header *h + 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); +-- +2.26.2 + diff --git a/SOURCES/dnsmasq-2.79-CVE-2020-25686-2.patch b/SOURCES/dnsmasq-2.79-CVE-2020-25686-2.patch new file mode 100644 index 0000000..6b3a9a8 --- /dev/null +++ b/SOURCES/dnsmasq-2.79-CVE-2020-25686-2.patch @@ -0,0 +1,61 @@ +From e9db3fdf55cdf3175d96db90313c33f848985960 Mon Sep 17 00:00:00 2001 +From: Simon Kelley +Date: Fri, 4 Dec 2020 18:35:11 +0000 +Subject: [PATCH] Small cleanups in frec_src datastucture handling. + +--- + src/forward.c | 22 +++++++++++++--------- + 1 file changed, 13 insertions(+), 9 deletions(-) + +diff --git a/src/forward.c b/src/forward.c +index 25ad8b1..c496f86 100644 +--- a/src/forward.c ++++ b/src/forward.c +@@ -364,7 +364,10 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, + 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) +@@ -401,6 +404,7 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, + 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); +@@ -2262,16 +2266,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; +-- +2.26.2 + diff --git a/SOURCES/dnsmasq-2.79-CVE-2020-25686.patch b/SOURCES/dnsmasq-2.79-CVE-2020-25686.patch new file mode 100644 index 0000000..fd9ad9b --- /dev/null +++ b/SOURCES/dnsmasq-2.79-CVE-2020-25686.patch @@ -0,0 +1,327 @@ +From 7d98347aeeb575a022ffae630ee02da215b6f37b Mon Sep 17 00:00:00 2001 +From: Simon Kelley +Date: Wed, 18 Nov 2020 18:34:55 +0000 +Subject: [PATCH 4/4] 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. +--- + src/dnsmasq.h | 19 ++++--- + src/forward.c | 141 ++++++++++++++++++++++++++++++++++++++++++-------- + 2 files changed, 132 insertions(+), 28 deletions(-) + +diff --git a/src/dnsmasq.h b/src/dnsmasq.h +index f31503d..6744e2b 100644 +--- a/src/dnsmasq.h ++++ b/src/dnsmasq.h +@@ -613,21 +613,26 @@ 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; +- struct all_addr dest; ++ struct frec_src { ++ union mysockaddr source; ++ struct 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; + #ifdef HAVE_IPV6 + struct randfd *rfd6; + #endif +- 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 +@@ -1033,6 +1038,8 @@ extern struct daemon { + #endif + unsigned int local_answer, queries_forwarded, auth_answer; + struct frec *frec_list; ++ struct frec_src *free_frec_src; ++ int frec_src_count; + struct serverfd *sfds; + struct irec *interfaces; + struct listener *listeners; +diff --git a/src/forward.c b/src/forward.c +index 7ffcaf7..db378a5 100644 +--- a/src/forward.c ++++ b/src/forward.c +@@ -20,6 +20,8 @@ static struct frec *lookup_frec(unsigned short id, int fd, int family, void *has + 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); + +@@ -238,15 +240,28 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, + int type = SERV_DO_DNSSEC, norebind = 0; + struct 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 + int do_dnssec = 0; + #endif +- unsigned int gotname = extract_request(header, plen, daemon->namebuff, NULL); ++ 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))) + { +@@ -322,6 +337,39 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, + } + 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); + +@@ -329,22 +377,22 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, + 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) +@@ -400,9 +448,9 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, + 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; +@@ -539,7 +587,7 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr, + 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 */ + } + +@@ -774,8 +822,8 @@ void reply_query(int fd, int family, time_t now) + + /* 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)) +@@ -978,6 +1026,7 @@ void reply_query(int fd, int family, time_t now) + #ifdef HAVE_IPV6 + new->rfd6 = NULL; + #endif ++ new->frec_src.next = NULL; + new->flags &= ~(FREC_DNSKEY_QUERY | FREC_DS_QUERY); + + new->dependent = forward; /* to find query awaiting new one. */ +@@ -1099,9 +1148,11 @@ void reply_query(int fd, int family, time_t now) + + 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 +@@ -1116,9 +1167,22 @@ void reply_query(int fd, int family, time_t now) + nn = resize_packet(header, nn, NULL, 0); + } + #endif +- send_from(forward->fd, option_bool(OPT_NOWILD) || option_bool (OPT_CLEVERBIND), daemon->packet, nn, +- &forward->source, &forward->dest, forward->iface); ++ for (src = &forward->frec_src; src; src = src->next) ++ { ++ header->id = htons(src->orig_id); ++ ++ 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 */ + } + } +@@ -2053,6 +2117,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; +@@ -2196,17 +2271,39 @@ static struct frec *lookup_frec_by_sender(unsigned short id, + 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() + { +-- +2.26.2 + diff --git a/SPECS/dnsmasq.spec b/SPECS/dnsmasq.spec index 061f998..37482e0 100644 --- a/SPECS/dnsmasq.spec +++ b/SPECS/dnsmasq.spec @@ -13,7 +13,7 @@ Name: dnsmasq Version: 2.79 -Release: 13%{?extraversion:.%{extraversion}}%{?dist} +Release: 15%{?extraversion:.%{extraversion}}%{?dist} Summary: A lightweight DHCP/caching DNS server License: GPLv2 or GPLv3 @@ -52,6 +52,11 @@ Patch18: dnsmasq-2.81-tag-filtering-of-dhcp-host-directives.patch Patch19: dnsmasq-2.81-correct-range-check-of-dhcp-host-prefix.patch Patch20: dnsmasq-2.81-optimize-fds-close.patch Patch21: dnsmasq-2.81-rh1829448.patch +Patch22: dnsmasq-2.79-CVE-2020-25681.patch +Patch23: dnsmasq-2.79-CVE-2020-25684.patch +Patch24: dnsmasq-2.79-CVE-2020-25685.patch +Patch25: dnsmasq-2.79-CVE-2020-25686.patch +Patch26: dnsmasq-2.79-CVE-2020-25686-2.patch # This is workaround to nettle bug #1549190 # https://bugzilla.redhat.com/show_bug.cgi?id=1549190 @@ -106,6 +111,11 @@ server's leases. %patch19 -p1 -b .rh1779187-4 %patch20 -p1 -b .rh1816613 %patch21 -p1 -b .rh1829448 +%patch22 -p1 -b .CVE-2020-25681 +%patch23 -p1 -b .CVE-2020-25684 +%patch24 -p1 -b .CVE-2020-25685 +%patch25 -p1 -b .CVE-2020-25686 +%patch26 -p1 -b .CVE-2020-25686-2 # use /var/lib/dnsmasq instead of /var/lib/misc for file in dnsmasq.conf.example man/dnsmasq.8 man/es/dnsmasq.8 src/config.h; do @@ -171,11 +181,10 @@ install -Dpm 644 %{SOURCE2} %{buildroot}%{_sysusersdir}/dnsmasq.conf %pre #precreate users so that rpm can install files owned by that user -%sysusers_create_inline %(cat %{SOURCE2}) +%sysusers_create_package %{name} %{SOURCE2} %post #https://fedoraproject.org/wiki/Changes/SystemdSysusers -%sysusers_create %systemd_post dnsmasq.service %preun @@ -206,6 +215,14 @@ install -Dpm 644 %{SOURCE2} %{buildroot}%{_sysusersdir}/dnsmasq.conf %{_mandir}/man1/dhcp_* %changelog +* Wed Nov 25 2020 Petr Menšík - 2.79-15 +- Fix various issues in dnssec validation (CVE-2020-25681) +- Accept responses only on correct sockets (CVE-2020-25684) +- Use strong verification on queries (CVE-2020-25685) + +* Wed Aug 26 2020 Tomas Korbar - 2.79-14 +- Honor sysusers.d during installation (#1819684) + * Tue May 05 2020 Petr Menšík - 2.79-13 - Fix mixed address family reservations on DHCP (#1829448)