import dnsmasq-2.79-13.el8_3.1

This commit is contained in:
CentOS Sources 2021-01-19 07:38:27 -05:00 committed by Andrew Lukoshko
parent 7b92db154a
commit 6f3f42358a
6 changed files with 1449 additions and 1 deletions

View File

@ -0,0 +1,364 @@
From 6689e336042d2ca0b075f8db1fe30ed6b47c7d4c Mon Sep 17 00:00:00 2001
From: Simon Kelley <simon@thekelleys.org.uk>
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 *.<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, 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

View File

@ -0,0 +1,94 @@
From 04ef96f428beebd07b457b51b5d2f26d3099f1a5 Mon Sep 17 00:00:00 2001
From: Simon Kelley <simon@thekelleys.org.uk>
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

View File

@ -0,0 +1,586 @@
From 6cbbae54c9232f182cc76f05962a07244d748b75 Mon Sep 17 00:00:00 2001
From: Simon Kelley <simon@thekelleys.org.uk>
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 <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
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

View File

@ -0,0 +1,61 @@
From e9db3fdf55cdf3175d96db90313c33f848985960 Mon Sep 17 00:00:00 2001
From: Simon Kelley <simon@thekelleys.org.uk>
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

View File

@ -0,0 +1,327 @@
From 7d98347aeeb575a022ffae630ee02da215b6f37b Mon Sep 17 00:00:00 2001
From: Simon Kelley <simon@thekelleys.org.uk>
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

View File

@ -10,10 +10,11 @@
%endif
%define _hardened_build 1
%define __systemd_someargs_0(:) %{nil}
Name: dnsmasq
Version: 2.79
Release: 13%{?extraversion:.%{extraversion}}%{?dist}
Release: 13%{?extraversion:.%{extraversion}}%{?dist}.1
Summary: A lightweight DHCP/caching DNS server
License: GPLv2 or GPLv3
@ -52,6 +53,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 +112,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
@ -206,6 +217,11 @@ install -Dpm 644 %{SOURCE2} %{buildroot}%{_sysusersdir}/dnsmasq.conf
%{_mandir}/man1/dhcp_*
%changelog
* Wed Dec 16 2020 Petr Menšík <pemensik@redhat.com> - 2.79-13.1
- 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)
* Tue May 05 2020 Petr Menšík <pemensik@redhat.com> - 2.79-13
- Fix mixed address family reservations on DHCP (#1829448)