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

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