commit 5eab3c6f13562230f1eeab3788585f73ffbf469d
Author: Tomas Korbar <tkorbar@redhat.com>
Date:   Wed Feb 28 16:51:17 2024 +0100

    Fix CVE 2023-50387 and CVE 2023-50868
    
    Add limits on the resources used to do DNSSEC validation.
    DNSSEC introduces a potential CPU DoS, because a crafted domain
    can force a validator to a large number of cryptographic
    operations whilst attempting to do validation. When using TCP
    transport a DNSKEY RRset contain thousands of members and any
    RRset can have thousands of signatures. The potential number
    of signature validations to follow the RFC for validation
    for one RRset is the cross product of the keys and signatures,
    so millions. In practice, the actual numbers are much lower,
    so attacks can be mitigated by limiting the amount of
    cryptographic "work" to a much lower amount. The actual
    limits are number a signature validation fails per RRset(20),
    number of signature validations and hash computations
    per query(200), number of sub-queries  to fetch  DS and DNSKEY
    RRsets per query(40), and the number of iterations in a
    NSEC3 record(150). These values are sensible, but there is, as yet,
    no standardisation on the values for a "conforming" domain, so a
    new option --dnssec-limit is provided should they need to be altered.
    The algorithm to validate DS records has also been altered to reduce
    the maximum work from cross product of the number of DS records and
    number of DNSKEYs to the cross product of the number of DS records
    and supported DS digest types. As the number of DS digest types
    is in single figures, this reduces the exposure.
    
    Credit is due to Elias Heftrig, Haya Schulmann, Niklas Vogel,
    and Michael Waidner from the German National Research Center for
    Applied Cybersecurity ATHENE for finding this vulnerability.
    
    CVE 2023-50387 and CVE 2023-50868 apply.
    Note that the is a security vulnerablity only when DNSSEC validation
    is enabled.

Index: dnsmasq-2.78/man/dnsmasq.8
===================================================================
--- dnsmasq-2.78.orig/man/dnsmasq.8
+++ dnsmasq-2.78/man/dnsmasq.8
@@ -754,6 +754,15 @@ Copy the DNSSEC Authenticated Data bit f
 alternative to having dnsmasq validate DNSSEC, but it depends on the security of the network between 
 dnsmasq and the upstream servers, and the trustworthiness of the upstream servers.
 .TP
+.B --dnssec-limits=<limit>[,<limit>.......]
+Override the default resource limits applied to DNSSEC validation. Cryptographic operations are expensive and crafted domains
+can DoS a DNSSEC validator by forcing it to do hundreds of thousands of such operations. To avoid this, the dnsmasq validation code
+applies limits on how much work will be expended in validation. If any of the limits are exceeded, the validation will fail and the
+domain treated as BOGUS. There are four limits, in order(default values in parens): number a signature validation fails per RRset(20), number of signature validations and
+hash computations per query(200), number of sub-queries to fetch DS and DNSKEY RRsets per query(40), and the number of iterations in a NSEC3 record(150).
+The maximum values reached during validation are stored, and dumped as part of the stats generated by SIGUSR1. Supplying a limit value of 0 leaves the default in place, so
+\fB--dnssec-limits=0,0,20\fP sets the number of sub-queries to 20 whilst leaving the other limits at default values.
+.TP
 .B --dnssec-debug
 Set debugging mode for the DNSSEC validation, set the Checking Disabled bit on upstream queries, 
 and don't convert replies which do not validate to responses with
Index: dnsmasq-2.78/src/config.h
===================================================================
--- dnsmasq-2.78.orig/src/config.h
+++ dnsmasq-2.78/src/config.h
@@ -22,7 +22,10 @@
 #define EDNS_PKTSZ 4096 /* default max EDNS.0 UDP packet from RFC5625 */
 #define SAFE_PKTSZ 1280 /* "go anywhere" UDP packet size */
 #define KEYBLOCK_LEN 40 /* choose to minimise fragmentation when storing DNSSEC keys */
-#define DNSSEC_WORK 50 /* Max number of queries to validate one question */
+#define DNSSEC_LIMIT_WORK 40 /* Max number of queries to validate one question */
+#define DNSSEC_LIMIT_SIG_FAIL 20 /* Number of signature that can fail to validate in one answer */
+#define DNSSEC_LIMIT_CRYPTO 200 /* max no. of crypto operations to validate one query. */
+#define DNSSEC_LIMIT_NSEC3_ITERS 150 /* Max. number if iterations allowed in NSEC3 record. */
 #define TIMEOUT 10 /* drop UDP queries after TIMEOUT seconds */
 #define FORWARD_TEST 50 /* try all servers every 50 queries */
 #define FORWARD_TIME 20 /* or 20 seconds */
Index: dnsmasq-2.78/src/dnsmasq.h
===================================================================
--- dnsmasq-2.78.orig/src/dnsmasq.h
+++ dnsmasq-2.78/src/dnsmasq.h
@@ -598,15 +598,30 @@ struct hostsfile {
 
 
 /* DNSSEC status values. */
-#define STAT_SECURE             1
-#define STAT_INSECURE           2
-#define STAT_BOGUS              3
-#define STAT_NEED_DS            4
-#define STAT_NEED_KEY           5
-#define STAT_TRUNCATED          6
-#define STAT_SECURE_WILDCARD    7
-#define STAT_OK                 8
-#define STAT_ABANDONED          9
+#define STAT_SECURE             0x10000
+#define STAT_INSECURE           0x20000
+#define STAT_BOGUS              0x30000
+#define STAT_NEED_DS            0x40000
+#define STAT_NEED_KEY           0x50000
+#define STAT_TRUNCATED          0x60000
+#define STAT_SECURE_WILDCARD    0x70000
+#define STAT_OK                 0x80000
+#define STAT_ABANDONED          0x90000
+
+#define DNSSEC_FAIL_NYV         0x0001 /* key not yet valid */
+#define DNSSEC_FAIL_EXP         0x0002 /* key expired */
+#define DNSSEC_FAIL_INDET       0x0004 /* indetermined */
+#define DNSSEC_FAIL_NOKEYSUP    0x0008 /* no supported key algo. */
+#define DNSSEC_FAIL_NOSIG       0x0010 /* No RRsigs */
+#define DNSSEC_FAIL_NOZONE      0x0020 /* No Zone bit set */
+#define DNSSEC_FAIL_NONSEC      0x0040 /* No NSEC */
+#define DNSSEC_FAIL_NODSSUP     0x0080 /* no supported DS algo. */
+#define DNSSEC_FAIL_NOKEY       0x0100 /* no DNSKEY */
+#define DNSSEC_FAIL_NSEC3_ITERS 0x0200 /* too many iterations in NSEC3 */
+#define DNSSEC_FAIL_BADPACKET   0x0400 /* bad packet */
+#define DNSSEC_FAIL_WORK        0x0800 /* too much crypto */
+
+#define STAT_ISEQUAL(a, b)  (((a) & 0xffff0000) == (b))
 
 #define FREC_NOREBIND           1
 #define FREC_CHECKING_DISABLED  2
@@ -637,7 +652,7 @@ struct frec {
   time_t time;
   unsigned char *hash[HASH_SIZE];
 #ifdef HAVE_DNSSEC 
-  int class, work_counter;
+  int class, work_counter, validate_counter;
   struct blockdata *stash; /* Saved reply, whilst we validate */
   size_t stash_len;
   struct frec *dependent; /* Query awaiting internally-generated DNSKEY or DS query */
@@ -673,6 +688,12 @@ struct frec {
 #define LEASE_TA            64  /* IPv6 temporary lease */
 #define LEASE_HAVE_HWADDR  128  /* Have set hwaddress */
 
+#define LIMIT_SIG_FAIL    0
+#define LIMIT_CRYPTO      1
+#define LIMIT_WORK        2
+#define LIMIT_NSEC3_ITERS 3
+#define LIMIT_MAX         4
+
 struct dhcp_lease {
   int clid_len;          /* length of client identifier */
   unsigned char *clid;   /* clientid */
@@ -1024,6 +1045,7 @@ extern struct daemon {
   struct ds_config *ds;
   int dnssec_no_time_check;
   int back_to_the_future;
+  int limit[LIMIT_MAX];
   char *timestamp_file;
 #endif
 
@@ -1180,10 +1202,12 @@ int in_zone(struct auth_zone *zone, char
 
 /* dnssec.c */
 size_t dnssec_generate_query(struct dns_header *header, unsigned char *end, char *name, int class, int type, union mysockaddr *addr, int edns_pktsz);
-int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class);
-int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class);
+int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, char *name,
+			  char *keyname, int class, int *validate_count);
+int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char *name,
+		       char *keyname, int class, int *validate_count);
 int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int *class,
-			  int check_unsigned, int *neganswer, int *nons);
+			  int check_unsigned, int *neganswer, int *nons, int *nsec_ttl, int *validate_count);
 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);
@@ -1561,7 +1585,7 @@ int do_poll(int timeout);
 
 /* rrfilter.c */
 size_t rrfilter(struct dns_header *header, size_t plen, int mode);
-u16 *rrfilter_desc(int type);
+short *rrfilter_desc(int type);
 int expand_workspace(unsigned char ***wkspc, int *szp, int new);
 
 /* edns0.c */
Index: dnsmasq-2.78/src/dnssec.c
===================================================================
--- dnsmasq-2.78.orig/src/dnssec.c
+++ dnsmasq-2.78/src/dnssec.c
@@ -507,10 +507,8 @@ int setup_timestamp(void)
 }
 
 /* Check whether today/now is between date_start and date_end */
-static int check_date_range(u32 date_start, u32 date_end)
+static int is_check_date(unsigned long curtime)
 {
-  unsigned long curtime = time(0);
- 
   /* Checking timestamps may be temporarily disabled */
     
   /* If the current time if _before_ the timestamp
@@ -532,15 +530,10 @@ static int check_date_range(u32 date_sta
 	  queue_event(EVENT_RELOAD); /* purge cache */
 	} 
 
-      if (daemon->back_to_the_future == 0)
-	return 1;
+      return daemon->back_to_the_future;
     }
