Download raw body.
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;
};
/**
unbound 1.26.2