Index | Thread | Search

From:
Stuart Henderson <stu@spacehopper.org>
Subject:
unbound 1.26.2
To:
tech <tech@openbsd.org>
Date:
Wed, 21 Oct 2026 13:55:58 +0100

Download raw body.

Thread
  • Stuart Henderson:

    unbound 1.26.2

https://github.com/NLnetLabs/unbound/releases/tag/release-1.26.2

ok?

Index: configure
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/configure,v
diff -u -p -r1.64 configure
--- configure	21 Sep 2026 08:43:48 -0000	1.64
+++ configure	21 Oct 2026 12:55:21 -0000
@@ -1,6 +1,6 @@
 #! /bin/sh
 # Guess values for system-dependent variables and create Makefiles.
-# Generated by GNU Autoconf 2.71 for unbound 1.26.1.
+# Generated by GNU Autoconf 2.71 for unbound 1.26.2.
 #
 # Report bugs to <unbound-bugs@nlnetlabs.nl or https://github.com/NLnetLabs/unbound/issues>.
 #
@@ -622,8 +622,8 @@ MAKEFLAGS=
 # Identity of this package.
 PACKAGE_NAME='unbound'
 PACKAGE_TARNAME='unbound'
-PACKAGE_VERSION='1.26.1'
-PACKAGE_STRING='unbound 1.26.1'
+PACKAGE_VERSION='1.26.2'
+PACKAGE_STRING='unbound 1.26.2'
 PACKAGE_BUGREPORT='unbound-bugs@nlnetlabs.nl or https://github.com/NLnetLabs/unbound/issues'
 PACKAGE_URL=''
 
@@ -1516,7 +1516,7 @@ if test "$ac_init_help" = "long"; then
   # Omit some internal or obsolete options to make the list less imposing.
   # This message is too long to be a string in the A/UX 3.1 sh.
   cat <<_ACEOF
-\`configure' configures unbound 1.26.1 to adapt to many kinds of systems.
+\`configure' configures unbound 1.26.2 to adapt to many kinds of systems.
 
 Usage: $0 [OPTION]... [VAR=VALUE]...
 
@@ -1582,7 +1582,7 @@ fi
 
 if test -n "$ac_init_help"; then
   case $ac_init_help in
-     short | recursive ) echo "Configuration of unbound 1.26.1:";;
+     short | recursive ) echo "Configuration of unbound 1.26.2:";;
    esac
   cat <<\_ACEOF
 
@@ -1835,7 +1835,7 @@ fi
 test -n "$ac_init_help" && exit $ac_status
 if $ac_init_version; then
   cat <<\_ACEOF
-unbound configure 1.26.1
+unbound configure 1.26.2
 generated by GNU Autoconf 2.71
 
 Copyright (C) 2021 Free Software Foundation, Inc.
@@ -2492,7 +2492,7 @@ cat >config.log <<_ACEOF
 This file contains any messages produced by compilers while
 running configure, to aid debugging if configure makes a mistake.
 
-It was created by unbound $as_me 1.26.1, which was
+It was created by unbound $as_me 1.26.2, which was
 generated by GNU Autoconf 2.71.  Invocation command line was
 
   $ $0$ac_configure_args_raw
@@ -3256,11 +3256,11 @@ UNBOUND_VERSION_MAJOR=1
 
 UNBOUND_VERSION_MINOR=26
 
-UNBOUND_VERSION_MICRO=1
+UNBOUND_VERSION_MICRO=2
 
 
 LIBUNBOUND_CURRENT=9
-LIBUNBOUND_REVISION=40
+LIBUNBOUND_REVISION=41
 LIBUNBOUND_AGE=1
 # 1.0.0 had 0:12:0
 # 1.0.1 had 0:13:0
@@ -3368,6 +3368,7 @@ LIBUNBOUND_AGE=1
 # 1.25.2 had 9:38:1
 # 1.26.0 had 9:39:1
 # 1.26.1 had 9:40:1
+# 1.26.2 had 9:41:1
 
 #   Current  -- the number of the binary API that we're implementing
 #   Revision -- which iteration of the implementation of the binary
@@ -26467,7 +26468,7 @@ printf "%s\n" "#define MAXSYSLOGMSGLEN 1
 
 
 
-version=1.26.1
+version=1.26.2
 
 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for build time" >&5
 printf %s "checking for build time... " >&6; }
@@ -26997,7 +26998,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_wri
 # report actual input values of CONFIG_FILES etc. instead of their
 # values after options handling.
 ac_log="
-This file was extended by unbound $as_me 1.26.1, which was
+This file was extended by unbound $as_me 1.26.2, which was
 generated by GNU Autoconf 2.71.  Invocation command line was
 
   CONFIG_FILES    = $CONFIG_FILES
@@ -27065,7 +27066,7 @@ ac_cs_config_escaped=`printf "%s\n" "$ac
 cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
 ac_cs_config='$ac_cs_config_escaped'
 ac_cs_version="\\
-unbound config.status 1.26.1
+unbound config.status 1.26.2
 configured by $0, generated by GNU Autoconf 2.71,
   with options \\"\$ac_cs_config\\"
 
Index: configure.ac
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/configure.ac,v
diff -u -p -r1.64 configure.ac
--- configure.ac	21 Sep 2026 08:43:48 -0000	1.64
+++ configure.ac	21 Oct 2026 12:55:21 -0000
@@ -12,14 +12,14 @@ sinclude(dnscrypt/dnscrypt.m4)
 # must be numbers. ac_defun because of later processing
 m4_define([VERSION_MAJOR],[1])
 m4_define([VERSION_MINOR],[26])
