|
|
c7e749 |
From 312723debac9b9c328c9399a191a5fc637f56e73 Mon Sep 17 00:00:00 2001
|
|
|
c7e749 |
From: Simon Kelley <simon@thekelleys.org.uk>
|
|
|
c7e749 |
Date: Wed, 18 Nov 2020 18:34:55 +0000
|
|
|
c7e749 |
Subject: [PATCH] Handle multiple identical near simultaneous DNS queries
|
|
|
c7e749 |
better.
|
|
|
c7e749 |
|
|
|
c7e749 |
Previously, such queries would all be forwarded
|
|
|
c7e749 |
independently. This is, in theory, inefficent but in practise
|
|
|
c7e749 |
not a problem, _except_ that is means that an answer for any
|
|
|
c7e749 |
of the forwarded queries will be accepted and cached.
|
|
|
c7e749 |
An attacker can send a query multiple times, and for each repeat,
|
|
|
c7e749 |
another {port, ID} becomes capable of accepting the answer he is
|
|
|
c7e749 |
sending in the blind, to random IDs and ports. The chance of a
|
|
|
c7e749 |
succesful attack is therefore multiplied by the number of repeats
|
|
|
c7e749 |
of the query. The new behaviour detects repeated queries and
|
|
|
c7e749 |
merely stores the clients sending repeats so that when the
|
|
|
c7e749 |
first query completes, the answer can be sent to all the
|
|
|
c7e749 |
clients who asked. Refer: CERT VU#434904.
|
|
|
c7e749 |
---
|
|
|
c7e749 |
src/dnsmasq.h | 19 ++++---
|
|
|
c7e749 |
src/forward.c | 144 ++++++++++++++++++++++++++++++++++++++++++--------
|
|
|
c7e749 |
2 files changed, 135 insertions(+), 28 deletions(-)
|
|
|
c7e749 |
|
|
|
c7e749 |
diff --git a/src/dnsmasq.h b/src/dnsmasq.h
|
|
|
c7e749 |
index 9f0af7f..9f5b48b 100644
|
|
|
c7e749 |
--- a/src/dnsmasq.h
|
|
|
c7e749 |
+++ b/src/dnsmasq.h
|
|
|
c7e749 |
@@ -597,21 +597,26 @@ struct hostsfile {
|
|
|
c7e749 |
#define FREC_DO_QUESTION 64
|
|
|
c7e749 |
#define FREC_ADDED_PHEADER 128
|
|
|
c7e749 |
#define FREC_TEST_PKTSZ 256
|
|
|
c7e749 |
-#define FREC_HAS_EXTRADATA 512
|
|
|
c7e749 |
+#define FREC_HAS_EXTRADATA 512
|
|
|
c7e749 |
+#define FREC_HAS_PHEADER 1024
|
|
|
c7e749 |
|
|
|
c7e749 |
#define HASH_SIZE 32 /* SHA-256 digest size */
|
|
|
c7e749 |
|
|
|
c7e749 |
struct frec {
|
|
|
c7e749 |
- union mysockaddr source;
|
|
|
c7e749 |
- struct all_addr dest;
|
|
|
c7e749 |
+ struct frec_src {
|
|
|
c7e749 |
+ union mysockaddr source;
|
|
|
c7e749 |
+ struct all_addr dest;
|
|
|
c7e749 |
+ unsigned int iface, log_id;
|
|
|
c7e749 |
+ unsigned short orig_id;
|
|
|
c7e749 |
+ struct frec_src *next;
|
|
|
c7e749 |
+ } frec_src;
|
|
|
c7e749 |
struct server *sentto; /* NULL means free */
|
|
|
c7e749 |
struct randfd *rfd4;
|
|
|
c7e749 |
#ifdef HAVE_IPV6
|
|
|
c7e749 |
struct randfd *rfd6;
|
|
|
c7e749 |
#endif
|
|
|
c7e749 |
- unsigned int iface;
|
|
|
c7e749 |
- unsigned short orig_id, new_id;
|
|
|
c7e749 |
- int log_id, fd, forwardall, flags;
|
|
|
c7e749 |
+ unsigned short new_id;
|
|
|
c7e749 |
+ int fd, forwardall, flags;
|
|
|
c7e749 |
time_t time;
|
|
|
c7e749 |
unsigned char *hash[HASH_SIZE];
|
|
|
c7e749 |
#ifdef HAVE_DNSSEC
|
|
|
c7e749 |
@@ -1007,6 +1012,8 @@ extern struct daemon {
|
|
|
c7e749 |
#endif
|
|
|
c7e749 |
unsigned int local_answer, queries_forwarded, auth_answer;
|
|
|
c7e749 |
struct frec *frec_list;
|
|
|
c7e749 |
+ struct frec_src *free_frec_src;
|
|
|
c7e749 |
+ int frec_src_count;
|
|
|
c7e749 |
struct serverfd *sfds;
|
|
|
c7e749 |
struct irec *interfaces;
|
|
|
c7e749 |
struct listener *listeners;
|
|
|
c7e749 |
diff --git a/src/forward.c b/src/forward.c
|
|
|
c7e749 |
index 596f593..a0b1f1d 100644
|
|
|
c7e749 |
--- a/src/forward.c
|
|
|
c7e749 |
+++ b/src/forward.c
|
|
|
c7e749 |
@@ -20,6 +20,8 @@ static struct frec *lookup_frec(unsigned short id, int fd, int family, void *has
|
|
|
c7e749 |
static struct frec *lookup_frec_by_sender(unsigned short id,
|
|
|
c7e749 |
union mysockaddr *addr,
|
|
|
c7e749 |
void *hash);
|
|
|
c7e749 |
+static struct frec *lookup_frec_by_query(void *hash, unsigned int flags);
|
|
|
c7e749 |
+
|
|
|
c7e749 |
static unsigned short get_id(void);
|
|
|
c7e749 |
static void free_frec(struct frec *f);
|
|
|
c7e749 |
|
|
|
c7e749 |
@@ -236,15 +238,28 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
|
|
|
c7e749 |
int type = SERV_DO_DNSSEC, norebind = 0;
|
|
|
c7e749 |
struct all_addr *addrp = NULL;
|
|
|
c7e749 |
unsigned int flags = 0;
|
|
|
c7e749 |
+ unsigned int fwd_flags = 0;
|
|
|
c7e749 |
struct server *start = NULL;
|
|
|
c7e749 |
void *hash = hash_questions(header, plen, daemon->namebuff);
|
|
|
c7e749 |
#ifdef HAVE_DNSSEC
|
|
|
c7e749 |
int do_dnssec = 0;
|
|
|
c7e749 |
#endif
|
|
|
c7e749 |
- unsigned int gotname = extract_request(header, plen, daemon->namebuff, NULL);
|
|
|
c7e749 |
+ unsigned int gotname = extract_request(header, plen, daemon->namebuff, NULL);
|
|
|
c7e749 |
+ unsigned char *oph = find_pseudoheader(header, plen, NULL, NULL, NULL, NULL);
|
|
|
c7e749 |
|
|
|
c7e749 |
(void)do_bit;
|
|
|
c7e749 |
-
|
|
|
c7e749 |
+
|
|
|
c7e749 |
+ if (header->hb4 & HB4_CD)
|
|
|
c7e749 |
+ fwd_flags |= FREC_CHECKING_DISABLED;
|
|
|
c7e749 |
+ if (ad_reqd)
|
|
|
c7e749 |
+ fwd_flags |= FREC_AD_QUESTION;
|
|
|
c7e749 |
+ if (oph)
|
|
|
c7e749 |
+ fwd_flags |= FREC_HAS_PHEADER;
|
|
|
c7e749 |
+#ifdef HAVE_DNSSEC
|
|
|
c7e749 |
+ if (do_bit)
|
|
|
c7e749 |
+ fwd_flags |= FREC_DO_QUESTION;
|
|
|
c7e749 |
+#endif
|
|
|
c7e749 |
+
|
|
|
c7e749 |
/* may be no servers available. */
|
|
|
c7e749 |
if (forward || (forward = lookup_frec_by_sender(ntohs(header->id), udpaddr, hash)))
|
|
|
c7e749 |
{
|
|
|
c7e749 |
@@ -321,6 +336,41 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
|
|
|
c7e749 |
}
|
|
|
c7e749 |
else
|
|
|
c7e749 |
{
|
|
|
c7e749 |
+ /* Query from new source, but the same query may be in progress
|
|
|
c7e749 |
+ from another source. If so, just add this client to the
|
|
|
c7e749 |
+ list that will get the reply.
|
|
|
c7e749 |
+
|
|
|
c7e749 |
+ Note that is the EDNS client subnet option is in use, we can't do this,
|
|
|
c7e749 |
+ as the clients (and therefore query EDNS options) will be different
|
|
|
c7e749 |
+ for each query. The EDNS subnet code has checks to avoid
|
|
|
c7e749 |
+ attacks in this case. */
|
|
|
c7e749 |
+ if (!option_bool(OPT_CLIENT_SUBNET) && (forward = lookup_frec_by_query(hash, fwd_flags)))
|
|
|
c7e749 |
+ {
|
|
|
c7e749 |
+ if (!daemon->free_frec_src &&
|
|
|
c7e749 |
+ daemon->frec_src_count < daemon->ftabsize &&
|
|
|
c7e749 |
+ (daemon->free_frec_src = whine_malloc(sizeof(struct frec_src))))
|
|
|
c7e749 |
+ {
|
|
|
c7e749 |
+ daemon->frec_src_count++;
|
|
|
c7e749 |
+ daemon->free_frec_src->next = NULL;
|
|
|
c7e749 |
+ }
|
|
|
c7e749 |
+
|
|
|
c7e749 |
+ /* If we've been spammed with many duplicates, just drop the query. */
|
|
|
c7e749 |
+ if (daemon->free_frec_src)
|
|
|
c7e749 |
+ {
|
|
|
c7e749 |
+ struct frec_src *new = daemon->free_frec_src;
|
|
|
c7e749 |
+ daemon->free_frec_src = new->next;
|
|
|
c7e749 |
+ new->next = forward->frec_src.next;
|
|
|
c7e749 |
+ forward->frec_src.next = new;
|
|
|
c7e749 |
+ new->orig_id = ntohs(header->id);
|
|
|
c7e749 |
+ new->source = *udpaddr;
|
|
|
c7e749 |
+ new->dest = *dst_addr;
|
|
|
c7e749 |
+ new->log_id = daemon->log_id;
|
|
|
c7e749 |
+ new->iface = dst_iface;
|
|
|
c7e749 |
+ }
|
|
|
c7e749 |
+
|
|
|
c7e749 |
+ return 1;
|
|
|
c7e749 |
+ }
|
|
|
c7e749 |
+
|
|
|
c7e749 |
if (gotname)
|
|
|
c7e749 |
flags = search_servers(now, &addrp, gotname, daemon->namebuff, &type, &domain, &norebind);
|
|
|
c7e749 |
|
|
|
c7e749 |
@@ -328,22 +378,23 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
|
|
|
c7e749 |
do_dnssec = type & SERV_DO_DNSSEC;
|
|
|
c7e749 |
#endif
|
|
|
c7e749 |
type &= ~SERV_DO_DNSSEC;
|
|
|
c7e749 |
-
|
|
|
c7e749 |
+
|
|
|
c7e749 |
if (daemon->servers && !flags)
|
|
|
c7e749 |
forward = get_new_frec(now, NULL, 0);
|
|
|
c7e749 |
/* table full - flags == 0, return REFUSED */
|
|
|
c7e749 |
|
|
|
c7e749 |
if (forward)
|
|
|
c7e749 |
{
|
|
|
c7e749 |
- forward->source = *udpaddr;
|
|
|
c7e749 |
- forward->dest = *dst_addr;
|
|
|
c7e749 |
- forward->iface = dst_iface;
|
|
|
c7e749 |
- forward->orig_id = ntohs(header->id);
|
|
|
c7e749 |
+ forward->frec_src.source = *udpaddr;
|
|
|
c7e749 |
+ forward->frec_src.orig_id = ntohs(header->id);
|
|
|
c7e749 |
+ forward->frec_src.dest = *dst_addr;
|
|
|
c7e749 |
+ forward->frec_src.iface = dst_iface;
|
|
|
c7e749 |
+ forward->frec_src.next = NULL;
|
|
|
c7e749 |
forward->new_id = get_id();
|
|
|
c7e749 |
forward->fd = udpfd;
|
|
|
c7e749 |
memcpy(forward->hash, hash, HASH_SIZE);
|
|
|
c7e749 |
forward->forwardall = 0;
|
|
|
c7e749 |
- forward->flags = 0;
|
|
|
c7e749 |
+ forward->flags = fwd_flags;
|
|
|
c7e749 |
if (norebind)
|
|
|
c7e749 |
forward->flags |= FREC_NOREBIND;
|
|
|
c7e749 |
if (header->hb4 & HB4_CD)
|
|
|
c7e749 |
@@ -397,9 +448,9 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
|
|
|
c7e749 |
size_t edns0_len;
|
|
|
c7e749 |
|
|
|
c7e749 |
/* If a query is retried, use the log_id for the retry when logging the answer. */
|
|
|
c7e749 |
- forward->log_id = daemon->log_id;
|
|
|
c7e749 |
+ forward->frec_src.log_id = daemon->log_id;
|
|
|
c7e749 |
|
|
|
c7e749 |
- edns0_len = add_edns0_config(header, plen, ((unsigned char *)header) + PACKETSZ, &forward->source, now, &subnet;;
|
|
|
c7e749 |
+ edns0_len = add_edns0_config(header, plen, ((unsigned char *)header) + PACKETSZ, &forward->frec_src.source, now, &subnet;;
|
|
|
c7e749 |
|
|
|
c7e749 |
if (edns0_len != plen)
|
|
|
c7e749 |
{
|
|
|
c7e749 |
@@ -536,7 +587,7 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
|
|
|
c7e749 |
return 1;
|
|
|
c7e749 |
|
|
|
c7e749 |
/* could not send on, prepare to return */
|
|
|
c7e749 |
- header->id = htons(forward->orig_id);
|
|
|
c7e749 |
+ header->id = htons(forward->frec_src.orig_id);
|
|
|
c7e749 |
free_frec(forward); /* cancel */
|
|
|
c7e749 |
}
|
|
|
c7e749 |
|
|
|
c7e749 |
@@ -773,8 +824,8 @@ void reply_query(int fd, int family, time_t now)
|
|
|
c7e749 |
|
|
|
c7e749 |
/* log_query gets called indirectly all over the place, so
|
|
|
c7e749 |
pass these in global variables - sorry. */
|
|
|
c7e749 |
- daemon->log_display_id = forward->log_id;
|
|
|
c7e749 |
- daemon->log_source_addr = &forward->source;
|
|
|
c7e749 |
+ daemon->log_display_id = forward->frec_src.log_id;
|
|
|
c7e749 |
+ daemon->log_source_addr = &forward->frec_src.source;
|
|
|
c7e749 |
|
|
|
c7e749 |
if (daemon->ignore_addr && RCODE(header) == NOERROR &&
|
|
|
c7e749 |
check_for_ignored_address(header, n, daemon->ignore_addr))
|
|
|
c7e749 |
@@ -973,6 +1024,7 @@ void reply_query(int fd, int family, time_t now)
|
|
|
c7e749 |
#ifdef HAVE_IPV6
|
|
|
c7e749 |
new->rfd6 = NULL;
|
|
|
c7e749 |
#endif
|
|
|
c7e749 |
+ new->frec_src.next = NULL;
|
|
|
c7e749 |
new->flags &= ~(FREC_DNSKEY_QUERY | FREC_DS_QUERY);
|
|
|
c7e749 |
|
|
|
c7e749 |
new->dependent = forward; /* to find query awaiting new one. */
|
|
|
c7e749 |
@@ -1094,9 +1146,11 @@ void reply_query(int fd, int family, time_t now)
|
|
|
c7e749 |
|
|
|
c7e749 |
if ((nn = process_reply(header, now, forward->sentto, (size_t)n, check_rebind, no_cache_dnssec, cache_secure, bogusanswer,
|
|
|
c7e749 |
forward->flags & FREC_AD_QUESTION, forward->flags & FREC_DO_QUESTION,
|
|
|
c7e749 |
- forward->flags & FREC_ADDED_PHEADER, forward->flags & FREC_HAS_SUBNET, &forward->source)))
|
|
|
c7e749 |
+ forward->flags & FREC_ADDED_PHEADER, forward->flags & FREC_HAS_SUBNET, &forward->frec_src.source)))
|
|
|
c7e749 |
{
|
|
|
c7e749 |
- header->id = htons(forward->orig_id);
|
|
|
c7e749 |
+ struct frec_src *src;
|
|
|
c7e749 |
+
|
|
|
c7e749 |
+ header->id = htons(forward->frec_src.orig_id);
|
|
|
c7e749 |
header->hb4 |= HB4_RA; /* recursion if available */
|
|
|
c7e749 |
#ifdef HAVE_DNSSEC
|
|
|
c7e749 |
/* We added an EDNSO header for the purpose of getting DNSSEC RRs, and set the value of the UDP payload size
|
|
|
c7e749 |
@@ -1111,9 +1165,22 @@ void reply_query(int fd, int family, time_t now)
|
|
|
c7e749 |
nn = resize_packet(header, nn, NULL, 0);
|
|
|
c7e749 |
}
|
|
|
c7e749 |
#endif
|
|
|
c7e749 |
- send_from(forward->fd, option_bool(OPT_NOWILD) || option_bool (OPT_CLEVERBIND), daemon->packet, nn,
|
|
|
c7e749 |
- &forward->source, &forward->dest, forward->iface);
|
|
|
c7e749 |
+ for (src = &forward->frec_src; src; src = src->next)
|
|
|
c7e749 |
+ {
|
|
|
c7e749 |
+ header->id = htons(src->orig_id);
|
|
|
c7e749 |
+
|
|
|
c7e749 |
+ send_from(forward->fd, option_bool(OPT_NOWILD) || option_bool (OPT_CLEVERBIND), daemon->packet, nn,
|
|
|
c7e749 |
+ &src->source, &src->dest, src->iface);
|
|
|
c7e749 |
+
|
|
|
c7e749 |
+ if (option_bool(OPT_EXTRALOG) && src != &forward->frec_src)
|
|
|
c7e749 |
+ {
|
|
|
c7e749 |
+ daemon->log_display_id = src->log_id;
|
|
|
c7e749 |
+ daemon->log_source_addr = &src->source;
|
|
|
c7e749 |
+ log_query(F_UPSTREAM, "query", NULL, "duplicate");
|
|
|
c7e749 |
+ }
|
|
|
c7e749 |
+ }
|
|
|
c7e749 |
}
|
|
|
c7e749 |
+
|
|
|
c7e749 |
free_frec(forward); /* cancel */
|
|
|
c7e749 |
}
|
|
|
c7e749 |
}
|
|
|
c7e749 |
@@ -2034,6 +2101,17 @@ void free_rfd(struct randfd *rfd)
|
|
|
c7e749 |
|
|
|
c7e749 |
static void free_frec(struct frec *f)
|
|
|
c7e749 |
{
|
|
|
c7e749 |
+ struct frec_src *src, *tmp;
|
|
|
c7e749 |
+
|
|
|
c7e749 |
+ /* add back to freelist of not the record builtin to every frec. */
|
|
|
c7e749 |
+ for (src = f->frec_src.next; src; src = tmp)
|
|
|
c7e749 |
+ {
|
|
|
c7e749 |
+ tmp = src->next;
|
|
|
c7e749 |
+ src->next = daemon->free_frec_src;
|
|
|
c7e749 |
+ daemon->free_frec_src = src;
|
|
|
c7e749 |
+ }
|
|
|
c7e749 |
+
|
|
|
c7e749 |
+ f->frec_src.next = NULL;
|
|
|
c7e749 |
free_rfd(f->rfd4);
|
|
|
c7e749 |
f->rfd4 = NULL;
|
|
|
c7e749 |
f->sentto = NULL;
|
|
|
c7e749 |
@@ -2175,17 +2253,39 @@ static struct frec *lookup_frec_by_sender(unsigned short id,
|
|
|
c7e749 |
void *hash)
|
|
|
c7e749 |
{
|
|
|
c7e749 |
struct frec *f;
|
|
|
c7e749 |
+ struct frec_src *src;
|
|
|
c7e749 |
+
|
|
|
c7e749 |
+ for (f = daemon->frec_list; f; f = f->next)
|
|
|
c7e749 |
+ if (f->sentto &&
|
|
|
c7e749 |
+ !(f->flags & (FREC_DNSKEY_QUERY | FREC_DS_QUERY)) &&
|
|
|
c7e749 |
+ memcmp(hash, f->hash, HASH_SIZE) == 0)
|
|
|
c7e749 |
+ for (src = &f->frec_src; src; src = src->next)
|
|
|
c7e749 |
+ if (src->orig_id == id &&
|
|
|
c7e749 |
+ sockaddr_isequal(&src->source, addr))
|
|
|
c7e749 |
+ return f;
|
|
|
c7e749 |
+
|
|
|
c7e749 |
+ return NULL;
|
|
|
c7e749 |
+}
|
|
|
c7e749 |
+
|
|
|
c7e749 |
+static struct frec *lookup_frec_by_query(void *hash, unsigned int flags)
|
|
|
c7e749 |
+{
|
|
|
c7e749 |
+ struct frec *f;
|
|
|
c7e749 |
+
|
|
|
c7e749 |
+ /* FREC_DNSKEY and FREC_DS_QUERY are never set in flags, so the test below
|
|
|
c7e749 |
+ ensures that no frec created for internal DNSSEC query can be returned here. */
|
|
|
c7e749 |
+
|
|
|
c7e749 |
+#define FLAGMASK (FREC_CHECKING_DISABLED | FREC_AD_QUESTION | FREC_DO_QUESTION \
|
|
|
c7e749 |
+ | FREC_HAS_PHEADER | FREC_DNSKEY_QUERY | FREC_DS_QUERY)
|
|
|
c7e749 |
|
|
|
c7e749 |
for(f = daemon->frec_list; f; f = f->next)
|
|
|
c7e749 |
if (f->sentto &&
|
|
|
c7e749 |
- f->orig_id == id &&
|
|
|
c7e749 |
- memcmp(hash, f->hash, HASH_SIZE) == 0 &&
|
|
|
c7e749 |
- sockaddr_isequal(&f->source, addr))
|
|
|
c7e749 |
+ (f->flags & FLAGMASK) == flags &&
|
|
|
c7e749 |
+ memcmp(hash, f->hash, HASH_SIZE) == 0)
|
|
|
c7e749 |
return f;
|
|
|
c7e749 |
-
|
|
|
c7e749 |
+
|
|
|
c7e749 |
return NULL;
|
|
|
c7e749 |
}
|
|
|
c7e749 |
-
|
|
|
c7e749 |
+
|
|
|
c7e749 |
/* Send query packet again, if we can. */
|
|
|
c7e749 |
void resend_query()
|
|
|
c7e749 |
{
|
|
|
c7e749 |
--
|
|
|
c7e749 |
2.26.2
|
|
|
c7e749 |
|