-  else if (daemon->dnssec_no_time_check)
-    return 1;
-  
-  /* We must explicitly check against wanted values, because of SERIAL_UNDEF */
-  return serial_compare_32(curtime, date_start) == SERIAL_GT
-    && serial_compare_32(curtime, date_end) == SERIAL_LT;
+  else
+    return !daemon->dnssec_no_time_check;
 }
 
 /* Return bytes of canonicalised rrdata one by one.
@@ -553,7 +546,7 @@ static int check_date_range(u32 date_sta
    On returning 0, the end has been reached.
 */
 struct rdata_state {
-  u16 *desc;
+  short *desc;
   size_t c;
   unsigned char *end, *ip, *op;
   char *buff;
@@ -574,7 +567,7 @@ static int get_rdata(struct dns_header *
     {
       d = *(state->desc);
       
-      if (d == (u16)-1)
+      if (d == -1)
 	{
 	  /* all the bytes to the end. */
 	  if ((state->c = state->end - state->ip) != 0)
@@ -621,7 +614,7 @@ static int get_rdata(struct dns_header *
 
 /* Bubble sort the RRset into the canonical order. */
 
-static int sort_rrset(struct dns_header *header, size_t plen, u16 *rr_desc, int rrsetidx, 
+static int sort_rrset(struct dns_header *header, size_t plen, short *rr_desc, int rrsetidx, 
                       unsigned char **rrset, char *buff1, char *buff2)
 {
   int swap, i, j;
@@ -654,36 +647,63 @@ static int sort_rrset(struct dns_header
             return rrsetidx; /* short packet */
           state2.end = state2.ip + rdlen2; 
 	  
-          while (1)
+	  /* If the RR has no names in it then canonicalisation
+	     is the identity function and we can compare
+	     the RRs directly. If not we compare the 
+	     canonicalised RRs one byte at a time. */
+	  if (*rr_desc == -1)	
             {
-              int ok1, ok2;
-              
-              ok1 = get_rdata(header, plen, &state1);
-              ok2 = get_rdata(header, plen, &state2);
-	      
-              if (!ok1 && !ok2)
+	      int rdmin = rdlen1 > rdlen2 ? rdlen2 : rdlen1;
+	      int cmp = memcmp(state1.ip, state2.ip, rdmin);
+
+	      if (cmp > 0 || (cmp == 0 && rdlen1 > rdmin))
+		{
+		  unsigned char *tmp = rrset[i+1];
+		  rrset[i+1] = rrset[i];
+		  rrset[i] = tmp;
+		  swap = 1;
+		}
+	      else if (cmp == 0 && (rdlen1 == rdlen2))
                 {
                   /* 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 (ok2 && (!ok1 || *state2.op > *state1.op))
-                break;
-              
-              /* arrive here when bytes are equal, go round the loop again
-                 and compare the next ones. */
             }
+	  else
+	    /* Comparing canonicalised RRs, byte-at-a-time. */
+	    while (1)
+	      {
+		int ok1, ok2;
+		
+		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 (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);
   
@@ -703,7 +723,7 @@ static int explore_rrset(struct dns_head
   int gotkey = 0;
 
   if (!(p = skip_questions(header, plen)))
-    return STAT_BOGUS;
+    return 0;
 
    /* look for RRSIGs for this RRset and get pointers to each RR in the set. */
   for (rrsetidx = 0, sigidx = 0, j = ntohs(header->ancount) + ntohs(header->nscount); 
@@ -715,14 +735,14 @@ static int explore_rrset(struct dns_head
       pstart = p;
       
       if (!(res = extract_name(header, plen, &p, name, 0, 10)))
-	return STAT_BOGUS; /* bad packet */
+	return 0; /* bad packet */
       
       GETSHORT(stype, p);
       GETSHORT(sclass, p);
-      p += 4; /* TTL */
-      
+           
       pdata = p;
 
+      p += 4; /* TTL */
       GETSHORT(rdlen, p);
       
       if (!CHECK_LEN(header, p, plen, rdlen))
@@ -785,7 +805,7 @@ static int explore_rrset(struct dns_head
 		  sigs[sigidx++] = pdata;
 		} 
 	      
-	      p = pdata + 2; /* restore for ADD_RDLEN */
+	      p = pdata + 6; /* restore for ADD_RDLEN */
 	    }
 	}
       
@@ -799,12 +819,24 @@ static int explore_rrset(struct dns_head
   return 1;
 }
 
+int dec_counter(int *counter, char *message)
+{
+  if ((*counter)-- == 0)
+    {
+      my_syslog(LOG_WARNING, "limit exceeded: %s", message ? message : _("per-query crypto work"));
+      return 1;
+    }
+
+  return 0;
+}
+
 /* Validate a single RRset (class, type, name) in the supplied DNS reply 
    Return code:
    STAT_SECURE   if it validates.
    STAT_SECURE_WILDCARD if it validates and is the result of wildcard expansion.
    (In this case *wildcard_out points to the "body" of the wildcard within name.) 
    STAT_BOGUS    signature is wrong, bad packet.
+   STAT_ABANDONED validation abandoned do to excess resource usage.
    STAT_NEED_KEY need DNSKEY to complete validation (name is returned in keyname)
    STAT_NEED_DS  need DS to complete validation (name is returned in keyname)
 
@@ -814,16 +846,23 @@ static int explore_rrset(struct dns_head
    Name is unchanged on exit. keyname is used as workspace and trashed.
 
    Call explore_rrset first to find and count RRs and sigs.
+
+   ttl_out is the floor on TTL, based on TTL and orig_ttl and expiration of sig used to validate.
 */
 static int validate_rrset(time_t now, struct dns_header *header, size_t plen, int class, int type, int sigidx, int rrsetidx, 
-			  char *name, char *keyname, char **wildcard_out, struct blockdata *key, int keylen, int algo_in, int keytag_in)
+			  char *name, char *keyname, char **wildcard_out, struct blockdata *key, int keylen,
+			  int algo_in, int keytag_in, unsigned long *ttl_out, int *validate_counter)
 {
   unsigned char *p;
-  int rdlen, j, name_labels, algo, labels, orig_ttl, key_tag;
+  int rdlen, j, name_labels, algo, labels, key_tag, sig_fail_cnt;
   struct crec *crecp = NULL;
-  u16 *rr_desc = rrfilter_desc(type);
-  u32 sig_expiration, sig_inception
-;
+  short *rr_desc = rrfilter_desc(type);
+  u32 sig_expiration, sig_inception;
+  int failflags = DNSSEC_FAIL_NOSIG | DNSSEC_FAIL_NYV | DNSSEC_FAIL_EXP | DNSSEC_FAIL_NOKEYSUP;
+  
+  unsigned long curtime = time(0);
+  int time_check = is_check_date(curtime);
+  
   if (wildcard_out)
     *wildcard_out = NULL;
   
@@ -835,16 +874,19 @@ static int validate_rrset(time_t now, st
   sort_rrset(header, plen, rr_desc, rrsetidx, rrset, daemon->workspacename, keyname);
          
   /* Now try all the sigs to try and find one which validates */
-  for (j = 0; j <sigidx; j++)
+  for (sig_fail_cnt = daemon->limit[LIMIT_SIG_FAIL], j = 0; j <sigidx; j++)
     {
       unsigned char *psav, *sig, *digest;
       int i, wire_len, sig_len;
       const struct nettle_hash *hash;
       void *ctx;
       char *name_start;
-      u32 nsigttl;
+      u32 nsigttl, ttl, orig_ttl;
+
+      failflags &= ~DNSSEC_FAIL_NOSIG;
       
       p = sigs[j];
+      GETLONG(ttl, p);
       GETSHORT(rdlen, p); /* rdlen >= 18 checked previously */
       psav = p;
       
@@ -859,16 +901,47 @@ static int validate_rrset(time_t now, st
       if (!extract_name(header, plen, &p, keyname, 1, 0))
 	return STAT_BOGUS;
 
-      if (!check_date_range(sig_inception, sig_expiration) ||
-	  labels > name_labels ||
-	  !(hash = hash_find(algo_digest_name(algo))) ||
+      if (!time_check)
+	failflags &= ~(DNSSEC_FAIL_NYV | DNSSEC_FAIL_EXP);
+      else
+	{
+	  /* We must explicitly check against wanted values, because of SERIAL_UNDEF */
+	  if (serial_compare_32(curtime, sig_inception) == SERIAL_LT)
+	    continue;
+	  else
+	    failflags &= ~DNSSEC_FAIL_NYV;
+	  
+	  if (serial_compare_32(curtime, sig_expiration) == SERIAL_GT)
+	    continue;
+	  else
+	    failflags &= ~DNSSEC_FAIL_EXP;
+	}
+
+      if (!(hash = hash_find(algo_digest_name(algo))))
+	continue;
+      else
+	failflags &= ~DNSSEC_FAIL_NOKEYSUP;
+      
+      if (labels > name_labels ||
 	  !hash_init(hash, &ctx, &digest))
 	continue;
       
       /* OK, we have the signature record, see if the relevant DNSKEY is in the cache. */
       if (!key && !(crecp = cache_find_by_name(NULL, keyname, now, F_DNSKEY)))
 	return STAT_NEED_KEY;
-      
+
+       if (ttl_out)
+	 {
+	   /* 4035 5.3.3 rules on TTLs */
+	   if (orig_ttl < ttl)
+	     ttl = orig_ttl;
+	   
+	   if (time_check && difftime(sig_expiration, curtime) < ttl)
+	     ttl = difftime(sig_expiration, curtime);
+
+	   *ttl_out = ttl;
+	 }
+       
       sig = p;
       sig_len = rdlen - (p - psav);
               
@@ -879,7 +952,7 @@ static int validate_rrset(time_t now, st
       hash->update(ctx, (unsigned int)wire_len, (unsigned char*)keyname);
       from_wire(keyname);
 
-#define RRBUFLEN 300 /* Most RRs are smaller than this. */
+#define RRBUFLEN 128 /* Most RRs are smaller than this. */
       
       for (i = 0; i < rrsetidx; ++i)
 	{
@@ -916,63 +989,85 @@ static int validate_rrset(time_t now, st
 	  hash->update(ctx, (unsigned int)wire_len, (unsigned char *)name_start);
 	  hash->update(ctx, 4, p); /* class and type */
 	  hash->update(ctx, 4, (unsigned char *)&nsigttl);
-	  
-	  p += 8; /* skip class, type, ttl */
+
+	  p += 8; /* skip type, class, ttl */
 	  GETSHORT(rdlen, p);
 	  if (!CHECK_LEN(header, p, plen, rdlen))
 	    return STAT_BOGUS; 
-	  
-	  /* 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;
-	  
-	  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)
+
+	  /* Optimisation for RR types which need no cannonicalisation.
+	     This includes DNSKEY DS NSEC and NSEC3, which are also long, so
+	     it saves lots of calls to get_rdata, and avoids the pessimal
+	     segmented insertion, even with a small rrbuf[].
+	     
+	     If canonicalisation is not needed, a simple insertion into the hash works.
+	  */
+	  if (*rr_desc == -1)
+	    {
+	      len = htons(rdlen);
+	      hash->update(ctx, 2, (unsigned char *)&len);
+	      hash->update(ctx, rdlen, p);
+	    }
+	  else
 	    {
+	      /* 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;
+	      
 	      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)
 		{
-		   rrbuf[j] = *state.op;
-
-		   if (j == RRBUFLEN - 1)
-		     {
-		       hash->update(ctx, RRBUFLEN, rrbuf);
-		       j = -1;
-		     }
+		  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;
+			}
+		    }
 		}
+	      
+	      if (j != 0)
+		hash->update(ctx, j, rrbuf);
 	    }
-	  
-	  if (j != 0)
-	    hash->update(ctx, j, rrbuf);
 	}
      
       hash->digest(ctx, hash->digest_size, digest);
       
       /* namebuff used for workspace above, restore to leave unchanged on exit */
       p = (unsigned char*)(rrset[0]);
-      extract_name(header, plen, &p, name, 1, 0);
+      if (!extract_name(header, plen, &p, name, 1, 0))
+	return STAT_BOGUS;
 
       if (key)
 	{
-	  if (algo_in == algo && keytag_in == key_tag &&
-	      verify(key, keylen, sig, sig_len, digest, hash->digest_size, algo))
-	    return STAT_SECURE;
+	  if (algo_in == algo && keytag_in == key_tag)
+	    {
+	      if (dec_counter(validate_counter, NULL))
+		return STAT_ABANDONED;
+	     	      
+	      if (verify(key, keylen, sig, sig_len, digest, hash->digest_size, algo))
+		return STAT_SECURE;
+	    }
 	}
       else
 	{
@@ -980,13 +1075,25 @@ static int validate_rrset(time_t now, st
 	  for (; crecp; crecp = cache_find_by_name(crecp, keyname, now, F_DNSKEY))
 	    if (crecp->addr.key.algo == algo && 
 		crecp->addr.key.keytag == key_tag &&
-		crecp->uid == (unsigned int)class &&
-		verify(crecp->addr.key.keydata, crecp->addr.key.keylen, sig, sig_len, digest, hash->digest_size, algo))
-	      return (labels < name_labels) ? STAT_SECURE_WILDCARD : STAT_SECURE;
+		crecp->uid == (unsigned int)class)
+	      {
+		if (dec_counter(validate_counter, NULL))
+		  return STAT_ABANDONED;
+		
+		if (verify(crecp->addr.key.keydata, crecp->addr.key.keylen, sig, sig_len, digest, hash->digest_size, algo))
+		  return (labels < name_labels) ? STAT_SECURE_WILDCARD : STAT_SECURE;
+		
+		/* An attacker can waste a lot of our CPU by setting up a giant DNSKEY RRSET full of failing
+		   keys, all of which we have to try. Since many failing keys is not likely for
+		   a legitimate domain, set a limit on how many can fail. */
+		if (dec_counter(&sig_fail_cnt, _("per-RRSet signature fails")))
+		  return STAT_ABANDONED;
+	      }
 	}
     }
 
-  return STAT_BOGUS;
+  /* If we reach this point, no verifying key was found */
+  return STAT_BOGUS | failflags | DNSSEC_FAIL_NOKEY;
 }
  
 
@@ -996,36 +1103,45 @@ static int validate_rrset(time_t now, st
          STAT_OK        Done, key(s) in cache.
 	 STAT_BOGUS     No DNSKEYs found, which  can be validated with DS,
 	                or self-sign for DNSKEY RRset is not valid, bad packet.
+	 STAT_ABANDONED resource exhaustion.
 	 STAT_NEED_DS   DS records to validate a key not found, name in keyname 
-	 STAT_NEED_KEY  DNSKEY records to validate a key not found, name in keyname 
 */
-int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class)
+int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, char *name,
+			  char *keyname, int class, int *validate_counter)
 {
-  unsigned char *psave, *p = (unsigned char *)(header+1);
+  unsigned char *psave, *p = (unsigned char *)(header+1), *keyaddr;
   struct crec *crecp, *recp1;
-  int rc, j, qtype, qclass, ttl, rdlen, flags, algo, valid, keytag;
-  struct blockdata *key;
+  int rc, j, qtype, qclass, rdlen, flags, algo, keytag, sigcnt, rrcnt;
+  unsigned long ttl, sig_ttl;
   struct all_addr a;
+  int failflags = DNSSEC_FAIL_NODSSUP | DNSSEC_FAIL_NOZONE;
+  char valid_digest[255];
+  static unsigned char **cached_digest;
+  static size_t cached_digest_size = 0;
 
-  if (ntohs(header->qdcount) != 1 ||
-      !extract_name(header, plen, &p, name, 1, 4))
-    return STAT_BOGUS;
+  if (ntohs(header->qdcount) != 1 || RCODE(header) != NOERROR || !extract_name(header, plen, &p, name, 1, 4))
+    return STAT_BOGUS | DNSSEC_FAIL_NOKEY;
 
   GETSHORT(qtype, p);
   GETSHORT(qclass, p);
   
-  if (qtype != T_DNSKEY || qclass != class || ntohs(header->ancount) == 0)
-    return STAT_BOGUS;
+  if (qtype != T_DNSKEY || qclass != class ||
+      !explore_rrset(header, plen, class, T_DNSKEY, name, keyname, &sigcnt, &rrcnt) ||
+      rrcnt == 0)
+    return STAT_BOGUS | DNSSEC_FAIL_NOKEY;
 
+  if (sigcnt == 0)
+    return STAT_BOGUS | DNSSEC_FAIL_NOSIG;
+  
   /* See if we have cached a DS record which validates this key */
   if (!(crecp = cache_find_by_name(NULL, name, now, F_DS)))
     {
       strcpy(keyname, name);
       return STAT_NEED_DS;
     }
-  
+
   /* NOTE, we need to find ONE DNSKEY which matches the DS */
-  for (valid = 0, j = ntohs(header->ancount); j != 0 && !valid; j--) 
+  for (j = ntohs(header->ancount); j != 0; j--) 
     {
       /* Ensure we have type, class  TTL and length */
       if (!(rc = extract_name(header, plen, &p, name, 0, 10)))
@@ -1036,7 +1152,7 @@ int dnssec_validate_by_ds(time_t now, st
       GETLONG(ttl, p);
       GETSHORT(rdlen, p);
  
-      if (!CHECK_LEN(header, p, plen, rdlen) || rdlen < 4)
+      if (!CHECK_LEN(header, p, plen, rdlen))
 	return STAT_BOGUS; /* bad packet */
       
       if (qclass != class || qtype != T_DNSKEY || rc == 2)
@@ -1044,171 +1160,233 @@ int dnssec_validate_by_ds(time_t now, st
 	  p += rdlen;
 	  continue;
 	}
-            
+
+      if (rdlen < 5)
+	return STAT_BOGUS;  /* min 1 byte key! */
+
       psave = p;
       
       GETSHORT(flags, p);
       if (*p++ != 3)
-	return STAT_BOGUS;
-      algo = *p++;
-      keytag = dnskey_keytag(algo, flags, p, rdlen - 4);
-      key = NULL;
-      
-      /* key must have zone key flag set */
-      if (flags & 0x100)
-	key = blockdata_alloc((char*)p, rdlen - 4);
-      
-      p = psave;
-      
-      if (!ADD_RDLEN(header, p, plen, rdlen))
 	{
-	  if (key)
-	    blockdata_free(key);
-	  return STAT_BOGUS; /* bad packet */
+	  p = psave + rdlen;
+	  continue;
 	}
+      algo = *p++;
+      keyaddr = p;
+      keytag = dnskey_keytag(algo, flags, keyaddr, rdlen - 4);	      
+      
+      p = psave + rdlen; 
 
-      /* No zone key flag or malloc failure */
-      if (!key)
+       /* key must have zone key flag set */
+      if (!(flags & 0x100))
 	continue;
+
+      failflags &= ~DNSSEC_FAIL_NOZONE;
+      
+      /* clear digest cache. */
+      memset(valid_digest, 0, sizeof(valid_digest));
       
       for (recp1 = crecp; recp1; recp1 = cache_find_by_name(recp1, name, now, F_DS))
 	{
 	  void *ctx;
 	  unsigned char *digest, *ds_digest;
 	  const struct nettle_hash *hash;
-	  int sigcnt, rrcnt;
+	  int wire_len;
+	  
+	  if ((recp1->flags & F_NEG) ||
+	      recp1->addr.ds.algo != algo ||
+	      recp1->addr.ds.keytag != keytag ||
+	      recp1->uid != (unsigned int)class)
+	    continue;
+	  
+	  if (!(hash = hash_find(ds_digest_name(recp1->addr.ds.digest))))
+	    continue;
+	     
+	  failflags &= ~DNSSEC_FAIL_NODSSUP;
+	      
+	  if (recp1->addr.ds.keylen != (int)hash->digest_size ||
+	      !(ds_digest = blockdata_retrieve(recp1->addr.ds.keydata, recp1->addr.ds.keylen, NULL)))
+	    continue;
 
-	  if (recp1->addr.ds.algo == algo && 
-	      recp1->addr.ds.keytag == keytag &&
-	      recp1->uid == (unsigned int)class &&
-	      (hash = hash_find(ds_digest_name(recp1->addr.ds.digest))) &&
-	      hash_init(hash, &ctx, &digest))
-	    
+	  if (valid_digest[recp1->addr.ds.digest])
+	    digest = cached_digest[recp1->addr.ds.digest];
+	  else
 	    {
-	      int wire_len = to_wire(name);
+	      /* computing a hash is a unit of crypto work. */
+	      if (dec_counter(validate_counter, NULL))
+		return STAT_ABANDONED;
+	      
+	      if (!hash_init(hash, &ctx, &digest))
+		continue;
+	      
+	      wire_len = to_wire(name);
 	      
 	      /* Note that digest may be different between DSs, so 
-		 we can't move this outside the loop. */
+		 we can't move this outside the loop. We keep
+		 copies of each digest we make for this key,
+		 so maximum digest work is O(keys x digests_types)
+		 rather then O(keys x DSs) */
 	      hash->update(ctx, (unsigned int)wire_len, (unsigned char *)name);
 	      hash->update(ctx, (unsigned int)rdlen, psave);
 	      hash->digest(ctx, hash->digest_size, digest);
 	      
 	      from_wire(name);
 	      
-	      if (!(recp1->flags & F_NEG) &&
-		  recp1->addr.ds.keylen == (int)hash->digest_size &&
-		  (ds_digest = blockdata_retrieve(recp1->addr.key.keydata, recp1->addr.ds.keylen, NULL)) &&
-		  memcmp(ds_digest, digest, recp1->addr.ds.keylen) == 0 &&
-		  explore_rrset(header, plen, class, T_DNSKEY, name, keyname, &sigcnt, &rrcnt) &&
-		  sigcnt != 0 && rrcnt != 0 &&
-		  validate_rrset(now, header, plen, class, T_DNSKEY, sigcnt, rrcnt, name, keyname, 
-				 NULL, key, rdlen - 4, algo, keytag) == STAT_SECURE)
+	      if (recp1->addr.ds.digest >= cached_digest_size)
 		{
-		  valid = 1;
-		  break;
+		  unsigned char **new;
+		
+		  /* whine_malloc zeros memory */
+		  if ((new = whine_malloc((recp1->addr.ds.digest + 5) * sizeof(unsigned char *))))
+		    {
+		      if (cached_digest_size != 0)
+			{
+			  memcpy(new, cached_digest, cached_digest_size * sizeof(unsigned char *));
+			  free(cached_digest);
+			}
+		      
+		      cached_digest_size = recp1->addr.ds.digest + 5;
+		      cached_digest = new;
+		    }
+		}
+		    
+	      if (recp1->addr.ds.digest < cached_digest_size)
+		{
+		  if (!cached_digest[recp1->addr.ds.digest])
+		    cached_digest[recp1->addr.ds.digest] = whine_malloc(recp1->addr.ds.keylen);
+	      
+		  if (cached_digest[recp1->addr.ds.digest])
+		    {
+		      memcpy(cached_digest[recp1->addr.ds.digest], digest, recp1->addr.ds.keylen);
+		      valid_digest[recp1->addr.ds.digest] = 1;
+		    }
 		}
 	    }
-	}
-      blockdata_free(key);
-    }
 
-  if (valid)
-    {
-      /* DNSKEY RRset determined to be OK, now cache it. */
-      cache_start_insert();
-      
-      p = skip_questions(header, plen);
-
-      for (j = ntohs(header->ancount); j != 0; j--) 
-	{
-	  /* Ensure we have type, class  TTL and length */
-	  if (!(rc = extract_name(header, plen, &p, name, 0, 10)))
-	    return STAT_BOGUS; /* bad packet */
-	  
-	  GETSHORT(qtype, p); 
-	  GETSHORT(qclass, p);
-	  GETLONG(ttl, p);
-	  GETSHORT(rdlen, p);
-	    
-	  if (!CHECK_LEN(header, p, plen, rdlen))
-	    return STAT_BOGUS; /* bad packet */
-	  
-	  if (qclass == class && rc == 1)
+	  if (memcmp(ds_digest, digest, recp1->addr.ds.keylen) == 0)
 	    {
-	      psave = p;
+	      /* Found the key validated by a DS record.
+		 Now check the self-sig for the entire key RRset using that key.
+		 Note that validate_rrset() will never return STAT_NEED_KEY here,
+		 since we supply the key it will use as an argument. */
+	      struct blockdata *key;
+	     	      
+	      if (!(key = blockdata_alloc((char *)keyaddr, rdlen - 4)))
+		break;
+	      	      
+	      rc = validate_rrset(now, header, plen, class, T_DNSKEY, sigcnt, rrcnt, name, keyname, 
+				  NULL, key, rdlen - 4, algo, keytag, &sig_ttl, validate_counter);
+	      
+	      blockdata_free(key);
+	      
+	      if (STAT_ISEQUAL(rc, STAT_ABANDONED))
+		return rc;
+	      
+	      /* can't validate KEY RRset with this key, see if there's another that
+		 will, which is validated by another DS. */
+	      if (!STAT_ISEQUAL(rc, STAT_SECURE))
+		break;
+	      
+	      /* DNSKEY RRset determined to be OK, now cache it. */
+	      cache_start_insert();
+	      
+	      p = skip_questions(header, plen);
 	      
-	      if (qtype == T_DNSKEY)
+	      for (j = ntohs(header->ancount); j != 0; j--) 
 		{
-		  if (rdlen < 4)
+		  /* Ensure we have type, class  TTL and length */
+		  if (!(rc = extract_name(header, plen, &p, name, 0, 10)))
 		    return STAT_BOGUS; /* bad packet */
 		  
-		  GETSHORT(flags, p);
-		  if (*p++ != 3)
-		    return STAT_BOGUS;
-		  algo = *p++;
-		  keytag = dnskey_keytag(algo, flags, p, rdlen - 4);
+		  GETSHORT(qtype, p); 
+		  GETSHORT(qclass, p);
+		  GETLONG(ttl, p);
+		  GETSHORT(rdlen, p);
 		  
-		  /* Cache needs to known class for DNSSEC stuff */
-		  a.addr.dnssec.class = class;
+		  /* TTL may be limited by sig. */
+		  if (sig_ttl < ttl)
+		    ttl = sig_ttl;
+
+		  if (!CHECK_LEN(header, p, plen, rdlen))
+		    return STAT_BOGUS; /* bad packet */
 		  
-		  if ((key = blockdata_alloc((char*)p, rdlen - 4)))
-		    {
-		      if (!(recp1 = cache_insert(name, &a, now, ttl, F_FORWARD | F_DNSKEY | F_DNSSECOK)))
-			{
-			  blockdata_free(key);
-			  return STAT_BOGUS;
-			}
-		      else
-			{
-			  a.addr.log.keytag = keytag;
-			  a.addr.log.algo = algo;
-			  if (verify_func(algo))
-			    log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DNSKEY keytag %hu, algo %hu");
-			  else
-			    log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DNSKEY keytag %hu, algo %hu (not supported)");
-			  
-			  recp1->addr.key.keylen = rdlen - 4;
-			  recp1->addr.key.keydata = key;
-			  recp1->addr.key.algo = algo;
-			  recp1->addr.key.keytag = keytag;
-			  recp1->addr.key.flags = flags;
-			}
-		    }
+		   psave = p;
+
+		   if (qclass == class && rc == 1 && qtype == T_DNSKEY)
+		     {
+		       if (rdlen < 4)
+			 return STAT_BOGUS; /* min 1 byte key! */
+		       
+		       GETSHORT(flags, p);
+		       if (*p++ == 3)
+			 {
+			   algo = *p++;
+			   keytag = dnskey_keytag(algo, flags, p, rdlen - 4);
+			   
+			   /* Cache needs to known class for DNSSEC stuff */
+			   a.addr.dnssec.class = class;
+
+			   if (!(key = blockdata_alloc((char*)p, rdlen - 4)))
+			     return STAT_BOGUS;
+			   
+			   if (!(recp1 = cache_insert(name, &a, now, ttl, F_FORWARD | F_DNSKEY | F_DNSSECOK)))
+			     {
+			       blockdata_free(key);
+			       return STAT_BOGUS;
+			     }
+			   
+			   a.addr.log.keytag = keytag;
+			   a.addr.log.algo = algo;
+			   if (algo_digest_name(algo))
+			     log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DNSKEY keytag %hu, algo %hu");
+			   else
+			     log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DNSKEY keytag %hu, algo %hu (not supported)");
+
+				recp1->addr.key.keylen = rdlen - 4;
+			    recp1->addr.key.keydata = key;
+			    recp1->addr.key.algo = algo;
+			    recp1->addr.key.keytag = keytag;
+			    recp1->addr.key.flags = flags;
+			 }
+		     }
+				  
+		   p = psave + rdlen;
 		}
-	      	      
-	      p = psave;
+	      
+	      /* commit cache insert. */
+	      cache_end_insert();
+	      return STAT_OK;
 	    }
-
-	  if (!ADD_RDLEN(header, p, plen, rdlen))
-	    return STAT_BOGUS; /* bad packet */
 	}
-      
-      /* commit cache insert. */
-      cache_end_insert();
-      return STAT_OK;
     }
 
   log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "BOGUS DNSKEY");
-  return STAT_BOGUS;
+  return STAT_BOGUS | failflags;
 }
 
 /* The DNS packet is expected to contain the answer to a DS query
-   Put all DSs in the answer which are valid into the cache.
+   Put all DSs in the answer which are valid and have hash and signature algos
+   we support into the cache.
    Also handles replies which prove that there's no DS at this location, 
    either because the zone is unsigned or this isn't a zone cut. These are
    cached too.
+   If none of the DS's are for supported algos, treat the answer as if 
+   it's a proof of no DS at this location. RFC4035 para 5.2.
    return codes:
    STAT_OK          At least one valid DS found and in cache.
    STAT_BOGUS       no DS in reply or not signed, fails validation, bad packet.
    STAT_NEED_KEY    DNSKEY records to validate a DS not found, name in keyname
    STAT_NEED_DS     DS record needed.
+   STAT_ABANDONED   resource exhaustion.
 */
 
-int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class)
+int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char *name,
+		       char *keyname, int class, int *validate_counter)
 {
   unsigned char *p = (unsigned char *)(header+1);
-  int qtype, qclass, rc, i, neganswer, nons;
-  int aclass, atype, rdlen;
+  int qtype, qclass, rc, i, neganswer = 0, nons = 0, servfail = 0, neg_ttl = 0, found_supported = 0;
+  int aclass, atype, rdlen, flags;
   unsigned long ttl;
   struct all_addr a;
 
@@ -1220,36 +1398,51 @@ int dnssec_validate_ds(time_t now, struc
   GETSHORT(qclass, p);
 
   if (qtype != T_DS || qclass != class)
-    rc = STAT_BOGUS;
+    return STAT_BOGUS;
+
+  /* A SERVFAIL answer has been seen to a DS query not at start of authority,
+     so treat it as such and continue to search for a DS or proof of no existence
+     further down the tree. */
+  if (RCODE(header) == SERVFAIL)
+    servfail = neganswer = nons = 1;
   else
-    rc = dnssec_validate_reply(now, header, plen, name, keyname, NULL, 0, &neganswer, &nons);
-  /* Note dnssec_validate_reply() will have cached positive answers */
+    {
+      rc = dnssec_validate_reply(now, header, plen, name, keyname, NULL, 0, &neganswer, &nons, &neg_ttl, validate_counter);
   
-  if (rc == STAT_INSECURE)
-    rc = STAT_BOGUS;
- 
-  p = (unsigned char *)(header+1);
-  extract_name(header, plen, &p, name, 1, 4);
-  p += 4; /* qtype, qclass */
+      if (STAT_ISEQUAL(rc, STAT_INSECURE))
+	{
+	  my_syslog(LOG_WARNING, _("Insecure DS reply received for %s, check domain configuration and upstream DNS server DNSSEC support"), name);
+	  log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "BOGUS DS - not secure");
+	  return STAT_BOGUS | DNSSEC_FAIL_INDET;
+	}
+      
+      p = (unsigned char *)(header+1);
+      if (!extract_name(header, plen, &p, name, 1, 4))
+	return STAT_BOGUS;
+
+      p += 4; /* qtype, qclass */
+      
+      /* If the key needed to validate the DS is on the same domain as the DS, we'll
+	 loop getting nowhere. Stop that now. This can happen of the DS answer comes
+	 from the DS's zone, and not the parent zone. */
+      if (STAT_ISEQUAL(rc, STAT_NEED_KEY) && hostname_isequal(name, keyname))
+	{
+	  log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "BOGUS DS");
+	  return STAT_BOGUS;
+	}
   
-  /* If the key needed to validate the DS is on the same domain as the DS, we'll
-     loop getting nowhere. Stop that now. This can happen of the DS answer comes
-     from the DS's zone, and not the parent zone. */
-  if (rc == STAT_BOGUS || (rc == STAT_NEED_KEY && hostname_isequal(name, keyname)))
-    {
-      log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "BOGUS DS");
-      return STAT_BOGUS;
+      if (!STAT_ISEQUAL(rc, STAT_SECURE))
+	return rc;
     }
   
-  if (rc != STAT_SECURE)
-    return rc;
-   
   if (!neganswer)
     {
       cache_start_insert();
       
       for (i = 0; i < ntohs(header->ancount); i++)
 	{
+	  unsigned char *psave;
+
 	  if (!(rc = extract_name(header, plen, &p, name, 0, 10)))
 	    return STAT_BOGUS; /* bad packet */
 	  
@@ -1260,16 +1453,16 @@ int dnssec_validate_ds(time_t now, struc
 	  
 	  if (!CHECK_LEN(header, p, plen, rdlen))
 	    return STAT_BOGUS; /* bad packet */
+
+	  psave = p;
 	  
 	  if (aclass == class && atype == T_DS && rc == 1)
 	    { 
 	      int algo, digest, keytag;
-	      unsigned char *psave = p;
 	      struct blockdata *key;
-	      struct crec *crecp;
-
-	      if (rdlen < 4)
-		return STAT_BOGUS; /* bad packet */
+	   
+	      if (rdlen < 5)
+		return STAT_BOGUS; /* min 1 byte digest! */
 	      
 	      GETSHORT(keytag, p);
 	      algo = *p++;
@@ -1277,9 +1470,19 @@ int dnssec_validate_ds(time_t now, struc
 	      
 	      /* Cache needs to known class for DNSSEC stuff */
 	      a.addr.dnssec.class = class;
-	      
-	      if ((key = blockdata_alloc((char*)p, rdlen - 4)))
+
+	      if (!ds_digest_name(digest) || !algo_digest_name(algo))
 		{
+		    a.addr.log.keytag = keytag;
+		    a.addr.log.algo = algo;
+		    a.addr.log.digest = digest;
+		  log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DS for keytag %hu, algo %hu, digest %hu (not supported)");
+		  neg_ttl = ttl;
+		} 
+	      else if ((key = blockdata_alloc((char*)p, rdlen - 4)))
+		{
+		  struct crec *crecp;
+		  
 		  if (!(crecp = cache_insert(name, &a, now, ttl, F_FORWARD | F_DS | F_DNSSECOK)))
 		    {
 		      blockdata_free(key);
@@ -1290,36 +1493,33 @@ int dnssec_validate_ds(time_t now, struc
 		      a.addr.log.keytag = keytag;
 		      a.addr.log.algo = algo;
 		      a.addr.log.digest = digest;
-		      if (hash_find(ds_digest_name(digest)) && verify_func(algo))
-			log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DS keytag %hu, algo %hu, digest %hu");
-		      else
-			log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DS keytag %hu, algo %hu, digest %hu (not supported)");
-		      
-		      crecp->addr.ds.digest = digest;
-		      crecp->addr.ds.keydata = key;
-		      crecp->addr.ds.algo = algo;
-		      crecp->addr.ds.keytag = keytag;
-		      crecp->addr.ds.keylen = rdlen - 4; 
+		      log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DS for keytag %hu, algo %hu, digest %hu");
+
+			  crecp->addr.ds.digest = digest;
+			  crecp->addr.ds.keydata = key;
+			  crecp->addr.ds.algo = algo;
+			  crecp->addr.ds.keytag = keytag;
+			  crecp->addr.ds.keylen = rdlen - 4;
+
+		      found_supported = 1;
 		    } 
 		}
-	      
-	      p = psave;
 	    }
-	  if (!ADD_RDLEN(header, p, plen, rdlen))
-	    return STAT_BOGUS; /* bad packet */
+	  
+	  p = psave + rdlen;
 	}
 
       cache_end_insert();
 
+      /* Fall through if no supported algo DS found. */
+      if (found_supported)
+	return STAT_OK;
     }
-  else
+  
+  flags = F_FORWARD | F_DS | F_NEG | F_DNSSECOK;
+  
+  if (neganswer)
     {
-      int flags = F_FORWARD | F_DS | F_NEG | F_DNSSECOK;
-      unsigned long minttl = ULONG_MAX;
-      
-      if (!(p = skip_section(p, ntohs(header->ancount), header, plen)))
-	return STAT_BOGUS;
-      
       if (RCODE(header) == NXDOMAIN)
 	flags |= F_NXDOMAIN;
       
@@ -1327,57 +1527,21 @@ int dnssec_validate_ds(time_t now, struc
 	 to store presence/absence of NS. */
       if (nons)
 	flags &= ~F_DNSSECOK;
-      
-      for (i = ntohs(header->nscount); i != 0; i--)
-	{
-	  if (!(p = skip_name(p, header, plen, 0)))
-	    return STAT_BOGUS;
-	  
-	  GETSHORT(atype, p); 
-	  GETSHORT(aclass, p);
-	  GETLONG(ttl, p);
-	  GETSHORT(rdlen, p);
-	  
-	  if (!CHECK_LEN(header, p, plen, rdlen))
-	    return STAT_BOGUS; /* bad packet */
-	  
-	  if (aclass != class || atype != T_SOA)
-	    {
-	      p += rdlen;
-	      continue;
-	    }
-	  
-	  if (ttl < minttl)
-	    minttl = ttl;
-	  
-	  /* MNAME */
-	  if (!(p = skip_name(p, header, plen, 0)))
-	    return STAT_BOGUS;
-	  /* RNAME */
-	  if (!(p = skip_name(p, header, plen, 20)))
-	    return STAT_BOGUS;
-	  p += 16; /* SERIAL REFRESH RETRY EXPIRE */
-	  
-	  GETLONG(ttl, p); /* minTTL */
-	  if (ttl < minttl)
-	    minttl = ttl;
-	  
-	  break;
-	}
-      
-      if (i != 0)
-	{
-	  cache_start_insert();
-	  
-	  a.addr.dnssec.class = class;
-	  if (!cache_insert(name, &a, now, ttl, flags))
-	    return STAT_BOGUS;
-	  
-	  cache_end_insert();  
-	  
-	  log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "no DS");
-	}
     }
+  
+  cache_start_insert();
+  
+  a.addr.dnssec.class = class;
+
+  /* Use TTL from NSEC for negative cache entries */
+  if (!cache_insert(name, NULL, now, neg_ttl, flags))
+    return STAT_BOGUS;
+  
+  cache_end_insert();  
+  
+  if (neganswer)
+    log_query(F_NOEXTRA | F_UPSTREAM, name, NULL,
+	      servfail ? "SERVFAIL" : (nons ? "no DS/cut" : "no DS"));
       
   return STAT_OK;
 }
@@ -1447,6 +1611,7 @@ static int hostname_cmp(const char *a, c
     }
 }
 
+/* returns 0 on success, or DNSSEC_FAIL_* value on failure. */
 static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsigned char **nsecs, unsigned char **labels, int nsec_count,
 				    char *workspace1_in, char *workspace2, char *name, int type, int *nons)
 {
@@ -1466,12 +1631,12 @@ static int prove_non_existence_nsec(stru
 
       p = nsecs[i];
       if (!extract_name(header, plen, &p, workspace1, 1, 10))
-	return 0;
+	return DNSSEC_FAIL_BADPACKET;
       p += 8; /* class, type, TTL */
       GETSHORT(rdlen, p);
       psave = p;
-      if (!extract_name(header, plen, &p, workspace2, 1, 10))
-	return 0;
+      if (!extract_name(header, plen, &p, workspace2, 1, 0))
+	return DNSSEC_FAIL_BADPACKET;
 
       /* If NSEC comes from wildcard expansion, use original wildcard
 	 as name for computation. */
@@ -1499,12 +1664,13 @@ static int prove_non_existence_nsec(stru
 	{
 	  /* 4035 para 5.4. Last sentence */
 	  if (type == T_NSEC || type == T_RRSIG)
-	    return 1;
+	    return 0;
 
 	  /* NSEC with the same name as the RR we're testing, check
 	     that the type in question doesn't appear in the type map */
 	  rdlen -= p - psave;
-	  /* rdlen is now length of type map, and p points to it */
+	  /* rdlen is now length of type map, and p points to it 
+	     packet checked to be as long as rdlen implies in prove_non_existence() */
 	  
 	  /* If we can prove that there's no NS record, return that information. */
 	  if (nons && rdlen >= 2 && p[0] == 0 && (p[2] & (0x80 >> T_NS)) != 0)
@@ -1515,24 +1681,24 @@ static int prove_non_existence_nsec(stru
 	      /* A CNAME answer would also be valid, so if there's a CNAME is should 
 		 have been returned. */
 	      if ((p[2] & (0x80 >> T_CNAME)) != 0)
-		return 0;
+		return DNSSEC_FAIL_NONSEC;
 	      
 	      /* If the SOA bit is set for a DS record, then we have the
 		 DS from the wrong side of the delegation. */
 	      if (type == T_DS && (p[2] & (0x80 >> T_SOA)) != 0)
-		return 0;
+		return DNSSEC_FAIL_NONSEC;
 	    }
 
 	  while (rdlen >= 2)
 	    {
 	      if (!CHECK_LEN(header, p, plen, rdlen))
-		return 0;
+		return DNSSEC_FAIL_BADPACKET;
 	      
 	      if (p[0] == type >> 8)
 		{
 		  /* Does the NSEC say our type exists? */
 		  if (offset < p[1] && (p[offset+2] & mask) != 0)
-		    return 0;
+		    return DNSSEC_FAIL_NONSEC;
 		  
 		  break; /* finished checking */
 		}
@@ -1541,24 +1707,24 @@ static int prove_non_existence_nsec(stru
 	      p +=  p[1];
 	    }
 	  
-	  return 1;
+	  return 0;
 	}
       else if (rc == -1)
 	{
 	  /* Normal case, name falls between NSEC name and next domain name,
 	     wrap around case, name falls between NSEC name (rc == -1) and end */
 	  if (hostname_cmp(workspace2, name) >= 0 || hostname_cmp(workspace1, workspace2) >= 0)
-	    return 1;
+	    return 0;
 	}
       else 
 	{
 	  /* wrap around case, name falls between start and next domain name */
 	  if (hostname_cmp(workspace1, workspace2) >= 0 && hostname_cmp(workspace2, name) >=0 )
-	    return 1;
+	    return 0;
 	}
     }
   
-  return 0;
+  return DNSSEC_FAIL_NONSEC;
 }
 
 /* return digest length, or zero on error */
@@ -1632,23 +1798,23 @@ static int check_nsec3_coverage(struct d
   for (i = 0; i < nsec_count; i++)
     if ((p = nsecs[i]))
       {
-       	if (!extract_name(header, plen, &p, workspace1, 1, 0) ||
+       	if (!extract_name(header, plen, &p, workspace1, 1, 10) ||
 	    !(base32_len = base32_decode(workspace1, (unsigned char *)workspace2)))
 	  return 0;
 	
 	p += 8; /* class, type, TTL */
 	GETSHORT(rdlen, p);
+
 	psave = p;
+
+	/* packet checked to be as long as implied by rdlen, salt_len and hash_len in prove_non_existence() */
 	p++; /* algo */
 	flags = *p++; /* flags */
 	p += 2; /* iterations */
 	salt_len = *p++; /* salt_len */
 	p += salt_len; /* salt */
 	hash_len = *p++; /* p now points to next hashed name */
-	
-	if (!CHECK_LEN(header, p, plen, hash_len))
-	  return 0;
-	
+		
 	if (digest_len == base32_len && hash_len == base32_len)
 	  {
 	    int rc = memcmp(workspace2, digest, digest_len);
@@ -1656,7 +1822,8 @@ static int check_nsec3_coverage(struct d
 	    if (rc == 0)
 	      {
 		/* We found an NSEC3 whose hashed name exactly matches the query, so
-		   we just need to check the type map. p points to the RR data for the record. */
+		   we just need to check the type map. p points to the RR data for the record.
+		   Note we have packet length up to rdlen bytes checked. */
 		
 		int offset = (type & 0xff) >> 3;
 		int mask = 0x80 >> (type & 0x07);
@@ -1664,15 +1831,12 @@ static int check_nsec3_coverage(struct d
 		p += hash_len; /* skip next-domain hash */
 		rdlen -= p - psave;
 
-		if (!CHECK_LEN(header, p, plen, rdlen))
-		  return 0;
-		
 		if (rdlen >= 2 && p[0] == 0)
 		  {
 		    /* If we can prove that there's no NS record, return that information. */
 		    if (nons && (p[2] & (0x80 >> T_NS)) != 0)
 		      *nons = 0;
-		
+		    
 		    /* A CNAME answer would also be valid, so if there's a CNAME is should 
 		       have been returned. */
 		    if ((p[2] & (0x80 >> T_CNAME)) != 0)
@@ -1730,8 +1894,9 @@ static int check_nsec3_coverage(struct d
   return 0;
 }
 
-static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, unsigned char **nsecs, int nsec_count,
-				     char *workspace1, char *workspace2, char *name, int type, char *wildname, int *nons)
+/* returns 0 on success, or DNSSEC_FAIL_* value on failure. */
+static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, unsigned char **nsecs, int nsec_count, char *workspace1,
+				     char *workspace2, char *name, int type, char *wildname, int *nons, int *validate_counter)
 {
   unsigned char *salt, *p, *digest;
   int digest_len, i, iterations, salt_len, base32_len, algo = 0;
@@ -1751,7 +1916,7 @@ static int prove_non_existence_nsec3(str
   for (i = 0; i < nsec_count; i++)
     {
       if (!(p = skip_name(nsecs[i], header, plen, 15)))
-	return 0; /* bad packet */
+	return DNSSEC_FAIL_BADPACKET; /* bad packet */
       
       p += 10; /* type, class, TTL, rdlen */
       algo = *p++;
@@ -1762,23 +1927,18 @@ static int prove_non_existence_nsec3(str
 
   /* No usable NSEC3s */
   if (i == nsec_count)
-    return 0;
+    return DNSSEC_FAIL_NONSEC;
 
   p++; /* flags */
 
   GETSHORT (iterations, p);
-  /* Upper-bound iterations, to avoid DoS.
-     Strictly, there are lower bounds for small keys, but
-     since we don't have key size info here, at least limit
-     to the largest bound, for 4096-bit keys. RFC 5155 10.3 */
-  if (iterations > 2500)
-    return 0;
+  /* Upper-bound iterations, to avoid DoS. RFC 9276 refers. */
+  if (iterations > daemon->limit[LIMIT_NSEC3_ITERS])
+    return DNSSEC_FAIL_NSEC3_ITERS;
   
   salt_len = *p++;
   salt = p;
-  if (!CHECK_LEN(header, salt, plen, salt_len))
-    return 0; /* bad packet */
-    
+      
   /* Now prune so we only have NSEC3 records with same iterations, salt and algo */
   for (i = 0; i < nsec_count; i++)
     {
@@ -1788,7 +1948,7 @@ static int prove_non_existence_nsec3(str
       nsecs[i] = NULL; /* Speculative, will be restored if OK. */
       
       if (!(p = skip_name(nsec3p, header, plen, 15)))
-	return 0; /* bad packet */
+	return DNSSEC_FAIL_BADPACKET; /* bad packet */
       
       p += 10; /* type, class, TTL, rdlen */
       
@@ -1808,9 +1968,6 @@ static int prove_non_existence_nsec3(str
       if (salt_len != *p++)
 	continue;
       
-      if (!CHECK_LEN(header, p, plen, salt_len))
-	return 0; /* bad packet */
-
       if (memcmp(p, salt, salt_len) != 0)
 	continue;
 
@@ -1818,11 +1975,14 @@ static int prove_non_existence_nsec3(str
       nsecs[i] = nsec3p;
     }
 
+  if (dec_counter(validate_counter, NULL))
+    return DNSSEC_FAIL_WORK;
+
   if ((digest_len = hash_name(name, &digest, hash, salt, salt_len, iterations)) == 0)
-    return 0;
+    return DNSSEC_FAIL_NONSEC;
   
   if (check_nsec3_coverage(header, plen, digest_len, digest, type, workspace1, workspace2, nsecs, nsec_count, nons))
-    return 1;
+    return 0;
 
   /* Can't find an NSEC3 which covers the name directly, we need the "closest encloser NSEC3" 
      or an answer inferred from a wildcard record. */
@@ -1837,15 +1997,20 @@ static int prove_non_existence_nsec3(str
       if (wildname && hostname_isequal(closest_encloser, wildname))
 	break;
 
+      if (dec_counter(validate_counter, NULL))
+	return DNSSEC_FAIL_WORK;
+      
       if ((digest_len = hash_name(closest_encloser, &digest, hash, salt, salt_len, iterations)) == 0)
-	return 0;
+	return DNSSEC_FAIL_NONSEC;
       
       for (i = 0; i < nsec_count; i++)
 	if ((p = nsecs[i]))
 	  {
-	    if (!extract_name(header, plen, &p, workspace1, 1, 0) ||
-		!(base32_len = base32_decode(workspace1, (unsigned char *)workspace2)))
-	      return 0;
+	    if (!extract_name(header, plen, &p, workspace1, 1, 0))
+	      return DNSSEC_FAIL_BADPACKET;
+
+	    if (!(base32_len = base32_decode(workspace1, (unsigned char *)workspace2)))
+	      return DNSSEC_FAIL_NONSEC;
 	  
 	    if (digest_len == base32_len &&
 		memcmp(digest, workspace2, digest_len) == 0)
@@ -1860,35 +2025,43 @@ static int prove_non_existence_nsec3(str
   while ((closest_encloser = strchr(closest_encloser, '.')));
   
   if (!closest_encloser || !next_closest)
-    return 0;
+    return DNSSEC_FAIL_NONSEC;
   
   /* Look for NSEC3 that proves the non-existence of the next-closest encloser */
+  if (dec_counter(validate_counter, NULL))
+    return DNSSEC_FAIL_WORK;
+  
   if ((digest_len = hash_name(next_closest, &digest, hash, salt, salt_len, iterations)) == 0)
-    return 0;
+    return DNSSEC_FAIL_NONSEC;
 
   if (!check_nsec3_coverage(header, plen, digest_len, digest, type, workspace1, workspace2, nsecs, nsec_count, NULL))
-    return 0;
+    return DNSSEC_FAIL_NONSEC;
   
   /* Finally, check that there's no seat of wildcard synthesis */
   if (!wildname)
     {
       if (!(wildcard = strchr(next_closest, '.')) || wildcard == next_closest)
-	return 0;
+	return DNSSEC_FAIL_NONSEC;
       
       wildcard--;
       *wildcard = '*';
       
+      if (dec_counter(validate_counter, NULL))
+	return DNSSEC_FAIL_WORK;
+     
       if ((digest_len = hash_name(wildcard, &digest, hash, salt, salt_len, iterations)) == 0)
-	return 0;
+	return DNSSEC_FAIL_NONSEC;
       
       if (!check_nsec3_coverage(header, plen, digest_len, digest, type, workspace1, workspace2, nsecs, nsec_count, NULL))
-	return 0;
+	return DNSSEC_FAIL_NONSEC;
     }
   
-  return 1;
+  return 0;
 }
 
-static int prove_non_existence(struct dns_header *header, size_t plen, char *keyname, char *name, int qtype, int qclass, char *wildname, int *nons)
+/* returns 0 on success, or DNSSEC_FAIL_* value on failure. */
+static int prove_non_existence(struct dns_header *header, size_t plen, char *keyname, char *name, int qtype, int qclass,
+			       char *wildname, int *nons, int *nsec_ttl, int *validate_counter)
 {
   static unsigned char **nsecset = NULL, **rrsig_labels = NULL;
   static int nsecset_sz = 0, rrsig_labels_sz = 0;
@@ -1896,35 +2069,47 @@ static int prove_non_existence(struct dn
   int type_found = 0;
   unsigned char *auth_start, *p = skip_questions(header, plen);
   int type, class, rdlen, i, nsecs_found;
+  unsigned long ttl;
   
   /* Move to NS section */
   if (!p || !(p = skip_section(p, ntohs(header->ancount), header, plen)))
-    return 0;
+    return DNSSEC_FAIL_BADPACKET;
 
   auth_start = p;
   
-  for (nsecs_found = 0, i = ntohs(header->nscount); i != 0; i--)
+  for (nsecs_found = 0, i = 0; i < ntohs(header->nscount); i++)
     {
       unsigned char *pstart = p;
       
       if (!extract_name(header, plen, &p, daemon->workspacename, 1, 10))
-	return 0;
+	return DNSSEC_FAIL_BADPACKET;
 	  
       GETSHORT(type, p); 
       GETSHORT(class, p);
-      p += 4; /* TTL */
+      GETLONG(ttl, p);
       GETSHORT(rdlen, p);
-
+ 
+      if (!CHECK_LEN(header, p, plen, rdlen))
+	return DNSSEC_FAIL_BADPACKET;
+      
       if (class == qclass && (type == T_NSEC || type == T_NSEC3))
 	{
+	  if (nsec_ttl)
+	    {
+	      /* Limit TTL with sig TTL */
+	      if (daemon->rr_status[ntohs(header->ancount) + i] < ttl)
+		ttl = daemon->rr_status[ntohs(header->ancount) + i];
+	      *nsec_ttl = ttl;
+	    }
+	  
 	  /* No mixed NSECing 'round here, thankyouverymuch */
 	  if (type_found != 0 && type_found != type)
-	    return 0;
+	    return DNSSEC_FAIL_NONSEC;
 
 	  type_found = type;
 
 	  if (!expand_workspace(&nsecset, &nsecset_sz, nsecs_found))
-	    return 0; 
+	    return DNSSEC_FAIL_BADPACKET; 
 	  
 	  if (type == T_NSEC)
 	    {
@@ -1939,30 +2124,33 @@ static int prove_non_existence(struct dn
 	      int res, j, rdlen1, type1, class1;
 	      
 	      if (!expand_workspace(&rrsig_labels, &rrsig_labels_sz, nsecs_found))
-		return 0;
+		return DNSSEC_FAIL_BADPACKET;
 	      
 	      rrsig_labels[nsecs_found] = NULL;
 	      
-	      for (j = ntohs(header->nscount); j != 0; j--)
-		{
+ 	      for (j = ntohs(header->nscount); j != 0; j--)
+ 		{
+		  unsigned char *psav;
+ 
 		  if (!(res = extract_name(header, plen, &p1, daemon->workspacename, 0, 10)))
-		    return 0;
+		    return DNSSEC_FAIL_BADPACKET;
 
 		   GETSHORT(type1, p1); 
 		   GETSHORT(class1, p1);
 		   p1 += 4; /* TTL */
 		   GETSHORT(rdlen1, p1);
+ 
+		   psav = p1;
 
 		   if (!CHECK_LEN(header, p1, plen, rdlen1))
-		     return 0;
+		     return DNSSEC_FAIL_BADPACKET;
 		   
 		   if (res == 1 && class1 == qclass && type1 == T_RRSIG)
 		     {
 		       int type_covered;
-		       unsigned char *psav = p1;
 		       
 		       if (rdlen < 18)
-			 return 0; /* bad packet */
+			 return DNSSEC_FAIL_BADPACKET; /* bad packet */
 
 		       GETSHORT(type_covered, p1);
 
@@ -1974,33 +2162,56 @@ static int prove_non_existence(struct dn
 			   if (!rrsig_labels[nsecs_found])
 			     rrsig_labels[nsecs_found] = p1;
 			   else if (*rrsig_labels[nsecs_found] != *p1) /* algo */
-			     return 0;
-			   }
-		       p1 = psav;
+			     return DNSSEC_FAIL_NONSEC;
+			 }
 		     }
 		   
-		   if (!ADD_RDLEN(header, p1, plen, rdlen1))
-		     return 0;
+		   p1 = psav + rdlen1;
 		}
 
 	      /* Must have found at least one sig. */
 	      if (!rrsig_labels[nsecs_found])
-		return 0;
+		return DNSSEC_FAIL_NONSEC;
+	    }
+	  else if (type == T_NSEC3)
+	    {
+	      /* Decode the packet structure enough to check that rdlen is big enough
+		 to contain everything other than the type bitmap.
+		 (packet checked to be long enough to contain rdlen above)
+		 We don't need to do any further length checks in check_nes3_coverage()
+		 or prove_non_existence_nsec3() */
+	      
+	      int salt_len, hash_len;
+	      unsigned char *psav = p;
+	      
+	      if (rdlen < 5)
+		return DNSSEC_FAIL_BADPACKET;
+	      
+	      p += 4; /* algo, flags, iterations */
+	      salt_len = *p++; /* salt_len */
+	      if (rdlen < (6 + salt_len)) 
+		return DNSSEC_FAIL_BADPACKET; /* check up to hash_length */
+
+	      p += salt_len; /* salt */
+	      hash_len = *p++; 
+	      if (rdlen < (6 + salt_len + hash_len))
+		return DNSSEC_FAIL_BADPACKET; /* check to end of next hashed name */
+
+	      p = psav;
 	    }
 
 	  nsecset[nsecs_found++] = pstart;   
 	}
       
-      if (!ADD_RDLEN(header, p, plen, rdlen))
-	return 0;
+      p += rdlen;
     }
   
   if (type_found == T_NSEC)
     return prove_non_existence_nsec(header, plen, nsecset, rrsig_labels, nsecs_found, daemon->workspacename, keyname, name, qtype, nons);
   else if (type_found == T_NSEC3)
-    return prove_non_existence_nsec3(header, plen, nsecset, nsecs_found, daemon->workspacename, keyname, name, qtype, wildname, nons);
+    return prove_non_existence_nsec3(header, plen, nsecset, nsecs_found, daemon->workspacename, keyname, name, qtype, wildname, nons, validate_counter);
   else
-    return 0;
+    return DNSSEC_FAIL_NONSEC;
 }
 
 /* Check signing status of name.
@@ -2095,16 +2306,22 @@ static int zone_status(char *name, int c
    STAT_BOGUS    signature is wrong, bad packet, no validation where there should be.
    STAT_NEED_KEY need DNSKEY to complete validation (name is returned in keyname, class in *class)
    STAT_NEED_DS  need DS to complete validation (name is returned in keyname) 
+   STAT_ABANDONED resource exhaustion.
+
+   When validating replies to DS records, we're only interested in the NSEC{3} RRs in the auth section.
+   Other RRs in that section missing sigs will not cause am INSECURE reply. We determine this mode
+   if the nons argument is non-NULL.
 */
 int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, 
-			  int *class, int check_unsigned, int *neganswer, int *nons)
+			  int *class, int check_unsigned, int *neganswer, int *nons, int *nsec_ttl, int *validate_counter)
 {
   static unsigned char **targets = NULL;
   static int target_sz = 0;
 
   unsigned char *ans_start, *p1, *p2;
   int type1, class1, rdlen1, type2, class2, rdlen2, qclass, qtype, targetidx;
-  int i, j, rc;
+  int i, j, rc = STAT_INSECURE;
+  int rc_nsec;
 
   if (neganswer)
     *neganswer = 0;
@@ -2119,7 +2336,7 @@ int dnssec_validate_reply(time_t now, st
   
    /* Find all the targets we're looking for answers to.
      The zeroth array element is for the query, subsequent ones
-     for CNAME targets, unless the query is for a CNAME. */
+     for CNAME targets, unless the query is for a CNAME or ANY. */
 
   if (!expand_workspace(&targets, &target_sz, 0))
     return STAT_BOGUS;
@@ -2138,7 +2355,7 @@ int dnssec_validate_reply(time_t now, st
   if (qtype == T_RRSIG)
     return STAT_INSECURE;
   
-  if (qtype != T_CNAME)
+  if (qtype != T_CNAME && qtype != T_ANY)
     for (j = ntohs(header->ancount); j != 0; j--) 
       {
 	if (!(p1 = skip_name(p1, header, plen, 10)))
@@ -2162,6 +2379,9 @@ int dnssec_validate_reply(time_t now, st
   
   for (p1 = ans_start, i = 0; i < ntohs(header->ancount) + ntohs(header->nscount); i++)
     {
+      if (i != 0 && !ADD_RDLEN(header, p1, plen, rdlen1))
+	return STAT_BOGUS;
+
       if (!extract_name(header, plen, &p1, name, 1, 10))
 	return STAT_BOGUS; /* bad packet */
       
@@ -2203,18 +2423,31 @@ int dnssec_validate_reply(time_t now, st
 	      /* No signatures for RRset. We can be configured to assume this is OK and return a INSECURE result. */
 	      if (sigcnt == 0)
 		{
-		  if (check_unsigned)
-		    {
-		      rc = zone_status(name, class1, keyname, now);
-		      if (rc == STAT_SECURE)
-			rc = STAT_BOGUS;
-		       if (class)
-			 *class = class1; /* Class for NEED_DS or NEED_KEY */
+		  /* NSEC and NSEC3 records must be signed. We make this assumption elsewhere. */
+		  if (type1 == T_NSEC || type1 == T_NSEC3)
+		    return STAT_BOGUS | DNSSEC_FAIL_NOSIG;
+		  else if (nons && i >= ntohs(header->ancount))
+		  /* If we're validating a DS reply, rather than looking for the value of AD bit,
+		     we only care that NSEC and NSEC3 RRs in the auth section are signed. 
+		     Return SECURE even if others (SOA....) are not. */
+		  rc = STAT_SECURE;
+		    else
+		      {
+			/* unsigned RRsets in auth section are not BOGUS, but do make reply insecure. */
+			if (check_unsigned && i < ntohs(header->ancount))
+			  {
+			    rc = zone_status(name, class1, keyname, now);
+			    if (STAT_ISEQUAL(rc, STAT_SECURE))
+			      rc = STAT_BOGUS | DNSSEC_FAIL_NOSIG;
+
+			    if (class)
+			      *class = class1; /* Class for NEED_DS or NEED_KEY */
+			  }
+			  else 
+			    rc = STAT_INSECURE; 
+
+			  return rc;
 		    }
-		  else 
-		    rc = STAT_INSECURE; 
-		  
-		  return rc;
 		}
 	      
 	      /* explore_rrset() gives us key name from sigs in keyname.
@@ -2224,15 +2457,15 @@ int dnssec_validate_reply(time_t now, st
 
 	      if (rc != STAT_SECURE)
 		{
-		  /* Zone is insecure, don't need to validate RRset */
 		  if (class)
 		    *class = class1; /* Class for NEED_DS or NEED_KEY */
 		  return rc;
 		} 
 	      
-	      rc = validate_rrset(now, header, plen, class1, type1, sigcnt, rrcnt, name, keyname, &wildname, NULL, 0, 0, 0);
+	      unsigned long sig_ttl;
+	      rc = validate_rrset(now, header, plen, class1, type1, sigcnt, rrcnt, name, keyname, &wildname, NULL, 0, 0, 0, &sig_ttl, validate_counter);
 	      
-	      if (rc == STAT_BOGUS || rc == STAT_NEED_KEY || rc == STAT_NEED_DS)
+	      if (STAT_ISEQUAL(rc, STAT_BOGUS) || STAT_ISEQUAL(rc, STAT_NEED_KEY) || STAT_ISEQUAL(rc, STAT_NEED_DS) || STAT_ISEQUAL(rc, STAT_ABANDONED))
 		{
 		  if (class)
 		    *class = class1; /* Class for DS or DNSKEY */
@@ -2263,8 +2496,9 @@ int dnssec_validate_reply(time_t now, st
 		      Note that we may not yet have validated the NSEC/NSEC3 RRsets. 
 		      That's not a problem since if the RRsets later fail
 		      we'll return BOGUS then. */
-		  if (rc == STAT_SECURE_WILDCARD && !prove_non_existence(header, plen, keyname, name, type1, class1, wildname, NULL))
-		    return STAT_BOGUS;
+		  if (STAT_ISEQUAL(rc, STAT_SECURE_WILDCARD) &&
+		      ((rc_nsec = prove_non_existence(header, plen, keyname, name, type1, class1, wildname, NULL, NULL, validate_counter))) != 0)
+		    return  (rc_nsec & DNSSEC_FAIL_WORK) ? STAT_ABANDONED : (STAT_BOGUS | rc_nsec);
 		}
 	    }
 	}
@@ -2279,28 +2513,32 @@ int dnssec_validate_reply(time_t now, st
       {
 	if (neganswer)
 	  *neganswer = 1;
-
+	
 	if (!extract_name(header, plen, &p2, name, 1, 10))
 	  return STAT_BOGUS; /* bad packet */
-	    
+	
 	/* NXDOMAIN or NODATA reply, unanswered question is (name, qclass, qtype) */
-
+	
 	/* For anything other than a DS record, this situation is OK if either
 	   the answer is in an unsigned zone, or there's a NSEC records. */
-	if (!prove_non_existence(header, plen, keyname, name, qtype, qclass, NULL, nons))
+	if ((rc_nsec = prove_non_existence(header, plen, keyname, name, qtype, qclass, NULL, nons, nsec_ttl, validate_counter)) != 0)
 	  {
+	    if (rc_nsec & DNSSEC_FAIL_WORK)
+	      return STAT_ABANDONED;
+
 	    /* Empty DS without NSECS */
 	    if (qtype == T_DS)
-	      return STAT_BOGUS;
+	      return STAT_BOGUS | rc_nsec;
 	    
-	    if ((rc = zone_status(name, qclass, keyname, now)) != STAT_SECURE)
+	    if ((rc_nsec & (DNSSEC_FAIL_NONSEC | DNSSEC_FAIL_NSEC3_ITERS)) &&
+		!STAT_ISEQUAL((rc = zone_status(name, qclass, keyname, now)), STAT_SECURE))
 	      {
 		if (class)
 		  *class = qclass; /* Class for NEED_DS or NEED_KEY */
 		return rc;
 	      } 
 	    
-	    return STAT_BOGUS; /* signed zone, no NSECs */
+	    return STAT_BOGUS | rc_nsec; /* signed zone, no NSECs */
 	  }
       }
   
Index: dnsmasq-2.78/src/forward.c
===================================================================
--- dnsmasq-2.78.orig/src/forward.c
+++ dnsmasq-2.78/src/forward.c
@@ -394,7 +394,8 @@ static int forward_query(int udpfd, unio
 	  if (ad_reqd)
 	    forward->flags |= FREC_AD_QUESTION;
 #ifdef HAVE_DNSSEC
-	  forward->work_counter = DNSSEC_WORK;
+	  forward->work_counter = daemon->limit[LIMIT_WORK];
+	  forward->validate_counter = daemon->limit[LIMIT_CRYPTO];
 	  if (do_bit)
 	    forward->flags |= FREC_DO_QUESTION;
 #endif
@@ -748,6 +749,7 @@ void reply_query(int fd, time_t now)
   size_t nn;
   struct server *server;
   void *hash;
+  size_t plen;
 
   /* packet buffer overwritten */
   daemon->srv_save = NULL;
@@ -794,7 +796,6 @@ void reply_query(int fd, time_t now)
     /* for broken servers, attempt to send to another one. */
     {
       unsigned char *pheader;
-      size_t plen;
       int is_sign;
       
       /* recreate query from reply */
@@ -883,28 +884,37 @@ void reply_query(int fd, time_t now)
 	   */
 	  if (header->hb3 & HB3_TC)
 	    status = STAT_TRUNCATED;
+	  struct frec *orig;
+	  int log_resource = 0;
 	  
 	  while (1)
-	    {
 	      /* As soon as anything returns BOGUS, we stop and unwind, to do otherwise
 		 would invite infinite loops, since the answers to DNSKEY and DS queries
 		 will not be cached, so they'll be repeated. */
+		/* Find the original query that started it all.... */
+		for (orig = forward; orig->dependent; orig = orig->dependent);
+
 	      if (status != STAT_BOGUS && status != STAT_TRUNCATED && status != STAT_ABANDONED)
 		{
 		  if (forward->flags & FREC_DNSKEY_QUERY)
-		    status = dnssec_validate_by_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class);
+		    status = dnssec_validate_by_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class, &orig->validate_counter);
 		  else if (forward->flags & FREC_DS_QUERY)
-		    status = dnssec_validate_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class);
+		    status = dnssec_validate_ds(now, header, n, daemon->namebuff, daemon->keyname, forward->class, &orig->validate_counter);
 		  else
 		    status = dnssec_validate_reply(now, header, n, daemon->namebuff, daemon->keyname, &forward->class, 
-						   option_bool(OPT_DNSSEC_NO_SIGN) && (server->flags & SERV_DO_DNSSEC), NULL, NULL);
+						   option_bool(OPT_DNSSEC_NO_SIGN) && (server->flags & SERV_DO_DNSSEC),
+						   NULL, NULL, NULL, &orig->validate_counter);
 		}
+		status = status & 0xffff0000;
+
+		if (status == STAT_ABANDONED)
+		    log_resource = 1;
 	      
 	      /* Can't validate, as we're missing key data. Put this
 		 answer aside, whilst we get that. */     
 	      if (status == STAT_NEED_DS || status == STAT_NEED_KEY)
 		{
-		  struct frec *new, *orig;
+		  struct frec *new;
 		  
 		  /* Free any saved query */
 		  if (forward->stash)
@@ -915,11 +925,11 @@ void reply_query(int fd, time_t now)
 		    return;
 		  forward->stash_len = n;
 		  
-		  /* Find the original query that started it all.... */
-		  for (orig = forward; orig->dependent; orig = orig->dependent);
-		  
-		  if (--orig->work_counter == 0 || !(new = get_new_frec(now, NULL, 1)))
+		  if (--orig->work_counter == 0 || !(new = get_new_frec(now, NULL, 1))) {
 		    status = STAT_ABANDONED;
+			my_syslog(LOG_WARNING, _("limit exceeded: per-query subqueries"));
+	        log_resource = 1;
+		  }
 		  else
 		    {
 		      int fd, type = SERV_DO_DNSSEC;
@@ -1011,7 +1021,15 @@ void reply_query(int fd, time_t now)
 		    }		  
 		  return;
 		}
-	  
+	if (log_resource)
+    {
+      /* Log the actual validation that made us barf. */
+      unsigned char *p = (unsigned char *)(header+1);
+      if  (extract_name(header, plen, &p, daemon->namebuff, 0, 4) == 1)
+	my_syslog(LOG_WARNING, _("validation of %s failed: resource limit exceeded."),
+		  daemon->namebuff[0] ? daemon->namebuff : ".");
+    }
+  
 	      /* Validated original answer, all done. */
 	      if (!forward->dependent)
 		break;
@@ -1451,7 +1469,7 @@ void receive_query(struct listener *list
 /* Recurse up the key hierarchy */
 static int tcp_key_recurse(time_t now, int status, struct dns_header *header, size_t n, 
 			   int class, char *name, char *keyname, struct server *server, 
-			   int have_mark, unsigned int mark, int *keycount)
+			   int have_mark, unsigned int mark, int *keycount, int *validatecount)
 {
   int new_status;
   unsigned char *packet = NULL;
@@ -1468,19 +1486,35 @@ static int tcp_key_recurse(time_t now, i
       struct server *firstsendto = NULL;
       
       /* limit the amount of work we do, to avoid cycling forever on loops in the DNS */
-      if (--(*keycount) == 0)
-	new_status = STAT_ABANDONED;
-      else if (status == STAT_NEED_KEY)
-	new_status = dnssec_validate_by_ds(now, header, n, name, keyname, class);
+      if (status == STAT_NEED_KEY)
+	new_status = dnssec_validate_by_ds(now, header, n, name, keyname, class, validatecount);
       else if (status == STAT_NEED_DS)
-	new_status = dnssec_validate_ds(now, header, n, name, keyname, class);
+	new_status = dnssec_validate_ds(now, header, n, name, keyname, class, validatecount);
       else 
 	new_status = dnssec_validate_reply(now, header, n, name, keyname, &class,
-					   option_bool(OPT_DNSSEC_NO_SIGN) && (server->flags & SERV_DO_DNSSEC), NULL, NULL);
+					   !option_bool(OPT_DNSSEC_NO_SIGN) && (server->flags & SERV_DO_DNSSEC),
+					   NULL, NULL, NULL, validatecount);
+      new_status = new_status & 0xffff0000;
       
-      if (new_status != STAT_NEED_DS && new_status != STAT_NEED_KEY)
+      if (new_status != STAT_NEED_DS && new_status != STAT_NEED_KEY && new_status != STAT_ABANDONED)
 	break;
 
+      if ((*keycount)-- == 0)
+	{
+	  my_syslog(LOG_WARNING, _("limit exceeded: per-query subqueries"));
+	  new_status = STAT_ABANDONED;
+	}
+      
+      if (new_status == STAT_ABANDONED)
+	{
+	  /* Log the actual validation that made us barf. */
+	  unsigned char *p = (unsigned char *)(header+1);
+	  if  (extract_name(header, n, &p, daemon->namebuff, 0, 4) == 1)
+	    my_syslog(LOG_WARNING, _("validation of %s failed: resource limit exceeded."),
+		      daemon->namebuff[0] ? daemon->namebuff : ".");
+	  break;
+	}
+
       /* Can't validate because we need a key/DS whose name now in keyname.
 	 Make query for same, and recurse to validate */
       if (!packet)
@@ -1575,7 +1609,7 @@ static int tcp_key_recurse(time_t now, i
 	  server->flags |= SERV_GOT_TCP;
 	  
 	  m = (c1 << 8) | c2;
-	  new_status = tcp_key_recurse(now, new_status, new_header, m, class, name, keyname, server, have_mark, mark, keycount);
+	  new_status = tcp_key_recurse(now, new_status, new_header, m, class, name, keyname, server, have_mark, mark, keycount, validatecount);
 	  break;
 	}
       
@@ -1902,9 +1936,10 @@ unsigned char *tcp_request(int confd, ti
 #ifdef HAVE_DNSSEC
 		      if (option_bool(OPT_DNSSEC_VALID) && !checking_disabled && (last_server->flags & SERV_DO_DNSSEC))
 			{
-			  int keycount = DNSSEC_WORK; /* Limit to number of DNSSEC questions, to catch loops and avoid filling cache. */
+		      int keycount = daemon->limit[LIMIT_WORK]; /* Limit to number of DNSSEC questions, to catch loops and avoid filling cache. */
+		      int validatecount = daemon->limit[LIMIT_CRYPTO]; 
 			  int status = tcp_key_recurse(now, STAT_OK, header, m, 0, daemon->namebuff, daemon->keyname, 
-						       last_server, have_mark, mark, &keycount);
+						       last_server, have_mark, mark, &keycount, &validatecount);
 			  char *result, *domain = "result";
 			  
 			  if (status == STAT_ABANDONED)
Index: dnsmasq-2.78/src/option.c
===================================================================
--- dnsmasq-2.78.orig/src/option.c
+++ dnsmasq-2.78/src/option.c
@@ -160,6 +160,7 @@ struct myoption {
 #define LOPT_DHCPTTL       348
 #define LOPT_TFTP_MTU      349
 #define LOPT_REPLY_DELAY   350
+#define LOPT_DNSSEC_LIMITS 351
  
 #ifdef HAVE_GETOPT_LONG
 static const struct option opts[] =  
@@ -313,6 +314,7 @@ static const struct myoption opts[] =
     { "dnssec-check-unsigned", 0, 0, LOPT_DNSSEC_CHECK },
     { "dnssec-no-timecheck", 0, 0, LOPT_DNSSEC_TIME },
     { "dnssec-timestamp", 1, 0, LOPT_DNSSEC_STAMP },
+	{ "dnssec-limits", 1, 0, LOPT_DNSSEC_LIMITS },
 #ifdef OPTION6_PREFIX_CLASS 
     { "dhcp-prefix-class", 1, 0, LOPT_PREF_CLSS },
 #endif
@@ -485,6 +487,7 @@ static struct {
   { LOPT_DNSSEC_CHECK, OPT_DNSSEC_NO_SIGN, NULL, gettext_noop("Ensure answers without DNSSEC are in unsigned zones."), NULL },
   { LOPT_DNSSEC_TIME, OPT_DNSSEC_TIME, NULL, gettext_noop("Don't check DNSSEC signature timestamps until first cache-reload"), NULL },
   { LOPT_DNSSEC_STAMP, ARG_ONE, "<path>", gettext_noop("Timestamp file to verify system clock for DNSSEC"), NULL },
+  { LOPT_DNSSEC_LIMITS, ARG_ONE, "<limit>,..", gettext_noop("Set resource limits for DNSSEC validation"), NULL },
 #ifdef OPTION6_PREFIX_CLASS 
   { LOPT_PREF_CLSS, ARG_DUP, "set:tag,<class>", gettext_noop("Specify DHCPv6 prefix class"), NULL },
 #endif
@@ -4114,6 +4117,23 @@ err:
       }
 
 #ifdef HAVE_DNSSEC
+    case LOPT_DNSSEC_LIMITS:
+      {
+	int lim, val;
+
+	for (lim = LIMIT_SIG_FAIL; arg && lim < LIMIT_MAX ;  lim++, arg = comma)
+	  {
+	    comma = split(arg);
+
+	    if (!atoi_check(arg, &val))
+	      ret_err(gen_err);
+
+	    if (val != 0)
+	      daemon->limit[lim] = val;
+	  }
+	
+	break;
+      }
     case LOPT_DNSSEC_STAMP:
       daemon->timestamp_file = opt_string_alloc(arg); 
       break;
@@ -4640,6 +4660,12 @@ void read_opts(int argc, char **argv, ch
   daemon->soa_refresh = SOA_REFRESH;
   daemon->soa_retry = SOA_RETRY;
   daemon->soa_expiry = SOA_EXPIRY;
+#ifdef HAVE_DNSSEC
+  daemon->limit[LIMIT_SIG_FAIL] = DNSSEC_LIMIT_SIG_FAIL;
+  daemon->limit[LIMIT_CRYPTO] = DNSSEC_LIMIT_CRYPTO;
+  daemon->limit[LIMIT_WORK] = DNSSEC_LIMIT_WORK;
+  daemon->limit[LIMIT_NSEC3_ITERS] = DNSSEC_LIMIT_NSEC3_ITERS;
+#endif
   daemon->max_port = MAX_PORT;
 
 #ifndef NO_ID
Index: dnsmasq-2.78/src/rrfilter.c
===================================================================
--- dnsmasq-2.78.orig/src/rrfilter.c
+++ dnsmasq-2.78/src/rrfilter.c
@@ -136,9 +136,9 @@ static int check_rrs(unsigned char *p, s
 	  
 	  if (class == C_IN)
 	    {
-	      u16 *d;
+	      short *d;
  
-	      for (pp = p, d = rrfilter_desc(type); *d != (u16)-1; d++)
+	      for (pp = p, d = rrfilter_desc(type); *d != -1; d++)
 		{
 		  if (*d != 0)
 		    pp += *d;
@@ -266,7 +266,7 @@ size_t rrfilter(struct dns_header *heade
 }
 
 /* This is used in the DNSSEC code too, hence it's exported */
-u16 *rrfilter_desc(int type)
+short *rrfilter_desc(int type)
 {
   /* List of RRtypes which include domains in the data.
      0 -> domain
@@ -277,7 +277,7 @@ u16 *rrfilter_desc(int type)
      anything which needs no mangling.
   */
   
-  static u16 rr_desc[] = 
+  static short rr_desc[] = 
     { 
       T_NS, 0, -1, 
       T_MD, 0, -1,
@@ -302,10 +302,10 @@ u16 *rrfilter_desc(int type)
       0, -1 /* wildcard/catchall */
     }; 
   
-  u16 *p = rr_desc;
+  short *p = rr_desc;
   
   while (*p != type && *p != 0)
-    while (*p++ != (u16)-1);
+    while (*p++ != -1);
 
   return p+1;
 }
