--- unbound-1.22.0.orig/dnscrypt/dnscrypt.c
+++ unbound-1.22.0/dnscrypt/dnscrypt.c
@@ -361,7 +361,7 @@ dnscrypt_server_uncurve(struct dnsc_env*
 
     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) {
--- unbound-1.22.0.orig/iterator/iter_scrub.c
+++ unbound-1.22.0/iterator/iter_scrub.c
@@ -695,7 +695,13 @@ scrub_normalize(sldns_buffer* pkt, struc
 			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;
 	}
--- unbound-1.22.0.orig/services/cache/dns.c
+++ unbound-1.22.0/services/cache/dns.c
@@ -675,10 +675,16 @@ struct dns_msg*
 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);
--- unbound-1.22.0.orig/services/cache/rrset.c
+++ unbound-1.22.0/services/cache/rrset.c
@@ -147,6 +147,16 @@ need_to_update_rrset(void* nd, void* cd,
 		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 && 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 */
--- unbound-1.22.0.orig/services/mesh.c
+++ unbound-1.22.0/services/mesh.c
@@ -277,12 +277,14 @@ int mesh_make_new_space(struct mesh_area
 	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",
@@ -1734,6 +1736,10 @@ int mesh_state_add_reply(struct mesh_sta
 	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);
--- unbound-1.22.0.orig/services/mesh.h
+++ unbound-1.22.0/services/mesh.h
@@ -176,6 +176,12 @@ struct mesh_state {
 	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) 
--- unbound-1.22.0.orig/services/rpz.c
+++ unbound-1.22.0/services/rpz.c
@@ -2449,6 +2449,7 @@ rpz_callback_from_iterator_module(struct
 {
 	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;
@@ -2492,13 +2493,11 @@ rpz_callback_from_iterator_module(struct
 		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);
@@ -2513,9 +2512,12 @@ rpz_callback_from_iterator_module(struct
 		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,
--- unbound-1.22.0.orig/util/data/msgencode.c
+++ unbound-1.22.0/util/data/msgencode.c
@@ -352,7 +352,6 @@ compress_any_dname(uint8_t* dname, sldns
 		(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 @@ compress_any_dname(uint8_t* dname, sldns
 	if(*compress_count < MAX_COMPRESSION_PER_MESSAGE &&
 		!compress_tree_store(dname, labs, pos, region, p, insertpt))
 		return RETVAL_OUTMEM;
+	(*compress_count)++;
 	return RETVAL_OK;
 }
 
--- unbound-1.22.0.orig/util/data/msgparse.c
+++ unbound-1.22.0/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 @@ parse_edns_options_from_query(uint8_t* r
 	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 @@ parse_edns_options_from_query(uint8_t* r
 
 	/* 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 @@ parse_edns_options_from_query(uint8_t* r
 		/* 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 @@ parse_edns_options_from_query(uint8_t* r
 
 		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 @@ parse_edns_options_from_query(uint8_t* r
 			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 @@ parse_edns_options_from_query(uint8_t* r
 		}
 		rdata_ptr += opt_len;
 		rdata_len -= opt_len;
+		i++;
 	}
 	return LDNS_RCODE_NOERROR;
 }
@@ -1160,6 +1167,7 @@ parse_extract_edns_from_response_msg(str
 	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 @@ parse_extract_edns_from_response_msg(str
 
 	/* 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 @@ parse_extract_edns_from_response_msg(str
 		}
 		rdata_ptr += opt_len;
 		rdata_len -= opt_len;
+		i++;
 	}
 	/* ignore rrsigs */
 	return LDNS_RCODE_NOERROR;
--- unbound-1.22.0.orig/validator/val_neg.c
+++ unbound-1.22.0/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 @@ void neg_insert_data(struct val_neg_cach
 			(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 @@ neg_find_nsec3_ce(struct val_neg_zone* z
 	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 @@ neg_nsec3_proof_ds(struct val_neg_zone*
 	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;
--- unbound-1.22.0.orig/validator/val_nsec3.c
+++ unbound-1.22.0/validator/val_nsec3.c
@@ -60,11 +60,6 @@
 #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
  * erroneous data.
--- unbound-1.22.0.orig/validator/val_nsec3.h
+++ unbound-1.22.0/validator/val_nsec3.h
@@ -99,6 +99,12 @@ struct sldns_buffer;
 #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.
 */
--- unbound-1.22.0.orig/validator/val_utils.c
+++ unbound-1.22.0/validator/val_utils.c
@@ -1066,10 +1066,10 @@ val_fill_reply(struct reply_info* chase,
 			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];
 		}
 	}