-m4_define([VERSION_MICRO],[1])
+m4_define([VERSION_MICRO],[2])
 AC_INIT([unbound],m4_defn([VERSION_MAJOR]).m4_defn([VERSION_MINOR]).m4_defn([VERSION_MICRO]),[unbound-bugs@nlnetlabs.nl or https://github.com/NLnetLabs/unbound/issues],[unbound])
 AC_SUBST(UNBOUND_VERSION_MAJOR, [VERSION_MAJOR])
 AC_SUBST(UNBOUND_VERSION_MINOR, [VERSION_MINOR])
 AC_SUBST(UNBOUND_VERSION_MICRO, [VERSION_MICRO])
 
 LIBUNBOUND_CURRENT=9
-LIBUNBOUND_REVISION=40
+LIBUNBOUND_REVISION=41
 LIBUNBOUND_AGE=1
 # 1.0.0 had 0:12:0
 # 1.0.1 had 0:13:0
@@ -127,6 +127,7 @@ LIBUNBOUND_AGE=1
 # 1.25.2 had 9:38:1
 # 1.26.0 had 9:39:1
 # 1.26.1 had 9:40:1
+# 1.26.2 had 9:41:1
 
 #   Current  -- the number of the binary API that we're implementing
 #   Revision -- which iteration of the implementation of the binary
Index: doc/README
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/doc/README,v
diff -u -p -r1.48 README
--- doc/README	21 Sep 2026 08:43:48 -0000	1.48
+++ doc/README	21 Oct 2026 12:55:21 -0000
@@ -1,4 +1,4 @@
-README for Unbound 1.26.1
+README for Unbound 1.26.2
 Copyright 2007 NLnet Labs
 http://unbound.net
 
Index: doc/example.conf.in
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/doc/example.conf.in,v
diff -u -p -r1.53 example.conf.in
--- doc/example.conf.in	21 Sep 2026 08:43:48 -0000	1.53
+++ doc/example.conf.in	21 Oct 2026 12:55:21 -0000
@@ -1,7 +1,7 @@
 #
 # Example configuration file.
 #
-# See unbound.conf(5) man page, version 1.26.1.
+# See unbound.conf(5) man page, version 1.26.2.
 #
 # this is a comment.
 
Index: doc/libunbound.3.in
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/doc/libunbound.3.in,v
diff -u -p -r1.51 libunbound.3.in
--- doc/libunbound.3.in	21 Sep 2026 08:43:48 -0000	1.51
+++ doc/libunbound.3.in	21 Oct 2026 12:55:21 -0000
@@ -27,9 +27,9 @@ level margin: \\n[rst2man-indent\\n[rst2
 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
 .in \\n[rst2man-indent\\n[rst2man-indent-level]]u
 ..
-.TH "LIBUNBOUND" "3" "Sep 16, 2026" "1.26.1" "Unbound"
+.TH "LIBUNBOUND" "3" "Oct 21, 2026" "1.26.2" "Unbound"
 .SH NAME
-libunbound \- Unbound DNS validating resolver 1.26.1 functions.
+libunbound \- Unbound DNS validating resolver 1.26.2 functions.
 .SH SYNOPSIS
 .sp
 \fB#include <unbound.h>\fP
Index: doc/unbound-anchor.8.in
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/doc/unbound-anchor.8.in,v
diff -u -p -r1.50 unbound-anchor.8.in
--- doc/unbound-anchor.8.in	21 Sep 2026 08:43:48 -0000	1.50
+++ doc/unbound-anchor.8.in	21 Oct 2026 12:55:21 -0000
@@ -27,9 +27,9 @@ level margin: \\n[rst2man-indent\\n[rst2
 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
 .in \\n[rst2man-indent\\n[rst2man-indent-level]]u
 ..
-.TH "UNBOUND-ANCHOR" "8" "Sep 16, 2026" "1.26.1" "Unbound"
+.TH "UNBOUND-ANCHOR" "8" "Oct 21, 2026" "1.26.2" "Unbound"
 .SH NAME
-unbound-anchor \- Unbound 1.26.1 anchor utility.
+unbound-anchor \- Unbound 1.26.2 anchor utility.
 .SH SYNOPSIS
 .sp
 \fBunbound\-anchor\fP [\fBopts\fP]
Index: doc/unbound-checkconf.8.in
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/doc/unbound-checkconf.8.in,v
diff -u -p -r1.50 unbound-checkconf.8.in
--- doc/unbound-checkconf.8.in	21 Sep 2026 08:43:48 -0000	1.50
+++ doc/unbound-checkconf.8.in	21 Oct 2026 12:55:21 -0000
@@ -27,9 +27,9 @@ level margin: \\n[rst2man-indent\\n[rst2
 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
 .in \\n[rst2man-indent\\n[rst2man-indent-level]]u
 ..
-.TH "UNBOUND-CHECKCONF" "8" "Sep 16, 2026" "1.26.1" "Unbound"
+.TH "UNBOUND-CHECKCONF" "8" "Oct 21, 2026" "1.26.2" "Unbound"
 .SH NAME
-unbound-checkconf \- Check Unbound 1.26.1 configuration file for errors.
+unbound-checkconf \- Check Unbound 1.26.2 configuration file for errors.
 .SH SYNOPSIS
 .sp
 \fBunbound\-checkconf\fP [\fB\-hf\fP] [\fB\-o option\fP] [cfgfile]
Index: doc/unbound-control.8.in
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/doc/unbound-control.8.in,v
diff -u -p -r1.52 unbound-control.8.in
--- doc/unbound-control.8.in	21 Sep 2026 08:43:48 -0000	1.52
+++ doc/unbound-control.8.in	21 Oct 2026 12:55:21 -0000
@@ -27,9 +27,9 @@ level margin: \\n[rst2man-indent\\n[rst2
 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
 .in \\n[rst2man-indent\\n[rst2man-indent-level]]u
 ..
-.TH "UNBOUND-CONTROL" "8" "Sep 16, 2026" "1.26.1" "Unbound"
+.TH "UNBOUND-CONTROL" "8" "Oct 21, 2026" "1.26.2" "Unbound"
 .SH NAME
-unbound-control \- Unbound 1.26.1 remote server control utility.
+unbound-control \- Unbound 1.26.2 remote server control utility.
 .SH SYNOPSIS
 .sp
 \fBunbound\-control\fP [\fB\-hq\fP] [\fB\-c cfgfile\fP] [\fB\-s server\fP] command
Index: doc/unbound-host.1.in
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/doc/unbound-host.1.in,v
diff -u -p -r1.52 unbound-host.1.in
--- doc/unbound-host.1.in	21 Sep 2026 08:43:48 -0000	1.52
+++ doc/unbound-host.1.in	21 Oct 2026 12:55:21 -0000
@@ -27,9 +27,9 @@ level margin: \\n[rst2man-indent\\n[rst2
 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
 .in \\n[rst2man-indent\\n[rst2man-indent-level]]u
 ..
-.TH "UNBOUND-HOST" "1" "Sep 16, 2026" "1.26.1" "Unbound"
+.TH "UNBOUND-HOST" "1" "Oct 21, 2026" "1.26.2" "Unbound"
 .SH NAME
-unbound-host \- Unbound 1.26.1 DNS lookup utility.
+unbound-host \- Unbound 1.26.2 DNS lookup utility.
 .SH SYNOPSIS
 .sp
 \fBunbound\-host\fP [\fB\-C configfile\fP] [\fB\-vdhr46D\fP] [\fB\-c class\fP]
Index: doc/unbound.8.in
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/doc/unbound.8.in,v
diff -u -p -r1.53 unbound.8.in
--- doc/unbound.8.in	21 Sep 2026 08:43:48 -0000	1.53
+++ doc/unbound.8.in	21 Oct 2026 12:55:21 -0000
@@ -27,9 +27,9 @@ level margin: \\n[rst2man-indent\\n[rst2
 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
 .in \\n[rst2man-indent\\n[rst2man-indent-level]]u
 ..
-.TH "UNBOUND" "8" "Sep 16, 2026" "1.26.1" "Unbound"
+.TH "UNBOUND" "8" "Oct 21, 2026" "1.26.2" "Unbound"
 .SH NAME
-unbound \- Unbound DNS validating resolver 1.26.1.
+unbound \- Unbound DNS validating resolver 1.26.2.
 .SH SYNOPSIS
 .sp
 \fBunbound\fP [\fB\-hdpVv\fP] [\fB\-c <cfgfile>\fP]
Index: doc/unbound.conf.5.in
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/doc/unbound.conf.5.in,v
diff -u -p -r1.58 unbound.conf.5.in
--- doc/unbound.conf.5.in	21 Sep 2026 08:43:48 -0000	1.58
+++ doc/unbound.conf.5.in	21 Oct 2026 12:55:21 -0000
@@ -27,9 +27,9 @@ level margin: \\n[rst2man-indent\\n[rst2
 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
 .in \\n[rst2man-indent\\n[rst2man-indent-level]]u
 ..
-.TH "UNBOUND.CONF" "5" "Sep 16, 2026" "1.26.1" "Unbound"
+.TH "UNBOUND.CONF" "5" "Oct 21, 2026" "1.26.2" "Unbound"
 .SH NAME
-unbound.conf \- Unbound 1.26.1 configuration file.
+unbound.conf \- Unbound 1.26.2 configuration file.
 .SH SYNOPSIS
 .sp
 \fBunbound.conf\fP
Index: services/authzone.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/services/authzone.c,v
diff -u -p -r1.34 authzone.c
--- services/authzone.c	21 Sep 2026 08:43:48 -0000	1.34
+++ services/authzone.c	21 Oct 2026 12:55:21 -0000
@@ -98,6 +98,11 @@
 #define NUM_TIMEOUTS_FALLBACK_IXFR 3
 /** number of IXFRs before an AXFR is performed, to consolidate RPZ memory. */
 #define NUM_IXFR_BEFORE_AXFR 5
+/** The max number of decompression pointers that are allowed in a received
+ * xfr packet. The parse fails if the limit is exceeded.
+ * For a lot of PTR records, with compression for a deep zone, that is about
+ * 20x pointers per RR, of about 16 bytes, more than 4000*20 = 80000. */
+#define AUTH_XFR_DECOMPRESS_PACKET_LIMIT 128000
 
 /** pick up nextprobe task to start waiting to perform transfer actions */
 static void xfr_set_timeout(struct auth_xfer* xfr, struct module_env* env,
@@ -1363,7 +1368,8 @@ az_remove_rr(struct auth_zone* z, uint8_
 static int
 decompress_rr_into_buffer(struct sldns_buffer* buf, uint8_t* pkt,
 	size_t pktlen, uint8_t* dname, uint16_t rr_type, uint16_t rr_class,
-	uint32_t rr_ttl, uint8_t* rr_data, uint16_t rr_rdlen)
+	uint32_t rr_ttl, uint8_t* rr_data, uint16_t rr_rdlen, int* budget,
+	int limit)
 {
 	sldns_buffer pktbuf;
 	size_t dname_len = 0;
@@ -1377,7 +1383,7 @@ decompress_rr_into_buffer(struct sldns_b
 	/* decompress dname */
 	sldns_buffer_set_position(&pktbuf,
 		(size_t)(dname - sldns_buffer_current(&pktbuf)));
-	dname_len = pkt_dname_len(&pktbuf);
+	dname_len = pkt_dname_len_budget(&pktbuf, budget, limit);
 	if(dname_len == 0) return 0; /* parse fail on dname */
 	if(!sldns_buffer_available(buf, dname_len)) return 0;
 	dname_pkt_copy(&pktbuf, sldns_buffer_current(buf), dname);
@@ -1412,7 +1418,8 @@ decompress_rr_into_buffer(struct sldns_b
 				/* moves pktbuf to right after the
 				 * compressed dname, and returns uncompressed
 				 * dname length */
-				uncompressed_len = pkt_dname_len(&pktbuf);
+				uncompressed_len = pkt_dname_len_budget(
+					&pktbuf, budget, limit);
 				if(!uncompressed_len)
 					return 0; /* parse error in dname */
 				compressed_len = sldns_buffer_position(
@@ -1471,13 +1478,13 @@ static int
 az_insert_rr_decompress(struct auth_zone* z, uint8_t* pkt, size_t pktlen,
 	struct sldns_buffer* scratch_buffer, uint8_t* dname, uint16_t rr_type,
 	uint16_t rr_class, uint32_t rr_ttl, uint8_t* rr_data,
-	uint16_t rr_rdlen, int* duplicate)
+	uint16_t rr_rdlen, int* duplicate, int* budget, int limit)
 {
 	uint8_t* rr;
 	size_t rr_len;
 	size_t dname_len;
 	if(!decompress_rr_into_buffer(scratch_buffer, pkt, pktlen, dname,
-		rr_type, rr_class, rr_ttl, rr_data, rr_rdlen)) {
+		rr_type, rr_class, rr_ttl, rr_data, rr_rdlen, budget, limit)) {
 		log_err("could not decompress RR");
 		return 0;
 	}
@@ -1493,13 +1500,13 @@ static int
 az_remove_rr_decompress(struct auth_zone* z, uint8_t* pkt, size_t pktlen,
 	struct sldns_buffer* scratch_buffer, uint8_t* dname, uint16_t rr_type,
 	uint16_t rr_class, uint32_t rr_ttl, uint8_t* rr_data,
-	uint16_t rr_rdlen, int* nonexist)
+	uint16_t rr_rdlen, int* nonexist, int* budget, int limit)
 {
 	uint8_t* rr;
 	size_t rr_len;
 	size_t dname_len;
 	if(!decompress_rr_into_buffer(scratch_buffer, pkt, pktlen, dname,
-		rr_type, rr_class, rr_ttl, rr_data, rr_rdlen)) {
+		rr_type, rr_class, rr_ttl, rr_data, rr_rdlen, budget, limit)) {
 		log_err("could not decompress RR");
 		return 0;
 	}
@@ -4873,7 +4880,7 @@ chunk_rrlist_end(struct auth_chunk* rr_c
 /** RR list iterator, move to next RR */
 static void
 chunk_rrlist_gonext(struct auth_chunk** rr_chunk, int* rr_num,
-	size_t* rr_pos, size_t rr_nextpos)
+	size_t* rr_pos, size_t rr_nextpos, int* budget)
 {
 	/* already at end of chunks? */
 	if(!*rr_chunk)
@@ -4887,6 +4894,7 @@ chunk_rrlist_gonext(struct auth_chunk** 
 	}
 	/* no more RRs in this chunk */
 	/* continue with next chunk, see if it has RRs */
+	*budget = 0; /* new budget for next packet */
 	if(*rr_chunk)
 		*rr_chunk = (*rr_chunk)->next;
 	while(*rr_chunk) {
@@ -4970,11 +4978,12 @@ static int
 ixfr_start_serial(struct auth_chunk* rr_chunk, int rr_num, size_t rr_pos,
 	uint8_t* rr_dname, uint16_t rr_type, uint16_t rr_class,
 	uint32_t rr_ttl, uint16_t rr_rdlen, uint8_t* rr_rdata,
-	size_t rr_nextpos, uint32_t transfer_serial, uint32_t xfr_serial)
+	size_t rr_nextpos, uint32_t transfer_serial, uint32_t xfr_serial,
+	int* budget)
 {
 	uint32_t startserial;
 	/* move forward on RR */
-	chunk_rrlist_gonext(&rr_chunk, &rr_num, &rr_pos, rr_nextpos);
+	chunk_rrlist_gonext(&rr_chunk, &rr_num, &rr_pos, rr_nextpos, budget);
 	if(chunk_rrlist_end(rr_chunk, rr_num)) {
 		/* no second SOA */
 		verbose(VERB_OPS, "IXFR has no second SOA record");
@@ -5031,6 +5040,7 @@ apply_ixfr(struct auth_xfer* xfr, struct
 	size_t rr_counter = 0;
 	int delmode = 0;
 	int softfail = 0;
+	int budget = 0, limit = AUTH_XFR_DECOMPRESS_PACKET_LIMIT;
 
 	xfr->num_ixfrs++;
 
@@ -5066,7 +5076,8 @@ apply_ixfr(struct auth_xfer* xfr, struct
 				if(!ixfr_start_serial(rr_chunk, rr_num, rr_pos,
 					rr_dname, rr_type, rr_class, rr_ttl,
 					rr_rdlen, rr_rdata, rr_nextpos,
-					transfer_serial, xfr->serial)) {
+					transfer_serial, xfr->serial,
+					&budget)) {
 					return 0;
 				}
 			} else if(transfer_serial == serial) {
@@ -5110,7 +5121,7 @@ apply_ixfr(struct auth_xfer* xfr, struct
 			if(!az_remove_rr_decompress(z, rr_chunk->data,
 				rr_chunk->len, scratch_buffer, rr_dname,
 				rr_type, rr_class, rr_ttl, rr_rdata, rr_rdlen,
-				&nonexist)) {
+				&nonexist, &budget, limit)) {
 				/* failed, malloc error or so */
 				return 0;
 			}
@@ -5130,7 +5141,7 @@ apply_ixfr(struct auth_xfer* xfr, struct
 			if(!az_insert_rr_decompress(z, rr_chunk->data,
 				rr_chunk->len, scratch_buffer, rr_dname,
 				rr_type, rr_class, rr_ttl, rr_rdata, rr_rdlen,
-				&duplicate)) {
+				&duplicate, &budget, limit)) {
 				/* failed, malloc error or so */
 				return 0;
 			}
@@ -5144,7 +5155,8 @@ apply_ixfr(struct auth_xfer* xfr, struct
 		}
 
 		rr_counter++;
-		chunk_rrlist_gonext(&rr_chunk, &rr_num, &rr_pos, rr_nextpos);
+		chunk_rrlist_gonext(&rr_chunk, &rr_num, &rr_pos, rr_nextpos,
+			&budget);
 	}
 	if(softfail) {
 		verbose(VERB_ALGO, "IXFR did not apply cleanly, fetching full zone");
@@ -5168,6 +5180,7 @@ apply_axfr(struct auth_xfer* xfr, struct
 	size_t rr_nextpos;
 	size_t rr_counter = 0;
 	int have_end_soa = 0;
+	int budget = 0, limit = AUTH_XFR_DECOMPRESS_PACKET_LIMIT;
 
 	auth_zone_clear_data(z);
 	xfr->have_zone = 0;
@@ -5201,13 +5214,14 @@ apply_axfr(struct auth_xfer* xfr, struct
 		/* add this RR */
 		if(!az_insert_rr_decompress(z, rr_chunk->data, rr_chunk->len,
 			scratch_buffer, rr_dname, rr_type, rr_class, rr_ttl,
-			rr_rdata, rr_rdlen, NULL)) {
+			rr_rdata, rr_rdlen, NULL, &budget, limit)) {
 			/* failed, malloc error or so */
 			return 0;
 		}
 
 		rr_counter++;
-		chunk_rrlist_gonext(&rr_chunk, &rr_num, &rr_pos, rr_nextpos);
+		chunk_rrlist_gonext(&rr_chunk, &rr_num, &rr_pos, rr_nextpos,
+			&budget);
 	}
 	if(!have_end_soa) {
 		log_err("no end SOA record for AXFR");
@@ -6049,7 +6063,7 @@ check_xfer_packet(sldns_buffer* pkt, str
 	int* gonextonfail, int* transferdone)
 {
 	uint8_t* wire = sldns_buffer_begin(pkt);
-	int i;
+	int i, budget = 0, limit = AUTH_XFR_DECOMPRESS_PACKET_LIMIT;
 	if(sldns_buffer_limit(pkt) < LDNS_HEADER_SIZE) {
 		verbose(VERB_ALGO, "xfr to %s failed, packet too small",
 			xfr->task_transfer->master->host);
@@ -6111,7 +6125,7 @@ check_xfer_packet(sldns_buffer* pkt, str
 	for(i=0; i<(int)LDNS_QDCOUNT(wire); i++) {
 		size_t pos = sldns_buffer_position(pkt);
 		uint16_t qtype, qclass;
-		if(pkt_dname_len(pkt) == 0) {
+		if(pkt_dname_len_budget(pkt, &budget, limit) == 0) {
 			verbose(VERB_ALGO, "xfr to %s failed, packet with "
 				"malformed dname",
 				xfr->task_transfer->master->host);
@@ -6161,7 +6175,7 @@ check_xfer_packet(sldns_buffer* pkt, str
 	for(i=0; i<(int)LDNS_ANCOUNT(wire); i++) {
 		size_t pos = sldns_buffer_position(pkt);
 		uint16_t tp, rdlen;
-		if(pkt_dname_len(pkt) == 0) {
+		if(pkt_dname_len_budget(pkt, &budget, limit) == 0) {
 			verbose(VERB_ALGO, "xfr to %s failed, packet with "
 				"malformed dname in answer section",
 				xfr->task_transfer->master->host);
@@ -6277,7 +6291,7 @@ check_xfer_packet(sldns_buffer* pkt, str
 	/* we skip over the RRs checking packet format */
 	for(i=0; i<(int)LDNS_NSCOUNT(wire); i++) {
 		uint16_t rdlen;
-		if(pkt_dname_len(pkt) == 0) {
+		if(pkt_dname_len_budget(pkt, &budget, limit) == 0) {
 			verbose(VERB_ALGO, "xfr to %s failed, packet with "
 				"malformed dname in authority section",
 				xfr->task_transfer->master->host);
@@ -6306,7 +6320,7 @@ check_xfer_packet(sldns_buffer* pkt, str
 	/* check additional section */
 	for(i=0; i<(int)LDNS_ARCOUNT(wire); i++) {
 		uint16_t rdlen;
-		if(pkt_dname_len(pkt) == 0) {
+		if(pkt_dname_len_budget(pkt, &budget, limit) == 0) {
 			verbose(VERB_ALGO, "xfr to %s failed, packet with "
 				"malformed dname in additional section",
 				xfr->task_transfer->master->host);
Index: services/mesh.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/services/mesh.c,v
diff -u -p -r1.36 mesh.c
--- services/mesh.c	21 Sep 2026 08:43:48 -0000	1.36
+++ services/mesh.c	21 Oct 2026 12:55:21 -0000
@@ -506,8 +506,10 @@ void mesh_new_client(struct mesh_area* m
 				"incoming query.");
 			if(rep->c->use_h2)
 				http2_stream_remove_mesh_state(rep->c->h2_stream);
+#ifdef HAVE_NGTCP2
 			else if(rep->c->type == comm_doq && rep->doq_stream)
-				doq_stream_remove_mesh_state(rep->doq_stream);
+				doq_find_stream_remove_mesh_state(rep);
+#endif
 			comm_point_drop_reply(rep);
 			mesh->stats_dropped++;
 			return;
@@ -521,8 +523,10 @@ void mesh_new_client(struct mesh_area* m
 				"dropping incoming query.");
 			if(rep->c->use_h2)
 				http2_stream_remove_mesh_state(rep->c->h2_stream);
+#ifdef HAVE_NGTCP2
 			else if(rep->c->type == comm_doq && rep->doq_stream)
-				doq_stream_remove_mesh_state(rep->doq_stream);
+				doq_find_stream_remove_mesh_state(rep);
+#endif
 			comm_point_drop_reply(rep);
 			mesh->num_queries_replyaddr_limit++;
 			return;
@@ -598,6 +602,9 @@ void mesh_new_client(struct mesh_area* m
 	if(rep->c->use_h2) {
 		http2_stream_add_meshstate(rep->c->h2_stream, mesh, s);
 	} else if(rep->c->type == comm_doq && rep->doq_stream) {
+		/* The mesh_new_client call is in the worker callback, that
+		 * is called from the doq handler that has the conn locked
+		 * for this stream. It can add directly, without more locks. */
 		doq_stream_add_meshstate(rep->doq_stream, mesh, s);
 	}
 	/* add serve expired timer if required and not already there */
@@ -655,6 +662,9 @@ servfail_mem:
 	if(rep->c->use_h2)
 		http2_stream_remove_mesh_state(rep->c->h2_stream);
 	else if(rep->c->type == comm_doq && rep->doq_stream)
+		/* The mesh_new_client call is in the worker callback, that
+		 * is called from the doq handler that has the conn locked
+		 * for this stream. It can remove without more locks. */
 		doq_stream_remove_mesh_state(rep->doq_stream);
 	comm_point_send_reply(rep);
 	if(added_reply_without_accounting) {
@@ -1145,9 +1155,10 @@ mesh_state_cleanup(struct mesh_state* ms
 					mstate);
 			else if(rep->query_reply.c->use_h2)
 				http2_stream_remove_mesh_state(rep->h2_stream);
+#ifdef HAVE_NGTCP2
 			else if(rep->query_reply.doq_stream)
-				doq_stream_remove_mesh_state(
-					rep->query_reply.doq_stream);
+				doq_find_stream_remove_mesh_state(&rep->query_reply);
+#endif
 			comm_point_drop_reply(&rep->query_reply);
 			log_assert(mesh->num_reply_addrs > 0);
 			mesh->num_reply_addrs--;
@@ -1538,7 +1549,7 @@ mesh_send_reply(struct mesh_state* m, in
 		r->h2_stream->mesh_state = NULL;
 #ifdef HAVE_NGTCP2
 	} else if(r->query_reply.doq_stream) {
-		r->query_reply.doq_stream->mesh_state = NULL;
+		doq_find_stream_remove_mesh_state(&r->query_reply);
 #endif
 	}
 	/* send the reply */
@@ -1837,8 +1848,10 @@ void mesh_query_done(struct mesh_state* 
 						mstate);
 				else if(r->query_reply.c->use_h2)
 					http2_stream_remove_mesh_state(r->h2_stream);
+#ifdef HAVE_NGTCP2
 				else if(r->query_reply.doq_stream)
-					doq_stream_remove_mesh_state(r->query_reply.doq_stream);
+					doq_find_stream_remove_mesh_state(&r->query_reply);
+#endif
 				comm_point_drop_reply(&r->query_reply);
 				log_assert(mstate->s.env->mesh->num_reply_addrs > 0);
 				mstate->s.env->mesh->num_reply_addrs--;
@@ -1871,8 +1884,10 @@ void mesh_query_done(struct mesh_state* 
 					r->query_reply.c->tcp_req_info, mstate);
 			} else if(r->query_reply.c->use_h2) {
 				http2_stream_remove_mesh_state(r->h2_stream);
+#ifdef HAVE_NGTCP2
 			} else if(r->query_reply.doq_stream) {
-				doq_stream_remove_mesh_state(r->query_reply.doq_stream);
+				doq_find_stream_remove_mesh_state(&r->query_reply);
+#endif
 			}
 			comm_point_drop_reply(&r->query_reply);
 			log_assert(mstate->s.env->mesh->num_reply_addrs > 0);
@@ -2481,8 +2496,11 @@ void mesh_state_remove_reply(struct mesh
 			 * don't point back. */
 			if(n->h2_stream) n->h2_stream->mesh_state = NULL;
 #ifdef HAVE_NGTCP2
-			if(n->query_reply.doq_stream)
-				n->query_reply.doq_stream->mesh_state = NULL;
+			/* When doq calls with doq_stream nonNULL, it removes
+			 * the mesh_reply itself, and also already has the conn
+			 * locked, so no need to remove it here */
+			if(!doq_stream && n->query_reply.doq_stream)
+				doq_find_stream_remove_mesh_state(&n->query_reply);
 #endif
 
 			/* prev = prev; */
@@ -2663,8 +2681,10 @@ mesh_serve_expired_callback(void* arg)
 					r->query_reply.c->tcp_req_info, mstate);
 			else if(r->query_reply.c->use_h2)
 				http2_stream_remove_mesh_state(r->h2_stream);
+#ifdef HAVE_NGTCP2
 			else if(r->query_reply.doq_stream)
-				doq_stream_remove_mesh_state(r->query_reply.doq_stream);
+				doq_find_stream_remove_mesh_state(&r->query_reply);
+#endif
 			comm_point_drop_reply(&r->query_reply);
 			log_assert(mstate->s.env->mesh->num_reply_addrs > 0);
 			mstate->s.env->mesh->num_reply_addrs--;
Index: services/outside_network.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/services/outside_network.c,v
diff -u -p -r1.36 outside_network.c
--- services/outside_network.c	21 Sep 2026 08:43:48 -0000	1.36
+++ services/outside_network.c	21 Oct 2026 12:55:21 -0000
@@ -1524,6 +1524,49 @@ outnet_send_wait_udp(struct outside_netw
 	}
 }
 
+/** Clear out all pending requests that are on the port. */
+static void
+clear_pending_from_port(struct outside_network* outnet, struct comm_point* c)
+{
+	/* Otherwise, the clear the cache action of unwanted_threshold, can
+	 * have revealed this communication port. */
+	struct pending* p, *np, *list = NULL, *last = NULL;
+	/* First, remove the entries from the tree. */
+	/* The rbtree rbnode.right is used to build the temporary list here.
+	 * Otherwise the list could be malloced. */
+	p = (struct pending*)rbtree_first(outnet->pending);
+	while((rbnode_type*)p != RBTREE_NULL) {
+		np = (struct pending*)rbtree_next((rbnode_type*)p);
+		if(p->pc->cp == c) {
+			comm_timer_disable(p->timer);
+			(void)rbtree_delete(outnet->pending, p->node.key);
+			if(last)
+				last->node.right = (rbnode_type*)p;
+			else	list = p;
+			last = p;
+			p->node.right = NULL;
+		}
+		p = np;
+	}
+
+	/* The entries are removed from the tree, call their callbacks. */
+	p = list;
+	while(p) {
+		np = (struct pending*)p->node.right;
+		p->node.right = RBTREE_NULL;
+		/* callback error on pending */
+		if(p->cb) {
+			fptr_ok(fptr_whitelist_pending_udp(p->cb));
+			(void)(*p->cb)(p->pc->cp, p->cb_arg,
+				NETEVENT_CLOSED, NULL);
+		}
+		portcomm_loweruse(outnet, p->pc);
+		pending_delete(NULL, p);
+		outnet_send_wait_udp(outnet);
+		p = np;
+	}
+}
+
 int 
 outnet_udp_cb(struct comm_point* c, void* arg, int error,
 	struct comm_reply *reply_info)
@@ -1568,6 +1611,7 @@ outnet_udp_cb(struct comm_point* c, void
 				outnet->unwanted_action));
 			(*outnet->unwanted_action)(outnet->unwanted_param);
 			outnet->unwanted_total = 0;
+			clear_pending_from_port(outnet, c);
 		}
 		return 0;
 	}
@@ -1588,6 +1632,7 @@ outnet_udp_cb(struct comm_point* c, void
 				outnet->unwanted_action));
 			(*outnet->unwanted_action)(outnet->unwanted_param);
 			outnet->unwanted_total = 0;
+			clear_pending_from_port(outnet, c);
 		}
 		return 0;
 	}
Index: testcode/fake_event.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/testcode/fake_event.c,v
diff -u -p -r1.1.1.21 fake_event.c
--- testcode/fake_event.c	21 Sep 2026 08:42:30 -0000	1.1.1.21
+++ testcode/fake_event.c	21 Oct 2026 12:55:21 -0000
@@ -2046,6 +2046,10 @@ void doq_stream_remove_mesh_state(struct
 {
 }
 
+void doq_find_stream_remove_mesh_state(struct comm_reply* ATTR_UNUSED(repinfo))
+{
+}
+
 void fast_reload_service_cb(int ATTR_UNUSED(fd), short ATTR_UNUSED(event),
 	void* ATTR_UNUSED(arg))
 {
Index: testcode/unitdname.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/testcode/unitdname.c,v
diff -u -p -r1.1.1.4 unitdname.c
--- testcode/unitdname.c	26 Sep 2025 07:30:48 -0000	1.1.1.4
+++ testcode/unitdname.c	21 Oct 2026 12:55:21 -0000
@@ -269,6 +269,55 @@ dname_test_pkt_dname_len(sldns_buffer* b
 	unit_assert( pkt_dname_len(buff) == 0 );
 }
 
+/** test pkt_dname_len_budget */
+static void
+dname_test_pkt_dname_len_budget(sldns_buffer* buff)
+{
+	int budget = 0, limit = 20;
+	unit_show_func("util/data/dname.c", "pkt_dname_len_budget");
+
+	sldns_buffer_clear(buff);
+	sldns_buffer_write(buff, "\000", 1);
+	sldns_buffer_flip(buff);
+	unit_assert( pkt_dname_len_budget(buff, &budget, limit) == 1 );
+	unit_assert( sldns_buffer_position(buff) == 1);
+	unit_assert( budget == 0);
+
+	sldns_buffer_clear(buff);
+	sldns_buffer_write(buff, "\007example\003com\000", 13);
+	sldns_buffer_flip(buff);
+	unit_assert( pkt_dname_len_budget(buff, &budget, limit) == 13 );
+	unit_assert( sldns_buffer_position(buff) == 13);
+	unit_assert( budget == 0);
+
+	sldns_buffer_clear(buff);
+	sldns_buffer_skip(buff, 12); /* skip header */
+	sldns_buffer_write(buff, "\007example\003com\000", 13);
+	sldns_buffer_skip(buff, 10); /* more packet content */
+	sldns_buffer_write(buff, "\003www", 4);
+	sldns_buffer_write_u8(buff, 0xc0);
+	sldns_buffer_write_u8(buff, 0x0c);
+	sldns_buffer_flip(buff);
+	sldns_buffer_set_position(buff, 35);
+	unit_assert( pkt_dname_len_budget(buff, &budget, limit) == 17 );
+	unit_assert( sldns_buffer_position(buff) == 41);
+	unit_assert( budget == 1);
+
+	sldns_buffer_clear(buff);
+	sldns_buffer_skip(buff, 12); /* skip header */
+	sldns_buffer_write(buff, "\001a", 2);
+	sldns_buffer_write_u8(buff, 0xc0);
+	sldns_buffer_write_u8(buff, 0x0c);
+	sldns_buffer_skip(buff, 10); /* more packet content */
+	sldns_buffer_write(buff, "\003www", 4);
+	sldns_buffer_write_u8(buff, 0xc0);
+	sldns_buffer_write_u8(buff, 0x0c);
+	sldns_buffer_flip(buff);
+	sldns_buffer_set_position(buff, 26);
+	unit_assert( pkt_dname_len_budget(buff, &budget, limit) == 0 );
+	unit_assert( budget == 21);
+}
+
 /** test dname_lab_cmp */
 static void
 dname_test_dname_lab_cmp(void)
@@ -1296,6 +1345,7 @@ void dname_test(void)
 	dname_test_count_size_labels();
 	dname_test_dname_lab_cmp();
 	dname_test_pkt_dname_len(buff);
+	dname_test_pkt_dname_len_budget(buff);
 	dname_test_strict_subdomain();
 	dname_test_subdomain();
 	dname_test_isroot();
Index: util/netevent.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/util/netevent.c,v
diff -u -p -r1.45 netevent.c
--- util/netevent.c	21 Sep 2026 08:43:49 -0000	1.45
+++ util/netevent.c	21 Oct 2026 12:55:21 -0000
@@ -2828,7 +2828,7 @@ doq_lookup_repinfo(struct doq_table* tab
 /** doq find connection and stream. From inside callbacks from worker. */
 static int
 doq_lookup_conn_stream(struct comm_reply* repinfo, struct comm_point* c,
-	struct doq_conn** conn, struct doq_stream** stream)
+	struct doq_conn** conn, struct doq_stream** stream, int checkclose)
 {
 	log_assert(c->doq_socket);
 	if(c->doq_socket->current_conn) {
@@ -2856,7 +2856,7 @@ doq_lookup_conn_stream(struct comm_reply
 		}
 		return 0;
 	}
-	if((*stream)->is_closed) {
+	if(checkclose && (*stream)->is_closed) {
 		/* stream is closed, ignore reply or drop */
 		if(!c->doq_socket->current_conn) {
 			/* Not inside callbacks, we have our own lock on conn.
@@ -2868,6 +2868,24 @@ doq_lookup_conn_stream(struct comm_reply
 	return 1;
 }
 
+/** doq remove mesh state from stream */
+void doq_find_stream_remove_mesh_state(struct comm_reply* repinfo)
+{
+	struct doq_conn* conn;
+	struct doq_stream* stream;
+	log_assert(repinfo->c->type == comm_doq);
+	if(!doq_lookup_conn_stream(repinfo, repinfo->c, &conn, &stream, 0)) {
+		/* No stream, it may have been removed. */
+		return;
+	}
+	doq_stream_remove_mesh_state(stream);
+	if(!repinfo->c->doq_socket->current_conn) {
+		/* Not inside callbacks, we have our own lock on conn.
+		 * Release it. */
+		lock_basic_unlock(&conn->lock);
+	}
+}
+
 /** doq send a reply from a comm reply */
 static void
 doq_socket_send_reply(struct comm_reply* repinfo)
@@ -2875,7 +2893,7 @@ doq_socket_send_reply(struct comm_reply*
 	struct doq_conn* conn;
 	struct doq_stream* stream;
 	log_assert(repinfo->c->type == comm_doq);
-	if(!doq_lookup_conn_stream(repinfo, repinfo->c, &conn, &stream)) {
+	if(!doq_lookup_conn_stream(repinfo, repinfo->c, &conn, &stream, 1)) {
 		verbose(VERB_ALGO, "doq: send_reply but %s is gone",
 			(conn?"stream":"connection"));
 		/* No stream, it may have been closed. */
@@ -2906,7 +2924,7 @@ doq_socket_drop_reply(struct comm_reply*
 	struct doq_conn* conn;
 	struct doq_stream* stream;
 	log_assert(repinfo->c->type == comm_doq);
-	if(!doq_lookup_conn_stream(repinfo, repinfo->c, &conn, &stream)) {
+	if(!doq_lookup_conn_stream(repinfo, repinfo->c, &conn, &stream, 1)) {
 		verbose(VERB_ALGO, "doq: drop_reply but %s is gone",
 			(conn?"stream":"connection"));
 		/* The connection or stream is already gone. */
Index: util/netevent.h
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/util/netevent.h,v
diff -u -p -r1.28 netevent.h
--- util/netevent.h	21 Sep 2026 08:43:49 -0000	1.28
+++ util/netevent.h	21 Oct 2026 12:55:21 -0000
@@ -1136,6 +1136,10 @@ void doq_send_pkt(struct comm_point* c, 
 /** doq timer callback function. */
 void doq_timer_cb(void* arg);
 
+/** Find and lock doq connection and remove mesh state from the stream
+ * (found by id).*/
+void doq_find_stream_remove_mesh_state(struct comm_reply* repinfo);
+
 /** tcp read again callback function. For tcp req info listen. */
 void tcp_read_again_cb(void* arg);
 
Index: util/data/dname.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/util/data/dname.c,v
diff -u -p -r1.10 dname.c
--- util/data/dname.c	21 Sep 2026 08:43:49 -0000	1.10
+++ util/data/dname.c	21 Oct 2026 12:55:21 -0000
@@ -236,6 +236,61 @@ fail:
 	return 0;
 }
 
+size_t
+pkt_dname_len_budget(struct sldns_buffer* pkt, int* budget, int limit)
+{
+	size_t len = 0;
+	int ptrcount = 0;
+	uint8_t labellen;
+	size_t endpos = 0;
+
+	/* read dname and determine length */
+	/* check compression pointers, loops, out of bounds */
+	while(1) {
+		/* read next label */
+		if(sldns_buffer_remaining(pkt) < 1)
+			return 0;
+		labellen = sldns_buffer_read_u8(pkt);
+		if(LABEL_IS_PTR(labellen)) {
+			/* compression ptr */
+			uint16_t ptr;
+			if(sldns_buffer_remaining(pkt) < 1)
+				return 0;
+			ptr = PTR_OFFSET(labellen, sldns_buffer_read_u8(pkt));
+			if(++(*budget) > limit) {
+				verbose(VERB_ALGO, "The decompression pointer "
+					"limit of %d has been exceeded", limit);
+				return 0; /* too many decompression pointers */
+			}
+			if(ptrcount++ > MAX_COMPRESS_PTRS)
+				return 0; /* loop! */
+			if(sldns_buffer_limit(pkt) <= ptr)
+				return 0; /* out of bounds! */
+			if(!endpos)
+				endpos = sldns_buffer_position(pkt);
+			sldns_buffer_set_position(pkt, ptr);
+		} else {
+			/* label contents */
+			if(labellen > 0x3f)
+				return 0; /* label too long */
+			len += 1 + labellen;
+			if(len > LDNS_MAX_DOMAINLEN)
+				return 0;
+			if(labellen == 0) {
+				/* end of dname */
+				break;
+			}
+			if(sldns_buffer_remaining(pkt) < labellen)
+				return 0;
+			sldns_buffer_skip(pkt, (ssize_t)labellen);
+		}
+	}
+	if(endpos)
+		sldns_buffer_set_position(pkt, endpos);
+
+	return len;
+}
+
 int 
 dname_pkt_compare(sldns_buffer* pkt, uint8_t* d1, uint8_t* d2)
 {
Index: util/data/dname.h
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/util/data/dname.h,v
diff -u -p -r1.8 dname.h
--- util/data/dname.h	26 Sep 2025 07:32:37 -0000	1.8
+++ util/data/dname.h	21 Oct 2026 12:55:21 -0000
@@ -110,6 +110,22 @@ int query_dname_compare(uint8_t* d1, uin
 size_t pkt_dname_len(struct sldns_buffer* pkt);
 
 /**
+ * Determine correct, compressed, dname present in packet.
+ * Checks for parse errors. Has a budget and limit for decompression,
+ * if the limit is exceeded, this is an error.
+ * @param pkt: packet to read from (from current start position).
+ * @param budget: the budget of decompression pointer follows.
+ *	It is updated with the additional count (if any) of pointer follows,
+ *	so that a running total can be kept over a number of dnames.
+ * @param limit: the limit of decompression pointer follows.
+ * @return: 0 on parse error.
+ *	At exit the position is right after the (compressed) dname.
+ *	Compression pointers are followed and checked for loops.
+ *	The uncompressed wireformat length is returned.
+ */
+size_t pkt_dname_len_budget(struct sldns_buffer* pkt, int* budget, int limit);
+
+/**
  * Compare dnames in packet (compressed). Dnames must be valid.
  * routine performs lowercasing, so the packet casing is preserved.
  * @param pkt: packet, used to resolve compression pointers.
Index: util/data/msgparse.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/util/data/msgparse.c,v
diff -u -p -r1.16 msgparse.c
--- util/data/msgparse.c	21 Sep 2026 08:43:49 -0000	1.16
+++ util/data/msgparse.c	21 Oct 2026 12:55:21 -0000
@@ -54,6 +54,9 @@
 #include "sldns/wire2str.h"
 
 #define MAX_PARSED_EDNS_OPTIONS 100
+/** The max number of decompression pointers that are allowed in a received
+ * packet. The parse fails if the limit is exceeded. */
+#define PARSE_DECOMPRESS_LIMIT 2048
 
 /** smart comparison of (compressed, valid) dnames from packet */
 static int
@@ -114,7 +117,7 @@ new_rrset(struct msg_parse* msg, uint8_t
 
 /** See if next rrset is nsec at zone apex */
 static int
-nsec_at_apex(sldns_buffer* pkt)
+nsec_at_apex(sldns_buffer* pkt, int* budget, int limit)
 {
 	/* we are at ttl position in packet. */
 	size_t pos = sldns_buffer_position(pkt);
@@ -128,7 +131,7 @@ nsec_at_apex(sldns_buffer* pkt)
 		return 0; /* parse error happens later */
 	}
 	/* must validate the nsec next domain name format */
-	if(pkt_dname_len(pkt) == 0) {
+	if(pkt_dname_len_budget(pkt, budget, limit) == 0) {
 		sldns_buffer_set_position(pkt, pos);
 		return 0; /* parse error */
 	}
@@ -159,10 +162,11 @@ nsec_at_apex(sldns_buffer* pkt)
 
 /** Calculate rrset flags */
 static uint32_t
-pkt_rrset_flags(sldns_buffer* pkt, uint16_t type, sldns_pkt_section sec)
+pkt_rrset_flags(sldns_buffer* pkt, uint16_t type, sldns_pkt_section sec,
+	int* budget, int limit)
 {
 	uint32_t f = 0;
-	if(type == LDNS_RR_TYPE_NSEC && nsec_at_apex(pkt)) {
+	if(type == LDNS_RR_TYPE_NSEC && nsec_at_apex(pkt, budget, limit)) {
 		f |= PACKED_RRSET_NSEC_AT_APEX;
 	} else if(type == LDNS_RR_TYPE_SOA && sec == LDNS_SECTION_AUTHORITY) {
 		f |= PACKED_RRSET_SOA_NEG;
@@ -462,6 +466,8 @@ change_rrsig_rrset(struct rrset_parse* s
  * @param rrset_prev: last seen RRset.
  * @param section: the current section in the packet.
  * @param region: used to allocate temporary parsing data.
+ * @param budget: decompression budget.
+ * @param limit: decompression limit.
  * @return 0 on out of memory.
  */
 static int
@@ -471,7 +477,8 @@ find_rrset(struct msg_parse* msg, sldns_
 	uint8_t** prev_dname_first, uint8_t** prev_dname_last,
 	size_t* prev_dnamelen, uint16_t* prev_type,
 	uint16_t* prev_dclass, struct rrset_parse** rrset_prev,
-	sldns_pkt_section section, struct regional* region)
+	sldns_pkt_section section, struct regional* region, int* budget,
+	int limit)
 {
 	hashvalue_type dname_h = pkt_hash_rrset_first(pkt, dname);
 	uint16_t covtype;
@@ -498,7 +505,7 @@ find_rrset(struct msg_parse* msg, sldns_
 		}
 	}
 	/* find by hashing and lookup in hashtable */
-	*rrset_flags = pkt_rrset_flags(pkt, type, section);
+	*rrset_flags = pkt_rrset_flags(pkt, type, section, budget, limit);
 	
 	/* if rrsig - try to lookup matching data set first */
 	if(type == LDNS_RR_TYPE_RRSIG && pkt_rrsig_covered(pkt, 
@@ -580,10 +587,13 @@ find_rrset(struct msg_parse* msg, sldns_
  * @param pkt: packet, position at call must be at start of query section.
  *	at end position is after query section.
  * @param msg: store results here.
+ * @param budget: decompression budget.
+ * @param limit: decompression limit.
  * @return: 0 if OK, or rcode on error.
  */
 static int
-parse_query_section(sldns_buffer* pkt, struct msg_parse* msg)
+parse_query_section(sldns_buffer* pkt, struct msg_parse* msg, int* budget,
+	int limit)
 {
 	if(msg->qdcount == 0)
 		return 0;
@@ -593,7 +603,7 @@ parse_query_section(sldns_buffer* pkt, s
 	if(sldns_buffer_remaining(pkt) <= 0)
 		return LDNS_RCODE_FORMERR;
 	msg->qname = sldns_buffer_current(pkt);
-	if((msg->qname_len = pkt_dname_len(pkt)) == 0)
+	if((msg->qname_len = pkt_dname_len_budget(pkt, budget, limit)) == 0)
 		return LDNS_RCODE_FORMERR;
 	if(sldns_buffer_remaining(pkt) < sizeof(uint16_t)*2)
 		return LDNS_RCODE_FORMERR;
@@ -638,7 +648,8 @@ get_rdf_size(sldns_rdf_type rdf)
 
 /** calculate the size of one rr */
 static int
-calc_size(sldns_buffer* pkt, uint16_t type, struct rr_parse* rr)
+calc_size(sldns_buffer* pkt, uint16_t type, struct rr_parse* rr, int* budget,
+	int limit)
 {
 	const sldns_rr_descriptor* desc;
 	uint16_t pkt_len; /* length of rr inside the packet */
@@ -659,7 +670,8 @@ calc_size(sldns_buffer* pkt, uint16_t ty
 			case LDNS_RDF_TYPE_DNAME:
 				/* decompress every domain name */
 				oldpos = sldns_buffer_position(pkt);
-				if((len = pkt_dname_len(pkt)) == 0)
+				if((len = pkt_dname_len_budget(pkt, budget,
+					limit)) == 0)
 					return 0; /* malformed dname */
 				if(sldns_buffer_position(pkt)-oldpos > pkt_len)
 					return 0; /* dname exceeds rdata */
@@ -758,7 +770,7 @@ sig_is_double(sldns_buffer* pkt, struct 
 static int
 add_rr_to_rrset(struct rrset_parse* rrset, sldns_buffer* pkt, 
 	struct msg_parse* msg, struct regional* region, 
-	sldns_pkt_section section, uint16_t type)
+	sldns_pkt_section section, uint16_t type, int* budget, int limit)
 {
 	struct rr_parse* rr;
 	/* check section of rrset. */
@@ -809,8 +821,10 @@ add_rr_to_rrset(struct rrset_parse* rrse
 	}
 
 	/* calc decompressed size */
-	if(!calc_size(pkt, type, rr))
+	if(!calc_size(pkt, type, rr, budget, limit)) {
+		if(*budget > limit) return LDNS_RCODE_SERVFAIL;
 		return LDNS_RCODE_FORMERR;
+	}
 	rrset->size += rr->size;
 
 	return 0;
@@ -825,12 +839,14 @@ add_rr_to_rrset(struct rrset_parse* rrse
  * @param section: section enum.
  * @param num_rrs: how many rrs are in the section.
  * @param num_rrsets: returns number of rrsets in the section.
+ * @param budget: decompression budget.
+ * @param limit: decompression limit.
  * @return: 0 if OK, or rcode on error.
  */
 static int
 parse_section(sldns_buffer* pkt, struct msg_parse* msg, 
 	struct regional* region, sldns_pkt_section section, 
-	uint16_t num_rrs, size_t* num_rrsets)
+	uint16_t num_rrs, size_t* num_rrsets, int* budget, int limit)
 {
 	uint16_t i;
 	uint8_t* dname, *prev_dname_f = NULL, *prev_dname_l = NULL;
@@ -849,8 +865,10 @@ parse_section(sldns_buffer* pkt, struct 
 	for(i=0; i<num_rrs; i++) {
 		/* parse this RR. */
 		dname = sldns_buffer_current(pkt);
-		if((dnamelen = pkt_dname_len(pkt)) == 0)
+		if((dnamelen = pkt_dname_len_budget(pkt, budget, limit)) == 0) {
+			if(*budget > limit) return LDNS_RCODE_SERVFAIL;
 			return LDNS_RCODE_FORMERR;
+		}
 		if(sldns_buffer_remaining(pkt) < 10) /* type, class, ttl, len */
 			return LDNS_RCODE_FORMERR;
 		type = sldns_buffer_read_u16(pkt);
@@ -886,7 +904,7 @@ parse_section(sldns_buffer* pkt, struct 
 		if(!find_rrset(msg, pkt, dname, dnamelen, type, dclass, &hash, 
 			&rrset_flags, &prev_dname_f, &prev_dname_l, 
 			&prev_dnamelen, &prev_type, &prev_dclass, &rrset, 
-			section, region))
+			section, region, budget, limit))
 			return LDNS_RCODE_SERVFAIL;
 		if(!rrset) {
 			/* it is a new RR set. hash&flags already calculated.*/
@@ -906,7 +924,7 @@ parse_section(sldns_buffer* pkt, struct 
 		}
 		/* add to rrset. */
 		if((r=add_rr_to_rrset(rrset, pkt, msg, region, section, 
-			type)) != 0)
+			type, budget, limit)) != 0)
 			return r;
 	}
 	return 0;
@@ -915,7 +933,7 @@ parse_section(sldns_buffer* pkt, struct 
 int
 parse_packet(sldns_buffer* pkt, struct msg_parse* msg, struct regional* region)
 {
-	int ret;
+	int ret, budget = 0, limit = PARSE_DECOMPRESS_LIMIT;
 	if(sldns_buffer_remaining(pkt) < LDNS_HEADER_SIZE)
 		return LDNS_RCODE_FORMERR;
 	/* read the header */
@@ -927,19 +945,20 @@ parse_packet(sldns_buffer* pkt, struct m
 	msg->arcount = sldns_buffer_read_u16(pkt);
 	if(msg->qdcount > 1)
 		return LDNS_RCODE_FORMERR;
-	if((ret = parse_query_section(pkt, msg)) != 0)
+	if((ret = parse_query_section(pkt, msg, &budget, limit)) != 0)
 		return ret;
 	if((ret = parse_section(pkt, msg, region, LDNS_SECTION_ANSWER,
-		msg->ancount, &msg->an_rrsets)) != 0)
+		msg->ancount, &msg->an_rrsets, &budget, limit)) != 0)
 		return ret;
 	if((ret = parse_section(pkt, msg, region, LDNS_SECTION_AUTHORITY,
-		msg->nscount, &msg->ns_rrsets)) != 0)
+		msg->nscount, &msg->ns_rrsets, &budget, limit)) != 0)
 		return ret;
 	if(sldns_buffer_remaining(pkt) == 0 && msg->arcount == 1) {
 		/* BIND accepts leniently that an EDNS record is missing.
 		 * so, we do too. */
 	} else if((ret = parse_section(pkt, msg, region,
-		LDNS_SECTION_ADDITIONAL, msg->arcount, &msg->ar_rrsets)) != 0)
+		LDNS_SECTION_ADDITIONAL, msg->arcount, &msg->ar_rrsets,
+		&budget, limit)) != 0)
 		return ret;
 	/* if(sldns_buffer_remaining(pkt) > 0) { */
 		/* there is spurious data at end of packet. ignore */
Index: validator/validator.c
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/validator/validator.c,v
diff -u -p -r1.31 validator.c
--- validator/validator.c	21 Sep 2026 08:43:49 -0000	1.31
+++ validator/validator.c	21 Oct 2026 12:55:21 -0000
@@ -1963,14 +1963,7 @@ processInit(struct module_qstate* qstate
 	lookup_name = vq->qchase.qname;
 	lookup_len = vq->qchase.qname_len;
 	/* for type DS look at the parent side for keys/trustanchor */
-	/* also for NSEC not at apex */
-	if(vq->qchase.qtype == LDNS_RR_TYPE_DS ||
-		(vq->qchase.qtype == LDNS_RR_TYPE_NSEC && 
-		 vq->orig_msg->rep->rrset_count > vq->rrset_skip &&
-		 ntohs(vq->orig_msg->rep->rrsets[vq->rrset_skip]->rk.type) ==
-		 LDNS_RR_TYPE_NSEC &&
-		 !(vq->orig_msg->rep->rrsets[vq->rrset_skip]->
-		 rk.flags&PACKED_RRSET_NSEC_AT_APEX))) {
+	if(vq->qchase.qtype == LDNS_RR_TYPE_DS) {
 		dname_remove_label(&lookup_name, &lookup_len);
 	}
 
@@ -2007,13 +2000,15 @@ processInit(struct module_qstate* qstate
 		anchor = anchors_lookup(qstate->env->anchors, 
 			lookup_name, lookup_len, vq->qchase.qclass);
 		if(!anchor) { /* unsigned parent denies anchor*/
-			verbose(VERB_QUERY, "unsigned parent zone denies"
-				" trust anchor, indeterminate");
-			vq->chase_reply->security = sec_status_indeterminate;
-			update_reason_bogus(vq->chase_reply, LDNS_EDE_DNSSEC_INDETERMINATE);
+			verbose(VERB_ALGO, "unsigned parent zone denies"
+				" trust anchor, thus bogus");
+			errinf_ede(qstate, "unsigned parent zone denies trust anchor, thus bogus", LDNS_EDE_DNSSEC_BOGUS);
+			vq->chase_reply->security = sec_status_bogus;
+			update_reason_bogus(vq->chase_reply, LDNS_EDE_DNSSEC_BOGUS);
 			vq->state = VAL_FINISHED_STATE;
 			return 1;
 		}
+		vq->parent_denial_must_be_secure = 1;
 		verbose(VERB_ALGO, "trust anchor NXDOMAIN by signed parent");
 	} else if(subtype == VAL_CLASS_POSITIVE &&
 		qstate->qinfo.qtype == LDNS_RR_TYPE_DNSKEY &&
@@ -2538,6 +2533,15 @@ processFinished(struct module_qstate* qs
 	enum val_classification subtype = val_classify_response(
 		qstate->query_flags, &qstate->qinfo, &vq->qchase, 
 		vq->orig_msg->rep, vq->rrset_skip);
+	if(vq->parent_denial_must_be_secure) {
+		if(vq->chase_reply->security != sec_status_secure &&
+			vq->chase_reply->security != sec_status_bogus) {
+			verbose(VERB_ALGO, "parent denial of trust island subdomain is not secure, thus bogus");
+			vq->chase_reply->security = sec_status_bogus;
+			update_reason_bogus(vq->chase_reply, LDNS_EDE_DNSSEC_BOGUS);
+			errinf_ede(qstate, "parent denial of trust island subdomain is not secure, thus bogus", LDNS_EDE_DNSSEC_BOGUS);
+		}
+	}
 
 	/* store overall validation result in orig_msg */
 	if(vq->rrset_skip == 0) {
Index: validator/validator.h
===================================================================
RCS file: /cvs/src/usr.sbin/unbound/validator/validator.h,v
diff -u -p -r1.12 validator.h
--- validator/validator.h	21 Sep 2026 08:43:49 -0000	1.12
+++ validator/validator.h	21 Oct 2026 12:55:21 -0000
@@ -244,6 +244,10 @@ struct val_qstate {
 	int num_nsec_attempts;
 	/** The nsec attempts have been exceeded. */
 	int num_nsec_attempts_exceeded;
+
+	/** if validation must be secure, (else bogus) for NXDOMAIN
+	 * from signed parent to (removed) trust anchor subdomain. */
+	int parent_denial_must_be_secure;
 };
 
 /**