Blame SOURCES/dnsmasq-2.79-CVE-2020-25686.patch

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