--- contrib/unbound/dnscrypt/dnscrypt.c.orig
+++ contrib/unbound/dnscrypt/dnscrypt.c
@@ -361,7 +361,7 @@
 
     len -= DNSCRYPT_QUERY_HEADER_SIZE;
 
-    while (*sldns_buffer_at(buffer, --len) == 0)
+    while (len>0 && *sldns_buffer_at(buffer, --len) == 0)
         ;
 
     if (*sldns_buffer_at(buffer, len) != 0x80) {
--- contrib/unbound/iterator/iter_scrub.c.orig
+++ contrib/unbound/iterator/iter_scrub.c
@@ -725,7 +725,13 @@
 			rrset->rrset_all_next = NULL;
 			return 1;
 		}
-		mark_additional_rrset(pkt, msg, rrset);
+		/* Only mark glue as allowed for type NS in the authority
+		 * section. Other RR types do not get glue for them, it
+		 * is allowed from the answer section, but not authority
+		 * so that a message can not have address records cached
+		 * as a side effect to the query. */
+		if(rrset->type==LDNS_RR_TYPE_NS)
+			mark_additional_rrset(pkt, msg, rrset);
 		prev = rrset;
 		rrset = rrset->rrset_all_next;
 	}
--- contrib/unbound/services/cache/dns.c.orig
+++ contrib/unbound/services/cache/dns.c
@@ -675,10 +675,16 @@
 dns_msg_deepcopy_region(struct dns_msg* origin, struct regional* region)
 {
 	size_t i;
+	struct ub_packed_rrset_key** saved_rrsets;
 	struct dns_msg* res = NULL;
+	size_t rep_alloc_size = sizeof(struct reply_info)
+		- sizeof(struct rrset_ref);  /* this is the size of res->rep
+						allocated in gen_dns_msg() */
 	res = gen_dns_msg(region, &origin->qinfo, origin->rep->rrset_count);
 	if(!res) return NULL;
-	*res->rep = *origin->rep;
+	saved_rrsets = res->rep->rrsets; /* save rrsets alloc by gen_dns_msg */
+	memcpy(res->rep, origin->rep, rep_alloc_size);
+	res->rep->rrsets = saved_rrsets;
 	if(origin->rep->reason_bogus_str) {
 		res->rep->reason_bogus_str = regional_strdup(region,
 			origin->rep->reason_bogus_str);
--- contrib/unbound/services/cache/rrset.c.orig
+++ contrib/unbound/services/cache/rrset.c
@@ -149,6 +149,16 @@
 		if(equal && cached->ttl >= timenow && 
 			cached->security == sec_status_bogus)
 			return 0;
+		/* ghost-domain: never let an NS overwrite extend lifetime
+		 * past the entry it replaces, regardless of trust. */
+		if(ns && !TTL_IS_EXPIRED(cached->ttl, timenow) &&
+			newd->ttl > cached->ttl) {
+			size_t i;
+			newd->ttl = cached->ttl;
+			for(i=0; i<(newd->count+newd->rrsig_count); i++)
+				if(newd->rr_ttl[i] > newd->ttl)
+					newd->rr_ttl[i] = newd->ttl;
+		}
                 return 1;
 	}
 	/*	o item in cache has expired */
--- contrib/unbound/services/mesh.c.orig
+++ contrib/unbound/services/mesh.c
@@ -296,12 +296,14 @@
 	if(mesh->num_reply_states < mesh->max_reply_states)
 		return 1;
 	/* try to kick out a jostle-list item */
-	if(m && m->reply_list && m->list_select == mesh_jostle_list) {
+	if(m && m->list_select == mesh_jostle_list) {
 		/* how old is it? */
 		struct timeval age;
-		timeval_subtract(&age, mesh->env->now_tv,
-			&m->reply_list->start_time);
-		if(timeval_smaller(&mesh->jostle_max, &age)) {
+		if(m->has_first_reply_time)
+			timeval_subtract(&age, mesh->env->now_tv,
+				&m->first_reply_time);
+		if(!m->has_first_reply_time ||
+			timeval_smaller(&mesh->jostle_max, &age)) {
 			/* its a goner */
 			log_nametypeclass(VERB_ALGO, "query jostled out to "
 				"make space for a new one",
@@ -1960,6 +1962,10 @@
 	r->qid = qid;
 	r->qflags = qflags;
 	r->start_time = *s->s.env->now_tv;
+	if(s->reply_list == NULL && !s->has_first_reply_time) {
+		s->first_reply_time = r->start_time;
+		s->has_first_reply_time = 1;
+	}
 	r->next = s->reply_list;
 	r->qname = regional_alloc_init(s->s.region, qinfo->qname,
 		s->s.qinfo.qname_len);
--- contrib/unbound/services/mesh.h.orig
+++ contrib/unbound/services/mesh.h
@@ -189,6 +189,12 @@
 	struct module_qstate s;
 	/** the list of replies to clients for the results */
 	struct mesh_reply* reply_list;
+	/** if it has a first reply time */
+	int has_first_reply_time;
+	/** wall-clock time the first client reply was attached;
+	 *  used by mesh_make_new_space() so duplicate retransmits
+	 *  cannot reset jostle aging. */
+	struct timeval first_reply_time;
 	/** the list of callbacks for the results */
 	struct mesh_cb* cb_list;
 	/** set of superstates (that want this state's result) 
--- contrib/unbound/services/rpz.c.orig
+++ contrib/unbound/services/rpz.c
@@ -2468,6 +2468,7 @@
 {
 	struct auth_zones* az;
 	struct auth_zone* a;
+	struct dns_msg* ret = NULL;
 	struct clientip_synthesized_rr* raddr = NULL;
 	struct rpz* r = NULL;
 	struct local_zone* z = NULL;
@@ -2511,13 +2512,11 @@
 		z = rpz_delegation_point_zone_lookup(is->dp, r->nsdname_zones,
 						     is->qchase.qclass, &match);
 		if(z != NULL) {
-			lock_rw_unlock(&a->lock);
 			break;
 		}
 
 		raddr = rpz_delegation_point_ipbased_trigger_lookup(r, is);
 		if(raddr != NULL) {
-			lock_rw_unlock(&a->lock);
 			break;
 		}
 		lock_rw_unlock(&a->lock);
@@ -2532,9 +2531,12 @@
 		if(z) {
 			lock_rw_unlock(&z->lock);
 		}
-		return rpz_apply_nsip_trigger(ms, &is->qchase, r, raddr, a);
+		ret = rpz_apply_nsip_trigger(ms, &is->qchase, r, raddr, a);
+	} else {
+		ret = rpz_apply_nsdname_trigger(ms, &is->qchase, r, z, &match, a);
 	}
-	return rpz_apply_nsdname_trigger(ms, &is->qchase, r, z, &match, a);
+	lock_rw_unlock(&a->lock);
+	return ret;
 }
 
 struct dns_msg* rpz_callback_from_iterator_cname(struct module_qstate* ms,
--- contrib/unbound/util/data/msgencode.c.orig
+++ contrib/unbound/util/data/msgencode.c
@@ -352,7 +352,6 @@
 		(p = compress_tree_lookup(tree, dname, labs, &insertpt))) {
 		if(!write_compressed_dname(pkt, dname, labs, p))
 			return RETVAL_TRUNC;
-		(*compress_count)++;
 	} else {
 		if(!dname_buffer_write(pkt, dname))
 			return RETVAL_TRUNC;
@@ -360,6 +359,7 @@
 	if(*compress_count < MAX_COMPRESSION_PER_MESSAGE &&
 		!compress_tree_store(dname, labs, pos, region, p, insertpt))
 		return RETVAL_OUTMEM;
+	(*compress_count)++;
 	return RETVAL_OK;
 }
 
@@ -804,7 +804,7 @@
 	return 1;
 }
 
-uint16_t
+size_t
 calc_edns_field_size(struct edns_data* edns)
 {
 	size_t rdatalen = 0;
@@ -840,7 +840,7 @@
 }
 
 uint16_t
-calc_ede_option_size(struct edns_data* edns, uint16_t* txt_size)
+calc_ede_option_size(struct edns_data* edns, size_t* txt_size)
 {
 	size_t rdatalen = 0;
 	struct edns_option* opt;
@@ -942,6 +942,10 @@
 			padding_option = opt;
 			continue;
 		}
+		if(sldns_buffer_position(pkt) + opt->opt_len + 4 > max_msg_sz)
+			break; /* no space for it */
+		if(!sldns_buffer_available(pkt, 4 + opt->opt_len))
+			break;
 		sldns_buffer_write_u16(pkt, opt->opt_code);
 		sldns_buffer_write_u16(pkt, opt->opt_len);
 		if(opt->opt_len != 0)
@@ -952,12 +956,18 @@
 			padding_option = opt;
 			continue;
 		}
+		if(sldns_buffer_position(pkt) + opt->opt_len + 4 > max_msg_sz)
+			break; /* no space for it */
+		if(!sldns_buffer_available(pkt, 4 + opt->opt_len))
+			break;
 		sldns_buffer_write_u16(pkt, opt->opt_code);
 		sldns_buffer_write_u16(pkt, opt->opt_len);
 		if(opt->opt_len != 0)
 			sldns_buffer_write(pkt, opt->opt_data, opt->opt_len);
 	}
-	if (padding_option && edns->padding_block_size ) {
+	if (padding_option && edns->padding_block_size &&
+		sldns_buffer_position(pkt)+4 <= max_msg_sz &&
+		sldns_buffer_available(pkt, 4) /* if there is space for it */) {
 		size_t pad_pos = sldns_buffer_position(pkt);
 		size_t msg_sz = ((pad_pos + 3) / edns->padding_block_size + 1)
 		                               * edns->padding_block_size;
@@ -1001,7 +1011,7 @@
 {
 	uint16_t flags;
 	unsigned int attach_edns = 0;
-	uint16_t edns_field_size, ede_size, ede_txt_size;
+	size_t edns_field_size, ede_size, ede_txt_size;
 
 	if(!cached || rep->authoritative) {
 		/* original flags, copy RD and CD bits from query. */
@@ -1028,12 +1038,12 @@
 	 * calculate sizes once here */
 	edns_field_size = calc_edns_field_size(edns);
 	ede_size = calc_ede_option_size(edns, &ede_txt_size);
-	if(sldns_buffer_capacity(pkt) < udpsize)
+	if(sldns_buffer_capacity(pkt) < (size_t)udpsize)
 		udpsize = sldns_buffer_capacity(pkt);
 	if(!edns || !edns->edns_present) {
 		attach_edns = 0;
 	/* EDEs are optional, try to fit anything else before them */
-	} else if(udpsize < LDNS_HEADER_SIZE + edns_field_size - ede_size) {
+	} else if((size_t)udpsize < (size_t)LDNS_HEADER_SIZE + edns_field_size - ede_size) {
 		/* packet too small to contain edns, omit it. */
 		attach_edns = 0;
 	} else {
@@ -1047,13 +1057,13 @@
 		return 0;
 	}
 	if(attach_edns) {
-		if(udpsize >= sldns_buffer_limit(pkt) + edns_field_size)
+		if((size_t)udpsize >= sldns_buffer_limit(pkt) + edns_field_size)
 			attach_edns_record_max_msg_sz(pkt, edns, udpsize);
-		else if(udpsize >= sldns_buffer_limit(pkt) + edns_field_size - ede_txt_size) {
+		else if((size_t)udpsize >= sldns_buffer_limit(pkt) + edns_field_size - ede_txt_size) {
 			ede_trim_text(&edns->opt_list_inplace_cb_out);
 			ede_trim_text(&edns->opt_list_out);
 			attach_edns_record_max_msg_sz(pkt, edns, udpsize);
-		} else if(udpsize >= sldns_buffer_limit(pkt) + edns_field_size - ede_size) {
+		} else if((size_t)udpsize >= sldns_buffer_limit(pkt) + edns_field_size - ede_size) {
 			edns_opt_list_remove(&edns->opt_list_inplace_cb_out, LDNS_EDNS_EDE);
 			edns_opt_list_remove(&edns->opt_list_out, LDNS_EDNS_EDE);
 			attach_edns_record_max_msg_sz(pkt, edns, udpsize);
@@ -1115,22 +1125,30 @@
 		sldns_buffer_write_u16(buf, qinfo->qclass);
 	}
 	sldns_buffer_flip(buf);
-	if(edns) {
+	if(edns && edns->edns_present) {
+		size_t edns_field_size, ede_size, ede_txt_size;
 		struct edns_data es = *edns;
 		es.edns_version = EDNS_ADVERTISED_VERSION;
 		es.udp_size = EDNS_ADVERTISED_SIZE;
 		es.ext_rcode = (uint8_t)(rcode >> 4);
 		es.bits &= EDNS_DO;
-		if(sldns_buffer_limit(buf) + calc_edns_field_size(&es) >
-			edns->udp_size) {
+		/* EDEs are optional. If space is a concern try in order:
+		 * - removing any EXTRA-TEXT fields from explicit EDEs, or
+		 * - removing all EDEs,
+		 * to see if EDNS can fit. */
+		edns_field_size = calc_edns_field_size(&es);
+		ede_size = calc_ede_option_size(&es, &ede_txt_size);
+		if((size_t)edns->udp_size >= sldns_buffer_limit(buf) + edns_field_size)
+			attach_edns_record_max_msg_sz(buf, &es, edns->udp_size);
+		else if((size_t)edns->udp_size >= sldns_buffer_limit(buf) + edns_field_size - ede_txt_size) {
+			ede_trim_text(&es.opt_list_inplace_cb_out);
+			ede_trim_text(&es.opt_list_out);
+			attach_edns_record_max_msg_sz(buf, &es, edns->udp_size);
+		} else if((size_t)edns->udp_size >= sldns_buffer_limit(buf) + edns_field_size - ede_size) {
 			edns_opt_list_remove(&es.opt_list_inplace_cb_out, LDNS_EDNS_EDE);
 			edns_opt_list_remove(&es.opt_list_out, LDNS_EDNS_EDE);
-			if(sldns_buffer_limit(buf) + calc_edns_field_size(&es) >
-				edns->udp_size) {
-				return;
-			}
+			attach_edns_record_max_msg_sz(buf, &es, edns->udp_size);
 		}
-		attach_edns_record(buf, &es);
 	}
 }
 
--- contrib/unbound/util/data/msgencode.h.orig
+++ contrib/unbound/util/data/msgencode.h
@@ -106,7 +106,7 @@
  * @param edns: edns data or NULL.
  * @return octets to reserve for EDNS.
  */
-uint16_t calc_edns_field_size(struct edns_data* edns);
+size_t calc_edns_field_size(struct edns_data* edns);
 
 /**
  * Calculate the size of a specific EDNS option in packet.
@@ -127,7 +127,7 @@
  *	extra text.
  * @return octets the option will take up.
  */
-uint16_t calc_ede_option_size(struct edns_data* edns, uint16_t* txt_size);
+uint16_t calc_ede_option_size(struct edns_data* edns, size_t* txt_size);
 
 /**
  * Attach EDNS record to buffer. Buffer has complete packet. There must
--- contrib/unbound/util/data/msgparse.c.orig
+++ contrib/unbound/util/data/msgparse.c
@@ -53,6 +53,8 @@
 #include "sldns/parseutil.h"
 #include "sldns/wire2str.h"
 
+#define MAX_PARSED_EDNS_OPTIONS 100
+
 /** smart comparison of (compressed, valid) dnames from packet */
 static int
 smart_compare(sldns_buffer* pkt, uint8_t* dnow, 
@@ -950,6 +952,7 @@
 	struct comm_reply* repinfo, uint32_t now, struct regional* region,
 	struct cookie_secrets* cookie_secrets)
 {
+	int i = 0, nsid_seen = 0, cookie_seen = 0, padding_seen = 0;
 	/* To respond with a Keepalive option, the client connection must have
 	 * received one message with a TCP Keepalive EDNS option, and that
 	 * option must have 0 length data. Subsequent messages sent on that
@@ -969,7 +972,7 @@
 
 	/* while still more options, and have code+len to read */
 	/* ignores partial content (i.e. rdata len 3) */
-	while(rdata_len >= 4) {
+	while(rdata_len >= 4 && i < MAX_PARSED_EDNS_OPTIONS) {
 		uint16_t opt_code = sldns_read_uint16(rdata_ptr);
 		uint16_t opt_len = sldns_read_uint16(rdata_ptr+2);
 		uint8_t server_cookie[40];
@@ -984,8 +987,9 @@
 		/* handle parse time edns options here */
 		switch(opt_code) {
 		case LDNS_EDNS_NSID:
-			if (!cfg || !cfg->nsid)
+			if (!cfg || !cfg->nsid || nsid_seen)
 				break;
+			nsid_seen = 1;
 			if(!edns_opt_list_append(&edns->opt_list_out,
 						LDNS_EDNS_NSID, cfg->nsid_len,
 						cfg->nsid, region)) {
@@ -1027,8 +1031,9 @@
 
 		case LDNS_EDNS_PADDING:
 			if(!cfg || !cfg->pad_responses ||
-					!c || c->type != comm_tcp ||!c->ssl)
+					!c || c->type != comm_tcp ||!c->ssl || padding_seen)
 				break;
+			padding_seen = 1;
 			if(!edns_opt_list_append(&edns->opt_list_out,
 						LDNS_EDNS_PADDING,
 						0, NULL, region)) {
@@ -1039,8 +1044,9 @@
 			break;
 
 		case LDNS_EDNS_COOKIE:
-			if(!cfg || !cfg->do_answer_cookie || !repinfo)
+			if(!cfg || !cfg->do_answer_cookie || !repinfo || cookie_seen)
 				break;
+			cookie_seen = 1;
 			if(opt_len != 8 && (opt_len < 16 || opt_len > 40)) {
 				verbose(VERB_ALGO, "worker request: "
 					"badly formatted cookie");
@@ -1146,6 +1152,7 @@
 		}
 		rdata_ptr += opt_len;
 		rdata_len -= opt_len;
+		i++;
 	}
 	return LDNS_RCODE_NOERROR;
 }
@@ -1160,6 +1167,7 @@
 	struct rrset_parse* found_prev = 0;
 	size_t rdata_len;
 	uint8_t* rdata_ptr;
+	int i = 0;
 	/* since the class encodes the UDP size, we cannot use hash table to
 	 * find the EDNS OPT record. Scan the packet. */
 	while(rrset) {
@@ -1219,7 +1227,7 @@
 
 	/* while still more options, and have code+len to read */
 	/* ignores partial content (i.e. rdata len 3) */
-	while(rdata_len >= 4) {
+	while(rdata_len >= 4 && i < MAX_PARSED_EDNS_OPTIONS) {
 		uint16_t opt_code = sldns_read_uint16(rdata_ptr);
 		uint16_t opt_len = sldns_read_uint16(rdata_ptr+2);
 		rdata_ptr += 4;
@@ -1234,6 +1242,7 @@
 		}
 		rdata_ptr += opt_len;
 		rdata_len -= opt_len;
+		i++;
 	}
 	/* ignore rrsigs */
 	return LDNS_RCODE_NOERROR;
--- contrib/unbound/util/data/msgparse.h.orig
+++ contrib/unbound/util/data/msgparse.h
@@ -98,6 +98,10 @@
 /** If we serve the original TTL or decrementing TTLs */
 extern int SERVE_ORIGINAL_TTL;
 
+/** Check if TTL is expired. 0 TTL is considered expired.
+ *  Used mainly to identify parts of the code that do this comparison. */
+#define TTL_IS_EXPIRED(ttl, now) ((ttl) <= (now))
+
 /**
  * Data stored in scratch pad memory during parsing.
  * Stores the data that will enter into the msgreply and packet result.
--- contrib/unbound/validator/val_neg.c.orig
+++ contrib/unbound/validator/val_neg.c
@@ -62,6 +62,13 @@
 #include "sldns/rrdef.h"
 #include "sldns/sbuffer.h"
 
+/**
+ * The maximum salt length that the negative cache is willing to use.
+ * Larger salt increases the computation time, while recommendations are
+ * for zero salt length for zones.
+ */
+#define MAX_SALT_LENGTH 64
+
 int val_neg_data_compare(const void* a, const void* b)
 {
 	struct val_neg_data* x = (struct val_neg_data*)a;
@@ -826,7 +833,11 @@
 			(slen != 0 && zone->nsec3_salt && s
 			  && memcmp(zone->nsec3_salt, s, slen) != 0))) {
 
-			if(slen > 0) {
+			if(slen > MAX_SALT_LENGTH) {
+				/* RFC 9276 s3.1: operators SHOULD NOT use a salt; large
+				 * salts inflate per-hash block count. Decline to cache. */
+				return;
+			} else if(slen > 0) {
 				uint8_t* sa = memdup(s, slen);
 				if(sa) {
 					free(zone->nsec3_salt);
@@ -1169,6 +1180,15 @@
 	uint8_t hashce[NSEC3_SHA_LEN];
 	uint8_t b32[257];
 	size_t celen, b32len;
+	int hashmax = MAX_NSEC3_CALCULATIONS;
+	if(qlabs > hashmax) {
+		/* strip leading labels so the walk costs at most
+		 * MAX_NSEC3_CALCULATIONS hashes, mirroring val_nsec3.c */
+		while(qlabs > hashmax) {
+			dname_remove_label(&qname, &qname_len);
+			qlabs--;
+		}
+	}
 
 	*nclen = 0;
 	while(qlabs > 0) {
@@ -1269,6 +1289,12 @@
 	if(!zone->nsec3_hash) 
 		return NULL; /* not nsec3 zone */
 
+	if(!topname && qlabs > zone->labs + 1)
+		return NULL; /* iterator caller; opt-out proof would be discarded
+			     * at the !topname check below anyway.
+			     * The qlabs check allows the exact-match for
+			     * the one-label-below-zone case. */
+
 	if(!(data=neg_find_nsec3_ce(zone, qname, qname_len, qlabs, buf,
 		hashnc, &nclen))) {
 		return NULL;
--- contrib/unbound/validator/val_nsec3.c.orig
+++ contrib/unbound/validator/val_nsec3.c
@@ -59,11 +59,6 @@
 #include "sldns/sbuffer.h"
 #include "util/config_file.h"
 
-/**
- * Max number of NSEC3 calculations at once, suspend query for later.
- * 8 is low enough and allows for cases where multiple proofs are needed.
- */
-#define MAX_NSEC3_CALCULATIONS 8
 /**
  * When all allowed NSEC3 calculations at once resulted in error treat as
  * bogus. NSEC3 hash errors are not cached and this helps breaks loops with
@@ -456,6 +451,67 @@
 	}
 }
 
+/** Check if the NSEC3s have the same parameter set. */
+static int
+param_set_same(struct nsec3_filter* flt, char** reason)
+{
+	size_t rrsetnum;
+	int rrnum;
+	struct ub_packed_rrset_key* rrset;
+	int have_params = 0;
+	int first_algo = 0;
+	size_t first_iter = 0;
+	uint8_t* first_salt = NULL;
+	size_t first_saltlen = 0;
+
+	/* If the NSEC3 parameter sets have distinct values, then they are
+	 * from different NSEC3 chains, and we do not want that. */
+	for(rrset=filter_first(flt, &rrsetnum, &rrnum); rrset;
+		rrset=filter_next(flt, &rrsetnum, &rrnum)) {
+		if(!have_params) {
+			first_algo = nsec3_get_algo(rrset, rrnum);
+			first_iter = nsec3_get_iter(rrset, rrnum);
+			if(!nsec3_get_salt(rrset, rrnum, &first_salt,
+				&first_saltlen)) {
+				verbose(VERB_ALGO, "NSEC3 salt malformed");
+				if(reason)
+					*reason = "NSEC3 salt malformed";
+				return 0;
+			}
+			have_params = 1;
+		} else {
+			uint8_t* salt = NULL;
+			size_t saltlen = 0;
+			if(nsec3_get_algo(rrset, rrnum) != first_algo) {
+				verbose(VERB_ALGO, "NSEC3 algorithm mismatch");
+				if(reason)
+					*reason = "NSEC3 algorithm mismatch";
+				return 0;
+			}
+			if(nsec3_get_iter(rrset, rrnum) != first_iter) {
+				verbose(VERB_ALGO, "NSEC3 iterations mismatch");
+				if(reason)
+					*reason = "NSEC3 iterations mismatch";
+				return 0;
+			}
+			if(!nsec3_get_salt(rrset, rrnum, &salt, &saltlen)) {
+				verbose(VERB_ALGO, "NSEC3 salt malformed");
+				if(reason)
+					*reason = "NSEC3 salt malformed";
+				return 0;
+			}
+			if(saltlen != first_saltlen ||
+				memcmp(salt, first_salt, saltlen) != 0) {
+				verbose(VERB_ALGO, "NSEC3 salt mismatch");
+				if(reason)
+					*reason = "NSEC3 salt mismatch";
+				return 0;
+			}
+		}
+	}
+	return 1;
+}
+
 /**
  * Find max iteration count using config settings and key size
  * @param ve: validator environment with iteration count config settings.
@@ -1192,6 +1248,8 @@
 	filter_init(&flt, list, num, qinfo); /* init RR iterator */
 	if(!flt.zone)
 		return sec_status_bogus; /* no RRs */
+	if(!param_set_same(&flt, NULL))
+		return sec_status_bogus; /* nsec3 params from distinct chains*/
 	if(nsec3_iteration_count_high(ve, &flt, kkey))
 		return sec_status_insecure; /* iteration count too high */
 	log_nametypeclass(VERB_ALGO, "start nsec3 nameerror proof, zone", 
@@ -1378,6 +1436,8 @@
 	filter_init(&flt, list, num, qinfo); /* init RR iterator */
 	if(!flt.zone)
 		return sec_status_bogus; /* no RRs */
+	if(!param_set_same(&flt, NULL))
+		return sec_status_bogus; /* nsec3 params from distinct chains*/
 	if(nsec3_iteration_count_high(ve, &flt, kkey))
 		return sec_status_insecure; /* iteration count too high */
 	return nsec3_do_prove_nodata(env, &flt, ct, qinfo, calc);
@@ -1401,6 +1461,8 @@
 	filter_init(&flt, list, num, qinfo); /* init RR iterator */
 	if(!flt.zone)
 		return sec_status_bogus; /* no RRs */
+	if(!param_set_same(&flt, NULL))
+		return sec_status_bogus; /* nsec3 params from distinct chains*/
 	if(nsec3_iteration_count_high(ve, &flt, kkey))
 		return sec_status_insecure; /* iteration count too high */
 
@@ -1503,6 +1565,8 @@
 		*reason = "no NSEC3 records";
 		return sec_status_bogus; /* no RRs */
 	}
+	if(!param_set_same(&flt, reason))
+		return sec_status_bogus; /* nsec3 params from distinct chains*/
 	if(nsec3_iteration_count_high(ve, &flt, kkey))
 		return sec_status_insecure; /* iteration count too high */
 
@@ -1596,6 +1660,8 @@
 	filter_init(&flt, list, num, qinfo); /* init RR iterator */
 	if(!flt.zone)
 		return sec_status_bogus; /* no RRs */
+	if(!param_set_same(&flt, NULL))
+		return sec_status_bogus; /* nsec3 params from distinct chains*/
 	if(nsec3_iteration_count_high(ve, &flt, kkey))
 		return sec_status_insecure; /* iteration count too high */
 
--- contrib/unbound/validator/val_nsec3.h.orig
+++ contrib/unbound/validator/val_nsec3.h
@@ -98,6 +98,12 @@
 /** The SHA1 hash algorithm for NSEC3 */
 #define NSEC3_HASH_SHA1	0x01
 
+/**
+ * Max number of NSEC3 calculations at once, suspend query for later.
+ * 8 is low enough and allows for cases where multiple proofs are needed.
+ */
+#define MAX_NSEC3_CALCULATIONS 8
+
 /**
 * Cache table for NSEC3 hashes.
 * It keeps a *pointer* to the region its items are allocated.
--- contrib/unbound/validator/val_utils.c.orig
+++ contrib/unbound/validator/val_utils.c
@@ -1066,10 +1066,10 @@
 			if(query_dname_compare(name, 
 				orig->rrsets[i]->rk.dname) == 0)
 			    chase->rrsets[chase->an_numrrsets
-				+orig->ns_numrrsets+chase->ar_numrrsets++] 
+				+chase->ns_numrrsets+chase->ar_numrrsets++]
 				= orig->rrsets[i];
 		} else if(rrset_has_signer(orig->rrsets[i], name, len)) {
-			chase->rrsets[chase->an_numrrsets+orig->ns_numrrsets+
+			chase->rrsets[chase->an_numrrsets+chase->ns_numrrsets+
 				chase->ar_numrrsets++] = orig->rrsets[i];
 		}
 	}
