From 71a971d70c7f3f97911b5e8b9a73b7ec9fee10f6 Mon Sep 17 00:00:00 2001 From: "J. Dekker" Date: Thu, 9 Jul 2026 09:19:42 +0200 Subject: [PATCH 01/84] - Fix randomness generation on macOS/iOS under chroot (#1383) SecRandomCopyBytes() has existed since macOS 10.7 (2011) and iOS 2.0 (2008), and is the primary API for cryptographic random numbers. --- compat/getentropy_osx.c | 389 +--------------------------------------- 1 file changed, 7 insertions(+), 382 deletions(-) diff --git a/compat/getentropy_osx.c b/compat/getentropy_osx.c index 26dcc824d..d8134b30a 100644 --- a/compat/getentropy_osx.c +++ b/compat/getentropy_osx.c @@ -20,398 +20,23 @@ * http://man.openbsd.org/getentropy.2 */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include -#include -#include -#include -#include -#include -#if TARGET_OS_OSX -#include -#include -#endif -#include -#include -#if TARGET_OS_OSX -#include -#include -#include -#include -#endif -#include -#define SHA512_Update(a, b, c) (CC_SHA512_Update((a), (b), (c))) -#define SHA512_Init(xxx) (CC_SHA512_Init((xxx))) -#define SHA512_Final(xxx, yyy) (CC_SHA512_Final((xxx), (yyy))) -#define SHA512_CTX CC_SHA512_CTX -#define SHA512_DIGEST_LENGTH CC_SHA512_DIGEST_LENGTH - -#define REPEAT 5 -#define min(a, b) (((a) < (b)) ? (a) : (b)) - -#define HX(a, b) \ - do { \ - if ((a)) \ - HD(errno); \ - else \ - HD(b); \ - } while (0) - -#define HR(x, l) (SHA512_Update(&ctx, (char *)(x), (l))) -#define HD(x) (SHA512_Update(&ctx, (char *)&(x), sizeof (x))) -#define HF(x) (SHA512_Update(&ctx, (char *)&(x), sizeof (void*))) +#include int getentropy(void *buf, size_t len); -static int getentropy_urandom(void *buf, size_t len); -static int getentropy_fallback(void *buf, size_t len); - int getentropy(void *buf, size_t len) { - int ret = -1; - if (len > 256) { - errno = EIO; - return (-1); + goto error; } - /* - * Try to get entropy with /dev/urandom - * - * This can fail if the process is inside a chroot or if file - * descriptors are exhausted. - */ - ret = getentropy_urandom(buf, len); - if (ret != -1) - return (ret); - - /* - * Entropy collection via /dev/urandom and sysctl have failed. - * - * No other API exists for collecting entropy, and we have - * no failsafe way to get it on OSX that is not sensitive - * to resource exhaustion. - * - * We have very few options: - * - Even syslog_r is unsafe to call at this low level, so - * there is no way to alert the user or program. - * - Cannot call abort() because some systems have unsafe - * corefiles. - * - Could raise(SIGKILL) resulting in silent program termination. - * - Return EIO, to hint that arc4random's stir function - * should raise(SIGKILL) - * - Do the best under the circumstances.... - * - * This code path exists to bring light to the issue that OSX - * does not provide a failsafe API for entropy collection. - * - * We hope this demonstrates that OSX should consider - * providing a new failsafe API which works in a chroot or - * when file descriptors are exhausted. - */ -#undef FAIL_INSTEAD_OF_TRYING_FALLBACK -#ifdef FAIL_INSTEAD_OF_TRYING_FALLBACK - raise(SIGKILL); -#endif - ret = getentropy_fallback(buf, len); - if (ret != -1) - return (ret); + if (SecRandomCopyBytes(kSecRandomDefault, len, buf) == errSecSuccess) { + return 0; + } +error: errno = EIO; - return (ret); -} - -static int -getentropy_urandom(void *buf, size_t len) -{ - struct stat st; - size_t i; - int fd, flags; - int save_errno = errno; - -start: - - flags = O_RDONLY; -#ifdef O_NOFOLLOW - flags |= O_NOFOLLOW; -#endif -#ifdef O_CLOEXEC - flags |= O_CLOEXEC; -#endif - fd = open("/dev/urandom", flags, 0); - if (fd == -1) { - if (errno == EINTR) - goto start; - goto nodevrandom; - } -#ifndef O_CLOEXEC - fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC); -#endif - - /* Lightly verify that the device node looks sane */ - if (fstat(fd, &st) == -1 || !S_ISCHR(st.st_mode)) { - close(fd); - goto nodevrandom; - } - for (i = 0; i < len; ) { - size_t wanted = len - i; - ssize_t ret = read(fd, (char *)buf + i, wanted); - - if (ret == -1) { - if (errno == EAGAIN || errno == EINTR) - continue; - close(fd); - goto nodevrandom; - } - i += ret; - } - close(fd); - errno = save_errno; - return (0); /* satisfied */ -nodevrandom: - errno = EIO; - return (-1); -} - -#if TARGET_OS_OSX -static int tcpmib[] = { CTL_NET, AF_INET, IPPROTO_TCP, TCPCTL_STATS }; -static int udpmib[] = { CTL_NET, AF_INET, IPPROTO_UDP, UDPCTL_STATS }; -static int ipmib[] = { CTL_NET, AF_INET, IPPROTO_IP, IPCTL_STATS }; -#endif -static int kmib[] = { CTL_KERN, KERN_USRSTACK }; -static int hwmib[] = { CTL_HW, HW_USERMEM }; - -static int -getentropy_fallback(void *buf, size_t len) -{ - uint8_t results[SHA512_DIGEST_LENGTH]; - int save_errno = errno, e, pgs = getpagesize(), faster = 0, repeat; - static int cnt; - struct timespec ts; - struct timeval tv; - struct rusage ru; - sigset_t sigset; - struct stat st; - SHA512_CTX ctx; - static pid_t lastpid; - pid_t pid; - size_t i, ii, m; - char *p; -#if TARGET_OS_OSX - struct tcpstat tcpstat; - struct udpstat udpstat; - struct ipstat ipstat; -#endif - u_int64_t mach_time; - unsigned int idata; - void *addr; - - pid = getpid(); - if (lastpid == pid) { - faster = 1; - repeat = 2; - } else { - faster = 0; - lastpid = pid; - repeat = REPEAT; - } - for (i = 0; i < len; ) { - int j; - SHA512_Init(&ctx); - for (j = 0; j < repeat; j++) { - HX((e = gettimeofday(&tv, NULL)) == -1, tv); - if (e != -1) { - cnt += (int)tv.tv_sec; - cnt += (int)tv.tv_usec; - } - - mach_time = mach_absolute_time(); - HD(mach_time); - - ii = sizeof(addr); - HX(sysctl(kmib, sizeof(kmib) / sizeof(kmib[0]), - &addr, &ii, NULL, 0) == -1, addr); - - ii = sizeof(idata); - HX(sysctl(hwmib, sizeof(hwmib) / sizeof(hwmib[0]), - &idata, &ii, NULL, 0) == -1, idata); - -#if TARGET_OS_OSX - ii = sizeof(tcpstat); - HX(sysctl(tcpmib, sizeof(tcpmib) / sizeof(tcpmib[0]), - &tcpstat, &ii, NULL, 0) == -1, tcpstat); - - ii = sizeof(udpstat); - HX(sysctl(udpmib, sizeof(udpmib) / sizeof(udpmib[0]), - &udpstat, &ii, NULL, 0) == -1, udpstat); - - ii = sizeof(ipstat); - HX(sysctl(ipmib, sizeof(ipmib) / sizeof(ipmib[0]), - &ipstat, &ii, NULL, 0) == -1, ipstat); -#endif - - HX((pid = getpid()) == -1, pid); - HX((pid = getsid(pid)) == -1, pid); - HX((pid = getppid()) == -1, pid); - HX((pid = getpgid(0)) == -1, pid); - HX((e = getpriority(0, 0)) == -1, e); - - if (!faster) { - ts.tv_sec = 0; - ts.tv_nsec = 1; - (void) nanosleep(&ts, NULL); - } - - HX(sigpending(&sigset) == -1, sigset); - HX(sigprocmask(SIG_BLOCK, NULL, &sigset) == -1, - sigset); - - HF(getentropy); /* an addr in this library */ - HF(printf); /* an addr in libc */ - p = (char *)&p; - HD(p); /* an addr on stack */ - p = (char *)&errno; - HD(p); /* the addr of errno */ - - if (i == 0) { - struct sockaddr_storage ss; - struct statvfs stvfs; - struct termios tios; - struct statfs stfs; - socklen_t ssl; - off_t off; - - /* - * Prime-sized mappings encourage fragmentation; - * thus exposing some address entropy. - */ - struct mm { - size_t npg; - void *p; - } mm[] = { - { 17, MAP_FAILED }, { 3, MAP_FAILED }, - { 11, MAP_FAILED }, { 2, MAP_FAILED }, - { 5, MAP_FAILED }, { 3, MAP_FAILED }, - { 7, MAP_FAILED }, { 1, MAP_FAILED }, - { 57, MAP_FAILED }, { 3, MAP_FAILED }, - { 131, MAP_FAILED }, { 1, MAP_FAILED }, - }; - - for (m = 0; m < sizeof mm/sizeof(mm[0]); m++) { - HX(mm[m].p = mmap(NULL, - mm[m].npg * pgs, - PROT_READ|PROT_WRITE, - MAP_PRIVATE|MAP_ANON, -1, - (off_t)0), mm[m].p); - if (mm[m].p != MAP_FAILED) { - size_t mo; - - /* Touch some memory... */ - p = mm[m].p; - mo = cnt % - (mm[m].npg * pgs - 1); - p[mo] = 1; - cnt += (int)((long)(mm[m].p) - / pgs); - } - - /* Check cnts and times... */ - mach_time = mach_absolute_time(); - HD(mach_time); - cnt += (int)mach_time; - - HX((e = getrusage(RUSAGE_SELF, - &ru)) == -1, ru); - if (e != -1) { - cnt += (int)ru.ru_utime.tv_sec; - cnt += (int)ru.ru_utime.tv_usec; - } - } - - for (m = 0; m < sizeof mm/sizeof(mm[0]); m++) { - if (mm[m].p != MAP_FAILED) - munmap(mm[m].p, mm[m].npg * pgs); - mm[m].p = MAP_FAILED; - } - - HX(stat(".", &st) == -1, st); - HX(statvfs(".", &stvfs) == -1, stvfs); - HX(statfs(".", &stfs) == -1, stfs); - - HX(stat("/", &st) == -1, st); - HX(statvfs("/", &stvfs) == -1, stvfs); - HX(statfs("/", &stfs) == -1, stfs); - - HX((e = fstat(0, &st)) == -1, st); - if (e == -1) { - if (S_ISREG(st.st_mode) || - S_ISFIFO(st.st_mode) || - S_ISSOCK(st.st_mode)) { - HX(fstatvfs(0, &stvfs) == -1, - stvfs); - HX(fstatfs(0, &stfs) == -1, - stfs); - HX((off = lseek(0, (off_t)0, - SEEK_CUR)) < 0, off); - } - if (S_ISCHR(st.st_mode)) { - HX(tcgetattr(0, &tios) == -1, - tios); - } else if (S_ISSOCK(st.st_mode)) { - memset(&ss, 0, sizeof ss); - ssl = sizeof(ss); - HX(getpeername(0, - (void *)&ss, &ssl) == -1, - ss); - } - } - - HX((e = getrusage(RUSAGE_CHILDREN, - &ru)) == -1, ru); - if (e != -1) { - cnt += (int)ru.ru_utime.tv_sec; - cnt += (int)ru.ru_utime.tv_usec; - } - } else { - /* Subsequent hashes absorb previous result */ - HD(results); - } - - HX((e = gettimeofday(&tv, NULL)) == -1, tv); - if (e != -1) { - cnt += (int)tv.tv_sec; - cnt += (int)tv.tv_usec; - } - - HD(cnt); - } - - SHA512_Final(results, &ctx); - memcpy((char *)buf + i, results, min(sizeof(results), len - i)); - i += min(sizeof(results), len - i); - } - explicit_bzero(&ctx, sizeof ctx); - explicit_bzero(results, sizeof results); - errno = save_errno; - return (0); /* satisfied */ + return -1; } From 61ca4111a1a500214fb6824782ef1afbbfcebb07 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Thu, 9 Jul 2026 09:21:56 +0200 Subject: [PATCH 02/84] Changelog note and explanation comment for #1383 - Merge #1383 from jdek: Fix randomness generation on macOS/iOS under chroot. --- compat/getentropy_osx.c | 2 ++ doc/Changelog | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/compat/getentropy_osx.c b/compat/getentropy_osx.c index d8134b30a..ab9456a24 100644 --- a/compat/getentropy_osx.c +++ b/compat/getentropy_osx.c @@ -20,6 +20,8 @@ * http://man.openbsd.org/getentropy.2 */ +/* Modified to use SecRandomCopyBytes. It is from macOS 10.7 (2011) and + * iOS 2.0 (2008), and is the primary API for cryptographic random numbers. */ #include #include diff --git a/doc/Changelog b/doc/Changelog index 348c4e01c..15bb42477 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,7 @@ +9 July 2026: Wouter + - Merge #1383 from jdek: Fix randomness generation on + macOS/iOS under chroot. + 2 July 2026: Wouter - Merge #1087: Overload `local_data_remove` to support removing specific records. From ad9b12a8636dced8f7188a8e063ddc2c36c9de84 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Thu, 9 Jul 2026 09:52:09 +0200 Subject: [PATCH 03/84] - Fix unit test for malformed svcb for test on Windows. --- doc/Changelog | 1 + testdata/iter_svcb_malformed.rpl | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/doc/Changelog b/doc/Changelog index 15bb42477..d5276f302 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,6 +1,7 @@ 9 July 2026: Wouter - Merge #1383 from jdek: Fix randomness generation on macOS/iOS under chroot. + - Fix unit test for malformed svcb for test on Windows. 2 July 2026: Wouter - Merge #1087: Overload `local_data_remove` to support removing diff --git a/testdata/iter_svcb_malformed.rpl b/testdata/iter_svcb_malformed.rpl index 8ac31fd2d..e08ed8e52 100644 --- a/testdata/iter_svcb_malformed.rpl +++ b/testdata/iter_svcb_malformed.rpl @@ -151,7 +151,14 @@ REPLY QR RD RA NOERROR SECTION QUESTION www.example.com. IN HTTPS SECTION ANSWER -www.example.com. 0 IN HTTPS 1 . alpn="h2" alpn="h3" +; www.example.com. 0 IN HTTPS 1 . alpn="h2" alpn="h3" +; in unknown record format, otherwise the zonefile format reader converts +; the svcb and sorts the svcbparams, and puts them in-order. That qsort is +; not stable, on some systems(windows), and that would put the identical +; key elements in a different order. With the unknown record format this +; conversion is ommitted, and the bad svcb record with duplicate keys stays +; in the same byte format. +www.example.com. IN HTTPS \# 17 00 01 00 00 01 00 03 02 68 32 00 01 00 03 02 68 33 ENTRY_END STEP 20 QUERY From a2fe5356b59533d4c062a8e4567c6bb7f2088f61 Mon Sep 17 00:00:00 2001 From: Petr Vaganov Date: Mon, 20 Jul 2026 15:04:47 +0700 Subject: [PATCH 04/84] ipsecmod: fix deref on null in ipsecmod-whitelist after OOM (#1475) DEREF_OF_NULL.RET.STAT Return value of a function 'rbtree_create' is dereferenced at ipsecmod-whitelist.c:105 without checking for NULL, but it is usually checked for this function (5/6). In ipsecmod_whitelist_apply_cfg(), the return value of rbtree_create() is not checked for NULL before being used. Found by the static analyzer Svace (ISP RAS). Signed-off-by: Petr Vaganov --- ipsecmod/ipsecmod-whitelist.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ipsecmod/ipsecmod-whitelist.c b/ipsecmod/ipsecmod-whitelist.c index c2b1f5d4a..0ff59145f 100644 --- a/ipsecmod/ipsecmod-whitelist.c +++ b/ipsecmod/ipsecmod-whitelist.c @@ -100,6 +100,8 @@ ipsecmod_whitelist_apply_cfg(struct ipsecmod_env* ie, struct config_file* cfg) { ie->whitelist = rbtree_create(name_tree_compare); + if (!ie->whitelist) + return 0; if(!read_whitelist(ie->whitelist, cfg)) return 0; name_tree_init_parents(ie->whitelist); From 87f9258fb4b10ce47704c1d8f3bc1310d6813033 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Mon, 20 Jul 2026 10:05:45 +0200 Subject: [PATCH 05/84] Changelog entry for #1475 - Merge #1475 from petrvaganoff: ipsecmod: fix deref on null in ipsecmod-whitelist after OOM. --- doc/Changelog | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/Changelog b/doc/Changelog index d5276f302..795ea371b 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,7 @@ +20 July 2026: Wouter + - Merge #1475 from petrvaganoff: ipsecmod: fix deref on null + in ipsecmod-whitelist after OOM. + 9 July 2026: Wouter - Merge #1383 from jdek: Fix randomness generation on macOS/iOS under chroot. From fac75848306108f6ef8206e7c48ce8f13d8b808f Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Mon, 20 Jul 2026 10:14:26 +0200 Subject: [PATCH 06/84] =?UTF-8?q?-=20Fix=20#1474:=20DoQ=20responses=20are?= =?UTF-8?q?=20never=20padded=20-=20pad-responses=20=20=20does=20not=20appl?= =?UTF-8?q?y=20to=20comm=5Fdoq=20(RFC=209250=20=C2=A75.4=20MUST).?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc/Changelog | 2 ++ util/data/msgparse.c | 7 +++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/doc/Changelog b/doc/Changelog index 795ea371b..c16730f49 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,6 +1,8 @@ 20 July 2026: Wouter - Merge #1475 from petrvaganoff: ipsecmod: fix deref on null in ipsecmod-whitelist after OOM. + - Fix #1474: DoQ responses are never padded - pad-responses + does not apply to comm_doq (RFC 9250 ยง5.4 MUST). 9 July 2026: Wouter - Merge #1383 from jdek: Fix randomness generation on diff --git a/util/data/msgparse.c b/util/data/msgparse.c index 9239f8fe3..5ff5a82ae 100644 --- a/util/data/msgparse.c +++ b/util/data/msgparse.c @@ -1030,8 +1030,11 @@ parse_edns_options_from_query(uint8_t* rdata_ptr, size_t rdata_len, break; case LDNS_EDNS_PADDING: - if(!cfg || !cfg->pad_responses || - !c || c->type != comm_tcp ||!c->ssl || padding_seen) + if(!cfg || !cfg->pad_responses || !c || padding_seen) + break; + if(!((c->type == comm_tcp && c->ssl) || + (c->type == comm_http && c->ssl) || + c->type == comm_doq)) break; padding_seen = 1; if(!edns_opt_list_append(&edns->opt_list_out, From 7133e0d32a12707e02bc827f5549bdc0489e27f5 Mon Sep 17 00:00:00 2001 From: Petr Vaganov Date: Tue, 21 Jul 2026 16:56:29 +0700 Subject: [PATCH 07/84] ipsecmod: fix possible deref on null after reply_find_answer_rrset() (#1476) Return value of a function 'reply_find_answer_rrset' is dereferenced at ipsecmod.c:438 without checking for NULL, but it is usually checked for this function (10/12). Found by the static analyzer Svace (ISP RAS). Signed-off-by: Petr Vaganov --- ipsecmod/ipsecmod.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/ipsecmod/ipsecmod.c b/ipsecmod/ipsecmod.c index 2c146a775..d1c0d442f 100644 --- a/ipsecmod/ipsecmod.c +++ b/ipsecmod/ipsecmod.c @@ -294,6 +294,10 @@ call_hook(struct module_qstate* qstate, struct ipsecmod_qstate* iq, rrset_key = reply_find_answer_rrset(&qstate->return_msg->qinfo, qstate->return_msg->rep); + if(!rrset_key) { + log_err("ipsecmod: could not find answer rrset for A/AAAA"); + return 0; + } /* Double check that the records are indeed A/AAAA. * This should never happen as this function is only executed for A/AAAA * queries but make sure we don't pass anything other than A/AAAA to the @@ -475,6 +479,12 @@ ipsecmod_handle_query(struct module_qstate* qstate, * ipsecmod_max_ttl. */ rrset_key = reply_find_answer_rrset(&qstate->return_msg->qinfo, qstate->return_msg->rep); + if(!rrset_key) { + log_err("ipsecmod: reply-find-answer failed"); + errinf(qstate, "ipsecmod: reply-find-answer failed"); + ipsecmod_error(qstate, id); + return; + } rrset_data = (struct packed_rrset_data*)rrset_key->entry.data; if(rrset_data->ttl > (time_t)qstate->env->cfg->ipsecmod_max_ttl) { /* Update TTL for rrset to fixed value. */ From 25b2543e5eba25c62a8712aa9cf6212441735286 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Tue, 21 Jul 2026 11:57:14 +0200 Subject: [PATCH 08/84] Changelog note for #1476 - Merge #1476 from petrvaganoff: ipsecmod: fix possible deref on null after reply_find_answer_rrset(). --- doc/Changelog | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/Changelog b/doc/Changelog index c16730f49..843b1215d 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,7 @@ +21 July 2026: Wouter + - Merge #1476 from petrvaganoff: ipsecmod: fix possible deref + on null after reply_find_answer_rrset(). + 20 July 2026: Wouter - Merge #1475 from petrvaganoff: ipsecmod: fix deref on null in ipsecmod-whitelist after OOM. From 87d59bfcedef7fe9d3253594e836ed50c6d00de1 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Wed, 22 Jul 2026 10:06:30 +0200 Subject: [PATCH 09/84] Set version to 1.25.2 --- configure | 25 +++++++++++++------------ configure.ac | 5 +++-- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/configure b/configure index f9d46ea15..fd1e143e5 100755 --- a/configure +++ b/configure @@ -1,6 +1,6 @@ #! /bin/sh # Guess values for system-dependent variables and create Makefiles. -# Generated by GNU Autoconf 2.71 for unbound 1.25.1. +# Generated by GNU Autoconf 2.71 for unbound 1.25.2. # # Report bugs to . # @@ -622,8 +622,8 @@ MAKEFLAGS= # Identity of this package. PACKAGE_NAME='unbound' PACKAGE_TARNAME='unbound' -PACKAGE_VERSION='1.25.1' -PACKAGE_STRING='unbound 1.25.1' +PACKAGE_VERSION='1.25.2' +PACKAGE_STRING='unbound 1.25.2' PACKAGE_BUGREPORT='unbound-bugs@nlnetlabs.nl or https://github.com/NLnetLabs/unbound/issues' PACKAGE_URL='' @@ -1513,7 +1513,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.25.1 to adapt to many kinds of systems. +\`configure' configures unbound 1.25.2 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1579,7 +1579,7 @@ fi if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of unbound 1.25.1:";; + short | recursive ) echo "Configuration of unbound 1.25.2:";; esac cat <<\_ACEOF @@ -1832,7 +1832,7 @@ fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -unbound configure 1.25.1 +unbound configure 1.25.2 generated by GNU Autoconf 2.71 Copyright (C) 2021 Free Software Foundation, Inc. @@ -2489,7 +2489,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.25.1, which was +It was created by unbound $as_me 1.25.2, which was generated by GNU Autoconf 2.71. Invocation command line was $ $0$ac_configure_args_raw @@ -3253,11 +3253,11 @@ UNBOUND_VERSION_MAJOR=1 UNBOUND_VERSION_MINOR=25 -UNBOUND_VERSION_MICRO=1 +UNBOUND_VERSION_MICRO=2 LIBUNBOUND_CURRENT=9 -LIBUNBOUND_REVISION=37 +LIBUNBOUND_REVISION=38 LIBUNBOUND_AGE=1 # 1.0.0 had 0:12:0 # 1.0.1 had 0:13:0 @@ -3362,6 +3362,7 @@ LIBUNBOUND_AGE=1 # 1.24.2 had 9:35:1 # 1.25.0 had 9:36:1 # 1.25.1 had 9:37:1 +# 1.25.2 had 9:38:1 # Current -- the number of the binary API that we're implementing # Revision -- which iteration of the implementation of the binary @@ -25552,7 +25553,7 @@ printf "%s\n" "#define MAXSYSLOGMSGLEN 10240" >>confdefs.h -version=1.25.1 +version=1.25.2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for build time" >&5 printf %s "checking for build time... " >&6; } @@ -26082,7 +26083,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # 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.25.1, which was +This file was extended by unbound $as_me 1.25.2, which was generated by GNU Autoconf 2.71. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -26150,7 +26151,7 @@ ac_cs_config_escaped=`printf "%s\n" "$ac_cs_config" | sed "s/^ //; s/'/'\\\\\\\\ cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config='$ac_cs_config_escaped' ac_cs_version="\\ -unbound config.status 1.25.1 +unbound config.status 1.25.2 configured by $0, generated by GNU Autoconf 2.71, with options \\"\$ac_cs_config\\" diff --git a/configure.ac b/configure.ac index f1c5b0419..27e16f646 100644 --- a/configure.ac +++ b/configure.ac @@ -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],[25]) -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=37 +LIBUNBOUND_REVISION=38 LIBUNBOUND_AGE=1 # 1.0.0 had 0:12:0 # 1.0.1 had 0:13:0 @@ -124,6 +124,7 @@ LIBUNBOUND_AGE=1 # 1.24.2 had 9:35:1 # 1.25.0 had 9:36:1 # 1.25.1 had 9:37:1 +# 1.25.2 had 9:38:1 # Current -- the number of the binary API that we're implementing # Revision -- which iteration of the implementation of the binary From fea0ff550bb6193417c9b17ffff409eb6736f90d Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Wed, 22 Jul 2026 10:07:52 +0200 Subject: [PATCH 10/84] - Fix CVE-2026-46582, A wildcard replay, as another piece of data, triggers poisoning in the serve expired reply path. Thanks to Qifan Zhang, Palo Alto Networks, for the report. --- validator/val_utils.c | 15 ++++++++++++--- validator/validator.c | 31 ++++++++++++++++++++++++++++++- 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/validator/val_utils.c b/validator/val_utils.c index 8e4c91900..405cf897f 100644 --- a/validator/val_utils.c +++ b/validator/val_utils.c @@ -439,10 +439,15 @@ val_verify_rrset(struct module_env* env, struct val_env* ve, * only improves security status * and bogus is set only once, even if we rechecked the status */ if(sec > d->security) { + int wc_expanded = 0; d->security = sec; - if(sec == sec_status_secure) + if(sec == sec_status_secure) { + uint8_t* wc = NULL; + size_t wclen = 0; d->trust = rrset_trust_validated; - else if(sec == sec_status_bogus) { + if(val_rrset_wildcard(rrset, &wc, &wclen) && wc) + wc_expanded = 1; + } else if(sec == sec_status_bogus) { size_t i; /* update ttl for rrset to fixed value. */ d->ttl = ve->bogus_ttl; @@ -455,7 +460,11 @@ val_verify_rrset(struct module_env* env, struct val_env* ve, lock_basic_unlock(&ve->bogus_lock); } /* if status updated - store in cache for reuse */ - rrset_update_sec_status(env->rrset_cache, rrset, *env->now); + /* For a wildcard rrset, that is secure, do not store this + * into the cache, because it changes proofs around the + * item. */ + if(!wc_expanded) + rrset_update_sec_status(env->rrset_cache, rrset, *env->now); } return sec; diff --git a/validator/validator.c b/validator/validator.c index e7992b6e3..8fc9ffc94 100644 --- a/validator/validator.c +++ b/validator/validator.c @@ -1044,6 +1044,9 @@ validate_positive_response(struct module_env* env, struct val_env* ve, size_t wl; int wc_cached = 0; int wc_NSEC_ok = 0; + /* This is used to update the RRset cache, with the combination + * of the dname expansion and this wildcard, for security status. */ + struct ub_packed_rrset_key* wc_rrset = NULL; int nsec3s_seen = 0; size_t i; struct ub_packed_rrset_key* s; @@ -1062,6 +1065,9 @@ validate_positive_response(struct module_env* env, struct val_env* ve, ntohs(s->rk.type), ntohs(s->rk.rrset_class)); chase_reply->security = sec_status_bogus; update_reason_bogus(chase_reply, LDNS_EDE_DNSSEC_BOGUS); + if(wc_rrset) + ((struct packed_rrset_data*)wc_rrset-> + entry.data)->security = sec_status_bogus; return; } if(wc && !wc_cached && env->cfg->aggressive_nsec) { @@ -1069,7 +1075,7 @@ validate_positive_response(struct module_env* env, struct val_env* ve, env->alloc, *env->now); wc_cached = 1; } - + if(wc) wc_rrset = s; } /* validate the AUTHORITY section as well - this will generally be @@ -1126,6 +1132,9 @@ validate_positive_response(struct module_env* env, struct val_env* ve, "did not exist"); chase_reply->security = sec_status_bogus; update_reason_bogus(chase_reply, LDNS_EDE_DNSSEC_BOGUS); + if(wc_rrset) + ((struct packed_rrset_data*)wc_rrset-> + entry.data)->security = sec_status_bogus; return; } @@ -1527,6 +1536,16 @@ validate_any_response(struct module_env* env, struct val_env* ve, "did not exist"); chase_reply->security = sec_status_bogus; update_reason_bogus(chase_reply, LDNS_EDE_DNSSEC_BOGUS); + /* Make the expanded name and wildcard RRSIG rrsets bogus */ + for(i=0; ian_numrrsets; i++) { + uint8_t* cwc = NULL; + size_t cwl = 0; + s = chase_reply->rrsets[i]; + if(val_rrset_wildcard(s, &cwc, &cwl) && cwc) { + ((struct packed_rrset_data*)s-> + entry.data)->security = sec_status_bogus; + } + } return; } @@ -1564,6 +1583,9 @@ validate_cname_response(struct module_env* env, struct val_env* ve, uint8_t* wc = NULL; size_t wl; int wc_NSEC_ok = 0; + /* This is used to update the RRset cache, with the combination + * of the dname expansion and this wildcard, for security status. */ + struct ub_packed_rrset_key* wc_rrset = NULL; int nsec3s_seen = 0; size_t i; struct ub_packed_rrset_key* s; @@ -1584,6 +1606,7 @@ validate_cname_response(struct module_env* env, struct val_env* ve, update_reason_bogus(chase_reply, LDNS_EDE_DNSSEC_BOGUS); return; } + if(wc) wc_rrset = s; /* Refuse wildcarded DNAMEs rfc 4597. * Do not follow a wildcarded DNAME because @@ -1595,6 +1618,9 @@ validate_cname_response(struct module_env* env, struct val_env* ve, ntohs(s->rk.type), ntohs(s->rk.rrset_class)); chase_reply->security = sec_status_bogus; update_reason_bogus(chase_reply, LDNS_EDE_DNSSEC_BOGUS); + if(wc_rrset) + ((struct packed_rrset_data*)wc_rrset-> + entry.data)->security = sec_status_bogus; return; } @@ -1659,6 +1685,9 @@ validate_cname_response(struct module_env* env, struct val_env* ve, "did not exist"); chase_reply->security = sec_status_bogus; update_reason_bogus(chase_reply, LDNS_EDE_DNSSEC_BOGUS); + if(wc_rrset) + ((struct packed_rrset_data*)wc_rrset-> + entry.data)->security = sec_status_bogus; return; } From f157c691bbd7ac5a2b29d1bf30283cb043052e9a Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Wed, 22 Jul 2026 10:08:48 +0200 Subject: [PATCH 11/84] - Fix CVE-2026-14586, Assertion in libngtcp2 when under pressure in high concurrency DNS-over-QUIC environments. Thanks to Kunta Chu, Kaihua Wang, and Jianjun Chen from Tsinghua University, for the report. --- configure.ac | 16 +++++++ services/listen_dnsport.c | 99 ++++++++++++++++++++------------------- services/listen_dnsport.h | 21 +++++---- util/netevent.c | 38 ++++++--------- util/netevent.h | 4 +- 5 files changed, 98 insertions(+), 80 deletions(-) diff --git a/configure.ac b/configure.ac index 27e16f646..a4b6b17bd 100644 --- a/configure.ac +++ b/configure.ac @@ -1736,6 +1736,22 @@ if test x_$withval = x_yes -o x_$withval != x_no; then AC_MSG_RESULT(no) ]) + AC_CHECK_DECL([CLOCK_MONOTONIC] + , [] + , [AC_MSG_ERROR([ngtcp2 for QUIC needs at least CLOCK_MONOTONIC on the system])] + , [AC_INCLUDES_DEFAULT +#ifdef TIME_WITH_SYS_TIME +# include +# include +#else +# ifdef HAVE_SYS_TIME_H +# include +# else +# include +# endif +#endif + ]) + fi # set static linking for uninstalled libraries if requested diff --git a/services/listen_dnsport.c b/services/listen_dnsport.c index 5db2b940b..a08a5352e 100644 --- a/services/listen_dnsport.c +++ b/services/listen_dnsport.c @@ -42,7 +42,6 @@ #ifdef HAVE_SYS_TYPES_H # include #endif -#include #include #ifdef USE_TCP_FASTOPEN #include @@ -3399,14 +3398,13 @@ doq_table_delete(struct doq_table* table) } struct doq_timer* -doq_timer_find_time(struct doq_table* table, struct timeval* tv) +doq_timer_find_time(struct doq_table* table, ngtcp2_tstamp ts) { struct doq_timer key; struct rbnode_type* node; log_assert(table != NULL); memset(&key, 0, sizeof(key)); - key.time.tv_sec = tv->tv_sec; - key.time.tv_usec = tv->tv_usec; + key.time_mono = ts; node = rbtree_search(table->timer_tree, &key); if(node) return (struct doq_timer*)node->key; @@ -3454,7 +3452,7 @@ doq_timer_list_remove(struct doq_table* table, struct doq_timer* timer) if(!timer->timer_in_list) return; /* The item in the rbtree has the list start and end. */ - rb_timer = doq_timer_find_time(table, &timer->time); + rb_timer = doq_timer_find_time(table, timer->time_mono); if(rb_timer) { if(timer->setlist_prev) timer->setlist_prev->setlist_next = timer->setlist_next; @@ -3500,7 +3498,8 @@ doq_timer_unset(struct doq_table* table, struct doq_timer* timer) } void doq_timer_set(struct doq_table* table, struct doq_timer* timer, - struct doq_server_socket* worker_doq_socket, struct timeval* tv) + struct doq_server_socket* worker_doq_socket, struct timeval* tv, + ngtcp2_tstamp ts) { struct doq_timer* rb_timer; if(verbosity >= VERB_ALGO && timer->conn) { @@ -3514,14 +3513,14 @@ void doq_timer_set(struct doq_table* table, struct doq_timer* timer, (int)rel.tv_sec, (int)rel.tv_usec); } if(timer->timer_in_tree || timer->timer_in_list) { - if(timer->time.tv_sec == tv->tv_sec && - timer->time.tv_usec == tv->tv_usec) + if(timer->time_mono == ts) return; /* already set on that time */ doq_timer_unset(table, timer); } - timer->time.tv_sec = tv->tv_sec; - timer->time.tv_usec = tv->tv_usec; - rb_timer = doq_timer_find_time(table, tv); + timer->time_real.tv_sec = tv->tv_sec; + timer->time_real.tv_usec = tv->tv_usec; + timer->time_mono = ts; + rb_timer = doq_timer_find_time(table, ts); if(rb_timer) { /* There is a timeout already with this value. Timer is * added to the setlist. */ @@ -3700,13 +3699,9 @@ int doq_timer_cmp(const void* key1, const void* key2) { struct doq_timer* e = (struct doq_timer*)key1; struct doq_timer* f = (struct doq_timer*)key2; - if(e->time.tv_sec < f->time.tv_sec) + if(e->time_mono < f->time_mono) return -1; - if(e->time.tv_sec > f->time.tv_sec) - return 1; - if(e->time.tv_usec < f->time.tv_usec) - return -1; - if(e->time.tv_usec > f->time.tv_usec) + if(e->time_mono > f->time_mono) return 1; return 0; } @@ -4254,12 +4249,11 @@ doq_submit_new_token(struct doq_conn* conn) ngtcp2_ssize tokenlen; int ret; const ngtcp2_path* path = ngtcp2_conn_get_path(conn->conn); - ngtcp2_tstamp ts = doq_get_timestamp_nanosec(); tokenlen = ngtcp2_crypto_generate_regular_token(token, conn->doq_socket->static_secret, conn->doq_socket->static_secret_len, path->remote.addr, - path->remote.addrlen, ts); + path->remote.addrlen, doq_get_timestamp_nanosec()); if(tokenlen < 0) { log_err("doq ngtcp2_crypto_generate_regular_token failed"); return 1; @@ -5101,23 +5095,30 @@ doq_conn_clear_conids(struct doq_conn* conn) ngtcp2_tstamp doq_get_timestamp_nanosec(void) { -#ifdef CLOCK_REALTIME struct timespec tp; memset(&tp, 0, sizeof(tp)); - /* Get a nanosecond time, that can be compared with the event base. */ - if(clock_gettime(CLOCK_REALTIME, &tp) == -1) { - log_err("clock_gettime failed: %s", strerror(errno)); +#ifdef CLOCK_BOOTTIME + if(clock_gettime(CLOCK_BOOTTIME, &tp) == -1) { +#endif + if(clock_gettime(CLOCK_MONOTONIC, &tp) == -1) { + log_err("clock_gettime failed: %s", strerror(errno)); + } +#ifdef CLOCK_BOOTTIME } +#endif return ((uint64_t)tp.tv_sec)*((uint64_t)1000000000) + ((uint64_t)tp.tv_nsec); -#else +} + +static struct timeval doq_get_timevalue(void) +{ struct timeval tv; + memset(&tv, 0, sizeof(tv)); if(gettimeofday(&tv, NULL) < 0) { log_err("gettimeofday failed: %s", strerror(errno)); + memset(&tv, 0, sizeof(tv)); } - return ((uint64_t)tv.tv_sec)*((uint64_t)1000000000) + - ((uint64_t)tv.tv_usec)*((uint64_t)1000); -#endif /* CLOCK_REALTIME */ + return tv; } /** doq start the closing period for the connection. */ @@ -5240,18 +5241,17 @@ doq_conn_recv(struct comm_point* c, struct doq_pkt_addr* paddr, int* err_drop) { int ret; - ngtcp2_tstamp ts; struct ngtcp2_path path; memset(&path, 0, sizeof(path)); path.remote.addr = (struct sockaddr*)&paddr->addr; path.remote.addrlen = paddr->addrlen; path.local.addr = (struct sockaddr*)&paddr->localaddr; path.local.addrlen = paddr->localaddrlen; - ts = doq_get_timestamp_nanosec(); ret = ngtcp2_conn_read_pkt(conn->conn, &path, pi, sldns_buffer_begin(c->doq_socket->pkt_buf), - sldns_buffer_limit(c->doq_socket->pkt_buf), ts); + sldns_buffer_limit(c->doq_socket->pkt_buf), + doq_get_timestamp_nanosec()); if(ret != 0) { if(err_retry) *err_retry = 0; @@ -5339,7 +5339,6 @@ doq_conn_write_streams(struct comm_point* c, struct doq_conn* conn, { struct doq_stream* stream = conn->stream_write_first; ngtcp2_path_storage ps; - ngtcp2_tstamp ts = doq_get_timestamp_nanosec(); size_t num_packets = 0, max_packets = 65535; ngtcp2_path_storage_zero(&ps); @@ -5392,7 +5391,8 @@ doq_conn_write_streams(struct comm_point* c, struct doq_conn* conn, ret = ngtcp2_conn_writev_stream(conn->conn, &ps.path, &pi, sldns_buffer_begin(c->doq_socket->pkt_buf), sldns_buffer_remaining(c->doq_socket->pkt_buf), - &ndatalen, flags, stream_id, datav, datav_count, ts); + &ndatalen, flags, stream_id, datav, datav_count, + doq_get_timestamp_nanosec()); if(ret < 0) { if(ret == NGTCP2_ERR_WRITE_MORE) { verbose(VERB_ALGO, "doq: write more, ndatalen %d", (int)ndatalen); @@ -5464,7 +5464,8 @@ doq_conn_write_streams(struct comm_point* c, struct doq_conn* conn, if(ret == 0) { /* congestion limited */ doq_conn_write_disable(conn); - ngtcp2_conn_update_pkt_tx_time(conn->conn, ts); + ngtcp2_conn_update_pkt_tx_time(conn->conn, + doq_get_timestamp_nanosec()); return 1; } sldns_buffer_set_position(c->doq_socket->pkt_buf, ret); @@ -5478,7 +5479,7 @@ doq_conn_write_streams(struct comm_point* c, struct doq_conn* conn, if(stream) stream = stream->write_next; } - ngtcp2_conn_update_pkt_tx_time(conn->conn, ts); + ngtcp2_conn_update_pkt_tx_time(conn->conn, doq_get_timestamp_nanosec()); return 1; } @@ -5555,32 +5556,35 @@ doq_table_pop_first(struct doq_table* table) } int -doq_conn_check_timer(struct doq_conn* conn, struct timeval* tv) +doq_conn_check_timer(struct doq_conn* conn, struct timeval* tv, ngtcp2_tstamp* ts) { - ngtcp2_tstamp expiry = ngtcp2_conn_get_expiry(conn->conn); - ngtcp2_tstamp now = doq_get_timestamp_nanosec(); + ngtcp2_tstamp doq_expiry = ngtcp2_conn_get_expiry(conn->conn); + ngtcp2_tstamp doq_now = doq_get_timestamp_nanosec(); ngtcp2_tstamp t; + struct timeval now = doq_get_timevalue(); - if(expiry <= now) { + if(doq_expiry <= doq_now || doq_expiry == UINT64_MAX) { + /* UINT64_MAX means there is no next expiry. */ /* The timer has already expired, add with zero timeout. * This should call the callback straight away. Calling it * from the event callbacks is cleaner than calling it here, * because then it is always called with the same locks and * so on. This routine only has the conn.lock. */ - t = now; + t = doq_now; + memcpy(tv, &now, sizeof(*tv)); } else { - t = expiry; + t = doq_expiry; + memset(tv, 0, sizeof(*tv)); + tv->tv_sec = (doq_expiry - doq_now) / NGTCP2_SECONDS; + tv->tv_usec = ((doq_expiry - doq_now) / NGTCP2_MICROSECONDS)%1000000; + timeval_add(tv, &now); } - /* convert to timeval */ - memset(tv, 0, sizeof(*tv)); - tv->tv_sec = t / NGTCP2_SECONDS; - tv->tv_usec = (t / NGTCP2_MICROSECONDS)%1000000; + *ts = t; /* If we already have a timer, is it the right value? */ if(conn->timer.timer_in_tree || conn->timer.timer_in_list) { - if(conn->timer.time.tv_sec == tv->tv_sec && - conn->timer.time.tv_usec == tv->tv_usec) + if(conn->timer.time_mono == *ts) return 0; } return 1; @@ -5601,13 +5605,12 @@ doq_conn_log_line(struct doq_conn* conn, char* s) int doq_conn_handle_timeout(struct doq_conn* conn) { - ngtcp2_tstamp now = doq_get_timestamp_nanosec(); int rv; if(verbosity >= VERB_ALGO) doq_conn_log_line(conn, "timeout"); - rv = ngtcp2_conn_handle_expiry(conn->conn, now); + rv = ngtcp2_conn_handle_expiry(conn->conn, doq_get_timestamp_nanosec()); if(rv != 0) { verbose(VERB_ALGO, "ngtcp2_conn_handle_expiry failed: %s", ngtcp2_strerror(rv)); diff --git a/services/listen_dnsport.h b/services/listen_dnsport.h index 963595a1c..95aa3e11e 100644 --- a/services/listen_dnsport.h +++ b/services/listen_dnsport.h @@ -538,8 +538,11 @@ void doq_table_delete(struct doq_table* table); struct doq_timer { /** The rbnode in the tree sorted by timeout value. Key this struct. */ struct rbnode_type node; + /** The timeout value. Monotonic value used with ngtcp2. + * This time value is used for the tree operations. */ + ngtcp2_tstamp time_mono; /** The timeout value. Absolute time value. */ - struct timeval time; + struct timeval time_real; /** If the timer is in the time tree, with the node. */ int timer_in_tree; /** If there are more timers with the exact same timeout value, @@ -813,10 +816,12 @@ struct doq_conn* doq_table_pop_first(struct doq_table* table); * doq check if the timer for the conn needs to be changed. * @param conn: connection, caller must hold lock on it. * @param tv: time value, absolute time, returned. + * @param ts: time stamp, absolute time, returned. * @return true if timer needs to be set to tv, false if no change is needed * to the timer. The timer is already set to the right time in that case. */ -int doq_conn_check_timer(struct doq_conn* conn, struct timeval* tv); +int doq_conn_check_timer(struct doq_conn* conn, struct timeval* tv, + ngtcp2_tstamp* ts); /** doq remove timer from tree */ void doq_timer_tree_remove(struct doq_table* table, struct doq_timer* timer); @@ -829,11 +834,12 @@ void doq_timer_unset(struct doq_table* table, struct doq_timer* timer); /** doq set the timer and add it. */ void doq_timer_set(struct doq_table* table, struct doq_timer* timer, - struct doq_server_socket* worker_doq_socket, struct timeval* tv); + struct doq_server_socket* worker_doq_socket, struct timeval* tv, + ngtcp2_tstamp ts); /** doq find a timeout in the timer tree */ struct doq_timer* doq_timer_find_time(struct doq_table* table, - struct timeval* tv); + ngtcp2_tstamp ts); /** doq handle timeout for a connection. Pass conn locked. Returns false for * deletion. */ @@ -851,6 +857,9 @@ int doq_table_quic_size_available(struct doq_table* table, /** doq get the quic size value */ size_t doq_table_quic_size_get(struct doq_table* table); + +/** get a timestamp in nanoseconds */ +ngtcp2_tstamp doq_get_timestamp_nanosec(void); #endif /* HAVE_NGTCP2 */ char* set_ip_dscp(int socket, int addrfamily, int ds); @@ -866,8 +875,4 @@ void doq_client_event_cb(int fd, short event, void* arg); /** timer event callback for testcode/doqclient */ void doq_client_timer_cb(int fd, short event, void* arg); -#ifdef HAVE_NGTCP2 -/** get a timestamp in nanoseconds */ -ngtcp2_tstamp doq_get_timestamp_nanosec(void); -#endif #endif /* LISTEN_DNSPORT_H */ diff --git a/util/netevent.c b/util/netevent.c index a86e22518..e1480dc22 100644 --- a/util/netevent.c +++ b/util/netevent.c @@ -1827,7 +1827,6 @@ doq_send_retry(struct comm_point* c, struct doq_pkt_addr* paddr, char host[256], port[32]; struct ngtcp2_cid scid; uint8_t token[NGTCP2_CRYPTO_MAX_RETRY_TOKENLEN]; - ngtcp2_tstamp ts; ngtcp2_ssize tokenlen, ret; if(!doq_print_addr_port(&paddr->addr, paddr->addrlen, host, @@ -1841,12 +1840,10 @@ doq_send_retry(struct comm_point* c, struct doq_pkt_addr* paddr, scid.datalen = c->doq_socket->sv_scidlen; doq_cid_randfill(&scid, scid.datalen, c->doq_socket->rnd); - ts = doq_get_timestamp_nanosec(); - tokenlen = ngtcp2_crypto_generate_retry_token(token, c->doq_socket->static_secret, c->doq_socket->static_secret_len, hd->version, (void*)&paddr->addr, paddr->addrlen, &scid, - &hd->dcid, ts); + &hd->dcid, doq_get_timestamp_nanosec()); if(tokenlen < 0) { log_err("ngtcp2_crypto_generate_retry_token failed: %s", ngtcp2_strerror(tokenlen)); @@ -1895,13 +1892,11 @@ doq_verify_retry_token(struct comm_point* c, struct doq_pkt_addr* paddr, struct ngtcp2_cid* ocid, struct ngtcp2_pkt_hd* hd) { char host[256], port[32]; - ngtcp2_tstamp ts; if(!doq_print_addr_port(&paddr->addr, paddr->addrlen, host, sizeof(host), port, sizeof(port))) { log_err("doq_verify_retry_token failed"); return 0; } - ts = doq_get_timestamp_nanosec(); verbose(VERB_ALGO, "doq: verifying retry token from %s %s", host, port); if(ngtcp2_crypto_verify_retry_token(ocid, @@ -1913,7 +1908,7 @@ doq_verify_retry_token(struct comm_point* c, struct doq_pkt_addr* paddr, c->doq_socket->static_secret, c->doq_socket->static_secret_len, hd->version, (void*)&paddr->addr, paddr->addrlen, &hd->dcid, - 10*NGTCP2_SECONDS, ts) != 0) { + 10*NGTCP2_SECONDS, doq_get_timestamp_nanosec()) != 0) { verbose(VERB_ALGO, "doq: could not verify retry token " "from %s %s", host, port); return 0; @@ -1928,13 +1923,11 @@ doq_verify_token(struct comm_point* c, struct doq_pkt_addr* paddr, struct ngtcp2_pkt_hd* hd) { char host[256], port[32]; - ngtcp2_tstamp ts; if(!doq_print_addr_port(&paddr->addr, paddr->addrlen, host, sizeof(host), port, sizeof(port))) { log_err("doq_verify_token failed"); return 0; } - ts = doq_get_timestamp_nanosec(); verbose(VERB_ALGO, "doq: verifying token from %s %s", host, port); if(ngtcp2_crypto_verify_regular_token( #ifdef HAVE_STRUCT_NGTCP2_PKT_HD_TOKENLEN @@ -1944,7 +1937,7 @@ doq_verify_token(struct comm_point* c, struct doq_pkt_addr* paddr, #endif c->doq_socket->static_secret, c->doq_socket->static_secret_len, (void*)&paddr->addr, paddr->addrlen, 3600*NGTCP2_SECONDS, - ts) != 0) { + doq_get_timestamp_nanosec()) != 0) { verbose(VERB_ALGO, "doq: could not verify token from %s %s", host, port); return 0; @@ -2171,6 +2164,7 @@ doq_pickup_timer(struct comm_point* c) { struct doq_timer* t; struct timeval tv; + ngtcp2_tstamp ts = 0; int have_time = 0; memset(&tv, 0, sizeof(tv)); @@ -2180,27 +2174,24 @@ doq_pickup_timer(struct comm_point* c) t->worker_doq_socket == c->doq_socket) { /* pick up this element */ t->worker_doq_socket = c->doq_socket; + memcpy(&tv, &t->time_real, sizeof(tv)); + ts = t->time_mono; have_time = 1; - memcpy(&tv, &t->time, sizeof(tv)); break; } } lock_rw_unlock(&c->doq_socket->table->lock); - + c->doq_socket->marked_time = ts; if(have_time) { struct timeval rel; timeval_subtract(&rel, &tv, c->doq_socket->now_tv); comm_timer_set(c->doq_socket->timer, &rel); - memcpy(&c->doq_socket->marked_time, &tv, - sizeof(c->doq_socket->marked_time)); verbose(VERB_ALGO, "doq pickup timer at %d.%6.6d in %d.%6.6d", (int)tv.tv_sec, (int)tv.tv_usec, (int)rel.tv_sec, (int)rel.tv_usec); } else { if(comm_timer_is_set(c->doq_socket->timer)) comm_timer_disable(c->doq_socket->timer); - memset(&c->doq_socket->marked_time, 0, - sizeof(c->doq_socket->marked_time)); verbose(VERB_ALGO, "doq timer disabled"); } } @@ -2213,13 +2204,14 @@ doq_done_setup_timer_and_write(struct comm_point* c, struct doq_conn* conn) uint8_t cid[NGTCP2_MAX_CIDLEN]; rbnode_type* node; struct timeval new_tv; + ngtcp2_tstamp new_ts; int write_change = 0, timer_change = 0; /* No longer in callbacks, so the pointer to doq_socket is back * to NULL. */ conn->doq_socket = NULL; - if(doq_conn_check_timer(conn, &new_tv)) + if(doq_conn_check_timer(conn, &new_tv, &new_ts)) timer_change = 1; if( (conn->write_interest && !conn->on_write_list) || (!conn->write_interest && conn->on_write_list)) @@ -2265,7 +2257,7 @@ doq_done_setup_timer_and_write(struct comm_point* c, struct doq_conn* conn) } if(timer_change) { doq_timer_set(c->doq_socket->table, &conn->timer, - c->doq_socket, &new_tv); + c->doq_socket, &new_tv, new_ts); } lock_rw_unlock(&c->doq_socket->table->lock); lock_basic_unlock(&conn->lock); @@ -2429,7 +2421,7 @@ doq_write_blocked_pkt(struct comm_point* c) return 1; } -/** doq find a timer that timeouted and return the conn, locked. */ +/** doq find a timer that timed out and return the conn, locked. */ static struct doq_conn* doq_timer_timeout_conn(struct doq_server_socket* doq_socket) { @@ -2442,7 +2434,7 @@ doq_timer_timeout_conn(struct doq_server_socket* doq_socket) conn = t->conn; /* If now < timer then no further timeouts in tree. */ - if(timeval_smaller(doq_socket->now_tv, &t->time)) { + if(timeval_smaller(doq_socket->now_tv, &t->time_real)) { lock_rw_unlock(&doq_socket->table->lock); return NULL; } @@ -2465,11 +2457,11 @@ doq_timer_erase_marker(struct doq_server_socket* doq_socket) { struct doq_timer* t; lock_rw_wrlock(&doq_socket->table->lock); - t = doq_timer_find_time(doq_socket->table, &doq_socket->marked_time); + t = doq_timer_find_time(doq_socket->table, doq_socket->marked_time); if(t && t->worker_doq_socket == doq_socket) t->worker_doq_socket = NULL; lock_rw_unlock(&doq_socket->table->lock); - memset(&doq_socket->marked_time, 0, sizeof(doq_socket->marked_time)); + doq_socket->marked_time = 0; } void @@ -2776,7 +2768,7 @@ doq_server_socket_create(struct doq_table* table, struct ub_randstate* rnd, free(doq_socket); return NULL; } - memset(&doq_socket->marked_time, 0, sizeof(doq_socket->marked_time)); + doq_socket->marked_time = 0; comm_base_timept(base, &doq_socket->now_tt, &doq_socket->now_tv); doq_socket->cfg = cfg; return doq_socket; diff --git a/util/netevent.h b/util/netevent.h index c5114bbbe..7235843ee 100644 --- a/util/netevent.h +++ b/util/netevent.h @@ -1093,8 +1093,10 @@ struct doq_server_socket { struct doq_pkt_addr* blocked_paddr; /** timer for this worker on this comm_point to wait on. */ struct comm_timer* timer; +#ifdef HAVE_NGTCP2 /** the timer that is marked by the doq_socket as waited on. */ - struct timeval marked_time; + ngtcp2_tstamp marked_time; +#endif /** the current time for use by time functions, time_t. */ time_t* now_tt; /** the current time for use by time functions, timeval. */ From 01dfd2f466d383370405d8ecf939570947f3523e Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Wed, 22 Jul 2026 10:09:26 +0200 Subject: [PATCH 12/84] - Fix CVE-2026-32665, Remote DNS-over-QUIC denial of service due to `quic-size` budget bypass. Thanks to N0zoM1z0 (https://github.com/N0zoM1z0) for the report. In addition, thanks to Kunta Chu, Kaihua Wang, and Jianjun Chen from Tsinghua University, for also reporting this issue. In addition, thanks to Qifan Zhang, Palo Alto Networks, for also reporting this issue. In addition, thanks to Xuanchao Xie, for also reporting this issue. --- services/listen_dnsport.c | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/services/listen_dnsport.c b/services/listen_dnsport.c index a08a5352e..caae7fcc4 100644 --- a/services/listen_dnsport.c +++ b/services/listen_dnsport.c @@ -3964,7 +3964,8 @@ doq_stream_close(struct doq_conn* conn, struct doq_stream* stream, /** doq stream pick up answer data from buffer */ static int -doq_stream_pickup_answer(struct doq_stream* stream, struct sldns_buffer* buf) +doq_stream_pickup_answer(struct doq_conn* conn, struct doq_stream* stream, + struct sldns_buffer* buf) { stream->is_answer_available = 1; if(stream->out) { @@ -3974,6 +3975,11 @@ doq_stream_pickup_answer(struct doq_stream* stream, struct sldns_buffer* buf) } stream->nwrite = 0; stream->outlen = sldns_buffer_limit(buf); + if(!doq_table_quic_size_available(conn->doq_socket->table, + conn->doq_socket->cfg, stream->outlen)) { + verbose(VERB_ALGO, "doq stream: no space for reply length"); + return 0; + } /* For quic the output bytes have to stay allocated and available, * for potential resends, until the remote end has acknowledged them. * This includes the tcplen start uint16_t, in outlen_wire. */ @@ -4000,7 +4006,7 @@ doq_stream_send_reply(struct doq_conn* conn, struct doq_stream* stream, if(stream->out) doq_table_quic_size_subtract(conn->doq_socket->table, stream->outlen); - if(!doq_stream_pickup_answer(stream, buf)) + if(!doq_stream_pickup_answer(conn, stream, buf)) return 0; doq_table_quic_size_add(conn->doq_socket->table, stream->outlen); doq_stream_on_write_list(conn, stream); @@ -4011,13 +4017,19 @@ doq_stream_send_reply(struct doq_conn* conn, struct doq_stream* stream, /** doq stream data length has completed, allocations can be done. False on * allocation failure. */ static int -doq_stream_datalen_complete(struct doq_stream* stream, struct doq_table* table) +doq_stream_datalen_complete(struct doq_conn* conn, struct doq_stream* stream, + struct doq_table* table) { if(stream->inlen > 1024*1024) { log_err("doq stream in length too large %d", (int)stream->inlen); return 0; } + if(!doq_table_quic_size_available(table, conn->doq_socket->cfg, + stream->inlen)) { + verbose(VERB_ALGO, "doq stream: no space for query length"); + return 0; + } stream->in = calloc(1, stream->inlen); if(!stream->in) { log_err("doq could not read stream, calloc failed: " @@ -4078,8 +4090,9 @@ doq_stream_data_complete(struct doq_conn* conn, struct doq_stream* stream) /** doq receive data for a stream, more bytes of the incoming data */ static int -doq_stream_recv_data(struct doq_stream* stream, const uint8_t* data, - size_t datalen, int* recv_done, struct doq_table* table) +doq_stream_recv_data(struct doq_conn* conn, struct doq_stream* stream, + const uint8_t* data, size_t datalen, int* recv_done, + struct doq_table* table) { int got_data = 0; /* read the tcplength uint16_t at the start */ @@ -4100,7 +4113,7 @@ doq_stream_recv_data(struct doq_stream* stream, const uint8_t* data, if(stream->nread == 2) { /* the initial length value is completed */ stream->inlen = ntohs(tcplen); - if(!doq_stream_datalen_complete(stream, table)) + if(!doq_stream_datalen_complete(conn, stream, table)) return 0; } else { /* store for later */ @@ -4316,8 +4329,7 @@ doq_stream_open_cb(ngtcp2_conn* ATTR_UNUSED(conn), int64_t stream_id, verbose(VERB_ALGO, "doq: stream with this id already exists"); return 0; } - if(stream_id != 0 && stream_id != 4 && /* allow one stream on a new connection */ - !doq_table_quic_size_available(doq_conn->doq_socket->table, + if(!doq_table_quic_size_available(doq_conn->doq_socket->table, doq_conn->doq_socket->cfg, sizeof(*stream) + 100 /* estimated query in */ + 512 /* estimated response out */ @@ -4375,8 +4387,8 @@ doq_recv_stream_data_cb(ngtcp2_conn* ATTR_UNUSED(conn), uint32_t flags, return 0; } if(datalen != 0) { - if(!doq_stream_recv_data(stream, data, datalen, &recv_done, - doq_conn->doq_socket->table)) + if(!doq_stream_recv_data(doq_conn, stream, data, datalen, + &recv_done, doq_conn->doq_socket->table)) return NGTCP2_ERR_CALLBACK_FAILURE; } if((flags&NGTCP2_STREAM_DATA_FLAG_FIN)!=0) { From f54e0791ba4488284d9f6059014640a3d0bd30da Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Wed, 22 Jul 2026 10:09:50 +0200 Subject: [PATCH 13/84] - Fix CVE-2026-40691, Packet of death for DNSCrypt over TCP. Thanks to Qifan Zhang, Palo Alto Networks, for the report. In addition, thanks to Trung Nguyen (@everping) of CyStack, for also reporting this issue. --- daemon/worker.c | 10 +++++++++- dnscrypt/dnscrypt.c | 14 ++++++++++++-- dnscrypt/dnscrypt.h | 3 ++- testdata/dnscrypt_cert.tdir/dnscrypt_cert.test | 17 +++++++++-------- .../dnscrypt_cert_chacha.test | 17 +++++++++-------- util/netevent.c | 4 +++- 6 files changed, 44 insertions(+), 21 deletions(-) diff --git a/daemon/worker.c b/daemon/worker.c index a5dd9bc02..b4e50b345 100644 --- a/daemon/worker.c +++ b/daemon/worker.c @@ -1550,6 +1550,7 @@ worker_handle_request(struct comm_point* c, void* arg, int error, return 0; } query_error(c->buffer, LDNS_RCODE_FORMERR, 0); + sldns_buffer_copy(c->dnscrypt_buffer, c->buffer); return 1; } dname_str(qinfo.qname, buf); @@ -1568,6 +1569,7 @@ worker_handle_request(struct comm_point* c, void* arg, int error, query_error(c->buffer, LDNS_RCODE_SERVFAIL, qinfo.qname_len); worker->stats.num_query_dnscrypt_cleartext++; + sldns_buffer_copy(c->dnscrypt_buffer, c->buffer); return 1; } worker->stats.num_query_dnscrypt_cert++; @@ -1828,7 +1830,13 @@ worker_handle_request(struct comm_point* c, void* arg, int error, server_stats_insquery(&worker->stats, c, qinfo.qtype, qinfo.qclass, &edns, repinfo); if(c->type != comm_udp) +#ifdef USE_DNSCRYPT + edns.udp_size = (c->dnscrypt && repinfo->is_dnscrypted) + ? sldns_buffer_capacity(c->buffer) - DNSCRYPT_REPLY_HEADER_SIZE + : 65535; +#else edns.udp_size = 65535; /* max size for TCP replies */ +#endif if(qinfo.qclass == LDNS_RR_CLASS_CH && answer_chaos(worker, &qinfo, &edns, repinfo, c->buffer)) { regional_free_all(worker->scratchpad); @@ -2112,7 +2120,7 @@ send_reply_rc: } } #ifdef USE_DNSCRYPT - if(!dnsc_handle_uncurved_request(repinfo)) { + if(!dnsc_handle_uncurved_request(repinfo, c->buffer)) { return 0; } #endif diff --git a/dnscrypt/dnscrypt.c b/dnscrypt/dnscrypt.c index 173484cdf..08f9dcb8c 100644 --- a/dnscrypt/dnscrypt.c +++ b/dnscrypt/dnscrypt.c @@ -474,10 +474,18 @@ dnscrypt_server_curve(const dnsccert *cert, uint8_t *const buf = sldns_buffer_begin(buffer); size_t len = sldns_buffer_limit(buffer); + if(len + DNSCRYPT_REPLY_HEADER_SIZE > sldns_buffer_capacity(buffer)) + return -1; + sldns_buffer_clear(buffer); + if(udp){ if (max_len > max_reply_size) max_len = max_reply_size; } + if(max_len > sldns_buffer_capacity(buffer)) + max_len = sldns_buffer_capacity(buffer); + if(max_len > 65535) + max_len = 65535; memcpy(nonce, client_nonce, crypto_box_HALF_NONCEBYTES); @@ -520,6 +528,7 @@ dnscrypt_server_curve(const dnsccert *cert, DNSCRYPT_MAGIC_HEADER_LEN, nonce, crypto_box_NONCEBYTES); + sldns_buffer_flip(buffer); sldns_buffer_set_limit(buffer, len + DNSCRYPT_REPLY_HEADER_SIZE); return 0; } @@ -912,12 +921,13 @@ dnsc_handle_curved_request(struct dnsc_env* dnscenv, } int -dnsc_handle_uncurved_request(struct comm_reply *repinfo) +dnsc_handle_uncurved_request(struct comm_reply *repinfo, + struct sldns_buffer* buffer) { if(!repinfo->c->dnscrypt) { return 1; } - sldns_buffer_copy(repinfo->c->dnscrypt_buffer, repinfo->c->buffer); + sldns_buffer_copy(repinfo->c->dnscrypt_buffer, buffer); if(!repinfo->is_dnscrypted) { return 1; } diff --git a/dnscrypt/dnscrypt.h b/dnscrypt/dnscrypt.h index b0da9b732..998237d48 100644 --- a/dnscrypt/dnscrypt.h +++ b/dnscrypt/dnscrypt.h @@ -128,7 +128,8 @@ int dnsc_handle_curved_request(struct dnsc_env* dnscenv, * \return 0 in case of failure. */ -int dnsc_handle_uncurved_request(struct comm_reply *repinfo); +int dnsc_handle_uncurved_request(struct comm_reply *repinfo, + struct sldns_buffer* buffer); /** * Computes the size of the shared secret cache entry. diff --git a/testdata/dnscrypt_cert.tdir/dnscrypt_cert.test b/testdata/dnscrypt_cert.tdir/dnscrypt_cert.test index fdb88e8f9..4631d02e1 100644 --- a/testdata/dnscrypt_cert.tdir/dnscrypt_cert.test +++ b/testdata/dnscrypt_cert.tdir/dnscrypt_cert.test @@ -9,20 +9,21 @@ PRE="../.." # do the test -# Query plain request over DNSCrypt channel get closed -# We use TCP to avoid hanging on waiting for UDP. -# We expect `outfile` to contain no DNS payload -echo "> dig TCP www.example.com. DNSCrypt port" -dig +tcp @127.0.0.1 -p $DNSCRYPT_PORT www.example.com. A | tee outfile +# Query plain request over DNSCrypt. +# This used to close the channel; now it returns SERVFAIL. +# Old: We use TCP to avoid hanging on waiting for UDP. +# We expect `outfile` to contain no DNS payload +echo "> dig www.example.com. DNSCrypt port" +dig @127.0.0.1 -p $DNSCRYPT_PORT www.example.com. A | tee outfile echo "> cat logfiles" cat fwd.log cat unbound.log echo "> check answer" -if grep "QUESTION SECTION" outfile; then +if grep "SERVFAIL" outfile; then + echo "OK" +else echo "NOK" exit 1 -else - echo "OK" fi diff --git a/testdata/dnscrypt_cert_chacha.tdir/dnscrypt_cert_chacha.test b/testdata/dnscrypt_cert_chacha.tdir/dnscrypt_cert_chacha.test index 2db073ad6..a92f196f5 100644 --- a/testdata/dnscrypt_cert_chacha.tdir/dnscrypt_cert_chacha.test +++ b/testdata/dnscrypt_cert_chacha.tdir/dnscrypt_cert_chacha.test @@ -9,20 +9,21 @@ PRE="../.." # do the test -# Query plain request over DNSCrypt channel get closed -# We use TCP to avoid hanging on waiting for UDP. -# We expect `outfile` to contain no DNS payload -echo "> dig TCP www.example.com. DNSCrypt port" -dig +tcp @127.0.0.1 -p $DNSCRYPT_PORT www.example.com. A | tee outfile +# Query plain request over DNSCrypt. +# This used to close the channel; now it returns SERVFAIL. +# Old: We use TCP to avoid hanging on waiting for UDP. +# We expect `outfile` to contain no DNS payload +echo "> dig www.example.com. DNSCrypt port" +dig @127.0.0.1 -p $DNSCRYPT_PORT www.example.com. A | tee outfile echo "> cat logfiles" cat fwd.log cat unbound.log echo "> check answer" -if grep "QUESTION SECTION" outfile; then +if grep "SERVFAIL" outfile; then + echo "OK" +else echo "NOK" exit 1 -else - echo "OK" fi diff --git a/util/netevent.c b/util/netevent.c index e1480dc22..58938a220 100644 --- a/util/netevent.c +++ b/util/netevent.c @@ -6677,7 +6677,9 @@ comm_point_send_reply(struct comm_reply *repinfo) log_assert(repinfo && repinfo->c); #ifdef USE_DNSCRYPT buffer = repinfo->c->dnscrypt_buffer; - if(!dnsc_handle_uncurved_request(repinfo)) { + if(!dnsc_handle_uncurved_request(repinfo, + repinfo->c->tcp_req_info? + repinfo->c->tcp_req_info->spool_buffer:repinfo->c->buffer)) { return; } #else From 27f22b88081eca4faf6b51194bd5088a034fcf33 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Wed, 22 Jul 2026 10:10:24 +0200 Subject: [PATCH 14/84] - Fix CVE-2026-41637, Degradation of resolution service from improperly accounted client-terminated DNS-over-QUIC queries. Thanks to Qifan Zhang, Palo Alto Networks, for the report. --- services/listen_dnsport.c | 57 ++++++++++++++++++++++++++++++++++++--- services/listen_dnsport.h | 16 +++++++++++ services/mesh.c | 29 ++++++++++++++++++-- services/mesh.h | 4 ++- testcode/fake_event.c | 9 +++++++ util/netevent.c | 2 +- util/netevent.h | 2 ++ 7 files changed, 112 insertions(+), 7 deletions(-) diff --git a/services/listen_dnsport.c b/services/listen_dnsport.c index caae7fcc4..0500bff8d 100644 --- a/services/listen_dnsport.c +++ b/services/listen_dnsport.c @@ -2166,7 +2166,8 @@ void tcp_req_info_clear(struct tcp_req_info* req) open = req->open_req_list; while(open) { nopen = open->next; - mesh_state_remove_reply(open->mesh, open->mesh_state, req->cp); + mesh_state_remove_reply(open->mesh, open->mesh_state, req->cp, + NULL); free(open); open = nopen; } @@ -3596,15 +3597,29 @@ doq_conn_create(struct comm_point* c, struct doq_pkt_addr* paddr, return conn; } +/** The arguments for doq stream tree del. */ +struct doq_stream_tree_del_args { + /** The doq table. */ + struct doq_table* table; + /** The doq connection for the stream. */ + struct doq_conn* conn; +}; + /** delete stream tree node */ static void stream_tree_del(rbnode_type* node, void* arg) { - struct doq_table* table = (struct doq_table*)arg; + struct doq_stream_tree_del_args* args = (struct doq_stream_tree_del_args*)arg; + struct doq_table* table = args->table; struct doq_stream* stream; if(!node) return; stream = (struct doq_stream*)node; + if(stream->mesh_state) { + mesh_state_remove_reply(stream->mesh, stream->mesh_state, + args->conn->doq_socket->cp, stream); + stream->mesh_state = NULL; + } if(stream->in) doq_table_quic_size_subtract(table, stream->inlen); if(stream->out) @@ -3626,7 +3641,11 @@ doq_conn_delete(struct doq_conn* conn, struct doq_table* table) * because the ngtcp2 conn is deleted. */ SSL_set_app_data(conn->ssl, NULL); if(conn->stream_tree.count != 0) { - traverse_postorder(&conn->stream_tree, stream_tree_del, table); + struct doq_stream_tree_del_args args; + memset(&args, 0, sizeof(args)); + args.table = table; + args.conn = conn; + traverse_postorder(&conn->stream_tree, stream_tree_del, &args); } free(conn->key.dcid); SSL_free(conn->ssl); @@ -3935,6 +3954,11 @@ doq_stream_close(struct doq_conn* conn, struct doq_stream* stream, if(stream->is_closed) return 1; stream->is_closed = 1; + if(stream->mesh_state) { + mesh_state_remove_reply(stream->mesh, stream->mesh_state, + conn->doq_socket->cp, stream); + stream->mesh_state = NULL; + } doq_stream_off_write_list(conn, stream); if(send_shutdown) { verbose(VERB_ALGO, "doq: shutdown stream_id %d with app_error_code %d", @@ -4013,7 +4037,33 @@ doq_stream_send_reply(struct doq_conn* conn, struct doq_stream* stream, doq_conn_write_enable(conn); return 1; } +#endif /* HAVE_NGTCP2 */ +void +doq_stream_add_meshstate(struct doq_stream* stream, + struct mesh_area* mesh, struct mesh_state* m) +{ +#ifdef HAVE_NGTCP2 + stream->mesh = mesh; + stream->mesh_state = m; +#else + (void)stream; (void)mesh; (void)m; +#endif +} + +void +doq_stream_remove_mesh_state(struct doq_stream* stream) +{ +#ifdef HAVE_NGTCP2 + if(!stream) + return; + stream->mesh_state = NULL; +#else + (void)stream; +#endif +} + +#ifdef HAVE_NGTCP2 /** doq stream data length has completed, allocations can be done. False on * allocation failure. */ static int @@ -4074,6 +4124,7 @@ doq_stream_data_complete(struct doq_conn* conn, struct doq_stream* stream) return 0; } c->repinfo.doq_streamid = stream->stream_id; + c->repinfo.doq_stream = stream; conn->doq_socket->current_conn = conn; fptr_ok(fptr_whitelist_comm_point(c->callback)); if( (*c->callback)(c, c->cb_arg, NETEVENT_NOERROR, &c->repinfo)) { diff --git a/services/listen_dnsport.h b/services/listen_dnsport.h index 95aa3e11e..ae0463468 100644 --- a/services/listen_dnsport.h +++ b/services/listen_dnsport.h @@ -61,6 +61,8 @@ struct config_file; struct addrinfo; struct sldns_buffer; struct tcl_list; +struct mesh_area; +struct mesh_state; /** * Listening for queries structure. @@ -692,6 +694,11 @@ struct doq_stream { uint8_t* out; /** if the stream is on the write list */ uint8_t on_write_list; + /** The mesh area and mesh state, set when this stream's query was + * dispatched into the mesh; used to detach the reply on stream close */ + struct mesh_area* mesh; + /** the mesh state for the query, is nonNULL when there is one. */ + struct mesh_state* mesh_state; /** the prev and next on the write list, if on the list */ struct doq_stream* write_prev, *write_next; }; @@ -794,7 +801,16 @@ int doq_stream_close(struct doq_conn* conn, struct doq_stream* stream, /** send reply for a connection */ int doq_stream_send_reply(struct doq_conn* conn, struct doq_stream* stream, struct sldns_buffer* buf); +#endif /* HAVE_NGTCP2 */ +/** add mesh state to doq stream */ +void doq_stream_add_meshstate(struct doq_stream* stream, + struct mesh_area* mesh, struct mesh_state* m); + +/** remove mesh state from doq stream */ +void doq_stream_remove_mesh_state(struct doq_stream* stream); + +#ifdef HAVE_NGTCP2 /** the connection has write interest, wants to write packets */ void doq_conn_write_enable(struct doq_conn* conn); diff --git a/services/mesh.c b/services/mesh.c index 286901047..f06c1cb9d 100644 --- a/services/mesh.c +++ b/services/mesh.c @@ -467,6 +467,8 @@ void mesh_new_client(struct mesh_area* mesh, struct query_info* qinfo, "incoming query."); if(rep->c->use_h2) http2_stream_remove_mesh_state(rep->c->h2_stream); + else if(rep->c->type == comm_doq && rep->doq_stream) + doq_stream_remove_mesh_state(rep->doq_stream); comm_point_drop_reply(rep); mesh->stats_dropped++; return; @@ -480,6 +482,8 @@ void mesh_new_client(struct mesh_area* mesh, struct query_info* qinfo, "dropping incoming query."); if(rep->c->use_h2) http2_stream_remove_mesh_state(rep->c->h2_stream); + else if(rep->c->type == comm_doq && rep->doq_stream) + doq_stream_remove_mesh_state(rep->doq_stream); comm_point_drop_reply(rep); mesh->num_queries_replyaddr_limit++; return; @@ -552,6 +556,8 @@ void mesh_new_client(struct mesh_area* mesh, struct query_info* qinfo, } 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) { + doq_stream_add_meshstate(rep->doq_stream, mesh, s); } /* add serve expired timer if required and not already there */ if(timeout && !mesh_serve_expired_init(s, timeout)) { @@ -605,6 +611,8 @@ servfail_mem: qinfo, qid, qflags, edns); if(rep->c->use_h2) http2_stream_remove_mesh_state(rep->c->h2_stream); + else if(rep->c->type == comm_doq && rep->doq_stream) + doq_stream_remove_mesh_state(rep->doq_stream); comm_point_send_reply(rep); if(added) mesh_state_delete(&s->s); @@ -1484,6 +1492,10 @@ mesh_send_reply(struct mesh_state* m, int rcode, struct reply_info* rep, * for HTTP/2 stream to refer to mesh state, in case * connection gets cleanup before HTTP/2 stream close. */ r->h2_stream->mesh_state = NULL; +#ifdef HAVE_NGTCP2 + } else if(r->query_reply.doq_stream) { + r->query_reply.doq_stream->mesh_state = NULL; +#endif } /* send the reply */ /* We don't reuse the encoded answer if: @@ -1777,6 +1789,8 @@ void mesh_query_done(struct mesh_state* mstate) mstate->reply_list = NULL; if(r->query_reply.c->use_h2) http2_stream_remove_mesh_state(r->h2_stream); + else if(r->query_reply.doq_stream) + doq_stream_remove_mesh_state(r->query_reply.doq_stream); comm_point_drop_reply(&r->query_reply); mstate->reply_list = reply_list; log_assert(mstate->s.env->mesh->num_reply_addrs > 0); @@ -1814,6 +1828,8 @@ void mesh_query_done(struct mesh_state* mstate) mstate->reply_list = NULL; if(r->query_reply.c->use_h2) { http2_stream_remove_mesh_state(r->h2_stream); + } else if(r->query_reply.doq_stream) { + doq_stream_remove_mesh_state(r->query_reply.doq_stream); } comm_point_drop_reply(&r->query_reply); mstate->reply_list = reply_list; @@ -2009,6 +2025,8 @@ int mesh_state_add_reply(struct mesh_state* s, struct edns_data* edns, if(rep->c->use_h2) r->h2_stream = rep->c->h2_stream; else r->h2_stream = NULL; + if(rep->c->type != comm_doq) + r->query_reply.doq_stream = NULL; /* Data related to local alias stored in 'qinfo' (if any) is ephemeral * and can be different for different original queries (even if the @@ -2366,7 +2384,7 @@ void mesh_list_remove(struct mesh_state* m, struct mesh_state** fp, } void mesh_state_remove_reply(struct mesh_area* mesh, struct mesh_state* m, - struct comm_point* cp) + struct comm_point* cp, struct doq_stream* doq_stream) { struct mesh_reply* n, *prev = NULL; n = m->reply_list; @@ -2374,7 +2392,8 @@ void mesh_state_remove_reply(struct mesh_area* mesh, struct mesh_state* m, * there is no accounting twice */ if(!n) return; /* nothing to remove, also no accounting needed */ while(n) { - if(n->query_reply.c == cp) { + if(n->query_reply.c == cp + && (!doq_stream || n->query_reply.doq_stream == doq_stream)) { /* unlink it */ if(prev) prev->next = n->next; else m->reply_list = n->next; @@ -2387,6 +2406,10 @@ void mesh_state_remove_reply(struct mesh_area* mesh, struct mesh_state* m, * share the same comm_point); make sure the streams * 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; +#endif /* prev = prev; */ n = n->next; @@ -2554,6 +2577,8 @@ mesh_serve_expired_callback(void* arg) mstate->reply_list = NULL; if(r->query_reply.c->use_h2) http2_stream_remove_mesh_state(r->h2_stream); + else if(r->query_reply.doq_stream) + doq_stream_remove_mesh_state(r->query_reply.doq_stream); comm_point_drop_reply(&r->query_reply); mstate->reply_list = reply_list; mstate->s.env->mesh->num_queries_discard_timeout++; diff --git a/services/mesh.h b/services/mesh.h index 9ee585156..352260e26 100644 --- a/services/mesh.h +++ b/services/mesh.h @@ -683,9 +683,11 @@ void mesh_list_remove(struct mesh_state* m, struct mesh_state** fp, * @param mesh: to update the counters. * @param m: the mesh state. * @param cp: the comm_point to remove from the list. + * @param doq_stream: if not NULL, it specifies the doq_stream to match + * for the delete. */ void mesh_state_remove_reply(struct mesh_area* mesh, struct mesh_state* m, - struct comm_point* cp); + struct comm_point* cp, struct doq_stream* doq_stream); /** Callback for when the serve expired client timer has run out. Tries to * find an expired answer in the cache and reply that to the client. diff --git a/testcode/fake_event.c b/testcode/fake_event.c index ce439edd1..b127d0df8 100644 --- a/testcode/fake_event.c +++ b/testcode/fake_event.c @@ -2021,6 +2021,15 @@ void http2_stream_remove_mesh_state(struct http2_stream* ATTR_UNUSED(h2_stream)) { } +void doq_stream_add_meshstate(struct doq_stream* ATTR_UNUSED(stream), + struct mesh_area* ATTR_UNUSED(mesh), struct mesh_state* ATTR_UNUSED(m)) +{ +} + +void doq_stream_remove_mesh_state(struct doq_stream* ATTR_UNUSED(stream)) +{ +} + void fast_reload_service_cb(int ATTR_UNUSED(fd), short ATTR_UNUSED(event), void* ATTR_UNUSED(arg)) { diff --git a/util/netevent.c b/util/netevent.c index 58938a220..b7092b88a 100644 --- a/util/netevent.c +++ b/util/netevent.c @@ -3166,7 +3166,7 @@ static void http2_stream_delete(struct http2_session* h2_session, { if(h2_stream->mesh_state) { mesh_state_remove_reply(h2_stream->mesh, h2_stream->mesh_state, - h2_session->c); + h2_session->c, NULL); h2_stream->mesh_state = NULL; } http2_req_stream_clear(h2_stream); diff --git a/util/netevent.h b/util/netevent.h index 7235843ee..aee8a6539 100644 --- a/util/netevent.h +++ b/util/netevent.h @@ -187,6 +187,8 @@ struct comm_reply { /** port number for doq */ int doq_srcport; #endif /* HAVE_NGTCP2 */ + /** The doq stream to register mesh states to. */ + struct doq_stream* doq_stream; }; /** From 13ec8d0f261ee7900ac67cfece551e8a703d14b1 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Wed, 22 Jul 2026 10:11:04 +0200 Subject: [PATCH 15/84] - Fix CVE-2026-42955, Extra fix for CVE-2026-40622 to also clamp the TTL of A/AAAA records disallowing a one-time 'ghost domain' delegation renewal via glue records. Thanks to Qifan Zhang, Palo Alto Networks, for the report. --- services/cache/rrset.c | 12 +++++++++--- testdata/iter_prefetch_fail.rpl | 8 ++++---- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/services/cache/rrset.c b/services/cache/rrset.c index ab4f4c8e0..a5d320a30 100644 --- a/services/cache/rrset.c +++ b/services/cache/rrset.c @@ -126,7 +126,8 @@ rrset_cache_touch(struct rrset_cache* r, struct ub_packed_rrset_key* key, /** see if rrset needs to be updated in the cache */ static int -need_to_update_rrset(void* nd, void* cd, time_t timenow, int equal, int ns) +need_to_update_rrset(void* nd, void* cd, time_t timenow, int equal, int ns, + int a_aaaa) { struct packed_rrset_data* newd = (struct packed_rrset_data*)nd; struct packed_rrset_data* cached = (struct packed_rrset_data*)cd; @@ -151,9 +152,13 @@ need_to_update_rrset(void* nd, void* cd, time_t timenow, int equal, int ns) return 0; /* ghost-domain: never let an NS overwrite extend lifetime * past the entry it replaces, regardless of trust. */ - if(ns && !TTL_IS_EXPIRED(cached->ttl, timenow) && + /* Also for A/AAAA and it is glue. */ + if((ns || + (a_aaaa && cached->trust==rrset_trust_add_noAA)) + && !TTL_IS_EXPIRED(cached->ttl, timenow) && newd->ttl > cached->ttl) { size_t i; + if(a_aaaa) newd->trust=rrset_trust_add_noAA; newd->ttl = cached->ttl; for(i=0; i<(newd->count+newd->rrsig_count); i++) if(newd->rr_ttl[i] > newd->ttl) @@ -223,7 +228,8 @@ rrset_cache_update(struct rrset_cache* r, struct rrset_ref* ref, equal = rrsetdata_equal((struct packed_rrset_data*)k->entry. data, (struct packed_rrset_data*)e->data); if(!need_to_update_rrset(k->entry.data, e->data, timenow, - equal, (rrset_type==LDNS_RR_TYPE_NS))) { + equal, (rrset_type==LDNS_RR_TYPE_NS), + (rrset_type==LDNS_RR_TYPE_A || rrset_type==LDNS_RR_TYPE_AAAA))) { /* cache is superior, return that value */ lock_rw_unlock(&e->lock); ub_packed_rrset_parsedelete(k, alloc); diff --git a/testdata/iter_prefetch_fail.rpl b/testdata/iter_prefetch_fail.rpl index d1e308305..aa94d0fe5 100644 --- a/testdata/iter_prefetch_fail.rpl +++ b/testdata/iter_prefetch_fail.rpl @@ -319,7 +319,7 @@ example.com. 360 IN NS ns.example.com. SECTION ADDITIONAL ; this is picked up from the parent (because this simulation has the ; parent respond with servfail, not actually timeout) -ns.example.com. 3600 IN A 1.2.3.4 +ns.example.com. 360 IN A 1.2.3.4 ENTRY_END ; another query to see if there is another lookup towards the authority @@ -342,7 +342,7 @@ www.example.com. 360 IN A 10.20.30.40 SECTION AUTHORITY example.com. 360 IN NS ns.example.com. SECTION ADDITIONAL -ns.example.com. 3600 IN A 1.2.3.4 +ns.example.com. 360 IN A 1.2.3.4 ENTRY_END ; some time later another query, and now it is fine to bother the authority @@ -367,7 +367,7 @@ www.example.com. 330 IN A 10.20.30.40 SECTION AUTHORITY example.com. 330 IN NS ns.example.com. SECTION ADDITIONAL -ns.example.com. 3570 IN A 1.2.3.4 +ns.example.com. 330 IN A 1.2.3.4 ENTRY_END ; now the just-looked-up entry STEP 190 QUERY @@ -388,7 +388,7 @@ www.example.com. 3600 IN A 10.20.30.40 SECTION AUTHORITY example.com. 3600 IN NS ns.example.com. SECTION ADDITIONAL -ns.example.com. 3570 IN A 1.2.3.4 +ns.example.com. 3600 IN A 1.2.3.4 ENTRY_END From f52a9e864bfa0f10d8816b64130586b1d224c474 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Wed, 22 Jul 2026 10:11:26 +0200 Subject: [PATCH 16/84] - Fix CVE-2026-44621, Libunbound applications configured with 'unwanted-reply-threshold' could eventually be abruptly terminated. Thanks to Qifan Zhang, Palo Alto Networks, for the report. --- daemon/worker.c | 5 +++++ dnstap/unbound-dnstap-socket.c | 5 +++++ smallapp/worker_cb.c | 6 ++++++ testcode/doqclient.c | 5 +++++ util/fptr_wlist.c | 1 + 5 files changed, 22 insertions(+) diff --git a/daemon/worker.c b/daemon/worker.c index b4e50b345..1d61f8733 100644 --- a/daemon/worker.c +++ b/daemon/worker.c @@ -2646,6 +2646,11 @@ void libworker_event_done_cb(void* ATTR_UNUSED(arg), int ATTR_UNUSED(rcode), log_assert(0); } +void libworker_alloc_cleanup(void* ATTR_UNUSED(arg)) +{ + log_assert(0); +} + int context_query_cmp(const void* ATTR_UNUSED(a), const void* ATTR_UNUSED(b)) { log_assert(0); diff --git a/dnstap/unbound-dnstap-socket.c b/dnstap/unbound-dnstap-socket.c index 90b0f6003..2bf017430 100644 --- a/dnstap/unbound-dnstap-socket.c +++ b/dnstap/unbound-dnstap-socket.c @@ -1735,6 +1735,11 @@ void libworker_event_done_cb(void* ATTR_UNUSED(arg), int ATTR_UNUSED(rcode), log_assert(0); } +void libworker_alloc_cleanup(void* ATTR_UNUSED(arg)) +{ + log_assert(0); +} + int context_query_cmp(const void* ATTR_UNUSED(a), const void* ATTR_UNUSED(b)) { log_assert(0); diff --git a/smallapp/worker_cb.c b/smallapp/worker_cb.c index 92ebe386d..876c7db4e 100644 --- a/smallapp/worker_cb.c +++ b/smallapp/worker_cb.c @@ -128,6 +128,12 @@ worker_alloc_cleanup(void* ATTR_UNUSED(arg)) log_assert(0); } +void +libworker_alloc_cleanup(void* ATTR_UNUSED(arg)) +{ + log_assert(0); +} + struct outbound_entry* libworker_send_query( struct query_info* ATTR_UNUSED(qinfo), uint16_t ATTR_UNUSED(flags), int ATTR_UNUSED(dnssec), int ATTR_UNUSED(want_dnssec), diff --git a/testcode/doqclient.c b/testcode/doqclient.c index 8a34ca31b..ce4d3417d 100644 --- a/testcode/doqclient.c +++ b/testcode/doqclient.c @@ -2671,6 +2671,11 @@ void libworker_event_done_cb(void* ATTR_UNUSED(arg), int ATTR_UNUSED(rcode), log_assert(0); } +void libworker_alloc_cleanup(void* ATTR_UNUSED(arg)) +{ + log_assert(0); +} + int context_query_cmp(const void* ATTR_UNUSED(a), const void* ATTR_UNUSED(b)) { log_assert(0); diff --git a/util/fptr_wlist.c b/util/fptr_wlist.c index a45134065..5edd8adf2 100644 --- a/util/fptr_wlist.c +++ b/util/fptr_wlist.c @@ -610,6 +610,7 @@ int fptr_whitelist_alloc_cleanup(void (*fptr)(void*)) { if(fptr == &worker_alloc_cleanup) return 1; + else if(fptr == &libworker_alloc_cleanup) return 1; return 0; } From 1e1940383ab5ee655fe7fac0da606fe59c76ce86 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Wed, 22 Jul 2026 10:12:03 +0200 Subject: [PATCH 17/84] - Fix CVE-2026-44687, Off-by-one error in 'harden-below-nxdomain' logic can shadow a stub/forward zone by a legitimate parent's NXDOMAIN. Thanks to Qifan Zhang, Palo Alto Networks, for the report. --- services/cache/dns.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/cache/dns.c b/services/cache/dns.c index f6ce272a5..98d07bc37 100644 --- a/services/cache/dns.c +++ b/services/cache/dns.c @@ -1065,7 +1065,7 @@ dns_cache_lookup(struct module_env* env, if(env->cfg->harden_below_nxdomain) { while(!dname_is_root(k.qname)) { if(dpname && dpnamelen - && !dname_subdomain_c(k.qname, dpname)) + && !dname_strict_subdomain_c(k.qname, dpname)) break; /* no synth nxdomain above the stub */ dname_remove_label(&k.qname, &k.qname_len); h = query_info_hash(&k, flags); From f7637a4f1811f4a9331707d8a95cf0af65a97c0f Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Wed, 22 Jul 2026 10:12:38 +0200 Subject: [PATCH 18/84] - Fix CVE-2026-44690, Cross-zone wildcard cache poisoning via RRSIG.labels manipulation. Thanks to Qifan Zhang, Palo Alto Networks, for the report. --- services/cache/rrset.c | 32 ++++++++++++++++++++++++++++++++ validator/val_sigcrypt.c | 7 +++++++ validator/val_utils.c | 2 +- validator/val_utils.h | 4 ++++ validator/validator.c | 15 +++++++++++++-- 5 files changed, 57 insertions(+), 3 deletions(-) diff --git a/services/cache/rrset.c b/services/cache/rrset.c index a5d320a30..79bf473bb 100644 --- a/services/cache/rrset.c +++ b/services/cache/rrset.c @@ -50,6 +50,7 @@ #include "util/regional.h" #include "util/alloc.h" #include "util/net_help.h" +#include "validator/val_utils.h" void rrset_markdel(void* key) @@ -261,12 +262,43 @@ rrset_cache_update(struct rrset_cache* r, struct rrset_ref* ref, return 0; } +/** See if the name is a within signer authority */ +static int +dname_subdomain_rrsig_signers(uint8_t* dname, + struct ub_packed_rrset_key* rrset) +{ + struct packed_rrset_data* d = (struct packed_rrset_data*) + rrset->entry.data; + size_t i; + if(!d || !d->rrsig_count) + return 0; + for(i=0; irrsig_count; i++) { + uint8_t* sname = NULL; + size_t slen = 0; + rrsig_get_signer(d->rr_data[d->count+i], d->rr_len[d->count+i], + &sname, &slen); + if(!sname || !slen) + return 0; /* malformed */ + if(!dname_subdomain_c(dname, sname)) + return 0; /* not a subdomain */ + } + return 1; +} + void rrset_cache_update_wildcard(struct rrset_cache* rrset_cache, struct ub_packed_rrset_key* rrset, uint8_t* ce, size_t ce_len, struct alloc_cache* alloc, time_t timenow) { struct rrset_ref ref; uint8_t wc_dname[LDNS_MAX_DOMAINLEN+3]; + + /* See if the RRSIG signer name allows this wildcard, + * the new rrset should fall within the zone of the RRSIG signer(s). */ + if(!dname_subdomain_rrsig_signers(ce, rrset)) { + verbose(VERB_ALGO, "wildcard canonical parent outside signer authority"); + return; + } + rrset = packed_rrset_copy_alloc(rrset, alloc, timenow); if(!rrset) { log_err("malloc failure in rrset_cache_update_wildcard"); diff --git a/validator/val_sigcrypt.c b/validator/val_sigcrypt.c index 9f27f9cc9..46e6ac16b 100644 --- a/validator/val_sigcrypt.c +++ b/validator/val_sigcrypt.c @@ -1666,6 +1666,13 @@ dnskey_verify_rrset_sig(struct regional* region, sldns_buffer* buf, *reason_bogus = LDNS_EDE_DNSSEC_BOGUS; return sec_status_bogus; } + if((int)sig[2+3] < dname_signame_label_count(signer)) { + verbose(VERB_QUERY, "verify: RRSIG label count too low for signer"); + *reason = "signature labelcount lower than signature signer"; + if(reason_bogus) + *reason_bogus = LDNS_EDE_DNSSEC_BOGUS; + return sec_status_bogus; + } /* original ttl, always ok */ diff --git a/validator/val_utils.c b/validator/val_utils.c index 405cf897f..6754a8bab 100644 --- a/validator/val_utils.c +++ b/validator/val_utils.c @@ -157,7 +157,7 @@ val_classify_response(uint16_t query_flags, struct query_info* origqinf, } /** Get signer name from RRSIG */ -static void +void rrsig_get_signer(uint8_t* data, size_t len, uint8_t** sname, size_t* slen) { /* RRSIG rdata is not allowed to be compressed, it is stored diff --git a/validator/val_utils.h b/validator/val_utils.h index e0c649902..f3750742b 100644 --- a/validator/val_utils.h +++ b/validator/val_utils.h @@ -438,4 +438,8 @@ struct dns_msg* val_find_DS(struct module_env* env, uint8_t* nm, size_t nmlen, int derive_cname_from_dname(struct ub_packed_rrset_key* cname, struct ub_packed_rrset_key* dname, uint8_t* out, size_t outlen); +/** Get signer name from RRSIG, sname is NULL if malformed. */ +void rrsig_get_signer(uint8_t* data, size_t len, uint8_t** sname, + size_t* slen); + #endif /* VALIDATOR_VAL_UTILS_H */ diff --git a/validator/validator.c b/validator/validator.c index 8fc9ffc94..2099d9c19 100644 --- a/validator/validator.c +++ b/validator/validator.c @@ -1043,6 +1043,10 @@ validate_positive_response(struct module_env* env, struct val_env* ve, uint8_t* wc = NULL; size_t wl; int wc_cached = 0; + int wc_to_cache = 0; + uint8_t* cache_wc = NULL; + size_t cache_wl = 0; + struct ub_packed_rrset_key* cache_s = NULL; int wc_NSEC_ok = 0; /* This is used to update the RRset cache, with the combination * of the dname expansion and this wildcard, for security status. */ @@ -1071,8 +1075,11 @@ validate_positive_response(struct module_env* env, struct val_env* ve, return; } if(wc && !wc_cached && env->cfg->aggressive_nsec) { - rrset_cache_update_wildcard(env->rrset_cache, s, wc, wl, - env->alloc, *env->now); + /* Postpone cache adjust until proof has succeeded. */ + wc_to_cache = 1; + cache_wc = wc; + cache_wl = wl; + cache_s = s; wc_cached = 1; } if(wc) wc_rrset = s; @@ -1137,6 +1144,10 @@ validate_positive_response(struct module_env* env, struct val_env* ve, entry.data)->security = sec_status_bogus; return; } + if(wc_to_cache) { + rrset_cache_update_wildcard(env->rrset_cache, cache_s, + cache_wc, cache_wl, env->alloc, *env->now); + } verbose(VERB_ALGO, "Successfully validated positive response"); chase_reply->security = sec_status_secure; From 364ac737f713b2a606f0fddecddb675d72a6a991 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Wed, 22 Jul 2026 10:13:14 +0200 Subject: [PATCH 19/84] - Fix CVE-2026-50045, 'max-global-quota' reset by DNSSEC validation restarts. Thanks to Kunjie Shang, University of Science and Technology of China, for the report. --- iterator/iterator.c | 67 ++++++++++++++++++++++++++++++------------- util/module.h | 6 ++++ validator/validator.c | 13 +++++++++ 3 files changed, 66 insertions(+), 20 deletions(-) diff --git a/iterator/iterator.c b/iterator/iterator.c index cc38348b3..78cd9485b 100644 --- a/iterator/iterator.c +++ b/iterator/iterator.c @@ -81,7 +81,8 @@ int BLACKLIST_PENALTY = (120000*4); /** Timeout when only a single probe query per IP is allowed. */ int PROBE_MAXRTO = PROBE_MAXRTO_DEFAULT; /* in msec */ -static void target_count_increase_nx(struct iter_qstate* iq, int num); +static void target_count_increase_nx(struct module_qstate* qstate, + struct iter_qstate* iq, int num); int iter_init(struct module_env* env, int id) @@ -250,7 +251,7 @@ error_supers(struct module_qstate* qstate, int id, struct module_qstate* super) if((dpns->got4 == 2 || (!ie->supports_ipv4 && !ie->nat64.use_nat64)) && (dpns->got6 == 2 || !ie->supports_ipv6)) { dpns->resolved = 1; /* mark as failed */ - target_count_increase_nx(super_iq, 1); + target_count_increase_nx(super, super_iq, 1); } } if(qstate->qinfo.qtype == LDNS_RR_TYPE_NS) { @@ -734,7 +735,7 @@ is_caps_whitelisted(struct iter_env* ie, struct iter_qstate* iq) * created for the parent query. */ static void -target_count_create(struct iter_qstate* iq) +target_count_create(struct module_qstate* qstate, struct iter_qstate* iq) { if(!iq->target_count) { iq->target_count = (int*)calloc(TARGET_COUNT_MAX, sizeof(int)); @@ -742,33 +743,57 @@ target_count_create(struct iter_qstate* iq) if(iq->target_count) { iq->target_count[TARGET_COUNT_REF] = 1; iq->nxns_dp = (uint8_t**)calloc(1, sizeof(uint8_t*)); + /* continue global quota from where it was. */ + if(qstate->global_quota_reached > + iq->target_count[TARGET_COUNT_GLOBAL_QUOTA]) + iq->target_count[TARGET_COUNT_GLOBAL_QUOTA] = + qstate->global_quota_reached; } } } static void -target_count_increase(struct iter_qstate* iq, int num) +target_count_store(struct module_qstate* qstate, struct iter_qstate* iq) { - target_count_create(iq); + if(iq->target_count) { + /* By storing the global quota counter, it stays + * there to be picked up if the module is restarted, + * eg. due to a validator retry, and then the + * target_count_create routine picks it up. */ + if(iq->target_count[TARGET_COUNT_GLOBAL_QUOTA] > + qstate->global_quota_reached) + qstate->global_quota_reached = + iq->target_count[TARGET_COUNT_GLOBAL_QUOTA]; + } +} + +static void +target_count_increase(struct module_qstate* qstate, + struct iter_qstate* iq, int num) +{ + target_count_create(qstate, iq); if(iq->target_count) iq->target_count[TARGET_COUNT_QUERIES] += num; iq->dp_target_count++; } static void -target_count_increase_nx(struct iter_qstate* iq, int num) +target_count_increase_nx(struct module_qstate* qstate, + struct iter_qstate* iq, int num) { - target_count_create(iq); + target_count_create(qstate, iq); if(iq->target_count) iq->target_count[TARGET_COUNT_NX] += num; } static void -target_count_increase_global_quota(struct iter_qstate* iq, int num) +target_count_increase_global_quota(struct module_qstate* qstate, + struct iter_qstate* iq, int num) { - target_count_create(iq); + target_count_create(qstate, iq); if(iq->target_count) iq->target_count[TARGET_COUNT_GLOBAL_QUOTA] += num; + target_count_store(qstate, iq); } /** @@ -861,7 +886,7 @@ generate_sub_request(uint8_t* qname, size_t qnamelen, uint16_t qtype, subiq = (struct iter_qstate*)subq->minfo[id]; memset(subiq, 0, sizeof(*subiq)); subiq->num_target_queries = 0; - target_count_create(iq); + target_count_create(qstate, iq); subiq->target_count = iq->target_count; if(iq->target_count) { iq->target_count[TARGET_COUNT_REF] ++; /* extra reference */ @@ -2234,7 +2259,7 @@ processLastResort(struct module_qstate* qstate, struct iter_qstate* iq, return error_response_cache(qstate, id, LDNS_RCODE_SERVFAIL); } iq->num_target_queries += qs; - target_count_increase(iq, qs); + target_count_increase(qstate, iq, qs); if(qs != 0) { qstate->ext_state[id] = module_wait_subquery; return 0; /* and wait for them */ @@ -2290,7 +2315,7 @@ processLastResort(struct module_qstate* qstate, struct iter_qstate* iq, * lookups at a time. */ verbose(VERB_ALGO, "try parent-side glue lookup"); iq->num_target_queries += query_count; - target_count_increase(iq, query_count); + target_count_increase(qstate, iq, query_count); qstate->ext_state[id] = module_wait_subquery; return 0; } @@ -2310,7 +2335,7 @@ processLastResort(struct module_qstate* qstate, struct iter_qstate* iq, if(query_count != 0) { /* suspend to await results */ verbose(VERB_ALGO, "try parent-side glue lookup"); iq->num_target_queries += query_count; - target_count_increase(iq, query_count); + target_count_increase(qstate, iq, query_count); qstate->ext_state[id] = module_wait_subquery; return 0; } @@ -2788,7 +2813,7 @@ processQueryTargets(struct module_qstate* qstate, struct iter_qstate* iq, return error_response_cache(qstate, id, LDNS_RCODE_SERVFAIL); } iq->num_target_queries += extra; - target_count_increase(iq, extra); + target_count_increase(qstate, iq, extra); if(iq->num_target_queries > 0) { /* wait to get all targets, we want to try em */ verbose(VERB_ALGO, "wait for all targets for fallback"); @@ -2839,7 +2864,7 @@ processQueryTargets(struct module_qstate* qstate, struct iter_qstate* iq, /* errors ignored, these targets are not strictly necessary for * this result, we do not have to reply with SERVFAIL */ iq->num_target_queries += extra; - target_count_increase(iq, extra); + target_count_increase(qstate, iq, extra); } /* Add the current set of unused targets to our queue. */ @@ -2962,7 +2987,7 @@ processQueryTargets(struct module_qstate* qstate, struct iter_qstate* iq, LDNS_RCODE_SERVFAIL); } iq->num_target_queries += qs; - target_count_increase(iq, qs); + target_count_increase(qstate, iq, qs); } /* Since a target query might have been made, we * need to check again. */ @@ -3022,7 +3047,7 @@ processQueryTargets(struct module_qstate* qstate, struct iter_qstate* iq, * this result, we do not have to reply with SERVFAIL */ if(extra > 0) { iq->num_target_queries += extra; - target_count_increase(iq, extra); + target_count_increase(qstate, iq, extra); check_waiting_queries(iq, qstate, id); /* undo qname minimise step because we'll get back here * to do it again */ @@ -3035,7 +3060,7 @@ processQueryTargets(struct module_qstate* qstate, struct iter_qstate* iq, } } - target_count_increase_global_quota(iq, 1); + target_count_increase_global_quota(qstate, iq, 1); if(iq->target_count && iq->target_count[TARGET_COUNT_GLOBAL_QUOTA] > MAX_GLOBAL_QUOTA) { char s[LDNS_MAX_DOMAINLEN]; @@ -3879,7 +3904,7 @@ processTargetResponse(struct module_qstate* qstate, int id, /* no new addresses, increase the nxns counter, like * this could be a list of wildcards with no new * addresses */ - target_count_increase_nx(foriq, 1); + target_count_increase_nx(qstate, foriq, 1); } verbose(VERB_ALGO, "added target response"); delegpt_log(VERB_ALGO, foriq->dp); @@ -3891,7 +3916,7 @@ processTargetResponse(struct module_qstate* qstate, int id, dpns->resolved = 1; /* fail the target */ /* do not count cached answers */ if(qstate->reply_origin && qstate->reply_origin->len != 0) { - target_count_increase_nx(foriq, 1); + target_count_increase_nx(qstate, foriq, 1); } } } @@ -4116,6 +4141,7 @@ processFinished(struct module_qstate* qstate, struct iter_qstate* iq, iter_store_parentside_neg(qstate->env, &qstate->qinfo, iq->deleg_msg?iq->deleg_msg->rep: (iq->response?iq->response->rep:NULL)); + target_count_store(qstate, iq); if(!iq->response) { verbose(VERB_ALGO, "No response is set, servfail"); errinf(qstate, "(no response found at query finish)"); @@ -4531,6 +4557,7 @@ iter_clear(struct module_qstate* qstate, int id) iq = (struct iter_qstate*)qstate->minfo[id]; if(iq) { outbound_list_clear(&iq->outlist); + target_count_store(qstate, iq); if(iq->target_count && --iq->target_count[TARGET_COUNT_REF] == 0) { free(iq->target_count); if(*iq->nxns_dp) free(*iq->nxns_dp); diff --git a/util/module.h b/util/module.h index a6fa6be90..75c3675e1 100644 --- a/util/module.h +++ b/util/module.h @@ -721,6 +721,12 @@ struct module_qstate { /** whether the reply should be dropped */ int is_drop; + /** the global quota that was reached, by one of the modules. + * So that continued counting can go on from that point. */ + int global_quota_reached; + /** the global quota that a query started with, it is a subquery, + * so that calling mesh states can see the increase. */ + int global_quota_started; }; /** diff --git a/validator/validator.c b/validator/validator.c index 2099d9c19..f27ae5b42 100644 --- a/validator/validator.c +++ b/validator/validator.c @@ -517,6 +517,14 @@ generate_request(struct module_qstate* qstate, int id, uint8_t* name, /* add our blacklist to the query blacklist */ sock_list_merge(&(*newq)->blacklist, (*newq)->region, vq->chain_blacklist); + /* start its global quota counter where this one is. */ + if(qstate->global_quota_reached > + (*newq)->global_quota_reached) { + (*newq)->global_quota_started = + qstate->global_quota_reached; + (*newq)->global_quota_reached = + qstate->global_quota_reached; + } } qstate->ext_state[id] = module_wait_subquery; return 1; @@ -3576,6 +3584,11 @@ val_inform_super(struct module_qstate* qstate, int id, verbose(VERB_ALGO, "super: has no validator state"); return; } + /* Pick up the global quota limit from the subquery. */ + if(qstate->global_quota_reached > qstate->global_quota_started) { + super->global_quota_reached += qstate->global_quota_reached - + qstate->global_quota_started; + } if(vq->wait_prime_ta) { vq->wait_prime_ta = 0; process_prime_response(super, vq, id, qstate->return_rcode, From 1ad8d4c39594dcb28d636fb4922737a3640c9a65 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Wed, 22 Jul 2026 10:13:36 +0200 Subject: [PATCH 20/84] - Fix CVE-2026-50046, Possible heap use-after-free in an error path when a DoT forwarded query is jostled out. Thanks to Qifan Zhang, Palo Alto Networks, for the report. --- services/outside_network.c | 12 +++++++++++- services/outside_network.h | 2 +- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/services/outside_network.c b/services/outside_network.c index 8034ff60b..0fff18666 100644 --- a/services/outside_network.c +++ b/services/outside_network.c @@ -208,6 +208,7 @@ static void waiting_tcp_delete(struct waiting_tcp* w) { if(!w) return; + free(w->tls_auth_name); if(w->timer) comm_timer_delete(w->timer); free(w); @@ -2540,7 +2541,16 @@ pending_tcp_query(struct serviced_query* sq, sldns_buffer* packet, w->cb = callback; w->cb_arg = callback_arg; w->ssl_upstream = sq->ssl_upstream; - w->tls_auth_name = sq->tls_auth_name; + if(sq->tls_auth_name) { + w->tls_auth_name = strdup(sq->tls_auth_name); + if(!w->tls_auth_name) { + comm_timer_delete(w->timer); + free(w); + return NULL; + } + } else { + w->tls_auth_name = NULL; + } w->timeout = timeout; w->id_node.key = NULL; w->write_wait_prev = NULL; diff --git a/services/outside_network.h b/services/outside_network.h index e30ce92eb..18f86ce05 100644 --- a/services/outside_network.h +++ b/services/outside_network.h @@ -419,7 +419,7 @@ struct waiting_tcp { void* cb_arg; /** if it uses ssl upstream */ int ssl_upstream; - /** ref to the tls_auth_name from the serviced_query */ + /** owned copy of the tls_auth_name (malloced) */ char* tls_auth_name; /** the packet was involved in an error, to stop looping errors */ int error_count; From 02b16de1ae40e43a3e3804e98ab9868da33d72eb Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Wed, 22 Jul 2026 10:14:04 +0200 Subject: [PATCH 21/84] - Fix CVE-2026-50243, 'response-ip'/'rpz' can rewrite BOGUS answers instead of returning SERVFAIL. Thanks to Qifan Zhang, Palo Alto Networks, for the report. --- respip/respip.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/respip/respip.c b/respip/respip.c index ff12114de..0a7dedd07 100644 --- a/respip/respip.c +++ b/respip/respip.c @@ -1114,7 +1114,13 @@ respip_operate(struct module_qstate* qstate, enum module_ev event, int id, if((qstate->qinfo.qtype == LDNS_RR_TYPE_A || qstate->qinfo.qtype == LDNS_RR_TYPE_AAAA || qstate->qinfo.qtype == LDNS_RR_TYPE_ANY) && - qstate->return_msg && qstate->return_msg->rep) { + qstate->return_msg && qstate->return_msg->rep && + !(qstate->env->need_to_validate && + (!(qstate->query_flags & BIT_CD) + || qstate->env->cfg->ignore_cd) && + (qstate->return_msg->rep->security <= sec_status_bogus + || qstate->return_msg->rep->security == + sec_status_secure_sentinel_fail))) { struct reply_info* new_rep = qstate->return_msg->rep; struct ub_packed_rrset_key* alias_rrset = NULL; struct respip_action_info actinfo = {0, 0, 0, 0, NULL, 0, NULL}; From 3530c81e29e64ed19c612ae3dea21c8800d882e1 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Wed, 22 Jul 2026 10:14:35 +0200 Subject: [PATCH 22/84] - Fix CVE-2026-50248, BOGUS configured primary hostname accepted for XFR in auth/rpz zones. Thanks to Qifan Zhang, Palo Alto Networks, for the report. --- services/authzone.c | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/services/authzone.c b/services/authzone.c index ebcdc7e43..e7a553916 100644 --- a/services/authzone.c +++ b/services/authzone.c @@ -5745,8 +5745,7 @@ xfr_master_add_addrs(struct auth_master* m, struct ub_packed_rrset_key* rrset, /** callback for task_transfer lookup of host name, of A or AAAA */ void auth_xfer_transfer_lookup_callback(void* arg, int rcode, sldns_buffer* buf, - enum sec_status ATTR_UNUSED(sec), char* ATTR_UNUSED(why_bogus), - int ATTR_UNUSED(was_ratelimited)) + enum sec_status sec, char* why_bogus, int ATTR_UNUSED(was_ratelimited)) { struct auth_xfer* xfr = (struct auth_xfer*)arg; struct module_env* env; @@ -5759,7 +5758,16 @@ void auth_xfer_transfer_lookup_callback(void* arg, int rcode, sldns_buffer* buf, } /* process result */ - if(rcode == LDNS_RCODE_NOERROR) { + if(sec == sec_status_bogus || sec == sec_status_secure_sentinel_fail) { + if(verbosity >= VERB_OPS) { + char zname[LDNS_MAX_DOMAINLEN]; + dname_str(xfr->name, zname); + verbose(VERB_OPS, "auth zone %s: primary %s address lookup is DNSSEC bogus: %s", + zname, xfr->task_transfer->lookup_target->host, + (why_bogus?why_bogus:"")); + } + /* fall through to next-lookup / next-master */ + } else if(rcode == LDNS_RCODE_NOERROR) { uint16_t wanted_qtype = LDNS_RR_TYPE_A; struct regional* temp = env->scratch; struct query_info rq; @@ -6830,8 +6838,7 @@ xfr_probe_send_or_end(struct auth_xfer* xfr, struct module_env* env) /** callback for task_probe lookup of host name, of A or AAAA */ void auth_xfer_probe_lookup_callback(void* arg, int rcode, sldns_buffer* buf, - enum sec_status ATTR_UNUSED(sec), char* ATTR_UNUSED(why_bogus), - int ATTR_UNUSED(was_ratelimited)) + enum sec_status sec, char* why_bogus, int ATTR_UNUSED(was_ratelimited)) { struct auth_xfer* xfr = (struct auth_xfer*)arg; struct module_env* env; @@ -6844,7 +6851,16 @@ void auth_xfer_probe_lookup_callback(void* arg, int rcode, sldns_buffer* buf, } /* process result */ - if(rcode == LDNS_RCODE_NOERROR) { + if(sec == sec_status_bogus || sec == sec_status_secure_sentinel_fail) { + if(verbosity >= VERB_OPS) { + char zname[LDNS_MAX_DOMAINLEN]; + dname_str(xfr->name, zname); + verbose(VERB_OPS, "auth zone %s: primary %s address probe lookup is DNSSEC bogus: %s", + zname, xfr->task_transfer->lookup_target->host, + (why_bogus?why_bogus:"")); + } + /* fall through to next-lookup / next-master */ + } else if(rcode == LDNS_RCODE_NOERROR) { uint16_t wanted_qtype = LDNS_RR_TYPE_A; struct regional* temp = env->scratch; struct query_info rq; From e180b06298d8d39a764d3c5d4d4aca472c3a97d7 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Wed, 22 Jul 2026 10:15:02 +0200 Subject: [PATCH 23/84] - Fix CVE-2026-50251, Attacker supplied `0.0.0.0`/`::` glue triggers defensive full-cache flush. Thanks to Qifan Zhang, Palo Alto Networks, for the report. --- doc/unbound.conf.rst | 7 +++++++ iterator/iter_donotq.c | 12 ++++++++++++ testdata/dns_error_reporting.rpl | 1 + 3 files changed, 20 insertions(+) diff --git a/doc/unbound.conf.rst b/doc/unbound.conf.rst index cc01451cd..2e37e2e5d 100644 --- a/doc/unbound.conf.rst +++ b/doc/unbound.conf.rst @@ -2055,6 +2055,13 @@ These options are part of the ``server:`` section. flushing away any poison. A value of 10 million is suggested. + It is useful to add 0.0.0.0/8 and '::' to the + :ref:`do-not-query-address` list. + Otherwise they may be answered, from localhost, and the different source + makes an unwanted reply that unnecessarily ticks up. + The :ref:`do-not-query-localhost` + option includes them, the zero subnets, when it is enabled. + Default: 0 (disabled) diff --git a/iterator/iter_donotq.c b/iterator/iter_donotq.c index 40ffb45c4..7eecf1354 100644 --- a/iterator/iter_donotq.c +++ b/iterator/iter_donotq.c @@ -132,6 +132,18 @@ donotq_apply_cfg(struct iter_donotq* dq, struct config_file* cfg) if(cfg->do_ip6) { if(!donotq_str_cfg(dq, "::1")) return 0; + if(!donotq_str_cfg(dq, "::ffff:127.0.0.0/104")) + return 0; + } + /* RFC 1122 3.2.1.3 / RFC 6890 / RFC 4291 2.5.2: not valid as + * destination; on Linux these route to the local host. */ + if(!donotq_str_cfg(dq, "0.0.0.0/8")) + return 0; + if(cfg->do_ip6) { + if(!donotq_str_cfg(dq, "::")) + return 0; + if(!donotq_str_cfg(dq, "::ffff:0:0/96")) + return 0; } } addr_tree_init_parents(&dq->tree); diff --git a/testdata/dns_error_reporting.rpl b/testdata/dns_error_reporting.rpl index f1fac12a2..22175cade 100644 --- a/testdata/dns_error_reporting.rpl +++ b/testdata/dns_error_reporting.rpl @@ -12,6 +12,7 @@ server: ede: no # It is not needed for dns-error-reporting; only for clients to receive EDEs dns-error-reporting: yes do-ip6: no + do-not-query-localhost: no stub-zone: name: domain From 804cff4c152a121961b04605f75132370fc80df4 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Wed, 22 Jul 2026 10:15:31 +0200 Subject: [PATCH 24/84] - Fix CVE-2026-50252, Possible cache poisoning attack by mapping source port population per thread. Thanks to Inbal Schussheim and Amit Klein, Hebrew University, for the report. --- daemon/daemon.c | 12 +- daemon/daemon.h | 3 + daemon/worker.c | 15 +- daemon/worker.h | 8 +- libunbound/libworker.c | 13 +- libunbound/libworker.h | 3 + services/outside_network.c | 376 ++++++++++++++++++++++++++++++------- services/outside_network.h | 105 ++++++++++- testcode/fake_event.c | 21 ++- testcode/unittcpreuse.c | 276 +++++++++++++++++++++++++++ 10 files changed, 728 insertions(+), 104 deletions(-) diff --git a/daemon/daemon.c b/daemon/daemon.c index ea4e83e70..51dd51de3 100644 --- a/daemon/daemon.c +++ b/daemon/daemon.c @@ -79,6 +79,7 @@ #include "util/tcp_conn_limit.h" #include "util/edns.h" #include "services/listen_dnsport.h" +#include "services/outside_network.h" #include "services/cache/rrset.h" #include "services/cache/infra.h" #include "services/localzone.h" @@ -813,6 +814,10 @@ daemon_create_workers(struct daemon* daemon) fatal_exit("out of memory during daemon init"); numport = daemon_get_shufport(daemon, shufport); verbose(VERB_ALGO, "total of %d outgoing ports available", numport); + if(!(daemon->shared_ports = shared_ports_create(daemon->cfg->out_ifs, + daemon->cfg->num_out_ifs, daemon->cfg->do_ip4, + daemon->cfg->do_ip6, shufport, numport))) + fatal_exit("could not setup shared ports: out of memory"); #ifdef HAVE_NGTCP2 if (cfg_has_quic(daemon->cfg)) { @@ -843,10 +848,7 @@ daemon_create_workers(struct daemon* daemon) #endif } for(i=0; inum; i++) { - if(!(daemon->workers[i] = worker_create(daemon, i, - shufport+numport*i/daemon->num, - numport*(i+1)/daemon->num - numport*i/daemon->num))) - /* the above is not ports/numthr, due to rounding */ + if(!(daemon->workers[i] = worker_create(daemon, i))) fatal_exit("could not create worker"); } /* create per-worker alloc caches if not reusing existing ones. */ @@ -1204,6 +1206,8 @@ daemon_cleanup(struct daemon* daemon) if(!daemon->reuse_cache || daemon->need_to_exit) daemon_clear_allocs(daemon); daemon->num = 0; + shared_ports_delete(daemon->shared_ports); + daemon->shared_ports = NULL; #ifdef USE_DNSTAP dt_delete(daemon->dtenv); daemon->dtenv = NULL; diff --git a/daemon/daemon.h b/daemon/daemon.h index 20386d7fc..e6f099629 100644 --- a/daemon/daemon.h +++ b/daemon/daemon.h @@ -62,6 +62,7 @@ struct doq_table; struct cookie_secrets; struct fast_reload_thread; struct fast_reload_printq; +struct shared_ports; #include "dnstap/dnstap_config.h" #ifdef USE_DNSTAP @@ -97,6 +98,8 @@ struct daemon { int rc_port; /** listening ports for remote control */ struct listen_port* rc_ports; + /** the shared ports structure, with random ports numbers. */ + struct shared_ports* shared_ports; /** remote control connections management (for first worker) */ struct daemon_remote* rc; /** ssl context for listening to dnstcp over ssl */ diff --git a/daemon/worker.c b/daemon/worker.c index 1d61f8733..765fb2299 100644 --- a/daemon/worker.c +++ b/daemon/worker.c @@ -2233,23 +2233,16 @@ void worker_probe_timer_cb(void* arg) } struct worker* -worker_create(struct daemon* daemon, int id, int* ports, int n) +worker_create(struct daemon* daemon, int id) { unsigned int seed; struct worker* worker = (struct worker*)calloc(1, sizeof(struct worker)); if(!worker) return NULL; - worker->numports = n; - worker->ports = (int*)memdup(ports, sizeof(int)*n); - if(!worker->ports) { - free(worker); - return NULL; - } worker->daemon = daemon; worker->thread_num = id; if(!(worker->cmd = tube_create())) { - free(worker->ports); free(worker); return NULL; } @@ -2257,7 +2250,6 @@ worker_create(struct daemon* daemon, int id, int* ports, int n) if(!(worker->rndstate = ub_initstate(daemon->rand))) { log_err("could not init random numbers."); tube_delete(worker->cmd); - free(worker->ports); free(worker); return NULL; } @@ -2356,14 +2348,14 @@ worker_init(struct worker* worker, struct config_file *cfg, cfg->out_ifs, cfg->num_out_ifs, cfg->do_ip4, cfg->do_ip6, cfg->do_tcp?cfg->outgoing_num_tcp:0, cfg->ip_dscp, worker->daemon->env->infra_cache, worker->rndstate, - cfg->use_caps_bits_for_id, worker->ports, worker->numports, + cfg->use_caps_bits_for_id, cfg->unwanted_threshold, cfg->outgoing_tcp_mss, &worker_alloc_cleanup, worker, cfg->do_udp || cfg->udp_upstream_without_downstream, worker->daemon->connect_dot_sslctx, cfg->delay_close, cfg->tls_use_sni, dtenv, cfg->udp_connect, cfg->max_reuse_tcp_queries, cfg->tcp_reuse_timeout, - cfg->tcp_auth_query_timeout); + cfg->tcp_auth_query_timeout, worker->daemon->shared_ports); if(!worker->back) { log_err("could not create outgoing sockets"); worker_delete(worker); @@ -2514,7 +2506,6 @@ worker_delete(struct worker* worker) tube_delete(worker->cmd); comm_timer_delete(worker->stat_timer); comm_timer_delete(worker->env.probe_timer); - free(worker->ports); if(worker->thread_num == 0) { #ifdef UB_ON_WINDOWS wsvc_desetup_worker(worker); diff --git a/daemon/worker.h b/daemon/worker.h index b7bb52fd7..37f3728ef 100644 --- a/daemon/worker.h +++ b/daemon/worker.h @@ -104,10 +104,6 @@ struct worker { struct listen_dnsport* front; /** the backside outside network interface to the auth servers */ struct outside_network* back; - /** ports to be used by this worker. */ - int* ports; - /** number of ports for this worker */ - int numports; /** the signal handler */ struct comm_signal* comsig; /** commpoint to listen to commands. */ @@ -146,11 +142,9 @@ struct worker { * with backpointers only. Use worker_init on it later. * @param daemon: the daemon that this worker thread is part of. * @param id: the thread number from 0.. numthreads-1. - * @param ports: the ports it is allowed to use, array. - * @param n: the number of ports. * @return: the new worker or NULL on alloc failure. */ -struct worker* worker_create(struct daemon* daemon, int id, int* ports, int n); +struct worker* worker_create(struct daemon* daemon, int id); /** * Initialize worker. diff --git a/libunbound/libworker.c b/libunbound/libworker.c index 6e7244c03..d70527f59 100644 --- a/libunbound/libworker.c +++ b/libunbound/libworker.c @@ -105,6 +105,7 @@ libworker_delete_env(struct libworker* w) SSL_CTX_free(w->sslctx); #endif outside_network_delete(w->back); + shared_ports_delete(w->shared_ports); } /** delete libworker struct */ @@ -219,17 +220,25 @@ libworker_setup(struct ub_ctx* ctx, int is_bg, struct ub_event_base* eb) libworker_delete(w); return NULL; } + if(!(w->shared_ports = shared_ports_create(cfg->out_ifs, + cfg->num_out_ifs, cfg->do_ip4, cfg->do_ip6, ports, numports))) { + if(!w->is_bg || w->is_bg_thread) { + lock_basic_unlock(&ctx->cfglock); + } + libworker_delete(w); + return NULL; + } w->back = outside_network_create(w->base, cfg->msg_buffer_size, (size_t)cfg->outgoing_num_ports, cfg->out_ifs, cfg->num_out_ifs, cfg->do_ip4, cfg->do_ip6, cfg->do_tcp?cfg->outgoing_num_tcp:0, cfg->ip_dscp, w->env->infra_cache, w->env->rnd, cfg->use_caps_bits_for_id, - ports, numports, cfg->unwanted_threshold, + cfg->unwanted_threshold, cfg->outgoing_tcp_mss, &libworker_alloc_cleanup, w, cfg->do_udp || cfg->udp_upstream_without_downstream, w->sslctx, cfg->delay_close, cfg->tls_use_sni, NULL, cfg->udp_connect, cfg->max_reuse_tcp_queries, cfg->tcp_reuse_timeout, - cfg->tcp_auth_query_timeout); + cfg->tcp_auth_query_timeout, w->shared_ports); w->env->outnet = w->back; if(!w->is_bg || w->is_bg_thread) { lock_basic_unlock(&ctx->cfglock); diff --git a/libunbound/libworker.h b/libunbound/libworker.h index 42aa5bae3..f527cb093 100644 --- a/libunbound/libworker.h +++ b/libunbound/libworker.h @@ -60,6 +60,7 @@ struct tube; struct sldns_buffer; struct ub_event_base; struct query_info; +struct shared_ports; /** * The library-worker status structure @@ -84,6 +85,8 @@ struct libworker { struct comm_base* base; /** the backside outside network interface to the auth servers */ struct outside_network* back; + /** shared ports structure */ + struct shared_ports* shared_ports; /** random() table for this worker. */ struct ub_randstate* rndstate; /** sslcontext for SSL wrapped DNS over TCP queries */ diff --git a/services/outside_network.c b/services/outside_network.c index 0fff18666..9dfa8b4d0 100644 --- a/services/outside_network.c +++ b/services/outside_network.c @@ -1481,7 +1481,7 @@ portcomm_loweruse(struct outside_network* outnet, struct port_comm* pc) pif = pc->pif; log_assert(pif->inuse > 0); #ifndef DISABLE_EXPLICIT_PORT_RANDOMISATION - pif->avail_ports[pif->avail_total - pif->inuse] = pc->number; + shared_ports_return_port(outnet->shared_ports, pif->shpif, pc->number); #endif pif->inuse--; pif->out[pc->index] = pif->out[pif->inuse]; @@ -1695,19 +1695,19 @@ create_pending_tcp(struct outside_network* outnet, size_t bufsize) } /** setup an outgoing interface, ready address */ -static int setup_if(struct port_if* pif, const char* addrstr, - int* avail, int numavail, size_t numfd) +static int setup_if(struct port_if* pif, const char* addrstr, size_t numfd, + struct shared_ports* shp) { -#ifndef DISABLE_EXPLICIT_PORT_RANDOMISATION - pif->avail_total = numavail; - pif->avail_ports = (int*)memdup(avail, (size_t)numavail*sizeof(int)); - if(!pif->avail_ports) - return 0; -#endif if(!ipstrtoaddr(addrstr, UNBOUND_DNS_PORT, &pif->addr, &pif->addrlen) && !netblockstrtoaddr(addrstr, UNBOUND_DNS_PORT, &pif->addr, &pif->addrlen, &pif->pfxlen)) return 0; +#ifndef DISABLE_EXPLICIT_PORT_RANDOMISATION + pif->shpif = shared_ports_find_if(shp, &pif->addr, pif->addrlen, + pif->pfxlen); +#else + (void)shp; +#endif pif->maxout = (int)numfd; pif->inuse = 0; pif->out = (struct port_comm**)calloc(numfd, @@ -1721,12 +1721,12 @@ struct outside_network* outside_network_create(struct comm_base *base, size_t bufsize, size_t num_ports, char** ifs, int num_ifs, int do_ip4, int do_ip6, size_t num_tcp, int dscp, struct infra_cache* infra, - struct ub_randstate* rnd, int use_caps_for_id, int* availports, - int numavailports, size_t unwanted_threshold, int tcp_mss, + struct ub_randstate* rnd, int use_caps_for_id, + size_t unwanted_threshold, int tcp_mss, void (*unwanted_action)(void*), void* unwanted_param, int do_udp, void* sslctx, int delayclose, int tls_use_sni, struct dt_env* dtenv, int udp_connect, int max_reuse_tcp_queries, int tcp_reuse_timeout, - int tcp_auth_query_timeout) + int tcp_auth_query_timeout, struct shared_ports* shared_ports) { struct outside_network* outnet = (struct outside_network*) calloc(1, sizeof(struct outside_network)); @@ -1761,6 +1761,7 @@ outside_network_create(struct comm_base *base, size_t bufsize, outnet->do_udp = do_udp; outnet->tcp_mss = tcp_mss; outnet->ip_dscp = dscp; + outnet->shared_ports = shared_ports; #ifndef S_SPLINT_S if(delayclose) { outnet->delayclose = 1; @@ -1771,7 +1772,7 @@ outside_network_create(struct comm_base *base, size_t bufsize, if(udp_connect) { outnet->udp_connect = 1; } - if(numavailports == 0 || num_ports == 0) { + if(num_ports == 0) { log_err("no outgoing ports available"); outside_network_delete(outnet); return NULL; @@ -1832,13 +1833,13 @@ outside_network_create(struct comm_base *base, size_t bufsize, /* allocate interfaces */ if(num_ifs == 0) { if(do_ip4 && !setup_if(&outnet->ip4_ifs[0], "0.0.0.0", - availports, numavailports, num_ports)) { + num_ports, outnet->shared_ports)) { log_err("malloc failed"); outside_network_delete(outnet); return NULL; } if(do_ip6 && !setup_if(&outnet->ip6_ifs[0], "::", - availports, numavailports, num_ports)) { + num_ports, outnet->shared_ports)) { log_err("malloc failed"); outside_network_delete(outnet); return NULL; @@ -1849,7 +1850,7 @@ outside_network_create(struct comm_base *base, size_t bufsize, for(i=0; iip6_ifs[done_6], ifs[i], - availports, numavailports, num_ports)){ + num_ports, outnet->shared_ports)){ log_err("malloc failed"); outside_network_delete(outnet); return NULL; @@ -1858,7 +1859,7 @@ outside_network_create(struct comm_base *base, size_t bufsize, } if(!str_is_ip6(ifs[i]) && do_ip4) { if(!setup_if(&outnet->ip4_ifs[done_4], ifs[i], - availports, numavailports, num_ports)){ + num_ports, outnet->shared_ports)){ log_err("malloc failed"); outside_network_delete(outnet); return NULL; @@ -1936,9 +1937,6 @@ outside_network_delete(struct outside_network* outnet) comm_point_delete(pc->cp); free(pc); } -#ifndef DISABLE_EXPLICIT_PORT_RANDOMISATION - free(outnet->ip4_ifs[i].avail_ports); -#endif free(outnet->ip4_ifs[i].out); } free(outnet->ip4_ifs); @@ -1952,9 +1950,6 @@ outside_network_delete(struct outside_network* outnet) comm_point_delete(pc->cp); free(pc); } -#ifndef DISABLE_EXPLICIT_PORT_RANDOMISATION - free(outnet->ip6_ifs[i].avail_ports); -#endif free(outnet->ip6_ifs[i].out); } free(outnet->ip6_ifs); @@ -2164,7 +2159,10 @@ static int select_ifport(struct outside_network* outnet, struct pending* pend, int num_if, struct port_if* ifs) { - int my_if, my_port, fd, portno, inuse, tries=0; + int my_if, fd, portno, inuse, tries=0; +#ifndef DISABLE_EXPLICIT_PORT_RANDOMISATION + int reused; +#endif struct port_if* pif; /* randomly select interface and port */ if(num_if == 0) { @@ -2178,37 +2176,35 @@ select_ifport(struct outside_network* outnet, struct pending* pend, my_if = ub_random_max(outnet->rnd, num_if); pif = &ifs[my_if]; #ifndef DISABLE_EXPLICIT_PORT_RANDOMISATION - if(outnet->udp_connect) { - /* if we connect() we cannot reuse fds for a port */ - if(pif->inuse >= pif->avail_total) { - tries++; - if(tries < MAX_PORT_RETRY) - continue; - log_err("failed to find an open port, drop msg"); - return 0; - } - my_port = pif->inuse + ub_random_max(outnet->rnd, - pif->avail_total - pif->inuse); - } else { - my_port = ub_random_max(outnet->rnd, pif->avail_total); - if(my_port < pif->inuse) { - /* port already open */ - pend->pc = pif->out[my_port]; - verbose(VERB_ALGO, "using UDP if=%d port=%d", - my_if, pend->pc->number); - break; - } + if(!shared_ports_fetch_random(outnet->shared_ports, + pif->shpif, outnet->rnd, outnet->udp_connect, + pif->inuse, &portno, &reused)) { + tries++; + if(tries < MAX_PORT_RETRY) + continue; + log_err("failed to find an open port, drop msg"); + return 0; + } + if(reused) { + /* port already open */ + log_assert(portno < pif->inuse); + pend->pc = pif->out[portno]; + verbose(VERB_ALGO, "using UDP if=%d port=%d", + my_if, pend->pc->number); + break; } - /* try to open new port, if fails, loop to try again */ - log_assert(pif->inuse < pif->maxout); - portno = pif->avail_ports[my_port - pif->inuse]; #else - my_port = portno = 0; + portno = 0; #endif + /* try to open new port, if fails, loop to try again */ fd = udp_sockport(&pif->addr, pif->addrlen, pif->pfxlen, portno, &inuse, outnet->rnd, outnet->ip_dscp); if(fd == -1 && !inuse) { /* nonrecoverable error making socket */ +#ifndef DISABLE_EXPLICIT_PORT_RANDOMISATION + shared_ports_return_port(outnet->shared_ports, + pif->shpif, portno); +#endif return 0; } if(fd != -1) { @@ -2225,6 +2221,11 @@ select_ifport(struct outside_network* outnet, struct pending* pend, pend->addrlen); } sock_close(fd); +#ifndef DISABLE_EXPLICIT_PORT_RANDOMISATION + shared_ports_return_port( + outnet->shared_ports, + pif->shpif, portno); +#endif return 0; } } @@ -2242,14 +2243,14 @@ select_ifport(struct outside_network* outnet, struct pending* pend, /* grab port in interface */ pif->out[pif->inuse] = pend->pc; -#ifndef DISABLE_EXPLICIT_PORT_RANDOMISATION - pif->avail_ports[my_port - pif->inuse] = - pif->avail_ports[pif->avail_total-pif->inuse-1]; -#endif pif->inuse++; break; } /* failed, already in use */ +#ifndef DISABLE_EXPLICIT_PORT_RANDOMISATION + shared_ports_return_port(outnet->shared_ports, pif->shpif, + portno); +#endif verbose(VERB_QUERY, "port %d in use, trying another", portno); tries++; if(tries == MAX_PORT_RETRY) { @@ -3640,13 +3641,16 @@ fd_for_dest(struct outside_network* outnet, struct sockaddr_storage* to_addr, { struct sockaddr_storage* addr; socklen_t addrlen; - int i, try, pnum, dscp; + int i, try, dscp; struct port_if* pif; /* create fd */ dscp = outnet->ip_dscp; for(try = 0; try<1000; try++) { int port = 0; +#ifndef DISABLE_EXPLICIT_PORT_RANDOMISATION + int reused = 0; +#endif int freebind = 0; int noproto = 0; int inuse = 0; @@ -3675,16 +3679,18 @@ fd_for_dest(struct outside_network* outnet, struct sockaddr_storage* to_addr, addr = &pif->addr; addrlen = pif->addrlen; #ifndef DISABLE_EXPLICIT_PORT_RANDOMISATION - pnum = ub_random_max(outnet->rnd, pif->avail_total); - if(pnum < pif->inuse) { - /* port already open */ - port = pif->out[pnum]->number; - } else { - /* unused ports in start part of array */ - port = pif->avail_ports[pnum - pif->inuse]; + if(!shared_ports_fetch_random(outnet->shared_ports, + pif->shpif, outnet->rnd, 0, pif->inuse, + &port, &reused)) { + /* try again, perhaps another interface. */ + continue; + } + if(reused) { + log_assert(port < pif->inuse); + port = pif->out[port]->number; } #else - pnum = port = 0; + port = 0; #endif if(addr_is_ip6(to_addr, to_addrlen)) { struct sockaddr_in6 sa = *(struct sockaddr_in6*)addr; @@ -3699,6 +3705,14 @@ fd_for_dest(struct outside_network* outnet, struct sockaddr_storage* to_addr, (struct sockaddr*)addr, addrlen, 1, &inuse, &noproto, 0, 0, 0, NULL, 0, freebind, 0, dscp); } +#ifndef DISABLE_EXPLICIT_PORT_RANDOMISATION + if(!reused) { + /* Return the port to the pool, since the caller does + * not keep track of it, also have done fd, and bind. */ + shared_ports_return_port(outnet->shared_ports, + pif->shpif, port); + } +#endif if(fd != -1) { return fd; } @@ -3929,11 +3943,7 @@ if_get_mem(struct port_if* pif) { size_t s; int i; - s = sizeof(*pif) + -#ifndef DISABLE_EXPLICIT_PORT_RANDOMISATION - sizeof(int)*pif->avail_total + -#endif - sizeof(struct port_comm*)*pif->maxout; + s = sizeof(*pif) + sizeof(struct port_comm*)*pif->maxout; for(i=0; iinuse; i++) s += sizeof(*pif->out[i]) + comm_point_get_mem(pif->out[i]->cp); @@ -4021,3 +4031,237 @@ serviced_get_mem(struct serviced_query* sq) return s; } +#ifndef DISABLE_EXPLICIT_PORT_RANDOMISATION +/** Setup shared port interface */ +static int shared_ports_setup_if(struct shared_ports_if* shpif, char* str, + int* availports, int numavailports) +{ + shpif->avail_ports = (int*)memdup(availports, + (size_t)numavailports*sizeof(int)); + if(!shpif->avail_ports) + return 0; + shpif->avail_total = numavailports; + shpif->inuse = 0; + shpif->pfxlen = 0; + if(!ipstrtoaddr(str, UNBOUND_DNS_PORT, &shpif->addr, &shpif->addrlen) && + !netblockstrtoaddr(str, UNBOUND_DNS_PORT, &shpif->addr, + &shpif->addrlen, &shpif->pfxlen)) + return 0; + return 1; +} +#endif + +#ifndef DISABLE_EXPLICIT_PORT_RANDOMISATION +/** Allocate shared ports interfaces */ +static int shared_ports_alloc_ifs(struct shared_ports* shp, char** ifs, + int num_ifs, int do_ip4, int do_ip6, int* availports, + int numavailports) +{ +#ifndef INET6 + do_ip6 = 0; +#endif + calc_num46(ifs, num_ifs, do_ip4, do_ip6, + &shp->num_ip4, &shp->num_ip6); + if(shp->num_ip4 != 0) { + if(!(shp->ip4_ifs = (struct shared_ports_if*)calloc( + (size_t)shp->num_ip4, + sizeof(struct shared_ports_if)))) + return 0; + } + if(shp->num_ip6 != 0) { + if(!(shp->ip6_ifs = (struct shared_ports_if*)calloc( + (size_t)shp->num_ip6, + sizeof(struct shared_ports_if)))) + return 0; + } + if(num_ifs == 0) { + if(do_ip4 && !shared_ports_setup_if(&shp->ip4_ifs[0], + "0.0.0.0", availports, numavailports)) + return 0; + if(do_ip6 && !shared_ports_setup_if(&shp->ip6_ifs[0], + "::", availports, numavailports)) + return 0; + } else { + size_t done_4 = 0, done_6 = 0; + int i; + for(i=0; iip6_ifs[done_6], + ifs[i], availports, numavailports)) + return 0; + done_6++; + } + if(!str_is_ip6(ifs[i]) && do_ip4) { + if(!shared_ports_setup_if(&shp->ip4_ifs[done_4], + ifs[i], availports, numavailports)) + return 0; + done_4++; + } + } + } + return 1; +} +#endif + +struct shared_ports* shared_ports_create(char** ifs, int num_ifs, int do_ip4, + int do_ip6, int* availports, int numavailports) +{ + struct shared_ports* shp = calloc(1, sizeof(*shp)); + if(!shp) { + log_err("malloc failed"); + return NULL; + } + lock_basic_init(&shp->lock); + lock_protect(&shp->lock, shp, sizeof(*shp)); + +#ifndef DISABLE_EXPLICIT_PORT_RANDOMISATION + /* Allocate interfaces */ + if(!shared_ports_alloc_ifs(shp, ifs, num_ifs, do_ip4, do_ip6, + availports, numavailports)) { + log_err("malloc failed"); + shared_ports_delete(shp); + return NULL; + } +#else + (void)ifs; (void)num_ifs; (void)do_ip4; (void)do_ip6; + (void)availports; (void)numavailports; +#endif + return shp; +} + +#ifndef DISABLE_EXPLICIT_PORT_RANDOMISATION +/** Delete shared ports interface structure */ +static void shared_ports_if_delete(struct shared_ports_if* shpif) +{ + if(!shpif) + return; + free(shpif->avail_ports); +} +#endif + +void shared_ports_delete(struct shared_ports* shp) +{ +#ifndef DISABLE_EXPLICIT_PORT_RANDOMISATION + int i; +#endif + if(!shp) + return; + lock_basic_destroy(&shp->lock); +#ifndef DISABLE_EXPLICIT_PORT_RANDOMISATION + for(i=0; inum_ip4; i++) { + shared_ports_if_delete(&shp->ip4_ifs[i]); + } + free(shp->ip4_ifs); + for(i=0; inum_ip6; i++) { + shared_ports_if_delete(&shp->ip6_ifs[i]); + } + free(shp->ip6_ifs); +#endif + free(shp); +} + +struct shared_ports_if* shared_ports_find_if(struct shared_ports* shp, + struct sockaddr_storage* addr, socklen_t addrlen, int pfxlen) +{ +#ifndef DISABLE_EXPLICIT_PORT_RANDOMISATION + struct shared_ports_if* ret, *ifs = NULL; + int i, num_ifs = 0; + lock_basic_lock(&shp->lock); + if(addr_is_ip6(addr, addrlen)) { + ifs = shp->ip6_ifs; + num_ifs = shp->num_ip6; + } else { + ifs = shp->ip4_ifs; + num_ifs = shp->num_ip4; + } + for(i=0; ilock); + return ret; + } + } + lock_basic_unlock(&shp->lock); + return NULL; +#else + (void)shp; (void)addr; (void)addrlen; (void)pfxlen; + return NULL; +#endif +} + +int shared_ports_fetch_random(struct shared_ports* shp, + struct shared_ports_if* shpif, struct ub_randstate* rnd, + int udp_connect, int reusenum, int* port, int* reused) +{ +#ifndef DISABLE_EXPLICIT_PORT_RANDOMISATION + int portno = 0, my_port = 0; + if(!shpif) + return 0; + lock_basic_lock(&shp->lock); + if(udp_connect) { + /* if we connect() we cannot reuse fds for a port. */ + if(shpif->inuse >= shpif->avail_total) { + lock_basic_unlock(&shp->lock); + return 0; + } + my_port = ub_random_max(rnd, + shpif->avail_total - shpif->inuse); + } else { + /* select from free ports and open ports on this thread. */ + if(shpif->inuse >= shpif->avail_total) { + lock_basic_unlock(&shp->lock); + if(reusenum == 0) { + return 0; + } + my_port = ub_random_max(rnd, reusenum); + *port = my_port; + *reused = 1; + return 1; + } + my_port = ub_random_max(rnd, shpif->avail_total - shpif->inuse + + reusenum); + if(my_port < reusenum) { + /* port already open */ + lock_basic_unlock(&shp->lock); + *port = my_port; + *reused = 1; + return 1; + } + my_port -= reusenum; + } + log_assert(shpif->inuse < shpif->avail_total); + log_assert(my_port >= 0 && my_port < shpif->avail_total); + portno = shpif->avail_ports[my_port]; + shpif->avail_ports[my_port] = + shpif->avail_ports[shpif->avail_total-shpif->inuse-1]; + shpif->inuse++; + lock_basic_unlock(&shp->lock); + *port = portno; + *reused = 0; + return 1; +#else + (void)shp; (void)shpif; (void)rnd; (void)udp_connect; + (void)reusenum; + *port = 0; + *reused = 0; + return 1; +#endif +} + +void shared_ports_return_port(struct shared_ports* shp, + struct shared_ports_if* shpif, int port) +{ +#ifndef DISABLE_EXPLICIT_PORT_RANDOMISATION + if(!shpif) + return; + lock_basic_lock(&shp->lock); + log_assert(shpif->inuse > 0); + shpif->avail_ports[shpif->avail_total - shpif->inuse] = port; + shpif->inuse--; + lock_basic_unlock(&shp->lock); +#else + (void)shp; (void)shpif; (void)port; +#endif +} diff --git a/services/outside_network.h b/services/outside_network.h index 18f86ce05..8841c607d 100644 --- a/services/outside_network.h +++ b/services/outside_network.h @@ -70,6 +70,8 @@ struct module_env; struct module_qstate; struct query_info; struct config_file; +struct shared_ports; +struct shared_ports_if; /** * Send queries to outside servers and wait for answers from servers. @@ -119,6 +121,9 @@ struct outside_network { int udp_connect; /** number of udp packets sent. */ size_t num_udp_outgoing; + /** the shared ports structure, with random ports numbers. + * This is a reference to the member in the daemon structure. */ + struct shared_ports* shared_ports; /** array of outgoing IP4 interfaces */ struct port_if* ip4_ifs; @@ -211,11 +216,8 @@ struct port_if { int pfxlen; #ifndef DISABLE_EXPLICIT_PORT_RANDOMISATION - /** the available ports array. These are unused. - * Only the first total-inuse part is filled. */ - int* avail_ports; - /** the total number of available ports (size of the array) */ - int avail_total; + /** the shared port numbers for this interface. */ + struct shared_ports_if* shpif; #endif /** array of the commpoints currently in use. @@ -245,6 +247,42 @@ struct port_comm { struct comm_point* cp; }; +/** + * Shared ports, the list of ports shared across threads + */ +struct shared_ports { + /** mutex on the ports */ + lock_basic_type lock; + /** array of IP4 interfaces */ + struct shared_ports_if* ip4_ifs; + /** number of outgoing IP4 interfaces */ + int num_ip4; + /** array of IP6 interfaces */ + struct shared_ports_if* ip6_ifs; + /** number of outgoing IP6 interfaces */ + int num_ip6; +}; + +/** + * Shared ports for an interface. + */ +struct shared_ports_if { + /** address ready to allocate new socket (except port no). */ + struct sockaddr_storage addr; + /** length of addr field */ + socklen_t addrlen; + /** if a netblock, the prefix */ + int pfxlen; + + /** the available ports array. These are unused. + * Only the first total-inuse part is filled. */ + int* avail_ports; + /** the total number of available ports (size of the array) */ + int avail_total; + /** the number in use. */ + int inuse; +}; + /** * Reuse TCP connection, still open can be used again. */ @@ -551,8 +589,6 @@ struct serviced_query { * @param infra: pointer to infra cached used for serviced queries. * @param rnd: stored to create random numbers for serviced queries. * @param use_caps_for_id: enable to use 0x20 bits to encode id randomness. - * @param availports: array of available ports. - * @param numavailports: number of available ports in array. * @param unwanted_threshold: when to take defensive action. * @param unwanted_action: the action to take. * @param unwanted_param: user parameter to action. @@ -567,17 +603,18 @@ struct serviced_query { * @param max_reuse_tcp_queries: max number of queries on a reuse connection. * @param tcp_reuse_timeout: timeout for REUSE entries in milliseconds. * @param tcp_auth_query_timeout: timeout in milliseconds for TCP queries to auth servers. + * @param shared_ports: the shared_ports structure. * @return: the new structure (with no pending answers) or NULL on error. */ struct outside_network* outside_network_create(struct comm_base* base, size_t bufsize, size_t num_ports, char** ifs, int num_ifs, int do_ip4, int do_ip6, size_t num_tcp, int dscp, struct infra_cache* infra, - struct ub_randstate* rnd, int use_caps_for_id, int* availports, - int numavailports, size_t unwanted_threshold, int tcp_mss, + struct ub_randstate* rnd, int use_caps_for_id, + size_t unwanted_threshold, int tcp_mss, void (*unwanted_action)(void*), void* unwanted_param, int do_udp, void* sslctx, int delayclose, int tls_use_sni, struct dt_env *dtenv, int udp_connect, int max_reuse_tcp_queries, int tcp_reuse_timeout, - int tcp_auth_query_timeout); + int tcp_auth_query_timeout, struct shared_ports* shared_ports); /** * Delete outside_network structure. @@ -819,6 +856,54 @@ struct comm_point* outnet_comm_point_for_http(struct outside_network* outnet, /** connect tcp connection to addr, 0 on failure */ int outnet_tcp_connect(int s, struct sockaddr_storage* addr, socklen_t addrlen); +/** + * Create new shared ports structure. + * @param ifs: interface names (or NULL for default interface). + * These interfaces must be able to access all authoritative servers. + * @param num_ifs: number of names in array ifs. + * @param do_ip4: service IP4. + * @param do_ip6: service IP6. + * @param availports: array of available ports. + * @param numavailports: number of available ports in array. + * @return new, or NULL on failure. + */ +struct shared_ports* shared_ports_create(char** ifs, int num_ifs, int do_ip4, + int do_ip6, int* availports, int numavailports); + +/** + * Delete shared ports structure. + * @param shp: shared ports structure. + */ +void shared_ports_delete(struct shared_ports* shp); + +/** Find interface in shared ports. */ +struct shared_ports_if* shared_ports_find_if(struct shared_ports* shp, + struct sockaddr_storage* addr, socklen_t addrlen, int pfxlen); + +/** + * Get a shared port from the list of random ports. + * @param shp: shared ports structure. + * @param shpif: the shared ports interface. + * @param rnd: used to make random numbers. + * @param udp_connect: set to true if no reuse is possible. + * @param reusenum: number of ports that can be reused (already open). + * @param port: the port number is returned. + * @param reused: if the port numer is reused, returned. + * @return false on failure. That can mean no more free ports to use. + */ +int shared_ports_fetch_random(struct shared_ports* shp, + struct shared_ports_if* shpif, struct ub_randstate* rnd, + int udp_connect, int reusenum, int* port, int* reused); + +/** + * Return a shared port to the list of random ports. + * @param shp: shared ports structure. + * @param shpif: the shared ports interface. + * @param port: port number to return to be used again. + */ +void shared_ports_return_port(struct shared_ports* shp, + struct shared_ports_if* shpif, int port); + /** callback for incoming udp answers from the network */ int outnet_udp_cb(struct comm_point* c, void* arg, int error, struct comm_reply *reply_info); diff --git a/testcode/fake_event.c b/testcode/fake_event.c index b127d0df8..4ca357770 100644 --- a/testcode/fake_event.c +++ b/testcode/fake_event.c @@ -1126,15 +1126,16 @@ outside_network_create(struct comm_base* base, size_t bufsize, int ATTR_UNUSED(dscp), struct infra_cache* infra, struct ub_randstate* ATTR_UNUSED(rnd), - int ATTR_UNUSED(use_caps_for_id), int* ATTR_UNUSED(availports), - int ATTR_UNUSED(numavailports), size_t ATTR_UNUSED(unwanted_threshold), + int ATTR_UNUSED(use_caps_for_id), + size_t ATTR_UNUSED(unwanted_threshold), int ATTR_UNUSED(outgoing_tcp_mss), void (*unwanted_action)(void*), void* ATTR_UNUSED(unwanted_param), int ATTR_UNUSED(do_udp), void* ATTR_UNUSED(sslctx), int ATTR_UNUSED(delayclose), int ATTR_UNUSED(tls_use_sni), struct dt_env* ATTR_UNUSED(dtenv), int ATTR_UNUSED(udp_connect), int ATTR_UNUSED(max_reuse_tcp_queries), int ATTR_UNUSED(tcp_reuse_timeout), - int ATTR_UNUSED(tcp_auth_query_timeout)) + int ATTR_UNUSED(tcp_auth_query_timeout), + struct shared_ports* ATTR_UNUSED(shared_ports)) { struct replay_runtime* runtime = (struct replay_runtime*)base; struct outside_network* outnet = calloc(1, @@ -1980,6 +1981,20 @@ int outnet_tcp_connect(int ATTR_UNUSED(s), struct sockaddr_storage* ATTR_UNUSED( return 0; } +struct shared_ports* shared_ports_create(char** ATTR_UNUSED(ifs), + int ATTR_UNUSED(num_ifs), int ATTR_UNUSED(do_ip4), + int ATTR_UNUSED(do_ip6), int* ATTR_UNUSED(availports), + int ATTR_UNUSED(numavailports)) +{ + return calloc(1, sizeof(struct shared_ports)); +} + +void shared_ports_delete(struct shared_ports* shp) +{ + if(!shp) return; + free(shp); +} + int tcp_req_info_add_meshstate(struct tcp_req_info* ATTR_UNUSED(req), struct mesh_area* ATTR_UNUSED(mesh), struct mesh_state* ATTR_UNUSED(m)) { diff --git a/testcode/unittcpreuse.c b/testcode/unittcpreuse.c index 5f45a4b45..ce62e3325 100644 --- a/testcode/unittcpreuse.c +++ b/testcode/unittcpreuse.c @@ -41,6 +41,7 @@ #include "config.h" #include "testcode/unitmain.h" #include "util/log.h" +#include "util/net_help.h" #include "util/random.h" #include "services/outside_network.h" @@ -479,6 +480,278 @@ static void reuse_write_wait_test(void) check_reuse_write_wait_removal(1, &reuse, store, 0, 1); } +static void shared_port_test_ifs(void) +{ + struct shared_ports* shp; + struct shared_ports_if* shpif; + char* ifs[] = {"1.2.3.4", "1.2.3.5", "::1:2", "::1:3"}; + int availports[] = {1, 2, 3, 4}; + struct sockaddr_storage addr; + socklen_t addrlen; + + shp = shared_ports_create(ifs, 4, 1, 1, availports, 4); + unit_assert(shp); + + if(!ipstrtoaddr("1.2.3.4", UNBOUND_DNS_PORT, &addr, &addrlen)) + log_err("could not parse"); + shpif = shared_ports_find_if(shp, &addr, addrlen, 0); + unit_assert(shpif); + + if(!ipstrtoaddr("1.2.3.5", UNBOUND_DNS_PORT, &addr, &addrlen)) + log_err("could not parse"); + shpif = shared_ports_find_if(shp, &addr, addrlen, 0); + unit_assert(shpif); + + if(!ipstrtoaddr("::1:2", UNBOUND_DNS_PORT, &addr, &addrlen)) + log_err("could not parse"); + shpif = shared_ports_find_if(shp, &addr, addrlen, 0); + unit_assert(shpif); + + if(!ipstrtoaddr("::1:3", UNBOUND_DNS_PORT, &addr, &addrlen)) + log_err("could not parse"); + shpif = shared_ports_find_if(shp, &addr, addrlen, 0); + unit_assert(shpif); + + shared_ports_delete(shp); +} + +/** See if a port is on the shared_ports ports list */ +static int +pif_list_contains(struct shared_ports_if* shpif, int item) +{ + int i; + unit_assert(shpif->inuse >= 0 && shpif->inuse <= shpif->avail_total); + for(i=0; i< shpif->avail_total - shpif->inuse; i++) { + if(shpif->avail_ports[i] == item) + return 1; + } + return 0; +} + +/** See if a number of ports are on the shared_ports list */ +static int +pif_list_contains_items(struct shared_ports_if* shpif, int item1, + int item2, int item3, int item4) +{ + if(item1 != -1 && !pif_list_contains(shpif, item1)) + return 0; + if(item2 != -1 && !pif_list_contains(shpif, item2)) + return 0; + if(item3 != -1 && !pif_list_contains(shpif, item3)) + return 0; + if(item4 != -1 && !pif_list_contains(shpif, item4)) + return 0; + return 1; +} + +static void shared_port_test_port(void) +{ + struct shared_ports* shp; + struct shared_ports_if* shpif; + char* ifs[] = {"1.2.3.4", "1.2.3.5"}; + int availports[] = {1, 2, 3, 4}; + struct sockaddr_storage addr; + socklen_t addrlen; + int p1, p2, p3, reused; + struct ub_randstate* rnd; + + rnd = ub_initstate(NULL); + unit_assert(rnd); + + shp = shared_ports_create(ifs, 2, 1, 1, availports, 4); + unit_assert(shp); + + if(!ipstrtoaddr("1.2.3.4", UNBOUND_DNS_PORT, &addr, &addrlen)) + log_err("could not parse"); + shpif = shared_ports_find_if(shp, &addr, addrlen, 0); + unit_assert(shpif); + + unit_assert(shpif->avail_total == 4); + unit_assert(shpif->inuse == 0); + unit_assert(pif_list_contains_items(shpif, 1, 2, 3, 4)); + + if(!shared_ports_fetch_random(shp, shpif, rnd, + 0, 0, &p1, &reused)) { + unit_assert(0); /* should succeed */ + } + unit_assert(reused == 0); + unit_assert(p1 != 0); + unit_assert(!pif_list_contains(shpif, p1)); + if(p1 != 1) unit_assert(pif_list_contains(shpif, 1)); + if(p1 != 2) unit_assert(pif_list_contains(shpif, 2)); + if(p1 != 3) unit_assert(pif_list_contains(shpif, 3)); + if(p1 != 4) unit_assert(pif_list_contains(shpif, 4)); + unit_assert(shpif->avail_total == 4); + unit_assert(shpif->inuse == 1); + + shared_ports_return_port(shp, shpif, p1); + unit_assert(shpif->avail_total == 4); + unit_assert(shpif->inuse == 0); + unit_assert(pif_list_contains_items(shpif, 1, 2, 3, 4)); + + /* pick up two items */ + if(!shared_ports_fetch_random(shp, shpif, rnd, + 0, 0, &p1, &reused)) { + unit_assert(0); /* should succeed */ + } + unit_assert(reused == 0); + unit_assert(p1 != 0); + if(!shared_ports_fetch_random(shp, shpif, rnd, + 0, 0, &p2, &reused)) { + unit_assert(0); /* should succeed */ + } + unit_assert(reused == 0); + unit_assert(p2 != 0); + unit_assert(!pif_list_contains(shpif, p1)); + unit_assert(!pif_list_contains(shpif, p2)); + if(p1 != 1 && p2 != 1) unit_assert(pif_list_contains(shpif, 1)); + if(p1 != 2 && p2 != 2) unit_assert(pif_list_contains(shpif, 2)); + if(p1 != 3 && p2 != 3) unit_assert(pif_list_contains(shpif, 3)); + if(p1 != 4 && p2 != 4) unit_assert(pif_list_contains(shpif, 4)); + unit_assert(shpif->avail_total == 4); + unit_assert(shpif->inuse == 2); + + shared_ports_return_port(shp, shpif, p1); + unit_assert(pif_list_contains(shpif, p1)); + unit_assert(shpif->avail_total == 4); + unit_assert(shpif->inuse == 1); + + shared_ports_return_port(shp, shpif, p2); + unit_assert(pif_list_contains(shpif, p2)); + unit_assert(shpif->avail_total == 4); + unit_assert(shpif->inuse == 0); + unit_assert(pif_list_contains_items(shpif, 1, 2, 3, 4)); + + /* pick up three items */ + if(!shared_ports_fetch_random(shp, shpif, rnd, + 0, 0, &p1, &reused)) { + unit_assert(0); /* should succeed */ + } + unit_assert(reused == 0); + unit_assert(p1 != 0); + if(!shared_ports_fetch_random(shp, shpif, rnd, + 0, 0, &p2, &reused)) { + unit_assert(0); /* should succeed */ + } + unit_assert(reused == 0); + unit_assert(p2 != 0); + if(!shared_ports_fetch_random(shp, shpif, rnd, + 0, 0, &p3, &reused)) { + unit_assert(0); /* should succeed */ + } + unit_assert(reused == 0); + unit_assert(p3 != 0); + unit_assert(!pif_list_contains(shpif, p1)); + unit_assert(!pif_list_contains(shpif, p2)); + unit_assert(!pif_list_contains(shpif, p3)); + if(p1 != 1 && p2 != 1 && p3 != 1) + unit_assert(pif_list_contains(shpif, 1)); + if(p1 != 2 && p2 != 2 && p3 != 2) + unit_assert(pif_list_contains(shpif, 2)); + if(p1 != 3 && p2 != 3 && p3 != 3) + unit_assert(pif_list_contains(shpif, 3)); + if(p1 != 4 && p2 != 4 && p3 != 4) + unit_assert(pif_list_contains(shpif, 4)); + unit_assert(shpif->avail_total == 4); + unit_assert(shpif->inuse == 3); + + shared_ports_return_port(shp, shpif, p1); + unit_assert(pif_list_contains(shpif, p1)); + unit_assert(shpif->avail_total == 4); + unit_assert(shpif->inuse == 2); + + shared_ports_return_port(shp, shpif, p2); + unit_assert(pif_list_contains(shpif, p2)); + unit_assert(shpif->avail_total == 4); + unit_assert(shpif->inuse == 1); + + shared_ports_return_port(shp, shpif, p3); + unit_assert(pif_list_contains(shpif, p3)); + unit_assert(shpif->avail_total == 4); + unit_assert(shpif->inuse == 0); + unit_assert(pif_list_contains_items(shpif, 1, 2, 3, 4)); + + /* pick up all four items */ + if(!shared_ports_fetch_random(shp, shpif, rnd, + 0, 0, &p1, &reused)) { + unit_assert(0); /* should succeed */ + } + unit_assert(reused == 0); + unit_assert(p1 != 0); + + if(!shared_ports_fetch_random(shp, shpif, rnd, + 0, 0, &p1, &reused)) { + unit_assert(0); /* should succeed */ + } + unit_assert(reused == 0); + unit_assert(p1 != 0); + + if(!shared_ports_fetch_random(shp, shpif, rnd, + 0, 0, &p1, &reused)) { + unit_assert(0); /* should succeed */ + } + unit_assert(reused == 0); + unit_assert(p1 != 0); + + if(!shared_ports_fetch_random(shp, shpif, rnd, + 0, 0, &p1, &reused)) { + unit_assert(0); /* should succeed */ + } + unit_assert(reused == 0); + unit_assert(p1 != 0); + unit_assert(!pif_list_contains(shpif, 1)); + unit_assert(!pif_list_contains(shpif, 2)); + unit_assert(!pif_list_contains(shpif, 3)); + unit_assert(!pif_list_contains(shpif, 4)); + unit_assert(shpif->avail_total == 4); + unit_assert(shpif->inuse == 4); + + /* more fetches fail, it is fully inuse. */ + unit_assert(!shared_ports_fetch_random(shp, shpif, rnd, 0, 0, &p2, + &reused)); + unit_assert(!shared_ports_fetch_random(shp, shpif, rnd, 0, 0, &p3, + &reused)); + unit_assert(shpif->avail_total == 4); + unit_assert(shpif->inuse == 4); + + /* reuse is then always the case */ + if(!shared_ports_fetch_random(shp, shpif, rnd, + 0 /* can reuse */, 4 /* reusenum */, &p1, &reused)) { + unit_assert(0); /* should succeed */ + } + unit_assert(reused == 1); + unit_assert(p1 >= 0 && p1 < 4 /* reusenum */); + + if(!shared_ports_fetch_random(shp, shpif, rnd, + 0 /* can reuse */, 4 /* reusenum */, &p1, &reused)) { + unit_assert(0); /* should succeed */ + } + unit_assert(reused == 1); + unit_assert(p1 >= 0 && p1 < 4 /* reusenum */); + + /* return all the ports */ + shared_ports_return_port(shp, shpif, 1); + unit_assert(pif_list_contains(shpif, 1)); + unit_assert(shpif->avail_total == 4); + unit_assert(shpif->inuse == 3); + shared_ports_return_port(shp, shpif, 2); + unit_assert(pif_list_contains(shpif, 2)); + unit_assert(shpif->avail_total == 4); + unit_assert(shpif->inuse == 2); + shared_ports_return_port(shp, shpif, 3); + unit_assert(pif_list_contains(shpif, 3)); + unit_assert(shpif->avail_total == 4); + unit_assert(shpif->inuse == 1); + shared_ports_return_port(shp, shpif, 4); + unit_assert(pif_list_contains(shpif, 4)); + unit_assert(shpif->avail_total == 4); + unit_assert(shpif->inuse == 0); + unit_assert(pif_list_contains_items(shpif, 1, 2, 3, 4)); + + shared_ports_delete(shp); + ub_randfree(rnd); +} + void tcpreuse_test(void) { unit_show_feature("tcp_reuse"); @@ -486,4 +759,7 @@ void tcpreuse_test(void) tcp_reuse_tree_list_test(); waiting_tcp_list_test(); reuse_write_wait_test(); + unit_show_feature("shared_ports"); + shared_port_test_ifs(); + shared_port_test_port(); } From 8c702de175cb687d9645603ad3e8dc7c08a925e8 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Wed, 22 Jul 2026 10:16:03 +0200 Subject: [PATCH 25/84] - Fix CVE-2026-52863, Memory corruption could lead to crash and denial of service. Thanks to Qifan Zhang, Palo Alto Networks, for the report. --- services/mesh.c | 8 +++++-- services/mesh.h | 4 ++++ testcode/unitmain.c | 56 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 66 insertions(+), 2 deletions(-) diff --git a/services/mesh.c b/services/mesh.c index f06c1cb9d..cd78d1c29 100644 --- a/services/mesh.c +++ b/services/mesh.c @@ -933,8 +933,7 @@ cfg_region_strlist_copy(struct regional* region, struct config_strlist* list) return result; } -/** Copy the client info to the query region. */ -static struct respip_client_info* +struct respip_client_info* mesh_copy_client_info(struct regional* region, struct respip_client_info* cinfo) { size_t i; @@ -979,6 +978,11 @@ mesh_copy_client_info(struct regional* region, struct respip_client_info* cinfo) cinfo->view->name); if(!client_info->view_name) return NULL; + } else if(cinfo->view_name) { + client_info->view_name = regional_strdup(region, + cinfo->view_name); + if(!client_info->view_name) + return NULL; } return client_info; } diff --git a/services/mesh.h b/services/mesh.h index 352260e26..6ea63d098 100644 --- a/services/mesh.h +++ b/services/mesh.h @@ -738,4 +738,8 @@ void mesh_respond_serve_expired(struct mesh_state* mstate); void mesh_remove_callback(struct mesh_area* mesh, struct query_info* qinfo, uint16_t qflags, mesh_cb_func_type cb, void* cb_arg); +/** Copy the client info to the query region. */ +struct respip_client_info* mesh_copy_client_info(struct regional* region, + struct respip_client_info* cinfo); + #endif /* SERVICES_MESH_H */ diff --git a/testcode/unitmain.c b/testcode/unitmain.c index 4bc756a07..62f37375f 100644 --- a/testcode/unitmain.c +++ b/testcode/unitmain.c @@ -1282,6 +1282,61 @@ static void localzone_test(void) localzone_parents_test(); } +#include "services/mesh.h" +/** mesh unit tests */ +static void mesh_test(void) +{ + struct regional* r2, *r3; + struct respip_client_info* c1, *c2, *c3; + unit_show_func("services/mesh.c", "mesh_copy_client_info"); + r2 = regional_create(); + r3 = regional_create(); + if(!r2 || !r3) fatal_exit("out of memory"); + + c1 = calloc(1, sizeof(*c1)); + if(!c1) fatal_exit("out of memory"); + c1->view = calloc(1, sizeof(*c1->view)); + if(!c1->view) fatal_exit("out of memory"); + c1->view->name = strdup("view1"); + if(!c1->view->name) fatal_exit("out of memory"); + + c2 = mesh_copy_client_info(r2, c1); + if(!c2) fatal_exit("out of memory"); + c3 = mesh_copy_client_info(r3, c2); + if(!c3) fatal_exit("out of memory"); + + unit_assert(strcmp(c1->view->name, c2->view_name) == 0); + unit_assert(strcmp(c1->view->name, c3->view_name) == 0); + + /* make sure that the c3 view_name is in the r3 region. */ + unit_assert(r3->next == NULL); /* only the first chunk present atm */ + if(strlen(c3->view_name) >= r3->large_object_size) { + char* a = r3->large_list; + int found = 0; + while(a) { + if(strcmp(c3->view_name, + a + /* ALIGNEMENT */ sizeof(uint64_t)) == 0) { + found = 1; + break; + } + a = *(char**)a; + } + unit_assert(found == 1); + } else { + /* The allocation is expected in the r3 region first chunk */ + unit_assert((uint8_t*)c3->view_name < ((uint8_t*)r3)+r3->first_size); + } + + regional_destroy(r2); + /* ASAN should complain for the freed access below */ + unit_assert(strcmp(c1->view->name, c3->view_name) == 0); + + regional_destroy(r3); + free(c1->view->name); + free(c1->view); + free(c1); +} + void unit_show_func(const char* file, const char* func) { printf("test %s:%s\n", file, func); @@ -1356,6 +1411,7 @@ main(int argc, char* argv[]) msgparse_test(); edns_ede_answer_encode_test(); localzone_test(); + mesh_test(); #ifdef CLIENT_SUBNET ecs_test(); #endif /* CLIENT_SUBNET */ From 8a15ffee620bce05fbfd2c69b0d4c31c10a02431 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Wed, 22 Jul 2026 10:16:42 +0200 Subject: [PATCH 26/84] - Fix CVE-2026-54478, DNS Cookie bypass when combined with proxy-protocol use. Thanks to Qifan Zhang, Palo Alto Networks, for the report. --- util/data/msgparse.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/util/data/msgparse.c b/util/data/msgparse.c index 9239f8fe3..7b2de102e 100644 --- a/util/data/msgparse.c +++ b/util/data/msgparse.c @@ -1068,13 +1068,13 @@ parse_edns_options_from_query(uint8_t* rdata_ptr, size_t rdata_len, * purposes. It will be overwritten if (re)creation * is needed. */ - if(repinfo->remote_addr.ss_family == AF_INET) { + if(repinfo->client_addr.ss_family == AF_INET) { memcpy(server_cookie + 16, - &((struct sockaddr_in*)&repinfo->remote_addr)->sin_addr, 4); + &((struct sockaddr_in*)&repinfo->client_addr)->sin_addr, 4); } else { cookie_is_v4 = 0; memcpy(server_cookie + 16, - &((struct sockaddr_in6*)&repinfo->remote_addr)->sin6_addr, 16); + &((struct sockaddr_in6*)&repinfo->client_addr)->sin6_addr, 16); } if(cfg->cookie_secret_file && From c29ff70f6aa9bb2f02e5f21001832f2b4791bd76 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Wed, 22 Jul 2026 10:17:10 +0200 Subject: [PATCH 27/84] - Fix CVE-2026-55708, Privacy/configuration issue when adding local data in views through 'unbound-control'. Thanks to Qifan Zhang, Palo Alto Networks, for the report. --- daemon/remote.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/daemon/remote.c b/daemon/remote.c index 1eaf90016..61beb7c2f 100644 --- a/daemon/remote.c +++ b/daemon/remote.c @@ -1658,6 +1658,14 @@ do_view_data_add(RES* ssl, struct worker* worker, char* arg) ssl_printf(ssl,"error out of memory\n"); return; } + if(!v->isfirst) { + /* Global local-zone is not used for this view, + * therefore add defaults to this view-specific + * local-zone. */ + struct config_file lz_cfg; + memset(&lz_cfg, 0, sizeof(lz_cfg)); + local_zone_enter_defaults(v->local_zones, &lz_cfg); + } } do_data_add(ssl, v->local_zones, arg2); lock_rw_unlock(&v->lock); @@ -1683,6 +1691,14 @@ do_view_datas_add(struct daemon_remote* rc, RES* ssl, struct worker* worker, ssl_printf(ssl,"error out of memory\n"); return; } + if(!v->isfirst) { + /* Global local-zone is not used for this view, + * therefore add defaults to this view-specific + * local-zone. */ + struct config_file lz_cfg; + memset(&lz_cfg, 0, sizeof(lz_cfg)); + local_zone_enter_defaults(v->local_zones, &lz_cfg); + } } /* put the view name in the command buf */ (void)snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "%s ", arg); From 2ce2ca36912644d2dbf97249a41494a9e2500fc3 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Wed, 22 Jul 2026 10:17:32 +0200 Subject: [PATCH 28/84] - Fix CVE-2026-55717, 'serve-expired-client-timeout' and 'response-ip' CNAME redirect could lead to a crash. Thanks to Qifan Zhang, Palo Alto Networks, for the report. In addition, thanks to Xin Wang, Jiapeng Li, and Jiajia Liu, Northwestern Polytechnical University, for also reporting this issue. --- services/localzone.h | 2 +- services/mesh.c | 15 ++++++++++----- util/data/packed_rrset.c | 1 + 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/services/localzone.h b/services/localzone.h index 76c011836..e3fb0afe3 100644 --- a/services/localzone.h +++ b/services/localzone.h @@ -573,7 +573,7 @@ enum respip_action { respip_always_nxdomain = local_zone_always_nxdomain, /** answer with nodata response */ respip_always_nodata = local_zone_always_nodata, - /** answer with nodata response */ + /** drop query */ respip_always_deny = local_zone_always_deny, /** RPZ: truncate answer in order to force switch to tcp */ respip_truncate = local_zone_truncate, diff --git a/services/mesh.c b/services/mesh.c index cd78d1c29..8ce3dcee7 100644 --- a/services/mesh.c +++ b/services/mesh.c @@ -2454,9 +2454,10 @@ apply_respip_action(struct module_qstate* qstate, /* xxx_deny actions mean dropping the reply, unless the original reply * was redirected to response-ip data. */ - if((actinfo->action == respip_deny || + if(actinfo->action == respip_always_deny || + ((actinfo->action == respip_deny || actinfo->action == respip_inform_deny) && - *encode_repp == rep) + *encode_repp == rep)) *encode_repp = NULL; return 1; @@ -2521,12 +2522,15 @@ mesh_serve_expired_callback(void* arg) qstate->client_info, &actinfo, msg->rep, &alias_rrset, &encode_rep, qstate->env->auth_zones)) { return; - } else if(partial_rep && - !respip_merge_cname(partial_rep, &qstate->qinfo, msg->rep, + } else if(partial_rep) { + if(!respip_merge_cname(partial_rep, &qstate->qinfo, msg->rep, qstate->client_info, must_validate, &encode_rep, qstate->region, qstate->env->auth_zones, qstate->env->views, qstate->env->respip_set)) { - return; + return; + } + /* merge succeeded; final reply, no further alias pass */ + partial_rep = NULL; } if(!encode_rep || alias_rrset) { if(!encode_rep) { @@ -2537,6 +2541,7 @@ mesh_serve_expired_callback(void* arg) partial_rep = encode_rep; } } + msg->rep = encode_rep; /* We've found a partial reply ending with an * alias. Replace the lookup qinfo for the * alias target and lookup the cache again to diff --git a/util/data/packed_rrset.c b/util/data/packed_rrset.c index 89ece3c03..b695c1b71 100644 --- a/util/data/packed_rrset.c +++ b/util/data/packed_rrset.c @@ -198,6 +198,7 @@ get_cname_target(struct ub_packed_rrset_key* rrset, uint8_t** dname, { struct packed_rrset_data* d; size_t len; + if(!rrset) return; if(ntohs(rrset->rk.type) != LDNS_RR_TYPE_CNAME && ntohs(rrset->rk.type) != LDNS_RR_TYPE_DNAME) return; From 96f875552023c0ccf376ebe050519f12f3371dc9 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Wed, 22 Jul 2026 10:18:16 +0200 Subject: [PATCH 29/84] - Fix CVE-2026-55973, 'dns-error-reporting: yes' leads to stack buffer overflow. Thanks to Qifan Zhang, Palo Alto Networks, for the report. --- services/cache/dns.c | 2 ++ services/mesh.c | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/services/cache/dns.c b/services/cache/dns.c index 98d07bc37..9fc79dad0 100644 --- a/services/cache/dns.c +++ b/services/cache/dns.c @@ -277,6 +277,8 @@ find_closest_of_type(struct module_env* env, uint8_t* qname, size_t qnamelen, /* snip off front label */ lablen = *qname; + if(lablen == 0) + break; qname += lablen + 1; qnamelen -= lablen + 1; } diff --git a/services/mesh.c b/services/mesh.c index 8ce3dcee7..5051de9df 100644 --- a/services/mesh.c +++ b/services/mesh.c @@ -1654,9 +1654,9 @@ static void dns_error_reporting(struct module_qstate* qstate, opt = edns_opt_list_find(qstate->edns_opts_back_in, LDNS_EDNS_REPORT_CHANNEL); if(!opt) return; - agent_domain_len = opt->opt_len; agent_domain = opt->opt_data; - if(dname_valid(agent_domain, agent_domain_len) < 3) { + agent_domain_len = dname_valid(agent_domain, opt->opt_len); + if(agent_domain_len < 3) { /* The agent domain needs to be a valid dname that is not the * root; from RFC9567. */ return; From ae1b3810cc3a8eb9b43c378289f74020b42aa7f4 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Wed, 22 Jul 2026 10:18:41 +0200 Subject: [PATCH 30/84] - Fix CVE-2026-55990, Packet of death for a DNSCrypt misconfigured Unbound. Thanks to Qifan Zhang, Palo Alto Networks, for the report. --- dnscrypt/dnscrypt.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/dnscrypt/dnscrypt.c b/dnscrypt/dnscrypt.c index 08f9dcb8c..6f1a236ad 100644 --- a/dnscrypt/dnscrypt.c +++ b/dnscrypt/dnscrypt.c @@ -672,6 +672,8 @@ dnsc_find_cert(struct dnsc_env* dnscenv, struct sldns_buffer* buffer) } dnscrypt_header = (struct dnscrypt_query_header *)sldns_buffer_begin(buffer); for (i = 0U; i < dnscenv->signed_certs_count; i++) { + if(!certs[i].keypair) + continue; if (memcmp(certs[i].magic_query, dnscrypt_header->magic_query, DNSCRYPT_MAGIC_HEADER_LEN) == 0) { return &certs[i]; @@ -813,6 +815,7 @@ dnsc_parse_keys(struct dnsc_env *env, struct config_file *cfg) sizeof *env->keypairs); env->certs = sodium_allocarray(env->signed_certs_count, sizeof *env->certs); + memset(env->certs, 0, env->signed_certs_count * sizeof(*env->certs)); cert_id = 0U; keypair_id = 0U; @@ -973,12 +976,19 @@ dnsc_create(void) int dnsc_apply_cfg(struct dnsc_env *env, struct config_file *cfg) { + int nkeys; if(dnsc_parse_certs(env, cfg) <= 0) { fatal_exit("dnsc_apply_cfg: no cert file loaded"); } - if(dnsc_parse_keys(env, cfg) <= 0) { + nkeys = dnsc_parse_keys(env, cfg); + if(nkeys <= 0) { fatal_exit("dnsc_apply_cfg: no key file loaded"); } + if((size_t)nkeys < env->signed_certs_count) { + fatal_exit("dnsc_apply_cfg: %u dnscrypt-provider-cert file(s) have no " + "matching dnscrypt-secret-key", + (unsigned)(env->signed_certs_count - (size_t)nkeys)); + } randombytes_buf(env->hash_key, sizeof env->hash_key); env->provider_name = cfg->dnscrypt_provider; From aac261cbb3795cbd60af2f37ef57bfa5c186aae6 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Wed, 22 Jul 2026 10:19:02 +0200 Subject: [PATCH 31/84] - Fix CVE-2026-55991, Remote DNS-over-QUIC (DoQ) flow-control assertion failure in libngtcp2. Thanks to Qifan Zhang, Palo Alto Networks, for the report. In addition, thanks to Xuanchao Xie, for also reporting this issue. --- services/listen_dnsport.c | 46 +++++++++++++++++++++++++++------------ testcode/doqclient.c | 4 ++-- 2 files changed, 34 insertions(+), 16 deletions(-) diff --git a/services/listen_dnsport.c b/services/listen_dnsport.c index 0500bff8d..38b493a25 100644 --- a/services/listen_dnsport.c +++ b/services/listen_dnsport.c @@ -4508,6 +4508,29 @@ doq_stream_reset_cb(ngtcp2_conn* ATTR_UNUSED(conn), int64_t stream_id, return 0; } +/** ngtcp2 extend_max_stream_data function */ +int doq_extend_max_stream_data_cb(ngtcp2_conn* ATTR_UNUSED(conn), + int64_t stream_id, uint64_t max_data, void* user_data, + void* ATTR_UNUSED(stream_user_data)) +{ + struct doq_conn* doq_conn = (struct doq_conn*)user_data; + struct doq_stream* stream; + verbose(VERB_ALGO, "doq extend_max_stream_data stream id %d " + "max_data %d ", (int)stream_id, (int)max_data); + if(max_data == 0) + return 0; + stream = doq_stream_find(doq_conn, stream_id); + if(!stream) { + verbose(VERB_ALGO, "doq: unknown stream %d", (int)stream_id); + return 0; + } + if(!stream->is_answer_available) + return 0; + doq_stream_on_write_list(doq_conn, stream); + doq_conn_write_enable(doq_conn); + return 0; +} + /** ngtcp2 acked_stream_data_offset callback function */ static int doq_acked_stream_data_offset_cb(ngtcp2_conn* ATTR_UNUSED(conn), @@ -4882,6 +4905,7 @@ doq_conn_setup(struct doq_conn* conn, uint8_t* scid, size_t scidlen, callbacks.stream_open = doq_stream_open_cb; callbacks.stream_close = doq_stream_close_cb; callbacks.stream_reset = doq_stream_reset_cb; + callbacks.extend_max_stream_data = doq_extend_max_stream_data_cb; callbacks.acked_stream_data_offset = doq_acked_stream_data_offset_cb; callbacks.recv_stream_data = doq_recv_stream_data_cb; @@ -5470,26 +5494,20 @@ doq_conn_write_streams(struct comm_point* c, struct doq_conn* conn, continue; } else if(ret == NGTCP2_ERR_STREAM_DATA_BLOCKED) { verbose(VERB_ALGO, "doq: ngtcp2_conn_writev_stream returned NGTCP2_ERR_STREAM_DATA_BLOCKED"); -#ifdef HAVE_NGTCP2_CCERR_DEFAULT - ngtcp2_ccerr_set_application_error( - &conn->ccerr, -1, NULL, 0); -#else - ngtcp2_connection_close_error_set_application_error(&conn->last_error, -1, NULL, 0); -#endif - if(err_drop) - *err_drop = 0; - if(!doq_conn_close_error(c, conn)) { - if(err_drop) - *err_drop = 1; + if(stream) { + doq_stream_off_write_list(conn, stream); + stream = stream->write_next; + continue; + } else { + break; } - return 0; } else if(ret == NGTCP2_ERR_STREAM_SHUT_WR) { verbose(VERB_ALGO, "doq: ngtcp2_conn_writev_stream returned NGTCP2_ERR_STREAM_SHUT_WR"); #ifdef HAVE_NGTCP2_CCERR_DEFAULT ngtcp2_ccerr_set_application_error( - &conn->ccerr, -1, NULL, 0); + &conn->ccerr, DOQ_APP_ERROR_CODE, NULL, 0); #else - ngtcp2_connection_close_error_set_application_error(&conn->last_error, -1, NULL, 0); + ngtcp2_connection_close_error_set_application_error(&conn->last_error, DOQ_APP_ERROR_CODE, NULL, 0); #endif if(err_drop) *err_drop = 0; diff --git a/testcode/doqclient.c b/testcode/doqclient.c index ce4d3417d..4c2142211 100644 --- a/testcode/doqclient.c +++ b/testcode/doqclient.c @@ -1519,9 +1519,9 @@ doq_client_send_pkt(struct doq_client_data* data, uint32_t ecn, uint8_t* buf, } log_err("doq sendmsg: %s", strerror(errno)); #ifdef HAVE_NGTCP2_CCERR_DEFAULT - ngtcp2_ccerr_set_application_error(&data->ccerr, -1, NULL, 0); + ngtcp2_ccerr_set_application_error(&data->ccerr, 1, NULL, 0); #else - ngtcp2_connection_close_error_set_application_error(&data->last_error, -1, NULL, 0); + ngtcp2_connection_close_error_set_application_error(&data->last_error, 1, NULL, 0); #endif return 0; } From 4b1635e19406fd8040806f0fa4a4488c003b5d0d Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Wed, 22 Jul 2026 10:19:28 +0200 Subject: [PATCH 32/84] - Fix CVE-2026-56416, Possible heap buffer overflow when validator canonicalizes RDATA that contains domain name. Thanks to Qifan Zhang, Palo Alto Networks, for the report. --- util/data/msgparse.c | 3 +++ validator/val_sigcrypt.c | 11 +++++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/util/data/msgparse.c b/util/data/msgparse.c index 7b2de102e..4be1a72dc 100644 --- a/util/data/msgparse.c +++ b/util/data/msgparse.c @@ -687,6 +687,9 @@ calc_size(sldns_buffer* pkt, uint16_t type, struct rr_parse* rr) } rdf++; } + /* rdata ended before all _dname_count names were seen */ + if(count != 0) + return 0; /* the rdata is too short. */ } /* remaining rdata */ rr->size += pkt_len; diff --git a/validator/val_sigcrypt.c b/validator/val_sigcrypt.c index 46e6ac16b..16c01d2ee 100644 --- a/validator/val_sigcrypt.c +++ b/validator/val_sigcrypt.c @@ -1094,6 +1094,7 @@ canonicalize_rdata(sldns_buffer* buf, struct ub_packed_rrset_key* rrset, size_t len) { uint8_t* datstart = sldns_buffer_current(buf)-len+2; + size_t firstlen; switch(ntohs(rrset->rk.type)) { case LDNS_RR_TYPE_NXT: case LDNS_RR_TYPE_NS: @@ -1113,8 +1114,9 @@ canonicalize_rdata(sldns_buffer* buf, struct ub_packed_rrset_key* rrset, case LDNS_RR_TYPE_SOA: /* two names after another */ query_dname_tolower(datstart); - query_dname_tolower(datstart + - dname_valid(datstart, len-2)); + firstlen = dname_valid(datstart, len-2); + if(firstlen && firstlen < len-2) + query_dname_tolower(datstart + firstlen); return; case LDNS_RR_TYPE_RT: case LDNS_RR_TYPE_AFSDB: @@ -1141,8 +1143,9 @@ canonicalize_rdata(sldns_buffer* buf, struct ub_packed_rrset_key* rrset, return; datstart += 2; query_dname_tolower(datstart); - query_dname_tolower(datstart + - dname_valid(datstart, len-2-2)); + firstlen = dname_valid(datstart, len-2-2); + if(firstlen && firstlen < len-2-2) + query_dname_tolower(datstart + firstlen); return; case LDNS_RR_TYPE_NAPTR: if(len < 2+4) From 84d9682dd0876bc0cd118ecce03661f3443b0222 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Wed, 22 Jul 2026 10:19:50 +0200 Subject: [PATCH 33/84] - Fix CVE-2026-56444, Degradation of resolution service when 'discard-timeout' and 'serve-expired-client-timeout' are combined in unusual configuration. Thanks to Qifan Zhang, Palo Alto Networks, for the report. In addition, thanks to Xin Wang, Jiapeng Li, and Jiajia Liu, Northwestern Polytechnical University, for also reporting this issue. In addition, thanks to Haruki Oyama (Waseda University), for also reporting this issue. --- services/mesh.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/services/mesh.c b/services/mesh.c index 5051de9df..99361b17f 100644 --- a/services/mesh.c +++ b/services/mesh.c @@ -2568,9 +2568,10 @@ mesh_serve_expired_callback(void* arg) log_dns_msg("Serve expired lookup", &qstate->qinfo, msg->rep); for(r = mstate->reply_list; r; r = r->next) { - struct timeval old; - timeval_subtract(&old, mstate->s.env->now_tv, &r->start_time); - if(mstate->s.env->cfg->discard_timeout != 0 && + if(mesh_is_udp(r)) { + struct timeval old; + timeval_subtract(&old, mstate->s.env->now_tv, &r->start_time); + if(mstate->s.env->cfg->discard_timeout != 0 && ((int)old.tv_sec)*1000+((int)old.tv_usec)/1000 > mstate->s.env->cfg->discard_timeout) { /* Drop the reply, it is too old */ @@ -2590,8 +2591,11 @@ mesh_serve_expired_callback(void* arg) doq_stream_remove_mesh_state(r->query_reply.doq_stream); comm_point_drop_reply(&r->query_reply); mstate->reply_list = reply_list; + log_assert(mstate->s.env->mesh->num_reply_addrs > 0); + mstate->s.env->mesh->num_reply_addrs--; mstate->s.env->mesh->num_queries_discard_timeout++; continue; + } } i++; From c33ad1b1a21e48bfe4f140e90252a9be1b0c5745 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Wed, 22 Jul 2026 10:21:21 +0200 Subject: [PATCH 34/84] rerun autoconf. --- configure | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/configure b/configure index fd1e143e5..564f74afd 100755 --- a/configure +++ b/configure @@ -22923,6 +22923,29 @@ printf "%s\n" "no" >&6; } fi rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext + ac_fn_check_decl "$LINENO" "CLOCK_MONOTONIC + " "ac_cv_have_decl_CLOCK_MONOTONIC_________" "$ac_includes_default +#ifdef TIME_WITH_SYS_TIME +# include +# include +#else +# ifdef HAVE_SYS_TIME_H +# include +# else +# include +# endif +#endif + +" "$ac_c_undeclared_builtin_options" "CFLAGS" +if test "x$ac_cv_have_decl_CLOCK_MONOTONIC_________" = xyes +then : + + +else $as_nop + as_fn_error $? "ngtcp2 for QUIC needs at least CLOCK_MONOTONIC on the system" "$LINENO" 5 + +fi + fi # set static linking for uninstalled libraries if requested From ae685bc33de639205db62a79b6b5318395314c37 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Wed, 22 Jul 2026 11:34:48 +0200 Subject: [PATCH 35/84] Move repo to version 1.25.3. --- configure | 25 +++++++++++++------------ configure.ac | 5 +++-- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/configure b/configure index ae22669e2..f40c51bed 100755 --- a/configure +++ b/configure @@ -1,6 +1,6 @@ #! /bin/sh # Guess values for system-dependent variables and create Makefiles. -# Generated by GNU Autoconf 2.71 for unbound 1.25.2. +# Generated by GNU Autoconf 2.71 for unbound 1.25.3. # # Report bugs to . # @@ -622,8 +622,8 @@ MAKEFLAGS= # Identity of this package. PACKAGE_NAME='unbound' PACKAGE_TARNAME='unbound' -PACKAGE_VERSION='1.25.2' -PACKAGE_STRING='unbound 1.25.2' +PACKAGE_VERSION='1.25.3' +PACKAGE_STRING='unbound 1.25.3' PACKAGE_BUGREPORT='unbound-bugs@nlnetlabs.nl or https://github.com/NLnetLabs/unbound/issues' PACKAGE_URL='' @@ -1513,7 +1513,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.25.2 to adapt to many kinds of systems. +\`configure' configures unbound 1.25.3 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1579,7 +1579,7 @@ fi if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of unbound 1.25.2:";; + short | recursive ) echo "Configuration of unbound 1.25.3:";; esac cat <<\_ACEOF @@ -1832,7 +1832,7 @@ fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -unbound configure 1.25.2 +unbound configure 1.25.3 generated by GNU Autoconf 2.71 Copyright (C) 2021 Free Software Foundation, Inc. @@ -2489,7 +2489,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.25.2, which was +It was created by unbound $as_me 1.25.3, which was generated by GNU Autoconf 2.71. Invocation command line was $ $0$ac_configure_args_raw @@ -3253,11 +3253,11 @@ UNBOUND_VERSION_MAJOR=1 UNBOUND_VERSION_MINOR=25 -UNBOUND_VERSION_MICRO=2 +UNBOUND_VERSION_MICRO=3 LIBUNBOUND_CURRENT=9 -LIBUNBOUND_REVISION=38 +LIBUNBOUND_REVISION=39 LIBUNBOUND_AGE=1 # 1.0.0 had 0:12:0 # 1.0.1 had 0:13:0 @@ -3363,6 +3363,7 @@ LIBUNBOUND_AGE=1 # 1.25.0 had 9:36:1 # 1.25.1 had 9:37:1 # 1.25.2 had 9:38:1 +# 1.25.3 had 9:39:1 # Current -- the number of the binary API that we're implementing # Revision -- which iteration of the implementation of the binary @@ -25680,7 +25681,7 @@ printf "%s\n" "#define MAXSYSLOGMSGLEN 10240" >>confdefs.h -version=1.25.2 +version=1.25.3 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for build time" >&5 printf %s "checking for build time... " >&6; } @@ -26210,7 +26211,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # 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.25.2, which was +This file was extended by unbound $as_me 1.25.3, which was generated by GNU Autoconf 2.71. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -26278,7 +26279,7 @@ ac_cs_config_escaped=`printf "%s\n" "$ac_cs_config" | sed "s/^ //; s/'/'\\\\\\\\ cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config='$ac_cs_config_escaped' ac_cs_version="\\ -unbound config.status 1.25.2 +unbound config.status 1.25.3 configured by $0, generated by GNU Autoconf 2.71, with options \\"\$ac_cs_config\\" diff --git a/configure.ac b/configure.ac index 524a24860..f4697527f 100644 --- a/configure.ac +++ b/configure.ac @@ -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],[25]) -m4_define([VERSION_MICRO],[2]) +m4_define([VERSION_MICRO],[3]) 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=38 +LIBUNBOUND_REVISION=39 LIBUNBOUND_AGE=1 # 1.0.0 had 0:12:0 # 1.0.1 had 0:13:0 @@ -125,6 +125,7 @@ LIBUNBOUND_AGE=1 # 1.25.0 had 9:36:1 # 1.25.1 had 9:37:1 # 1.25.2 had 9:38:1 +# 1.25.3 had 9:39:1 # Current -- the number of the binary API that we're implementing # Revision -- which iteration of the implementation of the binary From 7a95bedc26f9f9381edc8532ab057361351ede2f Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Wed, 22 Jul 2026 11:36:06 +0200 Subject: [PATCH 36/84] Fix conflict merge fixup. --- services/mesh.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/services/mesh.c b/services/mesh.c index d664e4797..6159dd1cd 100644 --- a/services/mesh.c +++ b/services/mesh.c @@ -2470,8 +2470,7 @@ void mesh_state_remove_reply(struct mesh_area* mesh, struct mesh_state* m, while(n) { if(n->query_reply.c == cp && (!h2_stream || n->h2_stream == h2_stream) - && (!doq_stream || n->query_reply.doq_stream == doq_stre -am)) { + && (!doq_stream || n->query_reply.doq_stream == doq_stream)) { /* unlink it */ if(prev) prev->next = n->next; else m->reply_list = n->next; From 1df6c170ff3efadcab8cdbee3310267a928bf3e9 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Wed, 22 Jul 2026 11:38:48 +0200 Subject: [PATCH 37/84] Changelog entry for 1.25.2. - Set the repository to 1.25.3, it continues with the previous changes. --- doc/Changelog | 90 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/doc/Changelog b/doc/Changelog index 843b1215d..38667028c 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,93 @@ +22 July 2026: Wouter + - Release tag for 1.25.2, with the security commits: + - Fix CVE-2026-14586, Assertion in libngtcp2 when under pressure + in high concurrency DNS-over-QUIC environments. Thanks to Kunta + Chu, Kaihua Wang, and Jianjun Chen from Tsinghua University, + for the report. + - Fix CVE-2026-32665, Remote DNS-over-QUIC denial of + service due to `quic-size` budget bypass. Thanks to N0zoM1z0 + (https://github.com/N0zoM1z0) for the report. In addition, thanks to + Kunta Chu, Kaihua Wang, and Jianjun Chen from Tsinghua University, + for also reporting this issue. In addition, thanks to Qifan Zhang, + Palo Alto Networks, for also reporting this issue. In addition, + thanks to Xuanchao Xie, for also reporting this issue. + - Fix CVE-2026-40691, Packet of death for DNSCrypt over TCP. Thanks + to Qifan Zhang, Palo Alto Networks, for the report. In addition, + thanks to Trung Nguyen (@everping) of CyStack, for also reporting + this issue. + - Fix CVE-2026-41637, Degradation of resolution service from + improperly accounted client-terminated DNS-over-QUIC queries. Thanks + to Qifan Zhang, Palo Alto Networks, for the report. + - Fix CVE-2026-42955, Extra fix for CVE-2026-40622 to also clamp + the TTL of A/AAAA records disallowing a one-time 'ghost domain' + delegation renewal via glue records. Thanks to Qifan Zhang, Palo + Alto Networks, for the report. + - Fix CVE-2026-44621, Libunbound applications configured with + 'unwanted-reply-threshold' could eventually be abruptly + terminated. Thanks to Qifan Zhang, Palo Alto Networks, for the + report. + - Fix CVE-2026-44687, Off-by-one error in 'harden-below-nxdomain' + logic can shadow a stub/forward zone by a legitimate parent's + NXDOMAIN. Thanks to Qifan Zhang, Palo Alto Networks, for the report. + - Fix CVE-2026-44690, Cross-zone wildcard cache poisoning via + RRSIG.labels manipulation. Thanks to Qifan Zhang, Palo Alto + Networks, for the report. + - Fix CVE-2026-46582, A wildcard replay, as another piece of data, + triggers poisoning in the serve expired reply path. Thanks to + Qifan Zhang, Palo Alto Networks, for the report. + - Fix CVE-2026-50045, 'max-global-quota' reset by DNSSEC validation + restarts. Thanks to Kunjie Shang, University of Science and + Technology of China, for the report. + - Fix CVE-2026-50046, Possible heap use-after-free in an error path + when a DoT forwarded query is jostled out. Thanks to Qifan Zhang, + Palo Alto Networks, for the report. + - Fix CVE-2026-50243, 'response-ip'/'rpz' can rewrite BOGUS answers + instead of returning SERVFAIL. Thanks to Qifan Zhang, Palo Alto + Networks, for the report. + - Fix CVE-2026-50248, BOGUS configured primary hostname accepted for + XFR in auth/rpz zones. Thanks to Qifan Zhang, Palo Alto Networks, + for the report. + - Fix CVE-2026-50251, Attacker supplied `0.0.0.0`/`::` glue triggers + defensive full-cache flush. Thanks to Qifan Zhang, Palo Alto + Networks, for the report. + - Fix CVE-2026-50252, Possible cache poisoning attack by mapping + source port population per thread. Thanks to Inbal Schussheim and + Amit Klein, Hebrew University, for the report. + - Fix CVE-2026-52863, Memory corruption could lead to crash and + denial of service. Thanks to Qifan Zhang, Palo Alto Networks, + for the report. + - Fix CVE-2026-54478, DNS Cookie bypass when combined with + proxy-protocol use. Thanks to Qifan Zhang, Palo Alto Networks, + for the report. + - Fix CVE-2026-55708, Privacy/configuration issue when adding local + data in views through 'unbound-control'. Thanks to Qifan Zhang, + Palo Alto Networks, for the report. + - Fix CVE-2026-55717, 'serve-expired-client-timeout' and 'response-ip' + CNAME redirect could lead to a crash. Thanks to Qifan Zhang, Palo + Alto Networks, for the report. In addition, thanks to Xin Wang, + Jiapeng Li, and Jiajia Liu, Northwestern Polytechnical University, + for also reporting this issue. + - Fix CVE-2026-55973, 'dns-error-reporting: yes' leads to stack buffer + overflow. Thanks to Qifan Zhang, Palo Alto Networks, for the report. + - Fix CVE-2026-55990, Packet of death for a DNSCrypt misconfigured + Unbound. Thanks to Qifan Zhang, Palo Alto Networks, for the report. + - Fix CVE-2026-55991, Remote DNS-over-QUIC (DoQ) flow-control + assertion failure in libngtcp2. Thanks to Qifan Zhang, Palo Alto + Networks, for the report. In addition, thanks to Xuanchao Xie, + for also reporting this issue. + - Fix CVE-2026-56416, Possible heap buffer overflow when validator + canonicalizes RDATA that contains domain name. Thanks to Qifan + Zhang, Palo Alto Networks, for the report. + - Fix CVE-2026-56444, Degradation of resolution service when + 'discard-timeout' and 'serve-expired-client-timeout' are combined in + unusual configuration. Thanks to Qifan Zhang, Palo Alto Networks, + for the report. In addition, thanks to Xin Wang, Jiapeng Li, + and Jiajia Liu, Northwestern Polytechnical University, for also + reporting this issue. In addition, thanks to Haruki Oyama (Waseda + University), for also reporting this issue. + - Set the repository to 1.25.3, it continues with the previous + changes. + 21 July 2026: Wouter - Merge #1476 from petrvaganoff: ipsecmod: fix possible deref on null after reply_find_answer_rrset(). From 9f757aa9f3e869a944f24acd64cadfde55269575 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Wed, 22 Jul 2026 11:54:00 +0200 Subject: [PATCH 38/84] - Unit test for CVE-2026-42955. --- doc/Changelog | 1 + testdata/ghost_glue_a.rpl | 365 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 366 insertions(+) create mode 100644 testdata/ghost_glue_a.rpl diff --git a/doc/Changelog b/doc/Changelog index 38667028c..975737740 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -87,6 +87,7 @@ University), for also reporting this issue. - Set the repository to 1.25.3, it continues with the previous changes. + - Unit test for CVE-2026-42955. 21 July 2026: Wouter - Merge #1476 from petrvaganoff: ipsecmod: fix possible deref diff --git a/testdata/ghost_glue_a.rpl b/testdata/ghost_glue_a.rpl new file mode 100644 index 000000000..5468ab842 --- /dev/null +++ b/testdata/ghost_glue_a.rpl @@ -0,0 +1,365 @@ +; config options +; The island of trust is at test. +server: + target-fetch-policy: "0 0 0 0 0" + qname-minimisation: no + minimal-responses: yes + iter-scrub-promiscuous: yes + aggressive-nsec: no + local-zone: test. nodefault + log-servfail: yes + module-config: "iterator" + +stub-zone: + name: "." + stub-addr: 193.0.14.129 # K.ROOT-SERVERS.NET. +CONFIG_END + +SCENARIO_BEGIN Test ghost domain glue TTL extension +; for A and AAAA records. + +; K.ROOT-SERVERS.NET. +RANGE_BEGIN 0 100 + ADDRESS 193.0.14.129 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +. IN NS +SECTION ANSWER +. IN NS K.ROOT-SERVERS.NET. +SECTION ADDITIONAL +K.ROOT-SERVERS.NET. IN A 193.0.14.129 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode subdomain +ADJUST copy_id copy_query +REPLY QR NOERROR +SECTION QUESTION +test. IN NS +SECTION AUTHORITY +test. IN NS ns.test. +SECTION ADDITIONAL +ns.test. IN A 1.2.3.5 +ENTRY_END +RANGE_END + +; ns.test +RANGE_BEGIN 0 20 + ADDRESS 1.2.3.5 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +test. IN NS +SECTION ANSWER +test. IN NS ns.test +SECTION ADDITIONAL +ns.test. IN A 1.2.3.5 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +ns.test. IN A +SECTION ANSWER +ns.test. IN A 1.2.3.5 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +ns.test. IN AAAA +SECTION AUTHORITY +test. 3600 IN SOA ns.test. host.test. 20201 3600 1800 604800 3600 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode subdomain +ADJUST copy_id copy_query +REPLY QR NOERROR +SECTION QUESTION +example.test. IN NS +SECTION AUTHORITY +example.test. 4 IN NS ns.example.test. +SECTION ADDITIONAL +ns.example.test. 4 IN A 1.2.3.4 +ENTRY_END +RANGE_END + +; ns.test +RANGE_BEGIN 25 100 + ADDRESS 1.2.3.5 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +test. IN NS +SECTION ANSWER +test. IN NS ns.test +SECTION ADDITIONAL +ns.test. IN A 1.2.3.5 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +ns.test. IN A +SECTION ANSWER +ns.test. IN A 1.2.3.5 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +ns.test. IN AAAA +SECTION AUTHORITY +test. 3600 IN SOA ns.test. host.test. 20201 3600 1800 604800 3600 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode subdomain +ADJUST copy_id copy_query +REPLY QR NOERROR +SECTION QUESTION +example.test. IN NS +SECTION AUTHORITY +example.test. 4 IN NS ns.example.test. +SECTION ADDITIONAL +ns.example.test. 4 IN A 1.2.3.7 +ENTRY_END +RANGE_END + +; ns.example.test. +RANGE_BEGIN 0 20 + ADDRESS 1.2.3.4 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +example.test. IN NS +SECTION ANSWER +example.test. 7200 IN NS ns.example.test. +SECTION ADDITIONAL +ns.example.test. 7200 IN A 1.2.3.4 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +ns.example.test. IN A +SECTION ANSWER +ns.example.test. 7200 IN A 1.2.3.4 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +ns.example.test. IN AAAA +SECTION AUTHORITY +example.test. 3600 IN SOA ns.example.test. host.example.test. 20301 3600 1800 604800 3600 +ENTRY_END + +; response to query of interest +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +www.example.test. IN A +SECTION ANSWER +www.example.test. 1 IN A 192.0.2.1 +ENTRY_END +RANGE_END + +; ns.example.test. +; after the delegation change, the old hoster. +RANGE_BEGIN 20 100 + ADDRESS 1.2.3.4 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +www3.example.test. IN A +SECTION ANSWER +www3.example.test. IN A 10.20.30.46 +ENTRY_END +RANGE_END + +; ns.example.test. +; the new hoster +RANGE_BEGIN 20 100 + ADDRESS 1.2.3.7 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +ns.example.test. IN A +SECTION ANSWER +ns.example.test. IN A 1.2.3.7 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +ns.example.test. IN AAAA +SECTION AUTHORITY +example.test. 3600 IN SOA ns.example.test. host.example.test. 20301 3600 1800 604800 3600 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +www2.example.test. IN A +SECTION ANSWER +www2.example.test. IN A 10.20.30.47 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA SERVFAIL +SECTION QUESTION +www3.example.test. IN A +SECTION ANSWER +ENTRY_END +RANGE_END + +; The TTL of the NS and glue is 4 for example.test. +STEP 1 QUERY +ENTRY_BEGIN +REPLY RD DO +SECTION QUESTION +www.example.test. IN A +ENTRY_END + +STEP 2 CHECK_ANSWER +ENTRY_BEGIN +MATCH all ttl +REPLY QR RD RA DO NOERROR +SECTION QUESTION +www.example.test. IN A +SECTION ANSWER +www.example.test. 1 IN A 192.0.2.1 +ENTRY_END + +; query for the NS record +STEP 8 QUERY +ENTRY_BEGIN +REPLY RD DO +SECTION QUESTION +example.test. IN NS +ENTRY_END + +STEP 9 CHECK_ANSWER +ENTRY_BEGIN +MATCH all ttl +REPLY QR RD RA DO NOERROR +SECTION QUESTION +example.test. IN NS +SECTION ANSWER +example.test. 7200 IN NS ns.example.test. +SECTION ADDITIONAL +ns.example.test. 4 IN A 1.2.3.4 +ENTRY_END + +; query for glue specifically +STEP 10 QUERY +ENTRY_BEGIN +REPLY RD DO +SECTION QUESTION +ns.example.test. IN A +ENTRY_END + +STEP 11 CHECK_ANSWER +ENTRY_BEGIN +MATCH all ttl +REPLY QR RD RA DO NOERROR +SECTION QUESTION +ns.example.test. IN A +SECTION ANSWER +ns.example.test. 7200 IN A 1.2.3.4 +ENTRY_END + +; query for glue from cache again +STEP 12 QUERY +ENTRY_BEGIN +REPLY RD DO +SECTION QUESTION +ns.example.test. IN A +ENTRY_END + +STEP 13 CHECK_ANSWER +ENTRY_BEGIN +MATCH all ttl +REPLY QR RD RA DO NOERROR +SECTION QUESTION +ns.example.test. IN A +SECTION ANSWER +ns.example.test. 4 IN A 1.2.3.4 +ENTRY_END + +; Move time to expire the delegation +STEP 20 TIME_PASSES ELAPSE 6 + +; The upstream changes to delegate to another server. +STEP 30 QUERY +ENTRY_BEGIN +REPLY RD DO +SECTION QUESTION +www2.example.test. IN A +ENTRY_END + +STEP 31 CHECK_ANSWER +ENTRY_BEGIN +MATCH all ttl +REPLY QR RD RA DO NOERROR +SECTION QUESTION +www2.example.test. IN A +SECTION ANSWER +www2.example.test. IN A 10.20.30.47 +ENTRY_END + +; the new server, 1.2.3.7 is not responsive (SERVFAILs), it +; should not have the old one 1.2.3.4 now. +STEP 40 QUERY +ENTRY_BEGIN +REPLY RD DO +SECTION QUESTION +www3.example.test. IN A +ENTRY_END + +STEP 50 CHECK_ANSWER +ENTRY_BEGIN +MATCH all ttl +REPLY QR RD RA DO SERVFAIL +SECTION QUESTION +www3.example.test. IN A +SECTION ANSWER +ENTRY_END + + +SCENARIO_END From 23e19ca6fcffa68307dbb5cfc6a6251b3a8270ca Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Wed, 22 Jul 2026 11:55:09 +0200 Subject: [PATCH 39/84] - Unit test for CVE-2026-44687. --- doc/Changelog | 1 + testdata/stop_nxdomain_label.rpl | 115 +++++++++++++++++++++++++++++++ 2 files changed, 116 insertions(+) create mode 100644 testdata/stop_nxdomain_label.rpl diff --git a/doc/Changelog b/doc/Changelog index 975737740..286153833 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -88,6 +88,7 @@ - Set the repository to 1.25.3, it continues with the previous changes. - Unit test for CVE-2026-42955. + - Unit test for CVE-2026-44687. 21 July 2026: Wouter - Merge #1476 from petrvaganoff: ipsecmod: fix possible deref diff --git a/testdata/stop_nxdomain_label.rpl b/testdata/stop_nxdomain_label.rpl new file mode 100644 index 000000000..b882cf05f --- /dev/null +++ b/testdata/stop_nxdomain_label.rpl @@ -0,0 +1,115 @@ +; config options +server: + target-fetch-policy: "0 0 0 0 0" + qname-minimisation: "no" + harden-below-nxdomain: yes + trust-anchor: ". IN DNSKEY 257 3 5 AQPQ41chR9DEHt/aIzIFAqanbDlRflJoRs5yz1jFsoRIT7dWf0r+PeDuewdxkszNH6wnU4QL8pfKFRh5PIYVBLK3" + val-override-date: "20070916134226" + fake-sha1: yes + trust-anchor-signaling: no + aggressive-nsec: no + domain-insecure: "under.example.local" + +stub-zone: + name: "." + stub-addr: 193.0.14.129 # K.ROOT-SERVERS.NET. +stub-zone: + name: "under.example.local" + stub-addr: 1.2.3.4 +CONFIG_END + +SCENARIO_BEGIN Test stop cache on nxdomain, with stub under intermediate label + +; K.ROOT-SERVERS.NET. +RANGE_BEGIN 0 100 + ADDRESS 193.0.14.129 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +. IN NS +SECTION ANSWER +. IN NS K.ROOT-SERVERS.NET. +SECTION ADDITIONAL +K.ROOT-SERVERS.NET. IN A 193.0.14.129 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +. IN DNSKEY +SECTION ANSWER +. 3600 IN DNSKEY 257 3 5 AQPQ41chR9DEHt/aIzIFAqanbDlRflJoRs5yz1jFsoRIT7dWf0r+PeDuewdxkszNH6wnU4QL8pfKFRh5PIYVBLK3 ;{id = 30900 (ksk), size = 512b} +. 3600 IN RRSIG DNSKEY 5 0 3600 20070926134150 20070829134150 30900 . BlVcSh8xSgm7ne+XVCJwNHQKjk5kTJgG4Fa3sOSfp3YUjb2YclmVWyIw7XEHl0/C6CN5gdy18idnM6vT6Hy42A== ;{id = 30900} +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NXDOMAIN +SECTION QUESTION +example.local. IN A +SECTION AUTHORITY +. 86400 IN SOA a.root-servers.net. nstld.verisign-grs.com. 2010111601 1800 900 604800 86400 +. 86400 IN RRSIG SOA 5 0 86400 20070926134150 20070829134150 30900 . bOYbFZZp7vWWC2oxV+kph+YXjoQj2f6QJktlgmzRI7oReFX9jy/LibTPQi/sW0SGHpLaj3G5p4IfIlBibne4DA== ;{id = 30900} +. 86400 IN NSEC ac. NS SOA RRSIG NSEC DNSKEY +. 86400 IN RRSIG NSEC 5 0 86400 20070926134150 20070829134150 30900 . U+/m5+FmczzkosEx1aTP7MK/F3PpcKWct8CzM1jhjwNe2RlnW7qFe0IH8SLzD/elvxDTQMpJSMlKOhUUdapB8g== ;{id = 30900} +lk. 86400 IN NSEC lr. NS DS RRSIG NSEC +lk. 86400 IN RRSIG NSEC 5 1 86400 20070926134150 20070829134150 30900 . j6Pw5Eu9vGHDJcckTSWa8YD1b7FV7c/Z8aVkLfJCH+iPcaa40/LSp784+t2PnAAXL8fgriNL6jF/ve1rti3ANQ== ;{id = 30900} +ENTRY_END +RANGE_END + +; under.example.local +RANGE_BEGIN 0 100 + ADDRESS 1.2.3.4 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +www.under.example.local. IN A +SECTION ANSWER +www.under.example.local. IN A 10.20.30.40 +ENTRY_END +RANGE_END + +STEP 1 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +example.local. IN A +ENTRY_END + +; recursion happens here. +STEP 10 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR RD RA NXDOMAIN +SECTION QUESTION +example.local. IN A +SECTION AUTHORITY +. 86400 IN SOA a.root-servers.net. nstld.verisign-grs.com. 2010111601 1800 900 604800 86400 +ENTRY_END + +STEP 20 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +www.under.example.local. IN A +ENTRY_END + +; this query does not get sent to K-ROOT. +STEP 30 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR RD RA NOERROR +SECTION QUESTION +www.under.example.local. IN A +SECTION ANSWER +www.under.example.local. IN A 10.20.30.40 +ENTRY_END + +SCENARIO_END From 3d5e6c06923eff9eac2f5e31a69c43a15ca9d3c2 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Wed, 22 Jul 2026 11:56:08 +0200 Subject: [PATCH 40/84] - Unit test for CVE-2026-44690. --- doc/Changelog | 1 + testdata/rrsig_agr_wild.rpl | 374 ++++++++++++++++++++++++++++++++++++ 2 files changed, 375 insertions(+) create mode 100644 testdata/rrsig_agr_wild.rpl diff --git a/doc/Changelog b/doc/Changelog index 286153833..ffcfd3ec4 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -89,6 +89,7 @@ changes. - Unit test for CVE-2026-42955. - Unit test for CVE-2026-44687. + - Unit test for CVE-2026-44690. 21 July 2026: Wouter - Merge #1476 from petrvaganoff: ipsecmod: fix possible deref diff --git a/testdata/rrsig_agr_wild.rpl b/testdata/rrsig_agr_wild.rpl new file mode 100644 index 000000000..7281aa759 --- /dev/null +++ b/testdata/rrsig_agr_wild.rpl @@ -0,0 +1,374 @@ +; config options +; The island of trust is at com. +server: + trust-anchor: "test. DS 1444 8 2 8a87d067fd09a5965244fe2e317dd26d182c468e0a7f26ecc4c7b479bf89db9b" + val-override-date: "20201020135527" + target-fetch-policy: "0 0 0 0 0" + qname-minimisation: "no" + fake-sha1: yes + trust-anchor-signaling: no + minimal-responses: no + iter-scrub-promiscuous: no + aggressive-nsec: yes + local-zone: test. nodefault + log-servfail: yes + +stub-zone: + name: "." + stub-addr: 193.0.14.129 # K.ROOT-SERVERS.NET. +CONFIG_END + +SCENARIO_BEGIN Test with RRSIG labels for wildcard with aggressive cache. + +; K.ROOT-SERVERS.NET. +RANGE_BEGIN 0 100 + ADDRESS 193.0.14.129 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +. IN NS +SECTION ANSWER +. IN NS K.ROOT-SERVERS.NET. +SECTION ADDITIONAL +K.ROOT-SERVERS.NET. IN A 193.0.14.129 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode subdomain +ADJUST copy_id copy_query +REPLY QR NOERROR +SECTION QUESTION +test. IN NS +SECTION AUTHORITY +test. IN NS ns.test. +SECTION ADDITIONAL +ns.test. IN A 1.2.3.5 +ENTRY_END +RANGE_END + +; ns.test +RANGE_BEGIN 0 100 + ADDRESS 1.2.3.5 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +test. IN NS +SECTION ANSWER +test. IN NS ns.test +test. 3600 IN RRSIG NS 8 1 3600 20201116135527 20201019135527 1444 test. RGCxIO32TbbLTk6xZmTr+fjYPH50hntBxeOQ2DIj2pDsmjALcHYtVkOfpfk2EhOhHZd+9PLuoJPbJh6a9NqLSFeBvr0XZoCZoQ2g0tCHUNHcH5EVjA2TuYBQem6DVYnPLJ3914aRx0uA1j42b8dC2xsam/XkOo7U+dLbUW2Os1s= +SECTION ADDITIONAL +ns.test. IN A 1.2.3.5 +ns.test. 3600 IN RRSIG A 8 2 3600 20201116135527 20201019135527 1444 test. GskCc4/k6GjH9V9Jz2V5L2XLiizbOeWkB0feSbf+aN859S3vxVvtuqkvIgwY4LafUO1QAn/pUcv9zA7rcFO++rlg+8t6gvZTo9p3v0bfeIv2uJDsfSBD5jDh0WXlxjekfnrKrQp7zE+GiA93tWwKUWKPvxXDgP+n886e6WcbHJw= +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +ns.test. IN A +SECTION ANSWER +ns.test. IN A 1.2.3.5 +ns.test. 3600 IN RRSIG A 8 2 3600 20201116135527 20201019135527 1444 test. GskCc4/k6GjH9V9Jz2V5L2XLiizbOeWkB0feSbf+aN859S3vxVvtuqkvIgwY4LafUO1QAn/pUcv9zA7rcFO++rlg+8t6gvZTo9p3v0bfeIv2uJDsfSBD5jDh0WXlxjekfnrKrQp7zE+GiA93tWwKUWKPvxXDgP+n886e6WcbHJw= +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +ns.test. IN AAAA +SECTION AUTHORITY +test. 3600 IN SOA ns.test. host.test. 20201 3600 1800 604800 3600 +test. 3600 IN RRSIG SOA 8 1 3600 20201116135527 20201019135527 1444 test. IZJIDmEgf0W7A5G7hvvZ2hUqJ9Trbv1/i7ySapDmPbYV9lVCmHHobySxO01yDhI2/Pvpsvxqrm1Tiv3BxH8uzZ4keKgiQjBsSy4htAsFct9I4E7ly2glPj/Fm3oun3PsjJDv5QYhx0KS7w4IQKU7Nc9pfJc92uoUI5bdoC1pRGw= +ns.test. 3600 IN NSEC nz.test. A RRSIG +ns.test. 3600 IN RRSIG NSEC 8 2 3600 20201116135527 20201019135527 1444 test. PElArVB3KPg8KHAP7lzcNbhFuXNxTsHNTn1dZVncB5qmWRdIaeKpaXDjpH0JSXMaelGFS+/QhuQ6Hmw9+4VyZFRqMzGhw4agUR/2bxABHcDIG4ZpUwyeSP61ATTfHUkQVxaH2wjCWI/tfmesdP2xVE4GXyUvCIBxU914MkZbULU= +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +test. IN DNSKEY +SECTION ANSWER +test. 3600 IN DNSKEY 257 3 8 AwEAAbd9WqjzE2Pynz21OG5doSf9hFzMr5dhzz2waZ3vTa+0o5r7AjTAqmA1yH/B3+aAMihUm5ucZSfVqo7+kOaRE8yFj9aivOmA1n1+JLevJq/oyvQyjxQN2Qb89LyaNUT5oKZIiL+uyyhNW3KDR3SSbQ/GBwQNDHVcZi+JDR3RC0r7 ;{id = 1444 (ksk), size = 1024b} +test. 3600 IN RRSIG DNSKEY 8 1 3600 20201116135527 20201019135527 1444 test. UmRMS4iG9NBBHZYOtpwFFcJgbEb5SfHSgHd9XRe/8pTWM31WSDayn5ViPOBMqI1T5TXg2amc13dDI574xIM2oKMus3b5cBW72jJLW13jprBtslO6P8BMWb4HNnvLrJtQjwf3ErRirtTxinLmywQtmyr1cdthyG3Gp4N7i90fHSc= +SECTION ADDITIONAL +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qname qtype +ADJUST copy_id +REPLY QR AA NXDOMAIN +SECTION QUESTION +domain1.test. IN A +SECTION ANSWER +SECTION AUTHORITY +test. 3600 IN SOA ns.test. host.test. 20201 3600 1800 604800 3600 +test. 3600 IN RRSIG SOA 8 1 3600 20201116135527 20201019135527 1444 test. IZJIDmEgf0W7A5G7hvvZ2hUqJ9Trbv1/i7ySapDmPbYV9lVCmHHobySxO01yDhI2/Pvpsvxqrm1Tiv3BxH8uzZ4keKgiQjBsSy4htAsFct9I4E7ly2glPj/Fm3oun3PsjJDv5QYhx0KS7w4IQKU7Nc9pfJc92uoUI5bdoC1pRGw= +test. 3600 IN NSEC abc.test. NS SOA DNSKEY RRSIG NSEC +test. 3600 IN RRSIG NSEC 8 1 3600 20201116135527 20201019135527 1444 test. SHU0veyxtCpPvwdJxn2xCEq9xXJZLIvAlYYy7/dBMSjo6ugBPSxs1+8hUZFxks+YQoPLR5nTU0C0yuhZ9dfg/2VGkCYLsDLYnh1lJj6uQ2VgwfhbwSJC0E9hwYvD7yl6LcmpySbGiyI0cCbK/wWHE8wVw4VBcbuv01f3Cj6F+mo= +cee.test. 3600 IN NSEC erts.test. RRSIG DS +cee.test. 3600 IN RRSIG NSEC 8 2 3600 20201116135527 20201019135527 1444 test. kIveXrsXAeb7fsc3YNZ6UyJeCGxENpeUAl3mUCW2py+0vXfLjmDNs4FG5cwkLSIrni1z8k4939Bt+/3i+ABE84Utb77LpF29+dIay0L5V9c+avY2rJH8F1JU0kidtFQAccZqXuHtGSHHAi35w09UqghK+hNKfv+7qRUICX5ByCA= +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qname qtype +ADJUST copy_id +REPLY QR AA NXDOMAIN +SECTION QUESTION +domain2.test. IN DS +SECTION ANSWER +SECTION AUTHORITY +test. 3600 IN SOA ns.test. host.test. 20201 3600 1800 604800 3600 +test. 3600 IN RRSIG SOA 8 1 3600 20201116135527 20201019135527 1444 test. IZJIDmEgf0W7A5G7hvvZ2hUqJ9Trbv1/i7ySapDmPbYV9lVCmHHobySxO01yDhI2/Pvpsvxqrm1Tiv3BxH8uzZ4keKgiQjBsSy4htAsFct9I4E7ly2glPj/Fm3oun3PsjJDv5QYhx0KS7w4IQKU7Nc9pfJc92uoUI5bdoC1pRGw= +test. 3600 IN NSEC abc.test. NS SOA DNSKEY RRSIG NSEC +test. 3600 IN RRSIG NSEC 8 1 3600 20201116135527 20201019135527 1444 test. SHU0veyxtCpPvwdJxn2xCEq9xXJZLIvAlYYy7/dBMSjo6ugBPSxs1+8hUZFxks+YQoPLR5nTU0C0yuhZ9dfg/2VGkCYLsDLYnh1lJj6uQ2VgwfhbwSJC0E9hwYvD7yl6LcmpySbGiyI0cCbK/wWHE8wVw4VBcbuv01f3Cj6F+mo= +cee.test. 3600 IN NSEC erts.test. RRSIG DS +cee.test. 3600 IN RRSIG NSEC 8 2 3600 20201116135527 20201019135527 1444 test. kIveXrsXAeb7fsc3YNZ6UyJeCGxENpeUAl3mUCW2py+0vXfLjmDNs4FG5cwkLSIrni1z8k4939Bt+/3i+ABE84Utb77LpF29+dIay0L5V9c+avY2rJH8F1JU0kidtFQAccZqXuHtGSHHAi35w09UqghK+hNKfv+7qRUICX5ByCA= +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qname qtype +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +example.test. IN DS +SECTION ANSWER +example.test. 3600 IN DS 55567 8 2 a2d578906330a10a57d40462257b6ce038bad3f7bf4a45c46c46086e20a94b39 +example.test. 3600 IN RRSIG DS 8 2 3600 20201116135527 20201019135527 1444 test. P7+FTYW2qHuJ4I1YbuvseEz5X1lOYAraGEHB3C5y0OOCQFmhmSiFRdquNi2NlpcS6FXLdsE0EU+Bo1+0atTG4EkMWXbpF21lrtbB51BdsnlX4Mzc/o375fvjiOMwmF6wPCUaOUN62jrVrhsE/hedaVyDphDToqL17ETohwgUO2I= +ENTRY_END + +ENTRY_BEGIN +MATCH opcode subdomain +ADJUST copy_id copy_query +REPLY QR NOERROR +SECTION QUESTION +example.test. IN NS +SECTION AUTHORITY +example.test. IN NS ns.example.test. +example.test. 3600 IN DS 55567 8 2 a2d578906330a10a57d40462257b6ce038bad3f7bf4a45c46c46086e20a94b39 +example.test. 3600 IN RRSIG DS 8 2 3600 20201116135527 20201019135527 1444 test. P7+FTYW2qHuJ4I1YbuvseEz5X1lOYAraGEHB3C5y0OOCQFmhmSiFRdquNi2NlpcS6FXLdsE0EU+Bo1+0atTG4EkMWXbpF21lrtbB51BdsnlX4Mzc/o375fvjiOMwmF6wPCUaOUN62jrVrhsE/hedaVyDphDToqL17ETohwgUO2I= +SECTION ADDITIONAL +ns.example.test. IN A 1.2.3.4 +ENTRY_END + +RANGE_END + +; ns.example.test. +RANGE_BEGIN 0 100 + ADDRESS 1.2.3.4 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +example.test. IN NS +SECTION ANSWER +example.test. IN NS ns.example.test. +example.test. 3600 IN RRSIG NS 8 2 3600 20201116135527 20201019135527 55567 example.test. l1JT0wMlK0YI7/CWHzexf/k0iafUhCgN+BdgjBXIRXmSQNf4HDTiAkbcWL2/15qtnp12nQy9JeiTdSQ3vtPoHAJX4C5uTWaze4ms+Wrrf+n92sLCjacP9x50uuicH3URT6cKb1QCAPwlvlWxIlZjAMYFScSns7+C441NMJT8aE4= +SECTION ADDITIONAL +ns.example.test. IN A 1.2.3.4 +ns.example.test. 3600 IN RRSIG A 8 3 3600 20201116135527 20201019135527 55567 example.test. 2PWaVaccZFQgfPKXNsdEGYUVaashCAj1ZhBo9XRt5eQKUFvZcauBjMnXIuxZFyWeootn1fZGw6GuPI5W48Y0FDx38H6adprkFgQikso2Y64jDdDMWznSo38Z/XqP+U0+kq4vmwonvmEMpm7hKnNEXvhqGKyGzyBwb+CZVJ2L8Eo= +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +ns.example.test. IN A +SECTION ANSWER +ns.example.test. IN A 1.2.3.4 +ns.example.test. 3600 IN RRSIG A 8 3 3600 20201116135527 20201019135527 55567 example.test. 2PWaVaccZFQgfPKXNsdEGYUVaashCAj1ZhBo9XRt5eQKUFvZcauBjMnXIuxZFyWeootn1fZGw6GuPI5W48Y0FDx38H6adprkFgQikso2Y64jDdDMWznSo38Z/XqP+U0+kq4vmwonvmEMpm7hKnNEXvhqGKyGzyBwb+CZVJ2L8Eo= +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +ns.example.test. IN AAAA +SECTION AUTHORITY +example.test. 3600 IN SOA ns.example.test. host.example.test. 20301 3600 1800 604800 3600 +example.test. 3600 IN RRSIG SOA 8 2 3600 20201116135527 20201019135527 55567 example.test. 2UUkScBAN37fJpSrelhE8DotKvmOzj3q9wicaanCIaCv95DE4nQnePih5B+ek3FIRjB/Uv2+z4Ro5Uxy94XAnlK0rCkDLSa0U9U7KP0ytc88sevO0x1SCPAMoZoJO6JqHkv42pdh54WSz+Zb/D8npY0j/tksHe/uX+VQnMymgb8= +ns.example.test. 3600 IN NSEC nz.example.test. A RRSIG +example.test. 3600 IN SOA ns.example.test. host.example.test. 20301 3600 1800 604800 3600 +ENTRY_END + +; response to DNSKEY priming query +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +example.test. IN DNSKEY +SECTION ANSWER +example.test. 3600 IN DNSKEY 257 3 8 AwEAAdug/L739i0mgN2nuK/bhxu3wFn5Ud9nK2+XUmZQlPUEZUC5YZvm1rfMmEWTGBn87fFxEu/kjFZHJ55JLzqsbbpVHLbmKCTT2gYR2FV2WDKROGKuYbVkJIXdKAjJ0ONuK507NinYvlWXIoxHn22KAWOd9wKgSTNHBlmGkX+ts3hh ;{id = 55567 (ksk), size = 1024b} +example.test. 3600 IN RRSIG DNSKEY 8 2 3600 20201116135527 20201019135527 55567 example.test. IbWMC6quOuZFNPAVxQLqCJ9nLhindBo826rnLcg5yMgs9dGUSPOCXAfHTmbgJAUNs9HTFfrJWNvasnETs0UOpmEuifGwWdH1OlME7Gny4RL2QmITUFeMW81Jz1tiVQxFXl6yxT0jxOxvz+bqMHlrz+8IeWQXcO+GZTPu8ueq30g= +ENTRY_END + +; response to query of interest +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +wild.example.test. IN A +SECTION ANSWER +wild.example.test. IN A 10.20.30.40 +; normal RRSIG: +;wild.example.test. 3600 IN RRSIG A 8 3 3600 20201116135527 20201019135527 55567 example.test. RzIsliJeEcLIHQGJqr5U2tfgxjzyxvwpqWYMdF2qOmb5a5erx3AFwRLbHhl7383Kdpdi+KxVKIWmkG6YCta0sWE42UeDXAVtZnFK/VeADRWpzWljQaTdIG8eN6FdB/X3gSPXnxMhsd9OoAWHPJYrwXtoFbciH6Hy0gl4Cosc+7Q= +; with labelcount lower, 2 lower. +; like it is : *.test. IN A 10.20.30.40 +wild.example.test. 3600 IN RRSIG A 8 1 3600 20201116135527 20201019135527 55567 example.test. XR1dPkaZmhIXoKRBmVLiDdIROYxlVNuII0kD/Nh/L3QteSvjwdfDBbVvwWGc+y011is0eFZBlFHWxmhw8BXytktlcJ3rZ1GPbcixkX7XPIIZER7jvZiFnKDS2Fwgh2wgpAXdo8KrmMgeUGPjP9scbzbBxLfSV/ja93PTuTSi6VA= +SECTION AUTHORITY +un.example.test. 3600 IN NSEC xy.example.test. A RRSIG +un.example.test. 3600 IN RRSIG NSEC 8 3 3600 20201116135527 20201019135527 55567 example.test. rP0p0viq8A3y/RSth9BNkDVAufPRVp8uohX+Wp1lby4naxlOjZCONz3aeLz8c0XgrIUtdU4t2sZZz9gF9jfr/QnAr5JDMDAIpMXKYGx/3dk5mSP/OBRX9sNVuBdr9YuyYy5N9K97aKTSQTRNU8FdTVYx9yjImVlwA8KI+SkztJ4= +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +wild2.example.test. IN DS +SECTION ANSWER +wild2.example.test. IN DS 12345 250 2 00000000 +; normal RRSIG +; wild2.example.test. 3600 IN RRSIG DS 8 3 3600 20201116135527 20201019135527 55567 example.test. GdSaD9J3c2QwsAPRbzY7lbuqguaBNEu/19jnZIrdMx4nmRRaMOS1MqMGZyOJdYvupVA0zg32bhCbmZdHSQLwzybjIgixGeiPC9MnfHdSqZKG7QG/AlUR2A/Mhmedp7RngROgIf6c7de88zsX+60xSfvBgNtg55YmODHqOICvTLs= +; with labelcount lower, 2 lower. +; for *.test. IN DS 12345 250 2 00000000 +wild2.example.test. 3600 IN RRSIG DS 8 1 3600 20201116135527 20201019135527 55567 example.test. qZQ/bfTCVuGISm6Pkq6OYDnyZMx8/uQQE3vq402UVWyy7ZrDBVI/CAErEIqz7Xl4cGgFUoezL2LCBIS7BMERCbQ0cVm3YTpnSzCgiDNJsu9W778SLOTcWlqJaiJn9EieFGkiFo2k3c1iuW++yufdhQmuXGk+DdLzE87tgvIkQm0= +SECTION AUTHORITY +un.example.test. 3600 IN NSEC xy.example.test. A RRSIG +un.example.test. 3600 IN RRSIG NSEC 8 3 3600 20201116135527 20201019135527 55567 example.test. rP0p0viq8A3y/RSth9BNkDVAufPRVp8uohX+Wp1lby4naxlOjZCONz3aeLz8c0XgrIUtdU4t2sZZz9gF9jfr/QnAr5JDMDAIpMXKYGx/3dk5mSP/OBRX9sNVuBdr9YuyYy5N9K97aKTSQTRNU8FdTVYx9yjImVlwA8KI+SkztJ4= +ENTRY_END +RANGE_END + +STEP 1 QUERY +ENTRY_BEGIN +REPLY RD DO +SECTION QUESTION +domain1.test. IN A +ENTRY_END + +; Picks up NSECs for aggressive negative cache. +STEP 10 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR RD RA AD DO NXDOMAIN +SECTION QUESTION +domain1.test. IN A +SECTION AUTHORITY +test. 3600 IN SOA ns.test. host.test. 20201 3600 1800 604800 3600 +test. 3600 IN RRSIG SOA 8 1 3600 20201116135527 20201019135527 1444 test. IZJIDmEgf0W7A5G7hvvZ2hUqJ9Trbv1/i7ySapDmPbYV9lVCmHHobySxO01yDhI2/Pvpsvxqrm1Tiv3BxH8uzZ4keKgiQjBsSy4htAsFct9I4E7ly2glPj/Fm3oun3PsjJDv5QYhx0KS7w4IQKU7Nc9pfJc92uoUI5bdoC1pRGw= +test. 3600 IN NSEC abc.test. NS SOA DNSKEY RRSIG NSEC +test. 3600 IN RRSIG NSEC 8 1 3600 20201116135527 20201019135527 1444 test. SHU0veyxtCpPvwdJxn2xCEq9xXJZLIvAlYYy7/dBMSjo6ugBPSxs1+8hUZFxks+YQoPLR5nTU0C0yuhZ9dfg/2VGkCYLsDLYnh1lJj6uQ2VgwfhbwSJC0E9hwYvD7yl6LcmpySbGiyI0cCbK/wWHE8wVw4VBcbuv01f3Cj6F+mo= +cee.test. 3600 IN NSEC erts.test. RRSIG DS +cee.test. 3600 IN RRSIG NSEC 8 2 3600 20201116135527 20201019135527 1444 test. kIveXrsXAeb7fsc3YNZ6UyJeCGxENpeUAl3mUCW2py+0vXfLjmDNs4FG5cwkLSIrni1z8k4939Bt+/3i+ABE84Utb77LpF29+dIay0L5V9c+avY2rJH8F1JU0kidtFQAccZqXuHtGSHHAi35w09UqghK+hNKfv+7qRUICX5ByCA= +ENTRY_END + +STEP 20 QUERY +ENTRY_BEGIN +REPLY RD DO +SECTION QUESTION +wild.example.test. IN A +ENTRY_END + +; query for the wildcard record. +STEP 30 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR RD RA DO SERVFAIL +SECTION QUESTION +wild.example.test. IN A +ENTRY_END +;REPLY QR RD RA AD DO NXDOMAIN +;SECTION QUESTION +;domain1.test. IN A +;SECTION ANSWER +;wild.example.test. IN A 10.20.30.40 +;wild.example.test. 3600 IN RRSIG A 8 1 3600 20201116135527 20201019135527 55567 example.test. XR1dPkaZmhIXoKRBmVLiDdIROYxlVNuII0kD/Nh/L3QteSvjwdfDBbVvwWGc+y011is0eFZBlFHWxmhw8BXytktlcJ3rZ1GPbcixkX7XPIIZER7jvZiFnKDS2Fwgh2wgpAXdo8KrmMgeUGPjP9scbzbBxLfSV/ja93PTuTSi6VA= +;SECTION AUTHORITY +;un.example.test. 3600 IN NSEC xy.example.test. A RRSIG +;un.example.test. 3600 IN RRSIG NSEC 8 3 3600 20201116135527 20201019135527 55567 example.test. rP0p0viq8A3y/RSth9BNkDVAufPRVp8uohX+Wp1lby4naxlOjZCONz3aeLz8c0XgrIUtdU4t2sZZz9gF9jfr/QnAr5JDMDAIpMXKYGx/3dk5mSP/OBRX9sNVuBdr9YuyYy5N9K97aKTSQTRNU8FdTVYx9yjImVlwA8KI+SkztJ4= +;ENTRY_END + +;STEP 40 QUERY +;ENTRY_BEGIN +;REPLY RD DO +;SECTION QUESTION +;domain2.test. IN A +;ENTRY_END +; +;; aggressive cache synthesis, the '*.test' from the wildcard lookup. +;STEP 50 CHECK_ANSWER +;ENTRY_BEGIN +;MATCH all +;REPLY QR RD RA DO SERVFAIL +;SECTION QUESTION +;domain2.test. IN A +;SECTION ANSWER +;ENTRY_END + +STEP 60 QUERY +ENTRY_BEGIN +REPLY RD DO +SECTION QUESTION +wild2.example.test. IN DS +ENTRY_END + +; wild2 is for *.test DS +STEP 70 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR RD RA DO SERVFAIL +SECTION QUESTION +wild2.example.test. IN DS +SECTION ANSWER +ENTRY_END + +STEP 80 QUERY +ENTRY_BEGIN +REPLY RD DO +SECTION QUESTION +domain3.test. IN A +ENTRY_END + +; aggressive cache synthesis, the '*.test' from the wildcard lookup. +; with '*.test' DS record. +STEP 90 CHECK_ANSWER +ENTRY_BEGIN +MATCH all + +; The bad answer +;REPLY QR RD RA DO NOERROR +;SECTION QUESTION +;domain3.test. IN A +;SECTION ANSWER +;domain3.test. 0 IN A 10.20.30.40 +;domain3.test. 0 IN RRSIG A 8 1 3600 20201116135527 20201019135527 55567 example.test. XR1dPkaZmhIXoKRBmVLiDdIROYxlVNuII0kD/Nh/L3QteSvjwdfDBbVvwWGc+y011is0eFZBlFHWxmhw8BXytktlcJ3rZ1GPbcixkX7XPIIZER7jvZiFnKDS2Fwgh2wgpAXdo8KrmMgeUGPjP9scbzbBxLfSV/ja93PTuTSi6VA= ;{id = 55567} +;SECTION AUTHORITY +;cee.test. 0 IN NSEC erts.test. DS RRSIG +;cee.test. 0 IN RRSIG NSEC 8 2 3600 20201116135527 20201019135527 1444 test. kIveXrsXAeb7fsc3YNZ6UyJeCGxENpeUAl3mUCW2py+0vXfLjmDNs4FG5cwkLSIrni1z8k4939Bt+/3i+ABE84Utb77LpF29+dIay0L5V9c+avY2rJH8F1JU0kidtFQAccZqXuHtGSHHAi35w09UqghK+hNKfv+7qRUICX5ByCA= ;{id = 1444} + +; The correct answer +REPLY QR RD RA AD DO NXDOMAIN +SECTION QUESTION +domain3.test. IN A +SECTION AUTHORITY +test. 3600 IN NSEC abc.test. NS SOA DNSKEY RRSIG NSEC +test. 3600 IN RRSIG NSEC 8 1 3600 20201116135527 20201019135527 1444 test. SHU0veyxtCpPvwdJxn2xCEq9xXJZLIvAlYYy7/dBMSjo6ugBPSxs1+8hUZFxks+YQoPLR5nTU0C0yuhZ9dfg/2VGkCYLsDLYnh1lJj6uQ2VgwfhbwSJC0E9hwYvD7yl6LcmpySbGiyI0cCbK/wWHE8wVw4VBcbuv01f3Cj6F+mo= +cee.test. 3600 IN NSEC erts.test. RRSIG DS +cee.test. 3600 IN RRSIG NSEC 8 2 3600 20201116135527 20201019135527 1444 test. kIveXrsXAeb7fsc3YNZ6UyJeCGxENpeUAl3mUCW2py+0vXfLjmDNs4FG5cwkLSIrni1z8k4939Bt+/3i+ABE84Utb77LpF29+dIay0L5V9c+avY2rJH8F1JU0kidtFQAccZqXuHtGSHHAi35w09UqghK+hNKfv+7qRUICX5ByCA= +test. 3600 IN SOA ns.test. host.test. 20201 3600 1800 604800 3600 +test. 3600 IN RRSIG SOA 8 1 3600 20201116135527 20201019135527 1444 test. IZJIDmEgf0W7A5G7hvvZ2hUqJ9Trbv1/i7ySapDmPbYV9lVCmHHobySxO01yDhI2/Pvpsvxqrm1Tiv3BxH8uzZ4keKgiQjBsSy4htAsFct9I4E7ly2glPj/Fm3oun3PsjJDv5QYhx0KS7w4IQKU7Nc9pfJc92uoUI5bdoC1pRGw= +ENTRY_END + +SCENARIO_END From 9ad825b267d5c3d5ccc804973fe6dc02b192cbfa Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Wed, 22 Jul 2026 11:57:13 +0200 Subject: [PATCH 41/84] - Unit test for CVE-2026-50045. --- doc/Changelog | 1 + testdata/val_global_quota.rpl | 358 ++++++++++++++++++++++++++++++++++ 2 files changed, 359 insertions(+) create mode 100644 testdata/val_global_quota.rpl diff --git a/doc/Changelog b/doc/Changelog index ffcfd3ec4..05f81ebc8 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -90,6 +90,7 @@ - Unit test for CVE-2026-42955. - Unit test for CVE-2026-44687. - Unit test for CVE-2026-44690. + - Unit test for CVE-2026-50045. 21 July 2026: Wouter - Merge #1476 from petrvaganoff: ipsecmod: fix possible deref diff --git a/testdata/val_global_quota.rpl b/testdata/val_global_quota.rpl new file mode 100644 index 000000000..baeaa5cbb --- /dev/null +++ b/testdata/val_global_quota.rpl @@ -0,0 +1,358 @@ +; config options +; The island of trust is at test. +server: + trust-anchor: "test. DS 1444 8 2 8a87d067fd09a5965244fe2e317dd26d182c468e0a7f26ecc4c7b479bf89db9b" + val-override-date: "20201020135527" + target-fetch-policy: "0 0 0 0 0" + qname-minimisation: no + fake-sha1: yes + trust-anchor-signaling: no + minimal-responses: no + iter-scrub-promiscuous: no + aggressive-nsec: yes + local-zone: test. nodefault + log-servfail: yes + max-global-quota: 200 + val-max-restart: 5 + +stub-zone: + name: "." + stub-addr: 193.0.14.129 # K.ROOT-SERVERS.NET. +CONFIG_END + +SCENARIO_BEGIN Test global quota with validator + +; K.ROOT-SERVERS.NET. +RANGE_BEGIN 0 100 + ADDRESS 193.0.14.129 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +. IN NS +SECTION ANSWER +. IN NS K.ROOT-SERVERS.NET. +SECTION ADDITIONAL +K.ROOT-SERVERS.NET. IN A 193.0.14.129 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode subdomain +ADJUST copy_id copy_query +REPLY QR NOERROR +SECTION QUESTION +test. IN NS +SECTION AUTHORITY +test. IN NS ns.test. +SECTION ADDITIONAL +ns.test. IN A 1.2.3.5 +ENTRY_END +RANGE_END + +; ns.test +RANGE_BEGIN 0 100 + ADDRESS 1.2.3.5 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +test. IN NS +SECTION ANSWER +test. IN NS ns.test +test. 3600 IN RRSIG NS 8 1 3600 20201116135527 20201019135527 1444 test. RGCxIO32TbbLTk6xZmTr+fjYPH50hntBxeOQ2DIj2pDsmjALcHYtVkOfpfk2EhOhHZd+9PLuoJPbJh6a9NqLSFeBvr0XZoCZoQ2g0tCHUNHcH5EVjA2TuYBQem6DVYnPLJ3914aRx0uA1j42b8dC2xsam/XkOo7U+dLbUW2Os1s= +SECTION ADDITIONAL +ns.test. IN A 1.2.3.5 +ns.test. 3600 IN RRSIG A 8 2 3600 20201116135527 20201019135527 1444 test. GskCc4/k6GjH9V9Jz2V5L2XLiizbOeWkB0feSbf+aN859S3vxVvtuqkvIgwY4LafUO1QAn/pUcv9zA7rcFO++rlg+8t6gvZTo9p3v0bfeIv2uJDsfSBD5jDh0WXlxjekfnrKrQp7zE+GiA93tWwKUWKPvxXDgP+n886e6WcbHJw= +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +ns.test. IN A +SECTION ANSWER +ns.test. IN A 1.2.3.5 +ns.test. 3600 IN RRSIG A 8 2 3600 20201116135527 20201019135527 1444 test. GskCc4/k6GjH9V9Jz2V5L2XLiizbOeWkB0feSbf+aN859S3vxVvtuqkvIgwY4LafUO1QAn/pUcv9zA7rcFO++rlg+8t6gvZTo9p3v0bfeIv2uJDsfSBD5jDh0WXlxjekfnrKrQp7zE+GiA93tWwKUWKPvxXDgP+n886e6WcbHJw= +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +ns.test. IN AAAA +SECTION AUTHORITY +test. 3600 IN SOA ns.test. host.test. 20201 3600 1800 604800 3600 +test. 3600 IN RRSIG SOA 8 1 3600 20201116135527 20201019135527 1444 test. IZJIDmEgf0W7A5G7hvvZ2hUqJ9Trbv1/i7ySapDmPbYV9lVCmHHobySxO01yDhI2/Pvpsvxqrm1Tiv3BxH8uzZ4keKgiQjBsSy4htAsFct9I4E7ly2glPj/Fm3oun3PsjJDv5QYhx0KS7w4IQKU7Nc9pfJc92uoUI5bdoC1pRGw= +ns.test. 3600 IN NSEC nz.test. A RRSIG +ns.test. 3600 IN RRSIG NSEC 8 2 3600 20201116135527 20201019135527 1444 test. PElArVB3KPg8KHAP7lzcNbhFuXNxTsHNTn1dZVncB5qmWRdIaeKpaXDjpH0JSXMaelGFS+/QhuQ6Hmw9+4VyZFRqMzGhw4agUR/2bxABHcDIG4ZpUwyeSP61ATTfHUkQVxaH2wjCWI/tfmesdP2xVE4GXyUvCIBxU914MkZbULU= +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +test. IN DNSKEY +SECTION ANSWER +test. 3600 IN DNSKEY 257 3 8 AwEAAbd9WqjzE2Pynz21OG5doSf9hFzMr5dhzz2waZ3vTa+0o5r7AjTAqmA1yH/B3+aAMihUm5ucZSfVqo7+kOaRE8yFj9aivOmA1n1+JLevJq/oyvQyjxQN2Qb89LyaNUT5oKZIiL+uyyhNW3KDR3SSbQ/GBwQNDHVcZi+JDR3RC0r7 ;{id = 1444 (ksk), size = 1024b} +test. 3600 IN RRSIG DNSKEY 8 1 3600 20201116135527 20201019135527 1444 test. UmRMS4iG9NBBHZYOtpwFFcJgbEb5SfHSgHd9XRe/8pTWM31WSDayn5ViPOBMqI1T5TXg2amc13dDI574xIM2oKMus3b5cBW72jJLW13jprBtslO6P8BMWb4HNnvLrJtQjwf3ErRirtTxinLmywQtmyr1cdthyG3Gp4N7i90fHSc= +SECTION ADDITIONAL +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qname qtype +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +example.test. IN DS +SECTION ANSWER +example.test. 3600 IN DS 55567 8 2 a2d578906330a10a57d40462257b6ce038bad3f7bf4a45c46c46086e20a94b39 +example.test. 3600 IN RRSIG DS 8 2 3600 20201116135527 20201019135527 1444 test. P7+FTYW2qHuJ4I1YbuvseEz5X1lOYAraGEHB3C5y0OOCQFmhmSiFRdquNi2NlpcS6FXLdsE0EU+Bo1+0atTG4EkMWXbpF21lrtbB51BdsnlX4Mzc/o375fvjiOMwmF6wPCUaOUN62jrVrhsE/hedaVyDphDToqL17ETohwgUO2I= +ENTRY_END + +ENTRY_BEGIN +MATCH opcode subdomain +ADJUST copy_id copy_query +REPLY QR NOERROR +SECTION QUESTION +example.test. IN NS +SECTION AUTHORITY +example.test. IN NS ns.example.test. +example.test. 3600 IN DS 55567 8 2 a2d578906330a10a57d40462257b6ce038bad3f7bf4a45c46c46086e20a94b39 +example.test. 3600 IN RRSIG DS 8 2 3600 20201116135527 20201019135527 1444 test. P7+FTYW2qHuJ4I1YbuvseEz5X1lOYAraGEHB3C5y0OOCQFmhmSiFRdquNi2NlpcS6FXLdsE0EU+Bo1+0atTG4EkMWXbpF21lrtbB51BdsnlX4Mzc/o375fvjiOMwmF6wPCUaOUN62jrVrhsE/hedaVyDphDToqL17ETohwgUO2I= +SECTION ADDITIONAL +ns.example.test. IN A 1.2.3.4 +ENTRY_END +RANGE_END + +; ns.example.test. +RANGE_BEGIN 0 100 + ADDRESS 1.2.3.4 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +example.test. IN NS +SECTION ANSWER +example.test. IN NS ns.example.test. +example.test. 3600 IN RRSIG NS 8 2 3600 20201116135527 20201019135527 55567 example.test. l1JT0wMlK0YI7/CWHzexf/k0iafUhCgN+BdgjBXIRXmSQNf4HDTiAkbcWL2/15qtnp12nQy9JeiTdSQ3vtPoHAJX4C5uTWaze4ms+Wrrf+n92sLCjacP9x50uuicH3URT6cKb1QCAPwlvlWxIlZjAMYFScSns7+C441NMJT8aE4= +SECTION ADDITIONAL +ns.example.test. IN A 1.2.3.4 +ns.example.test. 3600 IN RRSIG A 8 3 3600 20201116135527 20201019135527 55567 example.test. 2PWaVaccZFQgfPKXNsdEGYUVaashCAj1ZhBo9XRt5eQKUFvZcauBjMnXIuxZFyWeootn1fZGw6GuPI5W48Y0FDx38H6adprkFgQikso2Y64jDdDMWznSo38Z/XqP+U0+kq4vmwonvmEMpm7hKnNEXvhqGKyGzyBwb+CZVJ2L8Eo= +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +ns.example.test. IN A +SECTION ANSWER +ns.example.test. IN A 1.2.3.4 +ns.example.test. 3600 IN RRSIG A 8 3 3600 20201116135527 20201019135527 55567 example.test. 2PWaVaccZFQgfPKXNsdEGYUVaashCAj1ZhBo9XRt5eQKUFvZcauBjMnXIuxZFyWeootn1fZGw6GuPI5W48Y0FDx38H6adprkFgQikso2Y64jDdDMWznSo38Z/XqP+U0+kq4vmwonvmEMpm7hKnNEXvhqGKyGzyBwb+CZVJ2L8Eo= +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +ns.example.test. IN AAAA +SECTION AUTHORITY +example.test. 3600 IN SOA ns.example.test. host.example.test. 20301 3600 1800 604800 3600 +example.test. 3600 IN RRSIG SOA 8 2 3600 20201116135527 20201019135527 55567 example.test. 2UUkScBAN37fJpSrelhE8DotKvmOzj3q9wicaanCIaCv95DE4nQnePih5B+ek3FIRjB/Uv2+z4Ro5Uxy94XAnlK0rCkDLSa0U9U7KP0ytc88sevO0x1SCPAMoZoJO6JqHkv42pdh54WSz+Zb/D8npY0j/tksHe/uX+VQnMymgb8= +ns.example.test. 3600 IN NSEC nz.example.test. A RRSIG +example.test. 3600 IN SOA ns.example.test. host.example.test. 20301 3600 1800 604800 3600 +ENTRY_END + +; response to DNSKEY priming query +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +example.test. IN DNSKEY +SECTION ANSWER +example.test. 3600 IN DNSKEY 257 3 8 AwEAAdug/L739i0mgN2nuK/bhxu3wFn5Ud9nK2+XUmZQlPUEZUC5YZvm1rfMmEWTGBn87fFxEu/kjFZHJ55JLzqsbbpVHLbmKCTT2gYR2FV2WDKROGKuYbVkJIXdKAjJ0ONuK507NinYvlWXIoxHn22KAWOd9wKgSTNHBlmGkX+ts3hh ;{id = 55567 (ksk), size = 1024b} +example.test. 3600 IN RRSIG DNSKEY 8 2 3600 20201116135527 20201019135527 55567 example.test. IbWMC6quOuZFNPAVxQLqCJ9nLhindBo826rnLcg5yMgs9dGUSPOCXAfHTmbgJAUNs9HTFfrJWNvasnETs0UOpmEuifGwWdH1OlME7Gny4RL2QmITUFeMW81Jz1tiVQxFXl6yxT0jxOxvz+bqMHlrz+8IeWQXcO+GZTPu8ueq30g= +ENTRY_END + +; response to query of interest +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +;a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.example.test. IN A +a.a.a.a.a.example.test. IN A +SECTION ANSWER +; unsigned answer +;a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.example.test. 3600 IN A 10.20.30.40 +a.a.a.a.a.example.test. IN A 10.20.30.40 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +a.example.test. IN DS +SECTION ANSWER +SECTION AUTHORITY +example.test. 3600 IN SOA ns.example.test. host.example.test. 20301 3600 1800 604800 3600 +example.test. 3600 IN RRSIG SOA 8 2 3600 20201116135527 20201019135527 55567 example.test. 2UUkScBAN37fJpSrelhE8DotKvmOzj3q9wicaanCIaCv95DE4nQnePih5B+ek3FIRjB/Uv2+z4Ro5Uxy94XAnlK0rCkDLSa0U9U7KP0ytc88sevO0x1SCPAMoZoJO6JqHkv42pdh54WSz+Zb/D8npY0j/tksHe/uX+VQnMymgb8= +a.example.test. IN NSEC \000.a.example.test. NSEC RRSIG TYPE128 +a.example.test. 3600 IN RRSIG NSEC 8 3 3600 20201116135527 20201019135527 55567 example.test. IFHxa61iaEHyRiTrez1FNy6TArOerKgaRwkhjwEk8basD2SZ7wP63ZvxfiNJlg3VAKxM8RApT4GIXDLBT2IlAK5I+K2Ti1dxUw9XwhnOpVf7/4WDEEgQKBsYPVrSAGVig9v5eefuEmVQTphHovDAAOkKYd4rCJ50WX+ckLsP658= +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +a.a.example.test. IN DS +SECTION ANSWER +SECTION AUTHORITY +example.test. 3600 IN SOA ns.example.test. host.example.test. 20301 3600 1800 604800 3600 +example.test. 3600 IN RRSIG SOA 8 2 3600 20201116135527 20201019135527 55567 example.test. 2UUkScBAN37fJpSrelhE8DotKvmOzj3q9wicaanCIaCv95DE4nQnePih5B+ek3FIRjB/Uv2+z4Ro5Uxy94XAnlK0rCkDLSa0U9U7KP0ytc88sevO0x1SCPAMoZoJO6JqHkv42pdh54WSz+Zb/D8npY0j/tksHe/uX+VQnMymgb8= +a.a.example.test. IN NSEC \000.a.a.example.test. NSEC RRSIG TYPE128 +a.a.example.test. 3600 IN RRSIG NSEC 8 4 3600 20201116135527 20201019135527 55567 example.test. hqwhNsdny2lWkGnWhF7WaIyWhGDJHBVzJbwO8wZ6e+SIfQtt4AFFqXqCtqOIN9u+jjU7YT8SdABQLEoFm6TN47+lC0689fpO59xx4qOnpSyCRA0sRMDW4AcwVDHLECLGEMzI7xHdCPGFGxYl+abr5lcw+A4sH5RomJ0DTRKbB10= +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +a.a.a.example.test. IN DS +SECTION ANSWER +SECTION AUTHORITY +example.test. 3600 IN SOA ns.example.test. host.example.test. 20301 3600 1800 604800 3600 +example.test. 3600 IN RRSIG SOA 8 2 3600 20201116135527 20201019135527 55567 example.test. 2UUkScBAN37fJpSrelhE8DotKvmOzj3q9wicaanCIaCv95DE4nQnePih5B+ek3FIRjB/Uv2+z4Ro5Uxy94XAnlK0rCkDLSa0U9U7KP0ytc88sevO0x1SCPAMoZoJO6JqHkv42pdh54WSz+Zb/D8npY0j/tksHe/uX+VQnMymgb8= +a.a.a.example.test. IN NSEC \000.a.a.a.example.test. NSEC RRSIG TYPE128 +a.a.a.example.test. 3600 IN RRSIG NSEC 8 5 3600 20201116135527 20201019135527 55567 example.test. afjzouI1IgcLgzkuTUTZC6yBZ2IDhWjV1s7zIRuL9fdf1sKvhooJK7+1mnozoQ4VCbLWFVoD4NX/nKcB5ALxJYjEw28qcAYtMwEme3Hj0FNoe7NR2M/nQonqvGfYo6FAUJBJfvcJaXMROAyw16R8qdlpqBgdecfk8LfEcJ3DcWA= +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +a.a.a.a.example.test. IN DS +SECTION ANSWER +SECTION AUTHORITY +example.test. 3600 IN SOA ns.example.test. host.example.test. 20301 3600 1800 604800 3600 +example.test. 3600 IN RRSIG SOA 8 2 3600 20201116135527 20201019135527 55567 example.test. 2UUkScBAN37fJpSrelhE8DotKvmOzj3q9wicaanCIaCv95DE4nQnePih5B+ek3FIRjB/Uv2+z4Ro5Uxy94XAnlK0rCkDLSa0U9U7KP0ytc88sevO0x1SCPAMoZoJO6JqHkv42pdh54WSz+Zb/D8npY0j/tksHe/uX+VQnMymgb8= +a.a.a.a.example.test. IN NSEC \000.a.a.a.a.example.test. NSEC RRSIG TYPE128 +a.a.a.a.example.test. 3600 IN RRSIG NSEC 8 6 3600 20201116135527 20201019135527 55567 example.test. F9mOk6KyGI6LSDgs7l50fPIWQVz65bO2ONxoZe4BzE0NqCTloznQ7r0QXXMmcI6IAC2W7RjsymsViTexuMI8sLaFvzNoM5jDLnic4KFZFIuUy1oA/2Hd56TwofK/KctbkQQpT8IDKc/pnxp8iXTjwNsV31fzZ5RPWt2YqpMfY4Q= +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +a.a.a.a.a.example.test. IN DS +SECTION ANSWER +SECTION AUTHORITY +example.test. 3600 IN SOA ns.example.test. host.example.test. 20301 3600 1800 604800 3600 +example.test. 3600 IN RRSIG SOA 8 2 3600 20201116135527 20201019135527 55567 example.test. 2UUkScBAN37fJpSrelhE8DotKvmOzj3q9wicaanCIaCv95DE4nQnePih5B+ek3FIRjB/Uv2+z4Ro5Uxy94XAnlK0rCkDLSa0U9U7KP0ytc88sevO0x1SCPAMoZoJO6JqHkv42pdh54WSz+Zb/D8npY0j/tksHe/uX+VQnMymgb8= +a.a.a.a.a.example.test. IN NSEC \000.a.a.a.a.a.example.test. NSEC RRSIG TYPE128 +a.a.a.a.a.example.test. 3600 IN RRSIG NSEC 8 7 3600 20201116135527 20201019135527 55567 example.test. AlEcceF3UARAczJQW7/skNbf0t6gum2LEfFWilZtakpdrnnl+HkIR8LttE/gOjLFv/MWzSWPLDmsj9Be/7PsWWddJwN0hgrskt/W4KBPdYkBhgnrfxOMYm6ZTDY6EUh9iTUAdWP0SDmfG7Zs5UIhIpsYEnxsYF0oRT5HZpYMBq8= +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +a.a.a.a.a.a.example.test. IN DS +SECTION ANSWER +SECTION AUTHORITY +example.test. 3600 IN SOA ns.example.test. host.example.test. 20301 3600 1800 604800 3600 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +a.example.test. IN A +SECTION ANSWER +SECTION AUTHORITY +example.test. 3600 IN SOA ns.example.test. host.example.test. 20301 3600 1800 604800 3600 +example.test. 3600 IN RRSIG SOA 8 2 3600 20201116135527 20201019135527 55567 example.test. 2UUkScBAN37fJpSrelhE8DotKvmOzj3q9wicaanCIaCv95DE4nQnePih5B+ek3FIRjB/Uv2+z4Ro5Uxy94XAnlK0rCkDLSa0U9U7KP0ytc88sevO0x1SCPAMoZoJO6JqHkv42pdh54WSz+Zb/D8npY0j/tksHe/uX+VQnMymgb8= +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +a.a.example.test. IN A +SECTION ANSWER +SECTION AUTHORITY +example.test. 3600 IN SOA ns.example.test. host.example.test. 20301 3600 1800 604800 3600 +example.test. 3600 IN RRSIG SOA 8 2 3600 20201116135527 20201019135527 55567 example.test. 2UUkScBAN37fJpSrelhE8DotKvmOzj3q9wicaanCIaCv95DE4nQnePih5B+ek3FIRjB/Uv2+z4Ro5Uxy94XAnlK0rCkDLSa0U9U7KP0ytc88sevO0x1SCPAMoZoJO6JqHkv42pdh54WSz+Zb/D8npY0j/tksHe/uX+VQnMymgb8= +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +a.a.a.example.test. IN A +SECTION ANSWER +SECTION AUTHORITY +example.test. 3600 IN SOA ns.example.test. host.example.test. 20301 3600 1800 604800 3600 +example.test. 3600 IN RRSIG SOA 8 2 3600 20201116135527 20201019135527 55567 example.test. 2UUkScBAN37fJpSrelhE8DotKvmOzj3q9wicaanCIaCv95DE4nQnePih5B+ek3FIRjB/Uv2+z4Ro5Uxy94XAnlK0rCkDLSa0U9U7KP0ytc88sevO0x1SCPAMoZoJO6JqHkv42pdh54WSz+Zb/D8npY0j/tksHe/uX+VQnMymgb8= +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +a.a.a.a.example.test. IN A +SECTION ANSWER +SECTION AUTHORITY +example.test. 3600 IN SOA ns.example.test. host.example.test. 20301 3600 1800 604800 3600 +example.test. 3600 IN RRSIG SOA 8 2 3600 20201116135527 20201019135527 55567 example.test. 2UUkScBAN37fJpSrelhE8DotKvmOzj3q9wicaanCIaCv95DE4nQnePih5B+ek3FIRjB/Uv2+z4Ro5Uxy94XAnlK0rCkDLSa0U9U7KP0ytc88sevO0x1SCPAMoZoJO6JqHkv42pdh54WSz+Zb/D8npY0j/tksHe/uX+VQnMymgb8= +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +a.a.a.a.a.example.test. IN A +SECTION ANSWER +SECTION AUTHORITY +example.test. 3600 IN SOA ns.example.test. host.example.test. 20301 3600 1800 604800 3600 +example.test. 3600 IN RRSIG SOA 8 2 3600 20201116135527 20201019135527 55567 example.test. 2UUkScBAN37fJpSrelhE8DotKvmOzj3q9wicaanCIaCv95DE4nQnePih5B+ek3FIRjB/Uv2+z4Ro5Uxy94XAnlK0rCkDLSa0U9U7KP0ytc88sevO0x1SCPAMoZoJO6JqHkv42pdh54WSz+Zb/D8npY0j/tksHe/uX+VQnMymgb8= +ENTRY_END + +RANGE_END + +STEP 1 QUERY +ENTRY_BEGIN +REPLY RD DO +SECTION QUESTION +a.a.a.a.a.example.test. IN A +ENTRY_END + +STEP 10 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR RD RA DO SERVFAIL +SECTION QUESTION +a.a.a.a.a.example.test. IN A +SECTION ANSWER +ENTRY_END + +SCENARIO_END From 1ae2570bda9972aa0459e8650db2d6107f5f07d9 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Wed, 22 Jul 2026 11:58:18 +0200 Subject: [PATCH 42/84] - Unit test for CVE-2026-46582. --- doc/Changelog | 1 + testdata/serve_expired_wildcard_swap_ad.rpl | 330 ++++++++++++++++++++ 2 files changed, 331 insertions(+) create mode 100644 testdata/serve_expired_wildcard_swap_ad.rpl diff --git a/doc/Changelog b/doc/Changelog index 05f81ebc8..b22dd316d 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -90,6 +90,7 @@ - Unit test for CVE-2026-42955. - Unit test for CVE-2026-44687. - Unit test for CVE-2026-44690. + - Unit test for CVE-2026-46582. - Unit test for CVE-2026-50045. 21 July 2026: Wouter diff --git a/testdata/serve_expired_wildcard_swap_ad.rpl b/testdata/serve_expired_wildcard_swap_ad.rpl new file mode 100644 index 000000000..9bfd5caaa --- /dev/null +++ b/testdata/serve_expired_wildcard_swap_ad.rpl @@ -0,0 +1,330 @@ +; config options +; The island of trust is at test. +server: + trust-anchor: "test. DS 1444 8 2 8a87d067fd09a5965244fe2e317dd26d182c468e0a7f26ecc4c7b479bf89db9b" + val-override-date: "20201020135527" + target-fetch-policy: "0 0 0 0 0" + qname-minimisation: "no" + fake-sha1: yes + trust-anchor-signaling: no + minimal-responses: no + iter-scrub-promiscuous: no + aggressive-nsec: no + local-zone: test. nodefault + log-servfail: yes + serve-expired: yes + serve-expired-client-timeout: 0 + +stub-zone: + name: "." + stub-addr: 193.0.14.129 # K.ROOT-SERVERS.NET. +CONFIG_END + +SCENARIO_BEGIN Test serve-expired on wildcard secure from another message + +; K.ROOT-SERVERS.NET. +RANGE_BEGIN 0 100 + ADDRESS 193.0.14.129 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +. IN NS +SECTION ANSWER +. IN NS K.ROOT-SERVERS.NET. +SECTION ADDITIONAL +K.ROOT-SERVERS.NET. IN A 193.0.14.129 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode subdomain +ADJUST copy_id copy_query +REPLY QR NOERROR +SECTION QUESTION +test. IN NS +SECTION AUTHORITY +test. IN NS ns.test. +SECTION ADDITIONAL +ns.test. IN A 1.2.3.5 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +alias.other.tld. IN A +SECTION ANSWER +alias.other.tld. IN CNAME www.example.test. +ENTRY_END +RANGE_END + +; ns.test +RANGE_BEGIN 0 100 + ADDRESS 1.2.3.5 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +test. IN NS +SECTION ANSWER +test. IN NS ns.test +test. 3600 IN RRSIG NS 8 1 3600 20201116135527 20201019135527 1444 test. RGCxIO32TbbLTk6xZmTr+fjYPH50hntBxeOQ2DIj2pDsmjALcHYtVkOfpfk2EhOhHZd+9PLuoJPbJh6a9NqLSFeBvr0XZoCZoQ2g0tCHUNHcH5EVjA2TuYBQem6DVYnPLJ3914aRx0uA1j42b8dC2xsam/XkOo7U+dLbUW2Os1s= +SECTION ADDITIONAL +ns.test. IN A 1.2.3.5 +ns.test. 3600 IN RRSIG A 8 2 3600 20201116135527 20201019135527 1444 test. GskCc4/k6GjH9V9Jz2V5L2XLiizbOeWkB0feSbf+aN859S3vxVvtuqkvIgwY4LafUO1QAn/pUcv9zA7rcFO++rlg+8t6gvZTo9p3v0bfeIv2uJDsfSBD5jDh0WXlxjekfnrKrQp7zE+GiA93tWwKUWKPvxXDgP+n886e6WcbHJw= +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +ns.test. IN A +SECTION ANSWER +ns.test. IN A 1.2.3.5 +ns.test. 3600 IN RRSIG A 8 2 3600 20201116135527 20201019135527 1444 test. GskCc4/k6GjH9V9Jz2V5L2XLiizbOeWkB0feSbf+aN859S3vxVvtuqkvIgwY4LafUO1QAn/pUcv9zA7rcFO++rlg+8t6gvZTo9p3v0bfeIv2uJDsfSBD5jDh0WXlxjekfnrKrQp7zE+GiA93tWwKUWKPvxXDgP+n886e6WcbHJw= +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +ns.test. IN AAAA +SECTION AUTHORITY +test. 3600 IN SOA ns.test. host.test. 20201 3600 1800 604800 3600 +test. 3600 IN RRSIG SOA 8 1 3600 20201116135527 20201019135527 1444 test. IZJIDmEgf0W7A5G7hvvZ2hUqJ9Trbv1/i7ySapDmPbYV9lVCmHHobySxO01yDhI2/Pvpsvxqrm1Tiv3BxH8uzZ4keKgiQjBsSy4htAsFct9I4E7ly2glPj/Fm3oun3PsjJDv5QYhx0KS7w4IQKU7Nc9pfJc92uoUI5bdoC1pRGw= +ns.test. 3600 IN NSEC nz.test. A RRSIG +ns.test. 3600 IN RRSIG NSEC 8 2 3600 20201116135527 20201019135527 1444 test. PElArVB3KPg8KHAP7lzcNbhFuXNxTsHNTn1dZVncB5qmWRdIaeKpaXDjpH0JSXMaelGFS+/QhuQ6Hmw9+4VyZFRqMzGhw4agUR/2bxABHcDIG4ZpUwyeSP61ATTfHUkQVxaH2wjCWI/tfmesdP2xVE4GXyUvCIBxU914MkZbULU= +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +test. IN DNSKEY +SECTION ANSWER +test. 3600 IN DNSKEY 257 3 8 AwEAAbd9WqjzE2Pynz21OG5doSf9hFzMr5dhzz2waZ3vTa+0o5r7AjTAqmA1yH/B3+aAMihUm5ucZSfVqo7+kOaRE8yFj9aivOmA1n1+JLevJq/oyvQyjxQN2Qb89LyaNUT5oKZIiL+uyyhNW3KDR3SSbQ/GBwQNDHVcZi+JDR3RC0r7 ;{id = 1444 (ksk), size = 1024b} +test. 3600 IN RRSIG DNSKEY 8 1 3600 20201116135527 20201019135527 1444 test. UmRMS4iG9NBBHZYOtpwFFcJgbEb5SfHSgHd9XRe/8pTWM31WSDayn5ViPOBMqI1T5TXg2amc13dDI574xIM2oKMus3b5cBW72jJLW13jprBtslO6P8BMWb4HNnvLrJtQjwf3ErRirtTxinLmywQtmyr1cdthyG3Gp4N7i90fHSc= +SECTION ADDITIONAL +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qname qtype +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +example.test. IN DS +SECTION ANSWER +example.test. 3600 IN DS 55567 8 2 a2d578906330a10a57d40462257b6ce038bad3f7bf4a45c46c46086e20a94b39 +example.test. 3600 IN RRSIG DS 8 2 3600 20201116135527 20201019135527 1444 test. P7+FTYW2qHuJ4I1YbuvseEz5X1lOYAraGEHB3C5y0OOCQFmhmSiFRdquNi2NlpcS6FXLdsE0EU+Bo1+0atTG4EkMWXbpF21lrtbB51BdsnlX4Mzc/o375fvjiOMwmF6wPCUaOUN62jrVrhsE/hedaVyDphDToqL17ETohwgUO2I= +ENTRY_END + +ENTRY_BEGIN +MATCH opcode subdomain +ADJUST copy_id copy_query +REPLY QR NOERROR +SECTION QUESTION +example.test. IN NS +SECTION AUTHORITY +example.test. IN NS ns.example.test. +example.test. 3600 IN DS 55567 8 2 a2d578906330a10a57d40462257b6ce038bad3f7bf4a45c46c46086e20a94b39 +example.test. 3600 IN RRSIG DS 8 2 3600 20201116135527 20201019135527 1444 test. P7+FTYW2qHuJ4I1YbuvseEz5X1lOYAraGEHB3C5y0OOCQFmhmSiFRdquNi2NlpcS6FXLdsE0EU+Bo1+0atTG4EkMWXbpF21lrtbB51BdsnlX4Mzc/o375fvjiOMwmF6wPCUaOUN62jrVrhsE/hedaVyDphDToqL17ETohwgUO2I= +SECTION ADDITIONAL +ns.example.test. IN A 1.2.3.4 +ENTRY_END +RANGE_END + +; ns.example.test. +RANGE_BEGIN 0 25 + ADDRESS 1.2.3.4 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +example.test. IN NS +SECTION ANSWER +example.test. IN NS ns.example.test. +example.test. 3600 IN RRSIG NS 8 2 3600 20201116135527 20201019135527 55567 example.test. l1JT0wMlK0YI7/CWHzexf/k0iafUhCgN+BdgjBXIRXmSQNf4HDTiAkbcWL2/15qtnp12nQy9JeiTdSQ3vtPoHAJX4C5uTWaze4ms+Wrrf+n92sLCjacP9x50uuicH3URT6cKb1QCAPwlvlWxIlZjAMYFScSns7+C441NMJT8aE4= +SECTION ADDITIONAL +ns.example.test. IN A 1.2.3.4 +ns.example.test. 3600 IN RRSIG A 8 3 3600 20201116135527 20201019135527 55567 example.test. 2PWaVaccZFQgfPKXNsdEGYUVaashCAj1ZhBo9XRt5eQKUFvZcauBjMnXIuxZFyWeootn1fZGw6GuPI5W48Y0FDx38H6adprkFgQikso2Y64jDdDMWznSo38Z/XqP+U0+kq4vmwonvmEMpm7hKnNEXvhqGKyGzyBwb+CZVJ2L8Eo= +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +ns.example.test. IN A +SECTION ANSWER +ns.example.test. IN A 1.2.3.4 +ns.example.test. 3600 IN RRSIG A 8 3 3600 20201116135527 20201019135527 55567 example.test. 2PWaVaccZFQgfPKXNsdEGYUVaashCAj1ZhBo9XRt5eQKUFvZcauBjMnXIuxZFyWeootn1fZGw6GuPI5W48Y0FDx38H6adprkFgQikso2Y64jDdDMWznSo38Z/XqP+U0+kq4vmwonvmEMpm7hKnNEXvhqGKyGzyBwb+CZVJ2L8Eo= +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +ns.example.test. IN AAAA +SECTION AUTHORITY +example.test. 3600 IN SOA ns.example.test. host.example.test. 20301 3600 1800 604800 3600 +example.test. 3600 IN RRSIG SOA 8 2 3600 20201116135527 20201019135527 55567 example.test. 2UUkScBAN37fJpSrelhE8DotKvmOzj3q9wicaanCIaCv95DE4nQnePih5B+ek3FIRjB/Uv2+z4Ro5Uxy94XAnlK0rCkDLSa0U9U7KP0ytc88sevO0x1SCPAMoZoJO6JqHkv42pdh54WSz+Zb/D8npY0j/tksHe/uX+VQnMymgb8= +ns.example.test. 3600 IN NSEC nz.example.test. A RRSIG +ENTRY_END + +; response to DNSKEY priming query +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +example.test. IN DNSKEY +SECTION ANSWER +example.test. 3600 IN DNSKEY 257 3 8 AwEAAdug/L739i0mgN2nuK/bhxu3wFn5Ud9nK2+XUmZQlPUEZUC5YZvm1rfMmEWTGBn87fFxEu/kjFZHJ55JLzqsbbpVHLbmKCTT2gYR2FV2WDKROGKuYbVkJIXdKAjJ0ONuK507NinYvlWXIoxHn22KAWOd9wKgSTNHBlmGkX+ts3hh ;{id = 55567 (ksk), size = 1024b} +example.test. 3600 IN RRSIG DNSKEY 8 2 3600 20201116135527 20201019135527 55567 example.test. IbWMC6quOuZFNPAVxQLqCJ9nLhindBo826rnLcg5yMgs9dGUSPOCXAfHTmbgJAUNs9HTFfrJWNvasnETs0UOpmEuifGwWdH1OlME7Gny4RL2QmITUFeMW81Jz1tiVQxFXl6yxT0jxOxvz+bqMHlrz+8IeWQXcO+GZTPu8ueq30g= +ENTRY_END + +; This wildcard exists in the zone. It is signed by example.test. +; *.example.test. 10 IN A 10.20.30.33 +; +; *.example.test. 10 IN A 10.20.30.33 +; *.example.test. 10 IN RRSIG A 8 2 10 20201116135527 20201019135527 55567 example.test. txqAQLRwy7ZdpExOnpLAQ1/xOz7gOp5C3XB/vg3CoTqvUtGqJ2MxEc3H0XtCfhSJJocbIof+lQSleAzs+Y/B0FV7YruCzPoNlZDW7qVaY0fITTwef97ui3AbxkOpNEptVN3xxsH3o5AYKAmh+oePzBsZlxmP4KuF9DoKl9/m52M= + +; response to query of interest +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +www.example.test. IN A +SECTION ANSWER +www.example.test. 10 IN A 10.20.30.40 +www.example.test. 10 IN RRSIG A 8 3 10 20201116135527 20201019135527 55567 example.test. 1by1cfB/FwdGm2gH/TUmn9KYzyIpd1i2iDwHXayd4uOuYC/v4CCHwl1pbhlz4J7WNoetG7QmVNKhXQyH1446BEUOpEe0skYOYb0r+gk3Cv6BwTH+bAzkiseFLUQ/YVbmUmLOXMm0fN1rP6sUM1aDBd+ugIr0UNPQJcbCTMjtvIQ= +ENTRY_END +RANGE_END + +RANGE_BEGIN 25 45 + ADDRESS 1.2.3.4 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +ns.example.test. IN A +SECTION ANSWER +ns.example.test. IN A 1.2.3.4 +ns.example.test. 3600 IN RRSIG A 8 3 3600 20201116135527 20201019135527 55567 example.test. 2PWaVaccZFQgfPKXNsdEGYUVaashCAj1ZhBo9XRt5eQKUFvZcauBjMnXIuxZFyWeootn1fZGw6GuPI5W48Y0FDx38H6adprkFgQikso2Y64jDdDMWznSo38Z/XqP+U0+kq4vmwonvmEMpm7hKnNEXvhqGKyGzyBwb+CZVJ2L8Eo= +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +ns.example.test. IN AAAA +SECTION AUTHORITY +example.test. 3600 IN SOA ns.example.test. host.example.test. 20301 3600 1800 604800 3600 +example.test. 3600 IN RRSIG SOA 8 2 3600 20201116135527 20201019135527 55567 example.test. 2UUkScBAN37fJpSrelhE8DotKvmOzj3q9wicaanCIaCv95DE4nQnePih5B+ek3FIRjB/Uv2+z4Ro5Uxy94XAnlK0rCkDLSa0U9U7KP0ytc88sevO0x1SCPAMoZoJO6JqHkv42pdh54WSz+Zb/D8npY0j/tksHe/uX+VQnMymgb8= +ns.example.test. 3600 IN NSEC nz.example.test. A RRSIG +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +www.example.test. IN A +SECTION ANSWER +; the wildcard put as the www.example.test. +www.example.test. 10 IN A 10.20.30.33 +www.example.test. 10 IN RRSIG A 8 2 10 20201116135527 20201019135527 55567 example.test. txqAQLRwy7ZdpExOnpLAQ1/xOz7gOp5C3XB/vg3CoTqvUtGqJ2MxEc3H0XtCfhSJJocbIof+lQSleAzs+Y/B0FV7YruCzPoNlZDW7qVaY0fITTwef97ui3AbxkOpNEptVN3xxsH3o5AYKAmh+oePzBsZlxmP4KuF9DoKl9/m52M= +; no wildcard NSEC proof +ENTRY_END +RANGE_END + +RANGE_BEGIN 45 65 + ADDRESS 1.2.3.4 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +www.example.test. IN A +SECTION ANSWER +www.example.test. 10 IN A 10.20.30.40 +www.example.test. 10 IN RRSIG A 8 3 10 20201116135527 20201019135527 55567 example.test. 1by1cfB/FwdGm2gH/TUmn9KYzyIpd1i2iDwHXayd4uOuYC/v4CCHwl1pbhlz4J7WNoetG7QmVNKhXQyH1446BEUOpEe0skYOYb0r+gk3Cv6BwTH+bAzkiseFLUQ/YVbmUmLOXMm0fN1rP6sUM1aDBd+ugIr0UNPQJcbCTMjtvIQ= +ENTRY_END +RANGE_END + +; query for a message in cache that is going to be expired, +; and then get an RRset replaced. +; The query is for a record not covered by the wildcard in the zone. + +STEP 1 QUERY +ENTRY_BEGIN +REPLY RD DO +SECTION QUESTION +www.example.test. IN A +ENTRY_END + +STEP 10 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR RD RA AD DO NOERROR +SECTION QUESTION +www.example.test. IN A +SECTION ANSWER +www.example.test. 10 IN A 10.20.30.40 +www.example.test. 10 IN RRSIG A 8 3 10 20201116135527 20201019135527 55567 example.test. 1by1cfB/FwdGm2gH/TUmn9KYzyIpd1i2iDwHXayd4uOuYC/v4CCHwl1pbhlz4J7WNoetG7QmVNKhXQyH1446BEUOpEe0skYOYb0r+gk3Cv6BwTH+bAzkiseFLUQ/YVbmUmLOXMm0fN1rP6sUM1aDBd+ugIr0UNPQJcbCTMjtvIQ= +ENTRY_END + +; wait to expire the message +STEP 20 TIME_PASSES ELAPSE 18 + +; swap the www.example.test. RRset with another. + +STEP 30 QUERY +ENTRY_BEGIN +REPLY RD DO +SECTION QUESTION +alias.other.tld. IN A +ENTRY_END + +STEP 40 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR RD RA DO SERVFAIL +SECTION QUESTION +alias.other.tld. IN A +SECTION ANSWER +; the wildcard proof fails here, but it validated the RRSIG over +; wildcard A record. +ENTRY_END + +; the serve expired response +STEP 50 QUERY +ENTRY_BEGIN +REPLY RD DO +SECTION QUESTION +www.example.test. IN A +ENTRY_END + +STEP 60 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR RD RA AD DO NOERROR +SECTION QUESTION +www.example.test. IN A +SECTION ANSWER +www.example.test. 10 IN A 10.20.30.40 +www.example.test. 10 IN RRSIG A 8 3 10 20201116135527 20201019135527 55567 example.test. 1by1cfB/FwdGm2gH/TUmn9KYzyIpd1i2iDwHXayd4uOuYC/v4CCHwl1pbhlz4J7WNoetG7QmVNKhXQyH1446BEUOpEe0skYOYb0r+gk3Cv6BwTH+bAzkiseFLUQ/YVbmUmLOXMm0fN1rP6sUM1aDBd+ugIr0UNPQJcbCTMjtvIQ= +ENTRY_END + +SCENARIO_END From 63501f51bb699cd47aca09c02fd8f6e24b6bf83c Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Wed, 22 Jul 2026 11:59:36 +0200 Subject: [PATCH 43/84] - Unit test for CVE-2026-50243. --- doc/Changelog | 1 + testdata/respip_bogus_rewrite.rpl | 216 ++++++++++++++++++++++++++++++ 2 files changed, 217 insertions(+) create mode 100644 testdata/respip_bogus_rewrite.rpl diff --git a/doc/Changelog b/doc/Changelog index b22dd316d..125f32d42 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -92,6 +92,7 @@ - Unit test for CVE-2026-44690. - Unit test for CVE-2026-46582. - Unit test for CVE-2026-50045. + - Unit test for CVE-2026-50243. 21 July 2026: Wouter - Merge #1476 from petrvaganoff: ipsecmod: fix possible deref diff --git a/testdata/respip_bogus_rewrite.rpl b/testdata/respip_bogus_rewrite.rpl new file mode 100644 index 000000000..7f80612dd --- /dev/null +++ b/testdata/respip_bogus_rewrite.rpl @@ -0,0 +1,216 @@ +; config options +; The island of trust is at test. +server: + trust-anchor: "test. DS 1444 8 2 8a87d067fd09a5965244fe2e317dd26d182c468e0a7f26ecc4c7b479bf89db9b" + val-override-date: "20201020135527" + target-fetch-policy: "0 0 0 0 0" + qname-minimisation: "no" + fake-sha1: yes + trust-anchor-signaling: no + minimal-responses: no + iter-scrub-promiscuous: no + aggressive-nsec: yes + local-zone: test. nodefault + log-servfail: yes + module-config: "respip validator iterator" + response-ip: 192.0.2.0/24 redirect + response-ip-data: 192.0.2.0/24 "A 10.10.10.10" + +stub-zone: + name: "." + stub-addr: 193.0.14.129 # K.ROOT-SERVERS.NET. +CONFIG_END + +SCENARIO_BEGIN Test respip with rewrite of a bogus reply + +; K.ROOT-SERVERS.NET. +RANGE_BEGIN 0 100 + ADDRESS 193.0.14.129 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +. IN NS +SECTION ANSWER +. IN NS K.ROOT-SERVERS.NET. +SECTION ADDITIONAL +K.ROOT-SERVERS.NET. IN A 193.0.14.129 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode subdomain +ADJUST copy_id copy_query +REPLY QR NOERROR +SECTION QUESTION +test. IN NS +SECTION AUTHORITY +test. IN NS ns.test. +SECTION ADDITIONAL +ns.test. IN A 1.2.3.5 +ENTRY_END +RANGE_END + +; ns.test +RANGE_BEGIN 0 100 + ADDRESS 1.2.3.5 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +test. IN NS +SECTION ANSWER +test. IN NS ns.test +test. 3600 IN RRSIG NS 8 1 3600 20201116135527 20201019135527 1444 test. RGCxIO32TbbLTk6xZmTr+fjYPH50hntBxeOQ2DIj2pDsmjALcHYtVkOfpfk2EhOhHZd+9PLuoJPbJh6a9NqLSFeBvr0XZoCZoQ2g0tCHUNHcH5EVjA2TuYBQem6DVYnPLJ3914aRx0uA1j42b8dC2xsam/XkOo7U+dLbUW2Os1s= +SECTION ADDITIONAL +ns.test. IN A 1.2.3.5 +ns.test. 3600 IN RRSIG A 8 2 3600 20201116135527 20201019135527 1444 test. GskCc4/k6GjH9V9Jz2V5L2XLiizbOeWkB0feSbf+aN859S3vxVvtuqkvIgwY4LafUO1QAn/pUcv9zA7rcFO++rlg+8t6gvZTo9p3v0bfeIv2uJDsfSBD5jDh0WXlxjekfnrKrQp7zE+GiA93tWwKUWKPvxXDgP+n886e6WcbHJw= +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +ns.test. IN A +SECTION ANSWER +ns.test. IN A 1.2.3.5 +ns.test. 3600 IN RRSIG A 8 2 3600 20201116135527 20201019135527 1444 test. GskCc4/k6GjH9V9Jz2V5L2XLiizbOeWkB0feSbf+aN859S3vxVvtuqkvIgwY4LafUO1QAn/pUcv9zA7rcFO++rlg+8t6gvZTo9p3v0bfeIv2uJDsfSBD5jDh0WXlxjekfnrKrQp7zE+GiA93tWwKUWKPvxXDgP+n886e6WcbHJw= +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +ns.test. IN AAAA +SECTION AUTHORITY +test. 3600 IN SOA ns.test. host.test. 20201 3600 1800 604800 3600 +test. 3600 IN RRSIG SOA 8 1 3600 20201116135527 20201019135527 1444 test. IZJIDmEgf0W7A5G7hvvZ2hUqJ9Trbv1/i7ySapDmPbYV9lVCmHHobySxO01yDhI2/Pvpsvxqrm1Tiv3BxH8uzZ4keKgiQjBsSy4htAsFct9I4E7ly2glPj/Fm3oun3PsjJDv5QYhx0KS7w4IQKU7Nc9pfJc92uoUI5bdoC1pRGw= +ns.test. 3600 IN NSEC nz.test. A RRSIG +ns.test. 3600 IN RRSIG NSEC 8 2 3600 20201116135527 20201019135527 1444 test. PElArVB3KPg8KHAP7lzcNbhFuXNxTsHNTn1dZVncB5qmWRdIaeKpaXDjpH0JSXMaelGFS+/QhuQ6Hmw9+4VyZFRqMzGhw4agUR/2bxABHcDIG4ZpUwyeSP61ATTfHUkQVxaH2wjCWI/tfmesdP2xVE4GXyUvCIBxU914MkZbULU= +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +test. IN DNSKEY +SECTION ANSWER +test. 3600 IN DNSKEY 257 3 8 AwEAAbd9WqjzE2Pynz21OG5doSf9hFzMr5dhzz2waZ3vTa+0o5r7AjTAqmA1yH/B3+aAMihUm5ucZSfVqo7+kOaRE8yFj9aivOmA1n1+JLevJq/oyvQyjxQN2Qb89LyaNUT5oKZIiL+uyyhNW3KDR3SSbQ/GBwQNDHVcZi+JDR3RC0r7 ;{id = 1444 (ksk), size = 1024b} +test. 3600 IN RRSIG DNSKEY 8 1 3600 20201116135527 20201019135527 1444 test. UmRMS4iG9NBBHZYOtpwFFcJgbEb5SfHSgHd9XRe/8pTWM31WSDayn5ViPOBMqI1T5TXg2amc13dDI574xIM2oKMus3b5cBW72jJLW13jprBtslO6P8BMWb4HNnvLrJtQjwf3ErRirtTxinLmywQtmyr1cdthyG3Gp4N7i90fHSc= +SECTION ADDITIONAL +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qname qtype +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +example.test. IN DS +SECTION ANSWER +example.test. 3600 IN DS 55567 8 2 a2d578906330a10a57d40462257b6ce038bad3f7bf4a45c46c46086e20a94b39 +example.test. 3600 IN RRSIG DS 8 2 3600 20201116135527 20201019135527 1444 test. P7+FTYW2qHuJ4I1YbuvseEz5X1lOYAraGEHB3C5y0OOCQFmhmSiFRdquNi2NlpcS6FXLdsE0EU+Bo1+0atTG4EkMWXbpF21lrtbB51BdsnlX4Mzc/o375fvjiOMwmF6wPCUaOUN62jrVrhsE/hedaVyDphDToqL17ETohwgUO2I= +ENTRY_END + +ENTRY_BEGIN +MATCH opcode subdomain +ADJUST copy_id copy_query +REPLY QR NOERROR +SECTION QUESTION +example.test. IN NS +SECTION AUTHORITY +example.test. IN NS ns.example.test. +example.test. 3600 IN DS 55567 8 2 a2d578906330a10a57d40462257b6ce038bad3f7bf4a45c46c46086e20a94b39 +example.test. 3600 IN RRSIG DS 8 2 3600 20201116135527 20201019135527 1444 test. P7+FTYW2qHuJ4I1YbuvseEz5X1lOYAraGEHB3C5y0OOCQFmhmSiFRdquNi2NlpcS6FXLdsE0EU+Bo1+0atTG4EkMWXbpF21lrtbB51BdsnlX4Mzc/o375fvjiOMwmF6wPCUaOUN62jrVrhsE/hedaVyDphDToqL17ETohwgUO2I= +SECTION ADDITIONAL +ns.example.test. IN A 1.2.3.4 +ENTRY_END +RANGE_END + +; ns.example.test. +RANGE_BEGIN 0 100 + ADDRESS 1.2.3.4 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +example.test. IN NS +SECTION ANSWER +example.test. IN NS ns.example.test. +example.test. 3600 IN RRSIG NS 8 2 3600 20201116135527 20201019135527 55567 example.test. l1JT0wMlK0YI7/CWHzexf/k0iafUhCgN+BdgjBXIRXmSQNf4HDTiAkbcWL2/15qtnp12nQy9JeiTdSQ3vtPoHAJX4C5uTWaze4ms+Wrrf+n92sLCjacP9x50uuicH3URT6cKb1QCAPwlvlWxIlZjAMYFScSns7+C441NMJT8aE4= +SECTION ADDITIONAL +ns.example.test. IN A 1.2.3.4 +ns.example.test. 3600 IN RRSIG A 8 3 3600 20201116135527 20201019135527 55567 example.test. 2PWaVaccZFQgfPKXNsdEGYUVaashCAj1ZhBo9XRt5eQKUFvZcauBjMnXIuxZFyWeootn1fZGw6GuPI5W48Y0FDx38H6adprkFgQikso2Y64jDdDMWznSo38Z/XqP+U0+kq4vmwonvmEMpm7hKnNEXvhqGKyGzyBwb+CZVJ2L8Eo= +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +ns.example.test. IN A +SECTION ANSWER +ns.example.test. IN A 1.2.3.4 +ns.example.test. 3600 IN RRSIG A 8 3 3600 20201116135527 20201019135527 55567 example.test. 2PWaVaccZFQgfPKXNsdEGYUVaashCAj1ZhBo9XRt5eQKUFvZcauBjMnXIuxZFyWeootn1fZGw6GuPI5W48Y0FDx38H6adprkFgQikso2Y64jDdDMWznSo38Z/XqP+U0+kq4vmwonvmEMpm7hKnNEXvhqGKyGzyBwb+CZVJ2L8Eo= +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +ns.example.test. IN AAAA +SECTION AUTHORITY +example.test. 3600 IN SOA ns.example.test. host.example.test. 20301 3600 1800 604800 3600 +example.test. 3600 IN RRSIG SOA 8 2 3600 20201116135527 20201019135527 55567 example.test. 2UUkScBAN37fJpSrelhE8DotKvmOzj3q9wicaanCIaCv95DE4nQnePih5B+ek3FIRjB/Uv2+z4Ro5Uxy94XAnlK0rCkDLSa0U9U7KP0ytc88sevO0x1SCPAMoZoJO6JqHkv42pdh54WSz+Zb/D8npY0j/tksHe/uX+VQnMymgb8= +ns.example.test. 3600 IN NSEC nz.example.test. A RRSIG +example.test. 3600 IN SOA ns.example.test. host.example.test. 20301 3600 1800 604800 3600 +ENTRY_END + +; response to DNSKEY priming query +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +example.test. IN DNSKEY +SECTION ANSWER +example.test. 3600 IN DNSKEY 257 3 8 AwEAAdug/L739i0mgN2nuK/bhxu3wFn5Ud9nK2+XUmZQlPUEZUC5YZvm1rfMmEWTGBn87fFxEu/kjFZHJ55JLzqsbbpVHLbmKCTT2gYR2FV2WDKROGKuYbVkJIXdKAjJ0ONuK507NinYvlWXIoxHn22KAWOd9wKgSTNHBlmGkX+ts3hh ;{id = 55567 (ksk), size = 1024b} +example.test. 3600 IN RRSIG DNSKEY 8 2 3600 20201116135527 20201019135527 55567 example.test. IbWMC6quOuZFNPAVxQLqCJ9nLhindBo826rnLcg5yMgs9dGUSPOCXAfHTmbgJAUNs9HTFfrJWNvasnETs0UOpmEuifGwWdH1OlME7Gny4RL2QmITUFeMW81Jz1tiVQxFXl6yxT0jxOxvz+bqMHlrz+8IeWQXcO+GZTPu8ueq30g= +ENTRY_END + +; response to query of interest +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +www.example.test. IN A +SECTION ANSWER +; this is expired +www.example.test. 3600 IN A 192.0.2.1 +www.example.test. 3600 IN RRSIG A 8 3 3600 20181116135527 20181019135527 55567 example.test. GIyjRM2i5plokjqjH7DRCaEi3AnP8+Wf02fOW6vrDSThr/yvvFXYKLhYwfddPNZRehANOmLQxuXyk6pEHh26Mi7T2Gh7n0SNkQ79e3Ba4Zu6Pih0nRBuEDvlSXjcFzvY9jx+7zZolg3KW8eC/Fn7moxAuDT/+1ZgZdhMOQ802+Q= +ENTRY_END +RANGE_END + +STEP 1 QUERY +ENTRY_BEGIN +REPLY RD DO +SECTION QUESTION +www.example.test. IN A +ENTRY_END + +STEP 10 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR RD RA DO SERVFAIL +SECTION QUESTION +www.example.test. IN A +SECTION ANSWER +ENTRY_END + +SCENARIO_END From eed3f1ab3805d85aeebbe000c360df5eb1e1dab9 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Wed, 22 Jul 2026 12:00:19 +0200 Subject: [PATCH 44/84] - Unit test for CVE-2026-50248. --- doc/Changelog | 1 + testdata/auth_xfr_host_bogus.rpl | 294 +++++++++++++++++++++++++++++++ 2 files changed, 295 insertions(+) create mode 100644 testdata/auth_xfr_host_bogus.rpl diff --git a/doc/Changelog b/doc/Changelog index 125f32d42..e6f663931 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -93,6 +93,7 @@ - Unit test for CVE-2026-46582. - Unit test for CVE-2026-50045. - Unit test for CVE-2026-50243. + - Unit test for CVE-2026-50248. 21 July 2026: Wouter - Merge #1476 from petrvaganoff: ipsecmod: fix possible deref diff --git a/testdata/auth_xfr_host_bogus.rpl b/testdata/auth_xfr_host_bogus.rpl new file mode 100644 index 000000000..bd8973855 --- /dev/null +++ b/testdata/auth_xfr_host_bogus.rpl @@ -0,0 +1,294 @@ +; config options +server: + trust-anchor: "example.net. 3600 IN DS 29332 8 2 fe9d2d1f797b8dbe717febca0b7ff2125e0bdc819eb529008aad5630e61d4d99" + val-override-date: "20201020135527" + target-fetch-policy: "0 0 0 0 0" + fake-sha1: yes + trust-anchor-signaling: no + target-fetch-policy: "0 0 0 0 0" + qname-minimisation: no + +auth-zone: + name: "example.com." + master: ns.example.net. + for-downstream: yes + for-upstream: yes + ## fallback-enabled: no + ## this line generates zonefile: \n"/tmp/xxx.example.com"\n + zonefile: +TEMPFILE_NAME example.com + ## this is the inline file /tmp/xxx.example.com + ## the tempfiles are deleted when the testrun is over. +TEMPFILE_CONTENTS example.com +TEMPFILE_END + +stub-zone: + name: "." + stub-addr: 193.0.14.129 # K.ROOT-SERVERS.NET. +CONFIG_END + +SCENARIO_BEGIN Test authority zone with bogus host name lookup + +; K.ROOT-SERVERS.NET. +RANGE_BEGIN 0 100 + ADDRESS 193.0.14.129 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +. IN NS +SECTION ANSWER +. IN NS K.ROOT-SERVERS.NET. +SECTION ADDITIONAL +K.ROOT-SERVERS.NET. IN A 193.0.14.129 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode subdomain +ADJUST copy_id copy_query +REPLY QR NOERROR +SECTION QUESTION +com. IN NS +SECTION AUTHORITY +com. IN NS a.gtld-servers.net. +SECTION ADDITIONAL +a.gtld-servers.net. IN A 192.5.6.30 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode subdomain +ADJUST copy_id copy_query +REPLY QR NOERROR +SECTION QUESTION +example.net. IN A +SECTION ANSWER +SECTION AUTHORITY +example.net. IN NS ns2.example.net. +SECTION ADDITIONAL +ns2.example.net. IN A 1.2.3.45 +ENTRY_END +RANGE_END + +; a.gtld-servers.net. +RANGE_BEGIN 0 100 + ADDRESS 192.5.6.30 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +com. IN NS +SECTION ANSWER +com. IN NS a.gtld-servers.net. +SECTION ADDITIONAL +a.gtld-servers.net. IN A 192.5.6.30 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode subdomain +ADJUST copy_id copy_query +REPLY QR NOERROR +SECTION QUESTION +example.com. IN NS +SECTION AUTHORITY +example.com. IN NS ns.example.com. +SECTION ADDITIONAL +ns.example.com. IN A 1.2.3.44 +ENTRY_END +RANGE_END + +; ns.example.com. +RANGE_BEGIN 0 100 + ADDRESS 1.2.3.44 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +example.com. IN NS +SECTION ANSWER +example.com. IN NS ns.example.com. +SECTION ADDITIONAL +ns.example.com. IN A 1.2.3.44 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +ns.example.com. IN A +SECTION ANSWER +ns.example.com. IN A 1.2.3.44 +SECTION AUTHORITY +example.com. IN NS ns.example.com. +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +ns.example.com. IN AAAA +SECTION AUTHORITY +example.com. IN NS ns.example.com. +SECTION ADDITIONAL +www.example.com. IN A 1.2.3.44 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +example.com. IN NS +SECTION ANSWER +example.com. IN NS ns.example.com. +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +www.example.com. IN A +SECTION ANSWER +www.example.com. IN A 10.20.30.40 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +example.com. IN SOA +SECTION ANSWER +; serial, refresh, retry, expire, minimum +example.com. IN SOA ns.example.com. hostmaster.example.com. 1 3600 900 86400 3600 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +example.com. IN AXFR +SECTION ANSWER +example.com. IN SOA ns.example.com. hostmaster.example.com. 1 3600 900 86400 3600 +example.com. IN NS ns.example.com. +www.example.com. IN A 1.2.3.4 +example.com. IN SOA ns.example.com. hostmaster.example.com. 1 3600 900 86400 3600 +ENTRY_END +RANGE_END + +; ns2.example.net +RANGE_BEGIN 0 100 + ADDRESS 1.2.3.45 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +ns2.example.net. IN A +SECTION ANSWER +ns2.example.net. 3600 IN A 1.2.3.45 +ns2.example.net. 3600 IN RRSIG A 8 3 3600 20201116135527 20201019135527 29332 example.net. ttH0qGYFJp0zfoqb6h9cDGhkosucRPI64gd3+i7gwAcbOtfGJhHR7+NQ7uH+gRRv4lzPEiWP6zM7IiSeC1o+gW/Y2u6J1a330KzikT1YxIWGQJ825NU3PJ5ifTC8IgrN8HFwBuof3K4x/ftdA9VRcyCbFicazOD4RLlbhffMpoVQKyRa/NqHT8mSWLPry9q9skgdyRk17f65i0sdSCEyCXv8+vX6vBxaMF3in+zQxvnA9nyB4omwLLJZx3jaF0+lSiBcx3u20DTbCC/cyjxJArhLlv1N5U3GRUpFXl1d7k0FmacQCP4H5UXSzy6vf6XoQwtfIgNzwYgFN5RuCdJ71w== +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +ns2.example.net. IN AAAA +SECTION ANSWER +SECTION AUTHORITY +example.net. 3600 IN SOA ns.example.com. root.example.com. 4 14400 3600 604800 3600 +example.net. 3600 IN RRSIG SOA 8 2 3600 20201116135527 20201019135527 29332 example.net. OWDPS0sJQOhZlqKUbdL8OVwT0u1e1asbjW+9dMRxIF/VoxxRaYIqD/lsn1U+irRrbIPDp9wxDdFu7ChddB1n2/do/by9xuIMLD00mkxSJduxMjRl/8hWvhBV6j8jqU0pbsxS3Oolcju8imrobEqqCDi1YVD6OQuBzwnQ7trF9mfANv208pDA4chWXWUimFETKzpc3aLarcm3qVnb53AQhggyLow/ZLG1egbwaGn3pcf+kPHw+G4MSOR1TtS0mWKiPgdYRiqSS+AqrZUu/ZuAKAGweKeIypDgm6RZC5M4FmRA+f8gZg2rI2Xog6TLt0qrjD8ARwXkyBq8wL3G0Ihkew== +ns2.example.net. 3600 IN NSEC ns3.example.net. A RRSIG NSEC +ns2.example.net. 3600 IN RRSIG NSEC 8 3 3600 20201116135527 20201019135527 29332 example.net. MMDdm3yz6Ocreg8HE7Cf9EnIJ5NFCVzEv+I9zBeUR90pFBlrBY4LqmMxC5GXoEEc1iql5XpPkIspsWTkCUSWutoiDh4Vlg54HrZ4ONy8GzVzg5ePcuXT51nYq1xjfDx4Yi124GT/QKx4+B7HFoyFfoRT1Kf+uP3c7F7qK+VB3FrBBQpl7f6dX87qO23Bb+Vp+L0RPCmuLkhdnrM34bB6jT1lGwgsD4upDy81XKSH6uces8D/fvl0+Evzcy3gkKlxY6uzV53cUD0FM9AVg7/ZWXwQe5n7PIU9gzQ3xtnH5MA8fG6iUyVQJixjqzEqjJdh2PMJA31qTT2X6LQO95ZM2w== +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +example.net. IN DNSKEY +SECTION ANSWER +example.net. 3600 IN DNSKEY 257 3 8 AwEAAb4WMOTBLTFvmBra5m6SK4VfViOzmvyUAU0qv861ZQXeEFvwlndqNU9rwRsMxrSWAYs5nHErKDn49usC/HyxxW1477iGFHhfgL4mjNreJm9zft2QFB1VLbRbEPYdDMLCn4co0qnG7/KG8W2i8Pym1L7f+aREwbLo+/716AS2PbaKMhfWLKLiq5wnBcUClQMNzCiwhqxDJp1oePqfkVdeUgXOtgi0dYRIKyQFhJ5VWJ22npoi/Gif0XLCADAlAwRLKc8o/yJkCxskzgpHpw5Cki1lclg0aq4ssOuPRQ+ne6IHYCz9D2mwzulblhLFamKdq7aHzNt4NlyxhpANVFiKLD8= ;{id = 29332 (ksk), size = 2048b} +example.net. 3600 IN RRSIG DNSKEY 8 2 3600 20201116135527 20201019135527 29332 example.net. a0AqvyBN1Dr1Try1RBjbWjhaaTj3WGpSBywSxLu09bElAFinC3kUgk/WTjfsIIxruUHmzVgPssYeb5g79rdaz7YanSi06LQsnEjMS+hexSU6TXBCtJnhA8taKPlPj+qBRQL/Ptju72upty6Mw8eMG05QOQOa2WC5mPLgo2k6PmgsBMyW3Rhn+lldlmz1NZIZ3udDHs6xxX6Gjio67ogGm0MUbWRZo68oGt/xYv6JzZAVzZROlWvs5D+pf1Mrfzn3yOMJ0jh2XTXJAiw3vX+i2k/P/Yfscm7BWULJ7fBx+0JcDuYccd2mj9ijmD7KuM/laFSIUvxAixu7gV2TDrKEjw== +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +ns.example.net. IN A +SECTION ANSWER +ns.example.net. IN A 1.2.3.44 +; bad RRSIG +ns.example.net. 3600 IN RRSIG A 8 2 3600 20201116135527 20201019135527 29332 example.net. a0AqvyBN1Dr1Try1RBjbWjhaaTj3WGpSBywSxLu09bElAFinC3kUgk/WTjfsIIxruUHmzVgPssYeb5g79rdaz7YanSi06LQsnEjMS+hexSU6TXBCtJnhA8taKPlPj+qBRQL/Ptju72upty6Mw8eMG05QOQOa2WC5mPLgo2k6PmgsBMyW3Rhn+lldlmz1NZIZ3udDHs6xxX6Gjio67ogGm0MUbWRZo68oGt/xYv6JzZAVzZROlWvs5D+pf1Mrfzn3yOMJ0jh2XTXJAiw3vX+i2k/P/Yfscm7BWULJ7fBx+0JcDuYccd2mj9ijmD7KuM/laFSIUvxAixu7gV2TDrKEjw== +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +ns.example.net. IN AAAA +SECTION ANSWER +SECTION AUTHORITY +example.net. 3600 IN SOA ns.example.com. root.example.com. 4 14400 3600 604800 3600 +example.net. 3600 IN RRSIG SOA 8 2 3600 20201116135527 20201019135527 29332 example.net. OWDPS0sJQOhZlqKUbdL8OVwT0u1e1asbjW+9dMRxIF/VoxxRaYIqD/lsn1U+irRrbIPDp9wxDdFu7ChddB1n2/do/by9xuIMLD00mkxSJduxMjRl/8hWvhBV6j8jqU0pbsxS3Oolcju8imrobEqqCDi1YVD6OQuBzwnQ7trF9mfANv208pDA4chWXWUimFETKzpc3aLarcm3qVnb53AQhggyLow/ZLG1egbwaGn3pcf+kPHw+G4MSOR1TtS0mWKiPgdYRiqSS+AqrZUu/ZuAKAGweKeIypDgm6RZC5M4FmRA+f8gZg2rI2Xog6TLt0qrjD8ARwXkyBq8wL3G0Ihkew== +ns.example.net. 3600 IN NSEC ns2.example.net. A RRSIG NSEC +ns.example.net. 3600 IN RRSIG NSEC 8 3 3600 20201116135527 20201019135527 29332 example.net. bwmn1nX0amfcIK6+NXdX7i3VvebPGpVLd0Ry0P+5JbiLCO3lI8kbXxpQh2jpIAKAdfSq+WZPGAhwOSOTVak1mEcYf5xLvmiKWmGz0LH8RTCzQTAlcQTnuybmQWuwBjIXaetVQ1ADiJZK57M41d5lOE0KqWe5xfAHE+UhMOQ6JhQwLFK/QfQJB7ke1itM/qfsJHgdb/rbT7v7G8Nd342NMCZEgzP/wFyZ3JRP0XY5D7K71IuFZd9NfxXkKRMn5UM/lMDITqE3MknzXnsKJcH9SpoykKMya9SsrwI+IuOxpQkyiyd+N33H3di4uWI1MiWdayQnR2D3HhHi1Vdp42CDxQ== +ENTRY_END +RANGE_END + + +STEP 1 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +www.example.com. IN A +ENTRY_END + +; recursion happens here. +STEP 20 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR AA RD RA SERVFAIL +SECTION QUESTION +www.example.com. IN A +SECTION ANSWER +ENTRY_END + +STEP 30 TIME_PASSES ELAPSE 10 +STEP 40 TRAFFIC + +STEP 50 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +www.example.com. IN A +ENTRY_END + +; The bogus host was not used. +STEP 60 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR AA RD RA SERVFAIL +SECTION QUESTION +www.example.com. IN A +SECTION ANSWER +ENTRY_END + +; the zonefile was updated with new contents +STEP 70 CHECK_TEMPFILE example.com +FILE_BEGIN +FILE_END + +SCENARIO_END From c163fbc505283b1900d4ff10708ae6e2665239af Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Wed, 22 Jul 2026 12:03:48 +0200 Subject: [PATCH 45/84] - Unit test for CVE-2026-55717. --- doc/Changelog | 1 + testdata/serve_expired_respip_cname.rpl | 290 +++++++++++++++++++++ testdata/serve_expired_respip_drop.rpl | 274 ++++++++++++++++++++ testdata/serve_expired_rpz_drop.rpl | 246 ++++++++++++++++++ testdata/serve_expired_rpz_nx.rpl | 319 ++++++++++++++++++++++++ 5 files changed, 1130 insertions(+) create mode 100644 testdata/serve_expired_respip_cname.rpl create mode 100644 testdata/serve_expired_respip_drop.rpl create mode 100644 testdata/serve_expired_rpz_drop.rpl create mode 100644 testdata/serve_expired_rpz_nx.rpl diff --git a/doc/Changelog b/doc/Changelog index e6f663931..55c9806e6 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -94,6 +94,7 @@ - Unit test for CVE-2026-50045. - Unit test for CVE-2026-50243. - Unit test for CVE-2026-50248. + - Unit test for CVE-2026-55717. 21 July 2026: Wouter - Merge #1476 from petrvaganoff: ipsecmod: fix possible deref diff --git a/testdata/serve_expired_respip_cname.rpl b/testdata/serve_expired_respip_cname.rpl new file mode 100644 index 000000000..977836f5a --- /dev/null +++ b/testdata/serve_expired_respip_cname.rpl @@ -0,0 +1,290 @@ +; config options +; The island of trust is at test. +server: + target-fetch-policy: "0 0 0 0 0" + qname-minimisation: "no" + fake-sha1: yes + trust-anchor-signaling: no + minimal-responses: no + iter-scrub-promiscuous: no + aggressive-nsec: no + local-zone: test. nodefault + log-servfail: yes + discard-timeout: 0 + module-config: "respip iterator" + serve-expired: yes + serve-expired-client-timeout: 500 + serve-expired-ttl: 3600 + serve-expired-reply-ttl: 30 + response-ip: 192.0.2.0/24 redirect + response-ip-data: 192.0.2.0/24 "CNAME tgt.far.test." + +stub-zone: + name: "." + stub-addr: 193.0.14.129 # K.ROOT-SERVERS.NET. +CONFIG_END + +SCENARIO_BEGIN Test expired response respip rewrite + +; K.ROOT-SERVERS.NET. +RANGE_BEGIN 0 100 + ADDRESS 193.0.14.129 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +. IN NS +SECTION ANSWER +. IN NS K.ROOT-SERVERS.NET. +SECTION ADDITIONAL +K.ROOT-SERVERS.NET. IN A 193.0.14.129 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode subdomain +ADJUST copy_id copy_query +REPLY QR NOERROR +SECTION QUESTION +test. IN NS +SECTION AUTHORITY +test. IN NS ns.test. +SECTION ADDITIONAL +ns.test. IN A 1.2.3.5 +ENTRY_END +RANGE_END + +; ns.test +RANGE_BEGIN 0 100 + ADDRESS 1.2.3.5 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +test. IN NS +SECTION ANSWER +test. IN NS ns.test +SECTION ADDITIONAL +ns.test. IN A 1.2.3.5 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +ns.test. IN A +SECTION ANSWER +ns.test. IN A 1.2.3.5 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +ns.test. IN AAAA +SECTION AUTHORITY +test. 3600 IN SOA ns.test. host.test. 20201 3600 1800 604800 3600 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode subdomain +ADJUST copy_id copy_query +REPLY QR NOERROR +SECTION QUESTION +example.test. IN NS +SECTION AUTHORITY +example.test. IN NS ns.example.test. +SECTION ADDITIONAL +ns.example.test. IN A 1.2.3.4 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode subdomain +ADJUST copy_id copy_query +REPLY QR NOERROR +SECTION QUESTION +far.test. IN NS +SECTION AUTHORITY +far.test. IN NS ns.far.test. +SECTION ADDITIONAL +ns.far.test. IN A 1.2.3.6 +ENTRY_END +RANGE_END + +; ns.example.test. +RANGE_BEGIN 0 20 + ADDRESS 1.2.3.4 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +example.test. IN NS +SECTION ANSWER +example.test. IN NS ns.example.test. +SECTION ADDITIONAL +ns.example.test. IN A 1.2.3.4 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +ns.example.test. IN A +SECTION ANSWER +ns.example.test. IN A 1.2.3.4 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +ns.example.test. IN AAAA +SECTION AUTHORITY +example.test. 3600 IN SOA ns.example.test. host.example.test. 20301 3600 1800 604800 3600 +ENTRY_END + +; response to query of interest +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +www.example.test. IN A +SECTION ANSWER +www.example.test. 1 IN A 192.0.2.1 +ENTRY_END +RANGE_END + +; ns.example.test. +RANGE_BEGIN 45 100 + ADDRESS 1.2.3.4 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +www.example.test. IN A +SECTION ANSWER +www.example.test. 1 IN A 192.0.2.1 +ENTRY_END +RANGE_END + +; ns.far.test. +RANGE_BEGIN 0 100 + ADDRESS 1.2.3.6 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +far.test. IN NS +SECTION ANSWER +far.test. IN NS ns.far.test. +SECTION ADDITIONAL +ns.far.test. IN A 1.2.3.6 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +ns.far.test. IN A +SECTION ANSWER +ns.far.test. IN A 1.2.3.6 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +ns.far.test. IN AAAA +SECTION AUTHORITY +far.test. 3600 IN SOA ns.far.test. host.far.test. 20301 3600 1800 604800 3600 +ENTRY_END + +; response to query of interest +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +tgt.far.test. IN A +SECTION ANSWER +tgt.far.test. 1 IN A 10.20.30.40 +ENTRY_END +RANGE_END + +; Put items with TTL 1 in cache. +STEP 1 QUERY +ENTRY_BEGIN +REPLY RD DO +SECTION QUESTION +tgt.far.test. IN A +ENTRY_END + +STEP 2 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR RD RA DO NOERROR +SECTION QUESTION +tgt.far.test. IN A +SECTION ANSWER +tgt.far.test. 1 IN A 10.20.30.40 +ENTRY_END + +STEP 10 QUERY +ENTRY_BEGIN +REPLY RD DO +SECTION QUESTION +www.example.test. IN A +ENTRY_END + +STEP 11 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR RD RA DO NOERROR +SECTION QUESTION +www.example.test. IN A +SECTION ANSWER +www.example.test. 3600 IN CNAME tgt.far.test. +tgt.far.test. 1 IN A 10.20.30.40 +ENTRY_END + +; Move time to expire the cache entries. +STEP 20 TIME_PASSES ELAPSE 2 + +; the upstream RANGE is removed, so serve-expired has to act. + +STEP 30 QUERY +ENTRY_BEGIN +REPLY RD DO +SECTION QUESTION +www.example.test. IN A +ENTRY_END + +; for serve expired callback. +STEP 31 TIME_PASSES ELAPSE 2 + +STEP 40 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR RD RA DO NOERROR +SECTION QUESTION +www.example.test. IN A +SECTION ANSWER +www.example.test. 3600 IN CNAME tgt.far.test. +tgt.far.test. 1 IN A 10.20.30.40 +ENTRY_END + +; The pending lookup for the data, that was answered with expired to the client. +STEP 50 TRAFFIC + +SCENARIO_END diff --git a/testdata/serve_expired_respip_drop.rpl b/testdata/serve_expired_respip_drop.rpl new file mode 100644 index 000000000..de9eee7be --- /dev/null +++ b/testdata/serve_expired_respip_drop.rpl @@ -0,0 +1,274 @@ +; config options +; The island of trust is at test. +server: + target-fetch-policy: "0 0 0 0 0" + qname-minimisation: "no" + fake-sha1: yes + trust-anchor-signaling: no + minimal-responses: no + iter-scrub-promiscuous: no + aggressive-nsec: no + local-zone: test. nodefault + log-servfail: yes + discard-timeout: 0 + module-config: "respip iterator" + serve-expired: yes + serve-expired-client-timeout: 500 + serve-expired-ttl: 3600 + serve-expired-reply-ttl: 30 + response-ip: 192.0.2.0/24 deny + +stub-zone: + name: "." + stub-addr: 193.0.14.129 # K.ROOT-SERVERS.NET. +CONFIG_END + +SCENARIO_BEGIN Test expired response respip drop + +; K.ROOT-SERVERS.NET. +RANGE_BEGIN 0 100 + ADDRESS 193.0.14.129 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +. IN NS +SECTION ANSWER +. IN NS K.ROOT-SERVERS.NET. +SECTION ADDITIONAL +K.ROOT-SERVERS.NET. IN A 193.0.14.129 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode subdomain +ADJUST copy_id copy_query +REPLY QR NOERROR +SECTION QUESTION +test. IN NS +SECTION AUTHORITY +test. IN NS ns.test. +SECTION ADDITIONAL +ns.test. IN A 1.2.3.5 +ENTRY_END +RANGE_END + +; ns.test +RANGE_BEGIN 0 100 + ADDRESS 1.2.3.5 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +test. IN NS +SECTION ANSWER +test. IN NS ns.test +SECTION ADDITIONAL +ns.test. IN A 1.2.3.5 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +ns.test. IN A +SECTION ANSWER +ns.test. IN A 1.2.3.5 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +ns.test. IN AAAA +SECTION AUTHORITY +test. 3600 IN SOA ns.test. host.test. 20201 3600 1800 604800 3600 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode subdomain +ADJUST copy_id copy_query +REPLY QR NOERROR +SECTION QUESTION +example.test. IN NS +SECTION AUTHORITY +example.test. IN NS ns.example.test. +SECTION ADDITIONAL +ns.example.test. IN A 1.2.3.4 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode subdomain +ADJUST copy_id copy_query +REPLY QR NOERROR +SECTION QUESTION +far.test. IN NS +SECTION AUTHORITY +far.test. IN NS ns.far.test. +SECTION ADDITIONAL +ns.far.test. IN A 1.2.3.6 +ENTRY_END +RANGE_END + +; ns.example.test. +RANGE_BEGIN 0 20 + ADDRESS 1.2.3.4 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +example.test. IN NS +SECTION ANSWER +example.test. IN NS ns.example.test. +SECTION ADDITIONAL +ns.example.test. IN A 1.2.3.4 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +ns.example.test. IN A +SECTION ANSWER +ns.example.test. IN A 1.2.3.4 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +ns.example.test. IN AAAA +SECTION AUTHORITY +example.test. 3600 IN SOA ns.example.test. host.example.test. 20301 3600 1800 604800 3600 +ENTRY_END + +; response to query of interest +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +www.example.test. IN A +SECTION ANSWER +www.example.test. 1 IN A 192.0.2.1 +ENTRY_END +RANGE_END + +; ns.example.test. +RANGE_BEGIN 45 100 + ADDRESS 1.2.3.4 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +www.example.test. IN A +SECTION ANSWER +www.example.test. 1 IN A 192.0.2.1 +ENTRY_END +RANGE_END + +; ns.far.test. +RANGE_BEGIN 0 100 + ADDRESS 1.2.3.6 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +far.test. IN NS +SECTION ANSWER +far.test. IN NS ns.far.test. +SECTION ADDITIONAL +ns.far.test. IN A 1.2.3.6 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +ns.far.test. IN A +SECTION ANSWER +ns.far.test. IN A 1.2.3.6 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +ns.far.test. IN AAAA +SECTION AUTHORITY +far.test. 3600 IN SOA ns.far.test. host.far.test. 20301 3600 1800 604800 3600 +ENTRY_END + +; response to query of interest +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +tgt.far.test. IN A +SECTION ANSWER +tgt.far.test. 1 IN A 10.20.30.40 +ENTRY_END +RANGE_END + +; Put items with TTL 1 in cache. +STEP 10 QUERY +ENTRY_BEGIN +REPLY RD DO +SECTION QUESTION +www.example.test. IN A +ENTRY_END + +; Answer is dropped +;STEP 11 CHECK_ANSWER +;ENTRY_BEGIN +;MATCH all +;REPLY QR RD RA DO NOERROR +;SECTION QUESTION +;www.example.test. IN A +;SECTION ANSWER +;www.example.test. 3600 IN CNAME tgt.far.test. +;tgt.far.test. 1 IN A 10.20.30.40 +;ENTRY_END + +; Move time to expire the cache entries. +STEP 20 TIME_PASSES ELAPSE 2 + +; the upstream RANGE is removed, so serve-expired has to act. + +STEP 30 QUERY +ENTRY_BEGIN +REPLY RD DO +SECTION QUESTION +www.example.test. IN A +ENTRY_END + +; for serve expired callback. +STEP 31 TIME_PASSES ELAPSE 2 + +; Answer is dropped +;STEP 40 CHECK_ANSWER +;ENTRY_BEGIN +;MATCH all +;REPLY QR RD RA DO NOERROR +;SECTION QUESTION +;www.example.test. IN A +;SECTION ANSWER +;www.example.test. 3600 IN CNAME tgt.far.test. +;tgt.far.test. 1 IN A 10.20.30.40 +;ENTRY_END + +; The pending lookup for the data, that was answered with expired to the client. +STEP 50 TRAFFIC + +SCENARIO_END diff --git a/testdata/serve_expired_rpz_drop.rpl b/testdata/serve_expired_rpz_drop.rpl new file mode 100644 index 000000000..ab5d49827 --- /dev/null +++ b/testdata/serve_expired_rpz_drop.rpl @@ -0,0 +1,246 @@ +; config options +; The island of trust is at test. +server: + target-fetch-policy: "0 0 0 0 0" + qname-minimisation: "no" + fake-sha1: yes + trust-anchor-signaling: no + minimal-responses: no + iter-scrub-promiscuous: no + aggressive-nsec: no + local-zone: test. nodefault + log-servfail: yes + discard-timeout: 0 + module-config: "respip iterator" + serve-expired: yes + serve-expired-client-timeout: 500 + serve-expired-ttl: 3600 + serve-expired-reply-ttl: 30 + +rpz: + name: "rpz.example.com." + rpz-log: yes + rpz-log-name: "rpz.example.com" + zonefile: +TEMPFILE_NAME rpz.example.com +TEMPFILE_CONTENTS rpz.example.com +$ORIGIN example.com. +rpz 3600 IN SOA ns1.rpz.example.com. hostmaster.rpz.example.com. ( + 1379078166 28800 7200 604800 7200 ) + 3600 IN NS ns1.rpz.example.com. + 3600 IN NS ns2.rpz.example.com. +$ORIGIN rpz.example.com. +24.0.2.0.192.rpz-ip CNAME rpz-drop. +TEMPFILE_END + +stub-zone: + name: "." + stub-addr: 193.0.14.129 # K.ROOT-SERVERS.NET. +CONFIG_END + +SCENARIO_BEGIN Test expired response RPZ drop + +; K.ROOT-SERVERS.NET. +RANGE_BEGIN 0 100 + ADDRESS 193.0.14.129 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +. IN NS +SECTION ANSWER +. IN NS K.ROOT-SERVERS.NET. +SECTION ADDITIONAL +K.ROOT-SERVERS.NET. IN A 193.0.14.129 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode subdomain +ADJUST copy_id copy_query +REPLY QR NOERROR +SECTION QUESTION +test. IN NS +SECTION AUTHORITY +test. IN NS ns.test. +SECTION ADDITIONAL +ns.test. IN A 1.2.3.5 +ENTRY_END +RANGE_END + +; ns.test +RANGE_BEGIN 0 100 + ADDRESS 1.2.3.5 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +test. IN NS +SECTION ANSWER +test. IN NS ns.test +SECTION ADDITIONAL +ns.test. IN A 1.2.3.5 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +ns.test. IN A +SECTION ANSWER +ns.test. IN A 1.2.3.5 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +ns.test. IN AAAA +SECTION AUTHORITY +test. 3600 IN SOA ns.test. host.test. 20201 3600 1800 604800 3600 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode subdomain +ADJUST copy_id copy_query +REPLY QR NOERROR +SECTION QUESTION +example.test. IN NS +SECTION AUTHORITY +example.test. IN NS ns.example.test. +SECTION ADDITIONAL +ns.example.test. IN A 1.2.3.4 +ENTRY_END +RANGE_END + +; ns.example.test. +RANGE_BEGIN 0 20 + ADDRESS 1.2.3.4 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +example.test. IN NS +SECTION ANSWER +example.test. IN NS ns.example.test. +SECTION ADDITIONAL +ns.example.test. IN A 1.2.3.4 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +ns.example.test. IN A +SECTION ANSWER +ns.example.test. IN A 1.2.3.4 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +ns.example.test. IN AAAA +SECTION AUTHORITY +example.test. 3600 IN SOA ns.example.test. host.example.test. 20301 3600 1800 604800 3600 +ENTRY_END + +; response to query of interest +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +www.example.test. IN A +SECTION ANSWER +www.example.test. 1 IN A 192.0.2.1 +ENTRY_END +RANGE_END + +; ns.example.test. +RANGE_BEGIN 50 100 + ADDRESS 1.2.3.4 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +ns.example.test. IN A +SECTION ANSWER +ns.example.test. IN A 1.2.3.4 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +ns.example.test. IN AAAA +SECTION AUTHORITY +example.test. 3600 IN SOA ns.example.test. host.example.test. 20301 3600 1800 604800 3600 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +www.example.test. IN A +SECTION ANSWER +www.example.test. 1 IN A 192.0.2.1 +ENTRY_END +RANGE_END + +; Put items with TTL 1 in cache. +STEP 10 QUERY +ENTRY_BEGIN +REPLY RD DO +SECTION QUESTION +www.example.test. IN A +ENTRY_END + +; answer is dropped. +;STEP 11 CHECK_ANSWER +;ENTRY_BEGIN +;MATCH all +;REPLY QR RD RA DO NXDOMAIN +;SECTION QUESTION +;www.example.test. IN A +;SECTION ANSWER +;ENTRY_END + +; Move time to expire the cache entries. +STEP 20 TIME_PASSES ELAPSE 2 + +; the upstream RANGE is removed, so serve-expired has to act. + +STEP 30 QUERY +ENTRY_BEGIN +REPLY RD DO +SECTION QUESTION +www.example.test. IN A +ENTRY_END + +; for serve expired callback. +STEP 31 TIME_PASSES ELAPSE 2 + +; answer is dropped +;STEP 40 CHECK_ANSWER +;ENTRY_BEGIN +;MATCH all +;REPLY QR RD RA DO NXDOMAIN +;SECTION QUESTION +;www.example.test. IN A +;SECTION ANSWER +;ENTRY_END + +; The pending lookup for the data, that was answered with expired to the client. +STEP 50 TRAFFIC + +SCENARIO_END diff --git a/testdata/serve_expired_rpz_nx.rpl b/testdata/serve_expired_rpz_nx.rpl new file mode 100644 index 000000000..19956545e --- /dev/null +++ b/testdata/serve_expired_rpz_nx.rpl @@ -0,0 +1,319 @@ +; config options +; The island of trust is at test. +server: + target-fetch-policy: "0 0 0 0 0" + qname-minimisation: "no" + fake-sha1: yes + trust-anchor-signaling: no + minimal-responses: no + iter-scrub-promiscuous: no + aggressive-nsec: no + local-zone: test. nodefault + log-servfail: yes + discard-timeout: 0 + module-config: "respip iterator" + serve-expired: yes + serve-expired-client-timeout: 500 + serve-expired-ttl: 3600 + serve-expired-reply-ttl: 30 + +rpz: + name: "rpz.example.com." + rpz-log: yes + rpz-log-name: "rpz.example.com" + zonefile: +TEMPFILE_NAME rpz.example.com +TEMPFILE_CONTENTS rpz.example.com +$ORIGIN example.com. +rpz 3600 IN SOA ns1.rpz.example.com. hostmaster.rpz.example.com. ( + 1379078166 28800 7200 604800 7200 ) + 3600 IN NS ns1.rpz.example.com. + 3600 IN NS ns2.rpz.example.com. +$ORIGIN rpz.example.com. +24.0.2.0.192.rpz-ip CNAME . +TEMPFILE_END + +stub-zone: + name: "." + stub-addr: 193.0.14.129 # K.ROOT-SERVERS.NET. +CONFIG_END + +SCENARIO_BEGIN Test expired response RPZ rewrite + +; K.ROOT-SERVERS.NET. +RANGE_BEGIN 0 100 + ADDRESS 193.0.14.129 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +. IN NS +SECTION ANSWER +. IN NS K.ROOT-SERVERS.NET. +SECTION ADDITIONAL +K.ROOT-SERVERS.NET. IN A 193.0.14.129 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode subdomain +ADJUST copy_id copy_query +REPLY QR NOERROR +SECTION QUESTION +test. IN NS +SECTION AUTHORITY +test. IN NS ns.test. +SECTION ADDITIONAL +ns.test. IN A 1.2.3.5 +ENTRY_END +RANGE_END + +; ns.test +RANGE_BEGIN 0 100 + ADDRESS 1.2.3.5 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +test. IN NS +SECTION ANSWER +test. IN NS ns.test +SECTION ADDITIONAL +ns.test. IN A 1.2.3.5 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +ns.test. IN A +SECTION ANSWER +ns.test. IN A 1.2.3.5 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +ns.test. IN AAAA +SECTION AUTHORITY +test. 3600 IN SOA ns.test. host.test. 20201 3600 1800 604800 3600 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode subdomain +ADJUST copy_id copy_query +REPLY QR NOERROR +SECTION QUESTION +example.test. IN NS +SECTION AUTHORITY +example.test. IN NS ns.example.test. +SECTION ADDITIONAL +ns.example.test. IN A 1.2.3.4 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode subdomain +ADJUST copy_id copy_query +REPLY QR NOERROR +SECTION QUESTION +far.test. IN NS +SECTION AUTHORITY +far.test. IN NS ns.far.test. +SECTION ADDITIONAL +ns.far.test. IN A 1.2.3.6 +ENTRY_END +RANGE_END + +; ns.example.test. +RANGE_BEGIN 0 20 + ADDRESS 1.2.3.4 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +example.test. IN NS +SECTION ANSWER +example.test. IN NS ns.example.test. +SECTION ADDITIONAL +ns.example.test. IN A 1.2.3.4 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +ns.example.test. IN A +SECTION ANSWER +ns.example.test. IN A 1.2.3.4 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +ns.example.test. IN AAAA +SECTION AUTHORITY +example.test. 3600 IN SOA ns.example.test. host.example.test. 20301 3600 1800 604800 3600 +ENTRY_END + +; response to query of interest +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +www.example.test. IN A +SECTION ANSWER +www.example.test. 1 IN A 192.0.2.1 +ENTRY_END +RANGE_END + +; ns.example.test. +RANGE_BEGIN 20 100 + ADDRESS 1.2.3.4 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +ns.example.test. IN A +SECTION ANSWER +ns.example.test. IN A 1.2.3.4 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +ns.example.test. IN AAAA +SECTION AUTHORITY +example.test. 3600 IN SOA ns.example.test. host.example.test. 20301 3600 1800 604800 3600 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR SERVFAIL +SECTION QUESTION +www.example.test. IN A +SECTION ANSWER +ENTRY_END +RANGE_END + +; ns.far.test. +RANGE_BEGIN 0 100 + ADDRESS 1.2.3.6 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +far.test. IN NS +SECTION ANSWER +far.test. IN NS ns.far.test. +SECTION ADDITIONAL +ns.far.test. IN A 1.2.3.6 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +ns.far.test. IN A +SECTION ANSWER +ns.far.test. IN A 1.2.3.6 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +ns.far.test. IN AAAA +SECTION AUTHORITY +far.test. 3600 IN SOA ns.far.test. host.far.test. 20301 3600 1800 604800 3600 +ENTRY_END + +; response to query of interest +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +tgt.far.test. IN A +SECTION ANSWER +tgt.far.test. 1 IN A 10.20.30.40 +ENTRY_END +RANGE_END + +; Put items with TTL 1 in cache. +STEP 1 QUERY +ENTRY_BEGIN +REPLY RD DO +SECTION QUESTION +tgt.far.test. IN A +ENTRY_END + +STEP 2 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR RD RA DO NOERROR +SECTION QUESTION +tgt.far.test. IN A +SECTION ANSWER +tgt.far.test. 1 IN A 10.20.30.40 +ENTRY_END + +STEP 10 QUERY +ENTRY_BEGIN +REPLY RD DO +SECTION QUESTION +www.example.test. IN A +ENTRY_END + +STEP 11 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR RD RA DO NXDOMAIN +SECTION QUESTION +www.example.test. IN A +SECTION ANSWER +ENTRY_END + +; Move time to expire the cache entries. +STEP 20 TIME_PASSES ELAPSE 2 + +; the upstream RANGE is removed, so serve-expired has to act. + +STEP 30 QUERY +ENTRY_BEGIN +REPLY RD DO +SECTION QUESTION +www.example.test. IN A +ENTRY_END + +; for serve expired callback. +STEP 31 TIME_PASSES ELAPSE 2 + +STEP 40 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR RD RA DO NXDOMAIN +SECTION QUESTION +www.example.test. IN A +SECTION ANSWER +ENTRY_END + +; The pending lookup for the data, that was answered with expired to the client. +STEP 50 TRAFFIC + +SCENARIO_END From b08723ef9736cd1246ef285360865f00cfa7e2ad Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Wed, 22 Jul 2026 12:04:35 +0200 Subject: [PATCH 46/84] - Unit test for CVE-2026-55973. --- doc/Changelog | 1 + testdata/errreport_agent_domain_len.rpl | 221 ++++++++++++++++++++++++ 2 files changed, 222 insertions(+) create mode 100644 testdata/errreport_agent_domain_len.rpl diff --git a/doc/Changelog b/doc/Changelog index 55c9806e6..6790f7361 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -95,6 +95,7 @@ - Unit test for CVE-2026-50243. - Unit test for CVE-2026-50248. - Unit test for CVE-2026-55717. + - Unit test for CVE-2026-55973. 21 July 2026: Wouter - Merge #1476 from petrvaganoff: ipsecmod: fix possible deref diff --git a/testdata/errreport_agent_domain_len.rpl b/testdata/errreport_agent_domain_len.rpl new file mode 100644 index 000000000..9efdbb659 --- /dev/null +++ b/testdata/errreport_agent_domain_len.rpl @@ -0,0 +1,221 @@ +; Test DNS Error Reporting. + +server: + module-config: "validator iterator" + trust-anchor-signaling: no + target-fetch-policy: "0 0 0 0 0" + verbosity: 4 + qname-minimisation: no + minimal-responses: no + rrset-roundrobin: no + trust-anchor: "test. DS 1444 8 2 8a87d067fd09a5965244fe2e317dd26d182c468e0a7f26ecc4c7b479bf89db9b" + val-override-date: "20201020135527" + ede: no # It is not needed for dns-error-reporting; only for clients to receive EDEs + dns-error-reporting: yes + do-ip6: no + local-zone: test. nodefault + log-servfail: yes + +stub-zone: + name: test + stub-addr: 1.2.3.5 +stub-zone: + name: an.agent + stub-addr: 0.0.0.2 +CONFIG_END + +SCENARIO_BEGIN Test DNS Error Reporting with agent domain len malformed. + +; ns.test +RANGE_BEGIN 0 100 + ADDRESS 1.2.3.5 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +test. IN NS +SECTION ANSWER +test. IN NS ns.test +test. 3600 IN RRSIG NS 8 1 3600 20201116135527 20201019135527 1444 test. RGCxIO32TbbLTk6xZmTr+fjYPH50hntBxeOQ2DIj2pDsmjALcHYtVkOfpfk2EhOhHZd+9PLuoJPbJh6a9NqLSFeBvr0XZoCZoQ2g0tCHUNHcH5EVjA2TuYBQem6DVYnPLJ3914aRx0uA1j42b8dC2xsam/XkOo7U+dLbUW2Os1s= +SECTION ADDITIONAL +ns.test. IN A 1.2.3.5 +ns.test. 3600 IN RRSIG A 8 2 3600 20201116135527 20201019135527 1444 test. GskCc4/k6GjH9V9Jz2V5L2XLiizbOeWkB0feSbf+aN859S3vxVvtuqkvIgwY4LafUO1QAn/pUcv9zA7rcFO++rlg+8t6gvZTo9p3v0bfeIv2uJDsfSBD5jDh0WXlxjekfnrKrQp7zE+GiA93tWwKUWKPvxXDgP+n886e6WcbHJw= +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +ns.test. IN A +SECTION ANSWER +ns.test. IN A 1.2.3.5 +ns.test. 3600 IN RRSIG A 8 2 3600 20201116135527 20201019135527 1444 test. GskCc4/k6GjH9V9Jz2V5L2XLiizbOeWkB0feSbf+aN859S3vxVvtuqkvIgwY4LafUO1QAn/pUcv9zA7rcFO++rlg+8t6gvZTo9p3v0bfeIv2uJDsfSBD5jDh0WXlxjekfnrKrQp7zE+GiA93tWwKUWKPvxXDgP+n886e6WcbHJw= +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +ns.test. IN AAAA +SECTION AUTHORITY +test. 3600 IN SOA ns.test. host.test. 20201 3600 1800 604800 3600 +test. 3600 IN RRSIG SOA 8 1 3600 20201116135527 20201019135527 1444 test. IZJIDmEgf0W7A5G7hvvZ2hUqJ9Trbv1/i7ySapDmPbYV9lVCmHHobySxO01yDhI2/Pvpsvxqrm1Tiv3BxH8uzZ4keKgiQjBsSy4htAsFct9I4E7ly2glPj/Fm3oun3PsjJDv5QYhx0KS7w4IQKU7Nc9pfJc92uoUI5bdoC1pRGw= +ns.test. 3600 IN NSEC nz.test. A RRSIG +ns.test. 3600 IN RRSIG NSEC 8 2 3600 20201116135527 20201019135527 1444 test. PElArVB3KPg8KHAP7lzcNbhFuXNxTsHNTn1dZVncB5qmWRdIaeKpaXDjpH0JSXMaelGFS+/QhuQ6Hmw9+4VyZFRqMzGhw4agUR/2bxABHcDIG4ZpUwyeSP61ATTfHUkQVxaH2wjCWI/tfmesdP2xVE4GXyUvCIBxU914MkZbULU= +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +test. IN DNSKEY +SECTION ANSWER +test. 3600 IN DNSKEY 257 3 8 AwEAAbd9WqjzE2Pynz21OG5doSf9hFzMr5dhzz2waZ3vTa+0o5r7AjTAqmA1yH/B3+aAMihUm5ucZSfVqo7+kOaRE8yFj9aivOmA1n1+JLevJq/oyvQyjxQN2Qb89LyaNUT5oKZIiL+uyyhNW3KDR3SSbQ/GBwQNDHVcZi+JDR3RC0r7 ;{id = 1444 (ksk), size = 1024b} +test. 3600 IN RRSIG DNSKEY 8 1 3600 20201116135527 20201019135527 1444 test. UmRMS4iG9NBBHZYOtpwFFcJgbEb5SfHSgHd9XRe/8pTWM31WSDayn5ViPOBMqI1T5TXg2amc13dDI574xIM2oKMus3b5cBW72jJLW13jprBtslO6P8BMWb4HNnvLrJtQjwf3ErRirtTxinLmywQtmyr1cdthyG3Gp4N7i90fHSc= +SECTION ADDITIONAL +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qname qtype +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +example.test. IN DS +SECTION ANSWER +example.test. 3600 IN DS 55567 8 2 a2d578906330a10a57d40462257b6ce038bad3f7bf4a45c46c46086e20a94b39 +example.test. 3600 IN RRSIG DS 8 2 3600 20201116135527 20201019135527 1444 test. P7+FTYW2qHuJ4I1YbuvseEz5X1lOYAraGEHB3C5y0OOCQFmhmSiFRdquNi2NlpcS6FXLdsE0EU+Bo1+0atTG4EkMWXbpF21lrtbB51BdsnlX4Mzc/o375fvjiOMwmF6wPCUaOUN62jrVrhsE/hedaVyDphDToqL17ETohwgUO2I= +ENTRY_END + +ENTRY_BEGIN +MATCH opcode subdomain +ADJUST copy_id copy_query +REPLY QR NOERROR +SECTION QUESTION +example.test. IN NS +SECTION AUTHORITY +example.test. IN NS ns.example.test. +example.test. 3600 IN DS 55567 8 2 a2d578906330a10a57d40462257b6ce038bad3f7bf4a45c46c46086e20a94b39 +example.test. 3600 IN RRSIG DS 8 2 3600 20201116135527 20201019135527 1444 test. P7+FTYW2qHuJ4I1YbuvseEz5X1lOYAraGEHB3C5y0OOCQFmhmSiFRdquNi2NlpcS6FXLdsE0EU+Bo1+0atTG4EkMWXbpF21lrtbB51BdsnlX4Mzc/o375fvjiOMwmF6wPCUaOUN62jrVrhsE/hedaVyDphDToqL17ETohwgUO2I= +SECTION ADDITIONAL +ns.example.test. IN A 1.2.3.4 +ENTRY_END +RANGE_END + +; ns.example.test. +RANGE_BEGIN 0 100 + ADDRESS 1.2.3.4 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +example.test. IN NS +SECTION ANSWER +example.test. IN NS ns.example.test. +example.test. 3600 IN RRSIG NS 8 2 3600 20201116135527 20201019135527 55567 example.test. l1JT0wMlK0YI7/CWHzexf/k0iafUhCgN+BdgjBXIRXmSQNf4HDTiAkbcWL2/15qtnp12nQy9JeiTdSQ3vtPoHAJX4C5uTWaze4ms+Wrrf+n92sLCjacP9x50uuicH3URT6cKb1QCAPwlvlWxIlZjAMYFScSns7+C441NMJT8aE4= +SECTION ADDITIONAL +ns.example.test. IN A 1.2.3.4 +ns.example.test. 3600 IN RRSIG A 8 3 3600 20201116135527 20201019135527 55567 example.test. 2PWaVaccZFQgfPKXNsdEGYUVaashCAj1ZhBo9XRt5eQKUFvZcauBjMnXIuxZFyWeootn1fZGw6GuPI5W48Y0FDx38H6adprkFgQikso2Y64jDdDMWznSo38Z/XqP+U0+kq4vmwonvmEMpm7hKnNEXvhqGKyGzyBwb+CZVJ2L8Eo= +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +ns.example.test. IN A +SECTION ANSWER +ns.example.test. IN A 1.2.3.4 +ns.example.test. 3600 IN RRSIG A 8 3 3600 20201116135527 20201019135527 55567 example.test. 2PWaVaccZFQgfPKXNsdEGYUVaashCAj1ZhBo9XRt5eQKUFvZcauBjMnXIuxZFyWeootn1fZGw6GuPI5W48Y0FDx38H6adprkFgQikso2Y64jDdDMWznSo38Z/XqP+U0+kq4vmwonvmEMpm7hKnNEXvhqGKyGzyBwb+CZVJ2L8Eo= +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +ns.example.test. IN AAAA +SECTION AUTHORITY +example.test. 3600 IN SOA ns.example.test. host.example.test. 20301 3600 1800 604800 3600 +example.test. 3600 IN RRSIG SOA 8 2 3600 20201116135527 20201019135527 55567 example.test. 2UUkScBAN37fJpSrelhE8DotKvmOzj3q9wicaanCIaCv95DE4nQnePih5B+ek3FIRjB/Uv2+z4Ro5Uxy94XAnlK0rCkDLSa0U9U7KP0ytc88sevO0x1SCPAMoZoJO6JqHkv42pdh54WSz+Zb/D8npY0j/tksHe/uX+VQnMymgb8= +ns.example.test. 3600 IN NSEC nz.example.test. A RRSIG +example.test. 3600 IN SOA ns.example.test. host.example.test. 20301 3600 1800 604800 3600 +ENTRY_END + +; response to DNSKEY priming query +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +example.test. IN DNSKEY +SECTION ANSWER +example.test. 3600 IN DNSKEY 257 3 8 AwEAAdug/L739i0mgN2nuK/bhxu3wFn5Ud9nK2+XUmZQlPUEZUC5YZvm1rfMmEWTGBn87fFxEu/kjFZHJ55JLzqsbbpVHLbmKCTT2gYR2FV2WDKROGKuYbVkJIXdKAjJ0ONuK507NinYvlWXIoxHn22KAWOd9wKgSTNHBlmGkX+ts3hh ;{id = 55567 (ksk), size = 1024b} +example.test. 3600 IN RRSIG DNSKEY 8 2 3600 20201116135527 20201019135527 55567 example.test. IbWMC6quOuZFNPAVxQLqCJ9nLhindBo826rnLcg5yMgs9dGUSPOCXAfHTmbgJAUNs9HTFfrJWNvasnETs0UOpmEuifGwWdH1OlME7Gny4RL2QmITUFeMW81Jz1tiVQxFXl6yxT0jxOxvz+bqMHlrz+8IeWQXcO+GZTPu8ueq30g= +ENTRY_END + +; response to query of interest +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +www.example.test. IN A +SECTION ANSWER +www.example.test. 3600 IN A 10.20.30.40 +; valid signature +;www.example.test. 3600 IN RRSIG A 8 3 3600 20201116135527 20201019135527 55567 example.test. OQEgDcpez8Bvdwd+hxA3v63FWJhutWkv9w+k+8RLcWv34WPhebsf7CBV74ggY2c+HafvYiuIFfhdF5CX28YQjxqWVzFgE6bEA6spPc6qdHiQaY/096/4SLCDcL+2EtOqcR/uZGj5uNhhaCJ9UjscBKfEZmHUOAMXKmjsvl0I/+I= +; invalid: expired signature +www.example.test. 3600 IN RRSIG A 8 3 3600 20200816135527 20200719135527 55567 example.test. DNM4PJALboBNDe5pJ2NScYqYYmmpq8E0NogjbDNithIcQ7HtzkssLIR46DiPb/B7QIhBRpfQ6sUwMb4l+NDhm82DxaecEwnAV6Y0zYK6dZ5jI7e8rDI2hkW/LO75qSZ8Y1I9pgX5uyeBCon42IVjc3vyYbRbFNv1xgJs5rk308U= +SECTION ADDITIONAL +HEX_EDNSDATA_BEGIN +; This dns error reporting option is malformed, with garbage at end. + 00 12 ; opt-code (Report-Channel) + 00 28 ; opt-len 10 + 30 + 02 61 6E 05 61 67 65 6E 74 00 ; an.agent. + ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ; 30 0xFF tail + ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff +HEX_EDNSDATA_END +ENTRY_END +RANGE_END + +; an.agent +RANGE_BEGIN 10 20 + ADDRESS 0.0.0.2 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +_er.1.www.example.test.7._er.an.agent. IN TXT +SECTION ANSWER +_er.1.www.example.test.7._er.an.agent. IN TXT "OK" +ENTRY_END +RANGE_END + +; Query again +STEP 10 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +www.example.test. IN A +ENTRY_END + +; Check that validation failed +; (a DNS Error Report query should have been generated) +STEP 11 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR RD RA SERVFAIL +SECTION QUESTION +www.example.test. IN A +ENTRY_END + +; answer the reporting agent reply. +STEP 20 TRAFFIC + +SCENARIO_END From 4941edf2757d535504f46974b711783d702068b3 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Wed, 22 Jul 2026 12:06:00 +0200 Subject: [PATCH 47/84] - Unit test for CVE-2026-56416. --- doc/Changelog | 1 + testdata/val_canon_short_px.rpl | 215 ++++++++++++++++++++++++++++++++ 2 files changed, 216 insertions(+) create mode 100644 testdata/val_canon_short_px.rpl diff --git a/doc/Changelog b/doc/Changelog index 6790f7361..0a5821c5a 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -96,6 +96,7 @@ - Unit test for CVE-2026-50248. - Unit test for CVE-2026-55717. - Unit test for CVE-2026-55973. + - Unit test for CVE-2026-56416. 21 July 2026: Wouter - Merge #1476 from petrvaganoff: ipsecmod: fix possible deref diff --git a/testdata/val_canon_short_px.rpl b/testdata/val_canon_short_px.rpl new file mode 100644 index 000000000..3dda85fbe --- /dev/null +++ b/testdata/val_canon_short_px.rpl @@ -0,0 +1,215 @@ +; config options +; The island of trust is at test. +server: + trust-anchor: "test. DS 1444 8 2 8a87d067fd09a5965244fe2e317dd26d182c468e0a7f26ecc4c7b479bf89db9b" + val-override-date: "20201020135527" + target-fetch-policy: "0 0 0 0 0" + qname-minimisation: "no" + fake-sha1: yes + trust-anchor-signaling: no + minimal-responses: no + iter-scrub-promiscuous: no + aggressive-nsec: yes + local-zone: test. nodefault + log-servfail: yes + ;msg-buffer-size: 4096 + +stub-zone: + name: "." + stub-addr: 193.0.14.129 # K.ROOT-SERVERS.NET. +CONFIG_END + +SCENARIO_BEGIN Test validator canonicalize of short PX record. +; The record ends just before the second dname. +; And the message buffer is small, and filled with previous content. + +; K.ROOT-SERVERS.NET. +RANGE_BEGIN 0 100 + ADDRESS 193.0.14.129 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +. IN NS +SECTION ANSWER +. IN NS K.ROOT-SERVERS.NET. +SECTION ADDITIONAL +K.ROOT-SERVERS.NET. IN A 193.0.14.129 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode subdomain +ADJUST copy_id copy_query +REPLY QR NOERROR +SECTION QUESTION +test. IN NS +SECTION AUTHORITY +test. IN NS ns.test. +SECTION ADDITIONAL +ns.test. IN A 1.2.3.5 +ENTRY_END +RANGE_END + +; ns.test +RANGE_BEGIN 0 100 + ADDRESS 1.2.3.5 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +test. IN NS +SECTION ANSWER +test. IN NS ns.test +test. 3600 IN RRSIG NS 8 1 3600 20201116135527 20201019135527 1444 test. RGCxIO32TbbLTk6xZmTr+fjYPH50hntBxeOQ2DIj2pDsmjALcHYtVkOfpfk2EhOhHZd+9PLuoJPbJh6a9NqLSFeBvr0XZoCZoQ2g0tCHUNHcH5EVjA2TuYBQem6DVYnPLJ3914aRx0uA1j42b8dC2xsam/XkOo7U+dLbUW2Os1s= +SECTION ADDITIONAL +ns.test. IN A 1.2.3.5 +ns.test. 3600 IN RRSIG A 8 2 3600 20201116135527 20201019135527 1444 test. GskCc4/k6GjH9V9Jz2V5L2XLiizbOeWkB0feSbf+aN859S3vxVvtuqkvIgwY4LafUO1QAn/pUcv9zA7rcFO++rlg+8t6gvZTo9p3v0bfeIv2uJDsfSBD5jDh0WXlxjekfnrKrQp7zE+GiA93tWwKUWKPvxXDgP+n886e6WcbHJw= +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +ns.test. IN A +SECTION ANSWER +ns.test. IN A 1.2.3.5 +ns.test. 3600 IN RRSIG A 8 2 3600 20201116135527 20201019135527 1444 test. GskCc4/k6GjH9V9Jz2V5L2XLiizbOeWkB0feSbf+aN859S3vxVvtuqkvIgwY4LafUO1QAn/pUcv9zA7rcFO++rlg+8t6gvZTo9p3v0bfeIv2uJDsfSBD5jDh0WXlxjekfnrKrQp7zE+GiA93tWwKUWKPvxXDgP+n886e6WcbHJw= +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +ns.test. IN AAAA +SECTION AUTHORITY +test. 3600 IN SOA ns.test. host.test. 20201 3600 1800 604800 3600 +test. 3600 IN RRSIG SOA 8 1 3600 20201116135527 20201019135527 1444 test. IZJIDmEgf0W7A5G7hvvZ2hUqJ9Trbv1/i7ySapDmPbYV9lVCmHHobySxO01yDhI2/Pvpsvxqrm1Tiv3BxH8uzZ4keKgiQjBsSy4htAsFct9I4E7ly2glPj/Fm3oun3PsjJDv5QYhx0KS7w4IQKU7Nc9pfJc92uoUI5bdoC1pRGw= +ns.test. 3600 IN NSEC nz.test. A RRSIG +ns.test. 3600 IN RRSIG NSEC 8 2 3600 20201116135527 20201019135527 1444 test. PElArVB3KPg8KHAP7lzcNbhFuXNxTsHNTn1dZVncB5qmWRdIaeKpaXDjpH0JSXMaelGFS+/QhuQ6Hmw9+4VyZFRqMzGhw4agUR/2bxABHcDIG4ZpUwyeSP61ATTfHUkQVxaH2wjCWI/tfmesdP2xVE4GXyUvCIBxU914MkZbULU= +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +test. IN DNSKEY +SECTION ANSWER +test. 3600 IN DNSKEY 257 3 8 AwEAAbd9WqjzE2Pynz21OG5doSf9hFzMr5dhzz2waZ3vTa+0o5r7AjTAqmA1yH/B3+aAMihUm5ucZSfVqo7+kOaRE8yFj9aivOmA1n1+JLevJq/oyvQyjxQN2Qb89LyaNUT5oKZIiL+uyyhNW3KDR3SSbQ/GBwQNDHVcZi+JDR3RC0r7 ;{id = 1444 (ksk), size = 1024b} +test. 3600 IN RRSIG DNSKEY 8 1 3600 20201116135527 20201019135527 1444 test. UmRMS4iG9NBBHZYOtpwFFcJgbEb5SfHSgHd9XRe/8pTWM31WSDayn5ViPOBMqI1T5TXg2amc13dDI574xIM2oKMus3b5cBW72jJLW13jprBtslO6P8BMWb4HNnvLrJtQjwf3ErRirtTxinLmywQtmyr1cdthyG3Gp4N7i90fHSc= +SECTION ADDITIONAL +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qname qtype +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +example.test. IN DS +SECTION ANSWER +example.test. 3600 IN DS 55567 8 2 a2d578906330a10a57d40462257b6ce038bad3f7bf4a45c46c46086e20a94b39 +example.test. 3600 IN RRSIG DS 8 2 3600 20201116135527 20201019135527 1444 test. P7+FTYW2qHuJ4I1YbuvseEz5X1lOYAraGEHB3C5y0OOCQFmhmSiFRdquNi2NlpcS6FXLdsE0EU+Bo1+0atTG4EkMWXbpF21lrtbB51BdsnlX4Mzc/o375fvjiOMwmF6wPCUaOUN62jrVrhsE/hedaVyDphDToqL17ETohwgUO2I= +ENTRY_END + +ENTRY_BEGIN +MATCH opcode subdomain +ADJUST copy_id copy_query +REPLY QR NOERROR +SECTION QUESTION +example.test. IN NS +SECTION AUTHORITY +example.test. IN NS ns.example.test. +example.test. 3600 IN DS 55567 8 2 a2d578906330a10a57d40462257b6ce038bad3f7bf4a45c46c46086e20a94b39 +example.test. 3600 IN RRSIG DS 8 2 3600 20201116135527 20201019135527 1444 test. P7+FTYW2qHuJ4I1YbuvseEz5X1lOYAraGEHB3C5y0OOCQFmhmSiFRdquNi2NlpcS6FXLdsE0EU+Bo1+0atTG4EkMWXbpF21lrtbB51BdsnlX4Mzc/o375fvjiOMwmF6wPCUaOUN62jrVrhsE/hedaVyDphDToqL17ETohwgUO2I= +SECTION ADDITIONAL +ns.example.test. IN A 1.2.3.4 +ENTRY_END +RANGE_END + +; ns.example.test. +RANGE_BEGIN 0 100 + ADDRESS 1.2.3.4 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +example.test. IN NS +SECTION ANSWER +example.test. IN NS ns.example.test. +example.test. 3600 IN RRSIG NS 8 2 3600 20201116135527 20201019135527 55567 example.test. l1JT0wMlK0YI7/CWHzexf/k0iafUhCgN+BdgjBXIRXmSQNf4HDTiAkbcWL2/15qtnp12nQy9JeiTdSQ3vtPoHAJX4C5uTWaze4ms+Wrrf+n92sLCjacP9x50uuicH3URT6cKb1QCAPwlvlWxIlZjAMYFScSns7+C441NMJT8aE4= +SECTION ADDITIONAL +ns.example.test. IN A 1.2.3.4 +ns.example.test. 3600 IN RRSIG A 8 3 3600 20201116135527 20201019135527 55567 example.test. 2PWaVaccZFQgfPKXNsdEGYUVaashCAj1ZhBo9XRt5eQKUFvZcauBjMnXIuxZFyWeootn1fZGw6GuPI5W48Y0FDx38H6adprkFgQikso2Y64jDdDMWznSo38Z/XqP+U0+kq4vmwonvmEMpm7hKnNEXvhqGKyGzyBwb+CZVJ2L8Eo= +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +ns.example.test. IN A +SECTION ANSWER +ns.example.test. IN A 1.2.3.4 +ns.example.test. 3600 IN RRSIG A 8 3 3600 20201116135527 20201019135527 55567 example.test. 2PWaVaccZFQgfPKXNsdEGYUVaashCAj1ZhBo9XRt5eQKUFvZcauBjMnXIuxZFyWeootn1fZGw6GuPI5W48Y0FDx38H6adprkFgQikso2Y64jDdDMWznSo38Z/XqP+U0+kq4vmwonvmEMpm7hKnNEXvhqGKyGzyBwb+CZVJ2L8Eo= +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +ns.example.test. IN AAAA +SECTION AUTHORITY +example.test. 3600 IN SOA ns.example.test. host.example.test. 20301 3600 1800 604800 3600 +example.test. 3600 IN RRSIG SOA 8 2 3600 20201116135527 20201019135527 55567 example.test. 2UUkScBAN37fJpSrelhE8DotKvmOzj3q9wicaanCIaCv95DE4nQnePih5B+ek3FIRjB/Uv2+z4Ro5Uxy94XAnlK0rCkDLSa0U9U7KP0ytc88sevO0x1SCPAMoZoJO6JqHkv42pdh54WSz+Zb/D8npY0j/tksHe/uX+VQnMymgb8= +ns.example.test. 3600 IN NSEC nz.example.test. A RRSIG +ENTRY_END + +; response to DNSKEY priming query +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +example.test. IN DNSKEY +SECTION ANSWER +example.test. 3600 IN DNSKEY 257 3 8 AwEAAdug/L739i0mgN2nuK/bhxu3wFn5Ud9nK2+XUmZQlPUEZUC5YZvm1rfMmEWTGBn87fFxEu/kjFZHJ55JLzqsbbpVHLbmKCTT2gYR2FV2WDKROGKuYbVkJIXdKAjJ0ONuK507NinYvlWXIoxHn22KAWOd9wKgSTNHBlmGkX+ts3hh ;{id = 55567 (ksk), size = 1024b} +example.test. 3600 IN RRSIG DNSKEY 8 2 3600 20201116135527 20201019135527 55567 example.test. IbWMC6quOuZFNPAVxQLqCJ9nLhindBo826rnLcg5yMgs9dGUSPOCXAfHTmbgJAUNs9HTFfrJWNvasnETs0UOpmEuifGwWdH1OlME7Gny4RL2QmITUFeMW81Jz1tiVQxFXl6yxT0jxOxvz+bqMHlrz+8IeWQXcO+GZTPu8ueq30g= +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +px.example.test. IN PX +SECTION ANSWER +; PX with preference 10, first name "." , second name is missing. +px.example.test. 3600 IN PX \# 3 000A00 +; invalid signature +px.example.test. 3600 IN RRSIG PX 8 2 3600 20201116135527 20201019135527 55567 example.test. IbWMC6quOuZFNPAVxQLqCJ9nLhindBo826rnLcg5yMgs9dGUSPOCXAfHTmbgJAUNs9HTFfrJWNvasnETs0UOpmEuifGwWdH1OlME7Gny4RL2QmITUFeMW81Jz1tiVQxFXl6yxT0jxOxvz+bqMHlrz+8IeWQXcO+GZTPu8ueq30g +ENTRY_END +RANGE_END + +STEP 10 QUERY +ENTRY_BEGIN +REPLY RD DO +SECTION QUESTION +px.example.test. IN PX +ENTRY_END + +STEP 20 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR RD RA DO SERVFAIL +SECTION QUESTION +px.example.test. IN PX +SECTION ANSWER +ENTRY_END + +SCENARIO_END From cf5e6e89a582151d5875fbd5db7d29fb3d9dc0cf Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Wed, 22 Jul 2026 12:16:49 +0200 Subject: [PATCH 48/84] - Fix error in log printout in fix for CVE-2026-50248, when the primary name is bogus. --- doc/Changelog | 2 ++ services/authzone.c | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/Changelog b/doc/Changelog index 0a5821c5a..7a0232e86 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -97,6 +97,8 @@ - Unit test for CVE-2026-55717. - Unit test for CVE-2026-55973. - Unit test for CVE-2026-56416. + - Fix error in log printout in fix for CVE-2026-50248, when the + primary name is bogus. 21 July 2026: Wouter - Merge #1476 from petrvaganoff: ipsecmod: fix possible deref diff --git a/services/authzone.c b/services/authzone.c index 4218b8e82..4dafabf15 100644 --- a/services/authzone.c +++ b/services/authzone.c @@ -7057,7 +7057,7 @@ void auth_xfer_probe_lookup_callback(void* arg, int rcode, sldns_buffer* buf, char zname[LDNS_MAX_DOMAINLEN]; dname_str(xfr->name, zname); verbose(VERB_OPS, "auth zone %s: primary %s address probe lookup is DNSSEC bogus: %s", - zname, xfr->task_transfer->lookup_target->host, + zname, xfr->task_probe->lookup_target->host, (why_bogus?why_bogus:"")); } /* fall through to next-lookup / next-master */ From 914dbfea4e8de887c4c7ecc0bace2faf3ecc0247 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Wed, 22 Jul 2026 14:12:34 +0200 Subject: [PATCH 49/84] - iana portlist update. --- doc/Changelog | 1 + util/iana_ports.inc | 1 + 2 files changed, 2 insertions(+) diff --git a/doc/Changelog b/doc/Changelog index 7a0232e86..748823d3e 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -99,6 +99,7 @@ - Unit test for CVE-2026-56416. - Fix error in log printout in fix for CVE-2026-50248, when the primary name is bogus. + - iana portlist update. 21 July 2026: Wouter - Merge #1476 from petrvaganoff: ipsecmod: fix possible deref diff --git a/util/iana_ports.inc b/util/iana_ports.inc index 9bb2fefbe..0d61e948e 100644 --- a/util/iana_ports.inc +++ b/util/iana_ports.inc @@ -4610,6 +4610,7 @@ 7101, 7107, 7121, +7123, 7128, 7129, 7161, From 22e2c5b6d177d786e617973aac3d63b48e7e0c55 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Thu, 23 Jul 2026 10:01:10 +0200 Subject: [PATCH 50/84] - Updated credits for Xuanchao Xie in 22 july changelog. --- doc/Changelog | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/doc/Changelog b/doc/Changelog index 748823d3e..f88b813ad 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,6 @@ +23 July 2026: Wouter + - Updated credits for Xuanchao Xie in 22 july changelog. + 22 July 2026: Wouter - Release tag for 1.25.2, with the security commits: - Fix CVE-2026-14586, Assertion in libngtcp2 when under pressure @@ -10,7 +13,9 @@ Kunta Chu, Kaihua Wang, and Jianjun Chen from Tsinghua University, for also reporting this issue. In addition, thanks to Qifan Zhang, Palo Alto Networks, for also reporting this issue. In addition, - thanks to Xuanchao Xie, for also reporting this issue. + thanks to Xuanchao Xie, Lutong Chen, and Kaiping Xue of the + University of Science and Technology of China (USTC), for also + reporting this issue. - Fix CVE-2026-40691, Packet of death for DNSCrypt over TCP. Thanks to Qifan Zhang, Palo Alto Networks, for the report. In addition, thanks to Trung Nguyen (@everping) of CyStack, for also reporting @@ -74,7 +79,8 @@ - Fix CVE-2026-55991, Remote DNS-over-QUIC (DoQ) flow-control assertion failure in libngtcp2. Thanks to Qifan Zhang, Palo Alto Networks, for the report. In addition, thanks to Xuanchao Xie, - for also reporting this issue. + Lutong Chen, and Kaiping Xue of the University of Science and + Technology of China (USTC), for also reporting this issue. - Fix CVE-2026-56416, Possible heap buffer overflow when validator canonicalizes RDATA that contains domain name. Thanks to Qifan Zhang, Palo Alto Networks, for the report. From 1bab2dfafa7e7cc6b016b9854a4ff5f1d9af1b50 Mon Sep 17 00:00:00 2001 From: Petr Vaganov Date: Thu, 23 Jul 2026 15:22:02 +0700 Subject: [PATCH 51/84] pythonmod: add check return value after ftell() (#1478) Variable 'flen', which might receive a negative value at pythonmod.c:493 by calling function 'ftell', is used without checking at pythonmod.c:508 by calling function 'fread'. Found by the static analyzer Svace (ISP RAS). Signed-off-by: Petr Vaganov --- pythonmod/pythonmod.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/pythonmod/pythonmod.c b/pythonmod/pythonmod.c index 1b077bb6f..045dd1bbd 100644 --- a/pythonmod/pythonmod.c +++ b/pythonmod/pythonmod.c @@ -487,10 +487,17 @@ int pythonmod_init(struct module_env* env, int id) /* for python 3.9 and newer */ char* fstr = NULL; size_t flen = 0; + long pos = 0; log_err("pythonmod: can't parse Python script %s", pe->fname); /* print the error to logs too, run it again */ fseek(script_py, 0, SEEK_END); - flen = (size_t)ftell(script_py); + pos = ftell(script_py); + if (pos == -1L) { + log_err("ftell failed to print parse error: %s: %s", + pe->fname, strerror(errno)); + goto fail_close_file; + } + flen = (size_t)pos; #ifdef SIZE_MAX if(flen > SIZE_MAX-2) { log_err("script file too large"); From 737c28e8361856ed08c13298c4bd773b0c4c2585 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Thu, 23 Jul 2026 10:22:53 +0200 Subject: [PATCH 52/84] Changelog entry for #1478 - Merge #1478 from petrvaganoff: pythonmod: add check return value after ftell(). --- doc/Changelog | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/Changelog b/doc/Changelog index f88b813ad..07093b1ca 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,5 +1,7 @@ 23 July 2026: Wouter - Updated credits for Xuanchao Xie in 22 july changelog. + - Merge #1478 from petrvaganoff: pythonmod: add check return + value after ftell(). 22 July 2026: Wouter - Release tag for 1.25.2, with the security commits: From 0735cb28d151a56776e1b073c50d0c4775637df4 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Thu, 23 Jul 2026 15:54:59 +0200 Subject: [PATCH 53/84] - Fix that for NSEC3 proofs the NSEC3 zone, as the b32.name is checked to be the same as the signer name. Also RRSIGs are not considered valid when an NSEC3 is not b32.signerzone. Thanks to Qifan Zhang, Palo Alto Networks, for the report. --- doc/Changelog | 4 ++++ validator/val_nsec3.c | 21 +++++++++++++++++++++ validator/val_sigcrypt.c | 14 ++++++++++++++ 3 files changed, 39 insertions(+) diff --git a/doc/Changelog b/doc/Changelog index 07093b1ca..112fc2465 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -2,6 +2,10 @@ - Updated credits for Xuanchao Xie in 22 july changelog. - Merge #1478 from petrvaganoff: pythonmod: add check return value after ftell(). + - Fix that for NSEC3 proofs the NSEC3 zone, as the b32.name is + checked to be the same as the signer name. Also RRSIGs are + not considered valid when an NSEC3 is not b32.signerzone. + Thanks to Qifan Zhang, Palo Alto Networks, for the report. 22 July 2026: Wouter - Release tag for 1.25.2, with the security commits: diff --git a/validator/val_nsec3.c b/validator/val_nsec3.c index 62effde20..d0385be68 100644 --- a/validator/val_nsec3.c +++ b/validator/val_nsec3.c @@ -1248,6 +1248,10 @@ nsec3_prove_nameerror(struct module_env* env, struct val_env* ve, filter_init(&flt, list, num, qinfo); /* init RR iterator */ if(!flt.zone) return sec_status_bogus; /* no RRs */ + if(query_dname_compare(flt.zone, kkey->name) != 0) { + verbose(VERB_ALGO, "NSEC3 name is not b32.signer name"); + return sec_status_bogus; + } if(!param_set_same(&flt, NULL)) return sec_status_bogus; /* nsec3 params from distinct chains*/ if(nsec3_iteration_count_high(ve, &flt, kkey)) @@ -1436,6 +1440,10 @@ nsec3_prove_nodata(struct module_env* env, struct val_env* ve, filter_init(&flt, list, num, qinfo); /* init RR iterator */ if(!flt.zone) return sec_status_bogus; /* no RRs */ + if(query_dname_compare(flt.zone, kkey->name) != 0) { + verbose(VERB_ALGO, "NSEC3 name is not b32.signer name"); + return sec_status_bogus; + } if(!param_set_same(&flt, NULL)) return sec_status_bogus; /* nsec3 params from distinct chains*/ if(nsec3_iteration_count_high(ve, &flt, kkey)) @@ -1461,6 +1469,10 @@ nsec3_prove_wildcard(struct module_env* env, struct val_env* ve, filter_init(&flt, list, num, qinfo); /* init RR iterator */ if(!flt.zone) return sec_status_bogus; /* no RRs */ + if(query_dname_compare(flt.zone, kkey->name) != 0) { + verbose(VERB_ALGO, "NSEC3 name is not b32.signer name"); + return sec_status_bogus; + } if(!param_set_same(&flt, NULL)) return sec_status_bogus; /* nsec3 params from distinct chains*/ if(nsec3_iteration_count_high(ve, &flt, kkey)) @@ -1565,6 +1577,11 @@ nsec3_prove_nods(struct module_env* env, struct val_env* ve, *reason = "no NSEC3 records"; return sec_status_bogus; /* no RRs */ } + if(query_dname_compare(flt.zone, kkey->name) != 0) { + verbose(VERB_ALGO, "NSEC3 name is not b32.signer name"); + *reason = "NSEC3 name is not b32.signer name"; + return sec_status_bogus; + } if(!param_set_same(&flt, reason)) return sec_status_bogus; /* nsec3 params from distinct chains*/ if(nsec3_iteration_count_high(ve, &flt, kkey)) @@ -1660,6 +1677,10 @@ nsec3_prove_nxornodata(struct module_env* env, struct val_env* ve, filter_init(&flt, list, num, qinfo); /* init RR iterator */ if(!flt.zone) return sec_status_bogus; /* no RRs */ + if(query_dname_compare(flt.zone, kkey->name) != 0) { + verbose(VERB_ALGO, "NSEC3 name is not b32.signer name"); + return sec_status_bogus; + } if(!param_set_same(&flt, NULL)) return sec_status_bogus; /* nsec3 params from distinct chains*/ if(nsec3_iteration_count_high(ve, &flt, kkey)) diff --git a/validator/val_sigcrypt.c b/validator/val_sigcrypt.c index 8cfbb1466..55e7f1266 100644 --- a/validator/val_sigcrypt.c +++ b/validator/val_sigcrypt.c @@ -1627,6 +1627,20 @@ dnskey_verify_rrset_sig(struct regional* region, sldns_buffer* buf, *reason_bogus = LDNS_EDE_DNSSEC_BOGUS; return sec_status_bogus; /* signer name offtree */ } + /* NSEC3, the owner name must be the .signername */ + if(ntohs(rrset->rk.type) == LDNS_RR_TYPE_NSEC3 && + rrset->rk.dname_len > 0) { + uint8_t* dnameless = rrset->rk.dname; + size_t dnamelesslen = rrset->rk.dname_len; + dname_remove_label(&dnameless, &dnamelesslen); + if(query_dname_compare(dnameless, signer) != 0) { + verbose(VERB_QUERY, "verify: NSEC3 owner name is not b32.signer name"); + *reason = "NSEC3 owner name is not b32.signer name"; + if(reason_bogus) + *reason_bogus = LDNS_EDE_DNSSEC_BOGUS; + return sec_status_bogus; /* NSEC3 owner not b32.signer */ + } + } sigblock = (unsigned char*)signer+signer_len; if(siglen < 2+18+signer_len+1) { verbose(VERB_QUERY, "verify: too short, no signature data"); From 5eb362a6c0da075fbf810c7247fc2cdbe50bc6e0 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Thu, 23 Jul 2026 16:17:59 +0200 Subject: [PATCH 54/84] - Fix that the aggressive negative cache does not insert NSEC records with overreaching next owner name. Also the result is not above the trust anchor's bailiwick. Also RRSIGS are not considered valid when an NSEC next owner name is not under the signer zone name. Thanks to Qifan Zhang, Palo Alto Networks, for the report. --- doc/Changelog | 6 + iterator/iterator.c | 2 +- testdata/nsec_cross_zone.rpl | 216 +++++++++++++++++++++++++++ testdata/stop_nxdomain_minimised.rpl | 25 +++- validator/val_neg.c | 21 ++- validator/val_sigcrypt.c | 10 ++ validator/val_utils.c | 17 +++ validator/val_utils.h | 3 + 8 files changed, 293 insertions(+), 7 deletions(-) create mode 100644 testdata/nsec_cross_zone.rpl diff --git a/doc/Changelog b/doc/Changelog index 112fc2465..fee4ed411 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -6,6 +6,12 @@ checked to be the same as the signer name. Also RRSIGs are not considered valid when an NSEC3 is not b32.signerzone. Thanks to Qifan Zhang, Palo Alto Networks, for the report. + - Fix that the aggressive negative cache does not insert NSEC + records with overreaching next owner name. Also the result + is not above the trust anchor's bailiwick. Also RRSIGS are + not considered valid when an NSEC next owner name is not + under the signer zone name. Thanks to Qifan Zhang, Palo + Alto Networks, for the report. 22 July 2026: Wouter - Release tag for 1.25.2, with the security commits: diff --git a/iterator/iterator.c b/iterator/iterator.c index 25d40f269..1f95039c8 100644 --- a/iterator/iterator.c +++ b/iterator/iterator.c @@ -1531,7 +1531,7 @@ processInitRequest(struct module_qstate* qstate, struct iter_qstate* iq, msg = val_neg_getmsg(qstate->env->neg_cache, &iq->qchase, qstate->region, qstate->env->rrset_cache, qstate->env->scratch_buffer, - *qstate->env->now, 1/*add SOA*/, NULL, + *qstate->env->now, 1/*add SOA*/, dpname, qstate->env->cfg); } /* item taken from cache does not match our query name, thus diff --git a/testdata/nsec_cross_zone.rpl b/testdata/nsec_cross_zone.rpl new file mode 100644 index 000000000..8973fae9f --- /dev/null +++ b/testdata/nsec_cross_zone.rpl @@ -0,0 +1,216 @@ +; config options +; The island of trust is at test. +server: + trust-anchor: "test. DS 1444 8 2 8a87d067fd09a5965244fe2e317dd26d182c468e0a7f26ecc4c7b479bf89db9b" + val-override-date: "20201020135527" + target-fetch-policy: "0 0 0 0 0" + qname-minimisation: "no" + fake-sha1: yes + trust-anchor-signaling: no + minimal-responses: no + iter-scrub-promiscuous: no + aggressive-nsec: yes + local-zone: test. nodefault + log-servfail: yes + +stub-zone: + name: "." + stub-addr: 193.0.14.129 # K.ROOT-SERVERS.NET. +CONFIG_END + +SCENARIO_BEGIN Test overreaching NSEC with aggressive cache + +; K.ROOT-SERVERS.NET. +RANGE_BEGIN 0 100 + ADDRESS 193.0.14.129 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +. IN NS +SECTION ANSWER +. IN NS K.ROOT-SERVERS.NET. +SECTION ADDITIONAL +K.ROOT-SERVERS.NET. IN A 193.0.14.129 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode subdomain +ADJUST copy_id copy_query +REPLY QR NOERROR +SECTION QUESTION +test. IN NS +SECTION AUTHORITY +test. IN NS ns.test. +SECTION ADDITIONAL +ns.test. IN A 1.2.3.5 +ENTRY_END +RANGE_END + +; ns.test +RANGE_BEGIN 0 100 + ADDRESS 1.2.3.5 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +test. IN NS +SECTION ANSWER +test. IN NS ns.test +test. 3600 IN RRSIG NS 8 1 3600 20201116135527 20201019135527 1444 test. RGCxIO32TbbLTk6xZmTr+fjYPH50hntBxeOQ2DIj2pDsmjALcHYtVkOfpfk2EhOhHZd+9PLuoJPbJh6a9NqLSFeBvr0XZoCZoQ2g0tCHUNHcH5EVjA2TuYBQem6DVYnPLJ3914aRx0uA1j42b8dC2xsam/XkOo7U+dLbUW2Os1s= +SECTION ADDITIONAL +ns.test. IN A 1.2.3.5 +ns.test. 3600 IN RRSIG A 8 2 3600 20201116135527 20201019135527 1444 test. GskCc4/k6GjH9V9Jz2V5L2XLiizbOeWkB0feSbf+aN859S3vxVvtuqkvIgwY4LafUO1QAn/pUcv9zA7rcFO++rlg+8t6gvZTo9p3v0bfeIv2uJDsfSBD5jDh0WXlxjekfnrKrQp7zE+GiA93tWwKUWKPvxXDgP+n886e6WcbHJw= +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +ns.test. IN A +SECTION ANSWER +ns.test. IN A 1.2.3.5 +ns.test. 3600 IN RRSIG A 8 2 3600 20201116135527 20201019135527 1444 test. GskCc4/k6GjH9V9Jz2V5L2XLiizbOeWkB0feSbf+aN859S3vxVvtuqkvIgwY4LafUO1QAn/pUcv9zA7rcFO++rlg+8t6gvZTo9p3v0bfeIv2uJDsfSBD5jDh0WXlxjekfnrKrQp7zE+GiA93tWwKUWKPvxXDgP+n886e6WcbHJw= +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +ns.test. IN AAAA +SECTION AUTHORITY +test. 3600 IN SOA ns.test. host.test. 20201 3600 1800 604800 3600 +test. 3600 IN RRSIG SOA 8 1 3600 20201116135527 20201019135527 1444 test. IZJIDmEgf0W7A5G7hvvZ2hUqJ9Trbv1/i7ySapDmPbYV9lVCmHHobySxO01yDhI2/Pvpsvxqrm1Tiv3BxH8uzZ4keKgiQjBsSy4htAsFct9I4E7ly2glPj/Fm3oun3PsjJDv5QYhx0KS7w4IQKU7Nc9pfJc92uoUI5bdoC1pRGw= +ns.test. 3600 IN NSEC nz.test. A RRSIG +ns.test. 3600 IN RRSIG NSEC 8 2 3600 20201116135527 20201019135527 1444 test. PElArVB3KPg8KHAP7lzcNbhFuXNxTsHNTn1dZVncB5qmWRdIaeKpaXDjpH0JSXMaelGFS+/QhuQ6Hmw9+4VyZFRqMzGhw4agUR/2bxABHcDIG4ZpUwyeSP61ATTfHUkQVxaH2wjCWI/tfmesdP2xVE4GXyUvCIBxU914MkZbULU= +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +test. IN DNSKEY +SECTION ANSWER +test. 3600 IN DNSKEY 257 3 8 AwEAAbd9WqjzE2Pynz21OG5doSf9hFzMr5dhzz2waZ3vTa+0o5r7AjTAqmA1yH/B3+aAMihUm5ucZSfVqo7+kOaRE8yFj9aivOmA1n1+JLevJq/oyvQyjxQN2Qb89LyaNUT5oKZIiL+uyyhNW3KDR3SSbQ/GBwQNDHVcZi+JDR3RC0r7 ;{id = 1444 (ksk), size = 1024b} +test. 3600 IN RRSIG DNSKEY 8 1 3600 20201116135527 20201019135527 1444 test. UmRMS4iG9NBBHZYOtpwFFcJgbEb5SfHSgHd9XRe/8pTWM31WSDayn5ViPOBMqI1T5TXg2amc13dDI574xIM2oKMus3b5cBW72jJLW13jprBtslO6P8BMWb4HNnvLrJtQjwf3ErRirtTxinLmywQtmyr1cdthyG3Gp4N7i90fHSc= +SECTION ADDITIONAL +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qname qtype +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +example.test. IN DS +SECTION ANSWER +example.test. 3600 IN DS 55567 8 2 a2d578906330a10a57d40462257b6ce038bad3f7bf4a45c46c46086e20a94b39 +example.test. 3600 IN RRSIG DS 8 2 3600 20201116135527 20201019135527 1444 test. P7+FTYW2qHuJ4I1YbuvseEz5X1lOYAraGEHB3C5y0OOCQFmhmSiFRdquNi2NlpcS6FXLdsE0EU+Bo1+0atTG4EkMWXbpF21lrtbB51BdsnlX4Mzc/o375fvjiOMwmF6wPCUaOUN62jrVrhsE/hedaVyDphDToqL17ETohwgUO2I= +ENTRY_END + +ENTRY_BEGIN +MATCH opcode subdomain +ADJUST copy_id copy_query +REPLY QR NOERROR +SECTION QUESTION +example.test. IN NS +SECTION AUTHORITY +example.test. IN NS ns.example.test. +example.test. 3600 IN DS 55567 8 2 a2d578906330a10a57d40462257b6ce038bad3f7bf4a45c46c46086e20a94b39 +example.test. 3600 IN RRSIG DS 8 2 3600 20201116135527 20201019135527 1444 test. P7+FTYW2qHuJ4I1YbuvseEz5X1lOYAraGEHB3C5y0OOCQFmhmSiFRdquNi2NlpcS6FXLdsE0EU+Bo1+0atTG4EkMWXbpF21lrtbB51BdsnlX4Mzc/o375fvjiOMwmF6wPCUaOUN62jrVrhsE/hedaVyDphDToqL17ETohwgUO2I= +SECTION ADDITIONAL +ns.example.test. IN A 1.2.3.4 +ENTRY_END +RANGE_END + +; ns.example.test. +RANGE_BEGIN 0 100 + ADDRESS 1.2.3.4 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +example.test. IN NS +SECTION ANSWER +example.test. IN NS ns.example.test. +example.test. 3600 IN RRSIG NS 8 2 3600 20201116135527 20201019135527 55567 example.test. l1JT0wMlK0YI7/CWHzexf/k0iafUhCgN+BdgjBXIRXmSQNf4HDTiAkbcWL2/15qtnp12nQy9JeiTdSQ3vtPoHAJX4C5uTWaze4ms+Wrrf+n92sLCjacP9x50uuicH3URT6cKb1QCAPwlvlWxIlZjAMYFScSns7+C441NMJT8aE4= +SECTION ADDITIONAL +ns.example.test. IN A 1.2.3.4 +ns.example.test. 3600 IN RRSIG A 8 3 3600 20201116135527 20201019135527 55567 example.test. 2PWaVaccZFQgfPKXNsdEGYUVaashCAj1ZhBo9XRt5eQKUFvZcauBjMnXIuxZFyWeootn1fZGw6GuPI5W48Y0FDx38H6adprkFgQikso2Y64jDdDMWznSo38Z/XqP+U0+kq4vmwonvmEMpm7hKnNEXvhqGKyGzyBwb+CZVJ2L8Eo= +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +ns.example.test. IN A +SECTION ANSWER +ns.example.test. IN A 1.2.3.4 +ns.example.test. 3600 IN RRSIG A 8 3 3600 20201116135527 20201019135527 55567 example.test. 2PWaVaccZFQgfPKXNsdEGYUVaashCAj1ZhBo9XRt5eQKUFvZcauBjMnXIuxZFyWeootn1fZGw6GuPI5W48Y0FDx38H6adprkFgQikso2Y64jDdDMWznSo38Z/XqP+U0+kq4vmwonvmEMpm7hKnNEXvhqGKyGzyBwb+CZVJ2L8Eo= +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +ns.example.test. IN AAAA +SECTION AUTHORITY +example.test. 3600 IN SOA ns.example.test. host.example.test. 20301 3600 1800 604800 3600 +example.test. 3600 IN RRSIG SOA 8 2 3600 20201116135527 20201019135527 55567 example.test. 2UUkScBAN37fJpSrelhE8DotKvmOzj3q9wicaanCIaCv95DE4nQnePih5B+ek3FIRjB/Uv2+z4Ro5Uxy94XAnlK0rCkDLSa0U9U7KP0ytc88sevO0x1SCPAMoZoJO6JqHkv42pdh54WSz+Zb/D8npY0j/tksHe/uX+VQnMymgb8= +ns.example.test. 3600 IN NSEC nz.example.test. A RRSIG +example.test. 3600 IN SOA ns.example.test. host.example.test. 20301 3600 1800 604800 3600 +ENTRY_END + +; response to DNSKEY priming query +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +example.test. IN DNSKEY +SECTION ANSWER +example.test. 3600 IN DNSKEY 257 3 8 AwEAAdug/L739i0mgN2nuK/bhxu3wFn5Ud9nK2+XUmZQlPUEZUC5YZvm1rfMmEWTGBn87fFxEu/kjFZHJ55JLzqsbbpVHLbmKCTT2gYR2FV2WDKROGKuYbVkJIXdKAjJ0ONuK507NinYvlWXIoxHn22KAWOd9wKgSTNHBlmGkX+ts3hh ;{id = 55567 (ksk), size = 1024b} +example.test. 3600 IN RRSIG DNSKEY 8 2 3600 20201116135527 20201019135527 55567 example.test. IbWMC6quOuZFNPAVxQLqCJ9nLhindBo826rnLcg5yMgs9dGUSPOCXAfHTmbgJAUNs9HTFfrJWNvasnETs0UOpmEuifGwWdH1OlME7Gny4RL2QmITUFeMW81Jz1tiVQxFXl6yxT0jxOxvz+bqMHlrz+8IeWQXcO+GZTPu8ueq30g= +ENTRY_END + +; response to query of interest +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +example.test. IN NSEC +SECTION ANSWER +; normal record has next owner in example.test. +; example.test. IN NSEC b.example.test. SOA DNSKEY NS RRSIG NSEC +example.test. IN NSEC b.foo.test. NS SOA RRSIG NSEC DNSKEY +example.test. 3600 IN RRSIG NSEC 8 2 3600 20201116135527 20201019135527 55567 example.test. xxepzzIxsJURk4/eZrwcDm5jhQNHtf1OmnPuu3T/w8y5NWwzlgn/hL17xoI71dIgTJg2GAq97wxEUhp951jtGMCeLH2Dz5lDZXxQI4wf2Wl43u2mTBQFRagDwfAauFc6Z4FYI/biDZyYcylZ3A5Q6j6ifFnsgMTL+cP0UIEZBTQ= +ENTRY_END +RANGE_END + +; CD=1 query for type NSEC +STEP 1 QUERY +ENTRY_BEGIN +REPLY RD CD DO +SECTION QUESTION +example.test. IN NSEC +ENTRY_END + +STEP 10 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR RD CD RA DO NOERROR +SECTION QUESTION +example.test. IN NSEC +SECTION ANSWER +; The overreaching NSEC is removed by the scrubber. +ENTRY_END + +SCENARIO_END diff --git a/testdata/stop_nxdomain_minimised.rpl b/testdata/stop_nxdomain_minimised.rpl index 0de22edde..d6e881f59 100644 --- a/testdata/stop_nxdomain_minimised.rpl +++ b/testdata/stop_nxdomain_minimised.rpl @@ -7,6 +7,7 @@ server: val-override-date: "20070916134226" fake-sha1: yes trust-anchor-signaling: no + domain-insecure: "anotherexample.local" stub-zone: name: "." @@ -69,7 +70,7 @@ REPLY QR AA NOERROR SECTION QUESTION anotherexample.local. IN TXT SECTION ANSWER -anotherexample.local. 86400 IN TXT "should not resolve this" +anotherexample.local. 86400 IN TXT "stub works" ENTRY_END RANGE_END @@ -95,7 +96,7 @@ STEP 20 QUERY ENTRY_BEGIN REPLY RD SECTION QUESTION -anotherexample.local. IN TXT +anotherexample2.local. IN TXT ENTRY_END ; query should be answered using NXDOMAIN for local in cache @@ -104,9 +105,27 @@ ENTRY_BEGIN MATCH all REPLY QR RD RA NXDOMAIN SECTION QUESTION -anotherexample.local. IN TXT +anotherexample2.local. IN TXT SECTION AUTHORITY . 86400 IN SOA a.root-servers.net. nstld.verisign-grs.com. 2010111601 1800 900 604800 86400 ENTRY_END +STEP 40 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +anotherexample.local. IN TXT +ENTRY_END + +; The stub stops going higher in the negative cache. +STEP 50 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR RD RA NOERROR +SECTION QUESTION +anotherexample.local. IN TXT +SECTION ANSWER +anotherexample.local. 86400 IN TXT "stub works" +ENTRY_END + SCENARIO_END diff --git a/validator/val_neg.c b/validator/val_neg.c index 5835fcbaf..63f380ad9 100644 --- a/validator/val_neg.c +++ b/validator/val_neg.c @@ -938,6 +938,10 @@ void val_neg_addreply(struct val_neg_cache* neg, struct reply_info* rep) continue; if(!dname_subdomain_c(rep->rrsets[i]->rk.dname, zone->name)) continue; + if(ntohs(rep->rrsets[i]->rk.type) == LDNS_RR_TYPE_NSEC && + !nsec_nextowner_subdomain(rep->rrsets[i], zone->name)) { + continue; /* nextowner not in zone */ + } /* insert NSEC into this zone's tree */ neg_insert_data(neg, zone, rep->rrsets[i]); } @@ -1022,6 +1026,10 @@ void val_neg_addreferral(struct val_neg_cache* neg, struct reply_info* rep, continue; if(!dname_subdomain_c(rep->rrsets[i]->rk.dname, zone->name)) continue; + if(ntohs(rep->rrsets[i]->rk.type) == LDNS_RR_TYPE_NSEC && + !nsec_nextowner_subdomain(rep->rrsets[i], zone->name)) { + continue; /* nextowner not in zone */ + } /* insert NSEC into this zone's tree */ neg_insert_data(neg, zone, rep->rrsets[i]); } @@ -1110,12 +1118,14 @@ grab_nsec(struct rrset_cache* rrset_cache, uint8_t* qname, size_t qname_len, * @param rrset_cache: rrset cache * @param now: to check ttl against * @param region: where to alloc result + * @param topname: do not look higher than this name, so that the + * result cannot be taken from a zone above the current trust anchor. * @return rrset or NULL */ static struct ub_packed_rrset_key* neg_find_nsec(struct val_neg_cache* neg_cache, uint8_t* qname, size_t qname_len, uint16_t qclass, struct rrset_cache* rrset_cache, time_t now, - struct regional* region) + struct regional* region, uint8_t* topname) { int labs; uint32_t flags; @@ -1133,6 +1143,11 @@ neg_find_nsec(struct val_neg_cache* neg_cache, uint8_t* qname, size_t qname_len, lock_basic_unlock(&neg_cache->lock); return NULL; } + if(topname && !dname_subdomain_c(zone->name, topname)) { + /* Reject NSEC not within trust anchor's bailiwick */ + lock_basic_unlock(&neg_cache->lock); + return NULL; + } /* NSEC only for now */ if(zone->nsec3_hash) { @@ -1430,7 +1445,7 @@ val_neg_getmsg(struct val_neg_cache* neg, struct query_info* qinfo, /* Get best available NSEC for qname */ nsec = neg_find_nsec(neg, qinfo->qname, qinfo->qname_len, qinfo->qclass, - rrset_cache, now, region); + rrset_cache, now, region, topname); /* Matching NSEC, use to generate No Data answer. Not creating answers * yet for No Data proven using wildcard. */ @@ -1510,7 +1525,7 @@ val_neg_getmsg(struct val_neg_cache* neg, struct query_info* qinfo, * proof */ if(!(wcrr = neg_find_nsec(neg, wc_qinfo.qname, wc_qinfo.qname_len, qinfo->qclass, - rrset_cache, now, region))) + rrset_cache, now, region, topname))) return NULL; nodata_wc = NULL; diff --git a/validator/val_sigcrypt.c b/validator/val_sigcrypt.c index 55e7f1266..4139cc1fe 100644 --- a/validator/val_sigcrypt.c +++ b/validator/val_sigcrypt.c @@ -1641,6 +1641,16 @@ dnskey_verify_rrset_sig(struct regional* region, sldns_buffer* buf, return sec_status_bogus; /* NSEC3 owner not b32.signer */ } } + /* NSEC, a next owner that is not under the signer is not allowed.*/ + if(ntohs(rrset->rk.type) == LDNS_RR_TYPE_NSEC && + !nsec_nextowner_subdomain(rrset, signer)) { + verbose(VERB_QUERY, "verify: NSEC next owner overreaches signer name"); + *reason = "NSEC next owner overreaches signer name"; + if(reason_bogus) + *reason_bogus = LDNS_EDE_DNSSEC_BOGUS; + return sec_status_bogus; /* nextowner overreaching */ + } + sigblock = (unsigned char*)signer+signer_len; if(siglen < 2+18+signer_len+1) { verbose(VERB_QUERY, "verify: too short, no signature data"); diff --git a/validator/val_utils.c b/validator/val_utils.c index 9ff1c224d..e77f93f5a 100644 --- a/validator/val_utils.c +++ b/validator/val_utils.c @@ -1384,3 +1384,20 @@ int derive_cname_from_dname(struct ub_packed_rrset_key* cname, memmove(out+prefix_len, dname_target, dname_target_len); return 1; } + +int nsec_nextowner_subdomain(struct ub_packed_rrset_key* rrset, uint8_t* name) +{ + struct packed_rrset_data* d; + uint8_t* next; + size_t nextlen; + if(ntohs(rrset->rk.type) != LDNS_RR_TYPE_NSEC) + return 0; + d = (struct packed_rrset_data*)rrset->entry.data; + if(!d || d->count == 0) + return 0; + next = d->rr_data[0]+2; + nextlen = dname_valid(next, d->rr_len[0]-2); + if(nextlen == 0) + return 0; /* malformed */ + return dname_subdomain_c(next, name); +} diff --git a/validator/val_utils.h b/validator/val_utils.h index b44915a2a..43386edbf 100644 --- a/validator/val_utils.h +++ b/validator/val_utils.h @@ -452,4 +452,7 @@ int derive_cname_from_dname(struct ub_packed_rrset_key* cname, void rrsig_get_signer(uint8_t* data, size_t len, uint8_t** sname, size_t* slen); +/** See if the NSEC nextowner name is a subdomain of the name. */ +int nsec_nextowner_subdomain(struct ub_packed_rrset_key* rrset, uint8_t* name); + #endif /* VALIDATOR_VAL_UTILS_H */ From a05d460e661b7793eccd6c660b71f3d4a1229915 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Thu, 23 Jul 2026 16:28:45 +0200 Subject: [PATCH 55/84] - Fix mesh cycle detection for configuration with respip CNAME loop and tagged clients. Thanks to Qifan Zhang, Palo Alto Networks, for the report. --- daemon/remote.c | 72 ++++++++ doc/Changelog | 3 + respip/respip.c | 6 +- services/mesh.c | 65 +++---- services/mesh.h | 7 + testdata/respip_cname_loop_tagged.rpl | 254 ++++++++++++++++++++++++++ 6 files changed, 369 insertions(+), 38 deletions(-) create mode 100644 testdata/respip_cname_loop_tagged.rpl diff --git a/daemon/remote.c b/daemon/remote.c index 4d9fe50ac..8e6ba19b1 100644 --- a/daemon/remote.c +++ b/daemon/remote.c @@ -5026,6 +5026,74 @@ fr_check_changed_cfg_str2list(struct config_str2list* cmp1, } } +/** fast reload thread, check if config str3list has changed. */ +#define FR_CHECK_CHANGED_CFG_STR3LIST(desc, var, buff) do { \ + fr_check_changed_cfg_str3list(cfg->var, newcfg->var, desc, buff,\ + sizeof(buff)); \ + } while(0); +static void +fr_check_changed_cfg_str3list(struct config_str3list* cmp1, + struct config_str3list* cmp2, const char* desc, char* str, size_t len) +{ + struct config_str3list* p1 = cmp1, *p2 = cmp2; + while(p1 && p2) { + if((!p1->str && p2->str) || + (p1->str && !p2->str) || + (p1->str && p2->str && strcmp(p1->str, p2->str) != 0)) { + /* The str3list is different. */ + fr_add_incompatible_option(desc, str, len); + return; + } + if((!p1->str2 && p2->str2) || + (p1->str2 && !p2->str2) || + (p1->str2 && p2->str2 && + strcmp(p1->str2, p2->str2) != 0)) { + /* The str3list is different. */ + fr_add_incompatible_option(desc, str, len); + return; + } + if((!p1->str3 && p2->str3) || + (p1->str3 && !p2->str3) || + (p1->str3 && p2->str3 && + strcmp(p1->str3, p2->str3) != 0)) { + /* The str3list is different. */ + fr_add_incompatible_option(desc, str, len); + return; + } + p1 = p1->next; + p2 = p2->next; + } + if((!p1 && p2) || (p1 && !p2)) { + fr_add_incompatible_option(desc, str, len); + } +} + +/** fast reload thread, check tag datas. */ +static int +fr_check_tag_datas(struct fast_reload_thread* fr, struct config_file* newcfg) +{ + char changed_str[1024]; + struct config_file* cfg = fr->worker->env.cfg; + changed_str[0]=0; + + /* Check for tag_datas in acl_addr. */ + FR_CHECK_CHANGED_CFG_STR3LIST("interface-tag-data", interface_tag_datas, changed_str); + FR_CHECK_CHANGED_CFG_STR3LIST("access-control-tag-data", acl_tag_datas, changed_str); + + if(changed_str[0] != 0) { + if(fr->fr_drop_mesh) + return 1; /* already dropping queries */ + fr->fr_drop_mesh = 1; + fr->worker->daemon->fast_reload_drop_mesh = fr->fr_drop_mesh; + if(!fr_output_printf(fr, "recursion referenced data has changed, with: '%s" + "', and the queries have to be dropped" + ", setting '+d'\n", changed_str)) + return 0; + fr_send_notification(fr, fast_reload_notification_printout); + } + return 1; +} + /** fast reload thread, check compatible config items */ static int fr_check_compat_cfg(struct fast_reload_thread* fr, struct config_file* newcfg) @@ -6911,6 +6979,10 @@ fr_load_config(struct fast_reload_thread* fr, struct timeval* time_read, config_delete(newcfg); return 0; } + if(!fr_check_tag_datas(fr, newcfg)) { + config_delete(newcfg); + return 0; + } if(!fr_check_compat_cfg(fr, newcfg)) { config_delete(newcfg); return 0; diff --git a/doc/Changelog b/doc/Changelog index fee4ed411..a40ea4aa3 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -12,6 +12,9 @@ not considered valid when an NSEC next owner name is not under the signer zone name. Thanks to Qifan Zhang, Palo Alto Networks, for the report. + - Fix mesh cycle detection for configuration with respip CNAME + loop and tagged clients. Thanks to Qifan Zhang, Palo Alto + Networks, for the report. 22 July 2026: Wouter - Release tag for 1.25.2, with the security commits: diff --git a/respip/respip.c b/respip/respip.c index 6ade552db..fc9de4c01 100644 --- a/respip/respip.c +++ b/respip/respip.c @@ -1164,8 +1164,10 @@ respip_operate(struct module_qstate* qstate, enum module_ev event, int id, * clients. */ qstate->is_drop = 1; } else if(alias_rrset) { - if(!generate_cname_request(qstate, alias_rrset)) + if(!generate_cname_request(qstate, alias_rrset)) { + errinf(qstate, "Could not generate CNAME request"); goto servfail; + } next_state = module_wait_subquery; } qstate->return_msg->rep = new_rep; @@ -1179,6 +1181,7 @@ respip_operate(struct module_qstate* qstate, enum module_ev event, int id, servfail: qstate->return_rcode = LDNS_RCODE_SERVFAIL; qstate->return_msg = NULL; + qstate->ext_state[id] = module_finished; } int @@ -1275,6 +1278,7 @@ respip_inform_super(struct module_qstate* qstate, int id, return; fail: + errinf(super, "CNAME lookup failed"); super->return_rcode = LDNS_RCODE_SERVFAIL; super->return_msg = NULL; return; diff --git a/services/mesh.c b/services/mesh.c index 6159dd1cd..add773b88 100644 --- a/services/mesh.c +++ b/services/mesh.c @@ -965,32 +965,9 @@ void mesh_report_reply(struct mesh_area* mesh, struct outbound_entry* e, mesh_run(mesh, e->qstate->mesh_info, event, e); } -/** copy strlist to region */ -static struct config_strlist* -cfg_region_strlist_copy(struct regional* region, struct config_strlist* list) -{ - struct config_strlist* result = NULL, *last = NULL, *s = list; - while(s) { - struct config_strlist* n = regional_alloc_zero(region, - sizeof(*n)); - if(!n) - return NULL; - n->str = regional_strdup(region, s->str); - if(!n->str) - return NULL; - if(last) - last->next = n; - else result = n; - last = n; - s = s->next; - } - return result; -} - struct respip_client_info* mesh_copy_client_info(struct regional* region, struct respip_client_info* cinfo) { - size_t i; struct respip_client_info* client_info; client_info = regional_alloc_init(region, cinfo, sizeof(*cinfo)); if(!client_info) @@ -1009,20 +986,13 @@ mesh_copy_client_info(struct regional* region, struct respip_client_info* cinfo) if(!client_info->tag_actions) return NULL; } - if(cinfo->tag_datas) { - client_info->tag_datas = regional_alloc_zero(region, - sizeof(struct config_strlist*)*cinfo->tag_datas_size); - if(!client_info->tag_datas) - return NULL; - for(i=0; itag_datas_size; i++) { - if(cinfo->tag_datas[i]) { - client_info->tag_datas[i] = cfg_region_strlist_copy( - region, cinfo->tag_datas[i]); - if(!client_info->tag_datas[i]) - return NULL; - } - } - } + /* tag_datas is owned by the matched acl_addr in config_file; its + * lifetime is until config reload, which tears down all mesh states + * first. Keep the original pointer so client_info_compare() + * can recognise two states from the same ACL entry. */ + /* fast reload insists on dropping the queries when interface-tag-data + * or access-control-tag-data are changed. */ + /* client_info->tag_datas already copied by regional_alloc_init above */ if(cinfo->view) { /* Do not copy the view pointer but store a name instead. * The name is looked up later when done, this means that @@ -2306,8 +2276,29 @@ void mesh_run(struct mesh_area* mesh, struct mesh_state* mstate, enum module_ev ev, struct outbound_entry* e) { enum module_ext_state s; + int numrun = 0; verbose(VERB_ALGO, "mesh_run: start"); while(mstate) { + if(numrun++ > MESH_MAX_RUN_ITER) { + /* These modules are too much to activate, stop them.*/ + log_err("Too many module run iterations, deleting"); + while(mstate) { + /* notify supers */ + if(mstate->super_set.count > 0) { + verbose(VERB_ALGO, "notify supers of failure"); + mstate->s.return_msg = NULL; + mstate->s.return_rcode = LDNS_RCODE_SERVFAIL; + mesh_walk_supers(mesh, mstate); + } + mesh_state_delete(&mstate->s); + if(mesh->run.count > 0) { + /* pop random element off the runnable tree */ + mstate = (struct mesh_state*)mesh->run.root->key; + (void)rbtree_delete(&mesh->run, mstate); + } else mstate = NULL; + } + break; + } /* run the module */ fptr_ok(fptr_whitelist_mod_operate( mesh->mods.mod[mstate->s.curmod]->operate)); diff --git a/services/mesh.h b/services/mesh.h index 0f1d91c00..9e3da53a7 100644 --- a/services/mesh.h +++ b/services/mesh.h @@ -69,6 +69,13 @@ struct respip_client_info; */ #define MESH_MAX_ACTIVATION 10000 +/** + * Maximum number of mesh state run items. These are different modules + * activated during a mesh run. Any more is likely an infinite loop + * in the module. It is then terminated, and states are deleted. + */ +#define MESH_MAX_RUN_ITER 10000 + /** * Max number of references-to-references-to-references.. search size. * Any more is treated like 'too large', and the creation of a new diff --git a/testdata/respip_cname_loop_tagged.rpl b/testdata/respip_cname_loop_tagged.rpl new file mode 100644 index 000000000..a97ae77ad --- /dev/null +++ b/testdata/respip_cname_loop_tagged.rpl @@ -0,0 +1,254 @@ +; config options +; The island of trust is at test. +server: + target-fetch-policy: "0 0 0 0 0" + qname-minimisation: "no" + fake-sha1: yes + trust-anchor-signaling: no + minimal-responses: no + iter-scrub-promiscuous: no + aggressive-nsec: no + local-zone: test. nodefault + log-servfail: yes + discard-timeout: 0 + module-config: "respip iterator" + define-tag: "turqoise" + access-control-tag: 127.0.0.0/8 "turqoise" + access-control-tag-data: 127.0.0.0/8 "turqoise" "A 127.0.0.1" + + ; These two CNAMEs form a loop. + response-ip: 192.0.2.1/32 redirect + response-ip-data: 192.0.2.1/32 "CNAME loop2.far.test." + response-ip: 192.0.2.2/32 redirect + response-ip-data: 192.0.2.2/32 "CNAME loop1.far.test." + +stub-zone: + name: "." + stub-addr: 193.0.14.129 # K.ROOT-SERVERS.NET. +CONFIG_END + +SCENARIO_BEGIN Test respip CNAME loop that is tagged. + +; K.ROOT-SERVERS.NET. +RANGE_BEGIN 0 100 + ADDRESS 193.0.14.129 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +. IN NS +SECTION ANSWER +. IN NS K.ROOT-SERVERS.NET. +SECTION ADDITIONAL +K.ROOT-SERVERS.NET. IN A 193.0.14.129 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode subdomain +ADJUST copy_id copy_query +REPLY QR NOERROR +SECTION QUESTION +test. IN NS +SECTION AUTHORITY +test. IN NS ns.test. +SECTION ADDITIONAL +ns.test. IN A 1.2.3.5 +ENTRY_END +RANGE_END + +; ns.test +RANGE_BEGIN 0 100 + ADDRESS 1.2.3.5 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +test. IN NS +SECTION ANSWER +test. IN NS ns.test +SECTION ADDITIONAL +ns.test. IN A 1.2.3.5 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +ns.test. IN A +SECTION ANSWER +ns.test. IN A 1.2.3.5 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +ns.test. IN AAAA +SECTION AUTHORITY +test. 3600 IN SOA ns.test. host.test. 20201 3600 1800 604800 3600 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode subdomain +ADJUST copy_id copy_query +REPLY QR NOERROR +SECTION QUESTION +example.test. IN NS +SECTION AUTHORITY +example.test. IN NS ns.example.test. +SECTION ADDITIONAL +ns.example.test. IN A 1.2.3.4 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode subdomain +ADJUST copy_id copy_query +REPLY QR NOERROR +SECTION QUESTION +far.test. IN NS +SECTION AUTHORITY +far.test. IN NS ns.far.test. +SECTION ADDITIONAL +ns.far.test. IN A 1.2.3.6 +ENTRY_END +RANGE_END + +; ns.example.test. +RANGE_BEGIN 0 20 + ADDRESS 1.2.3.4 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +example.test. IN NS +SECTION ANSWER +example.test. IN NS ns.example.test. +SECTION ADDITIONAL +ns.example.test. IN A 1.2.3.4 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +ns.example.test. IN A +SECTION ANSWER +ns.example.test. IN A 1.2.3.4 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +ns.example.test. IN AAAA +SECTION AUTHORITY +example.test. 3600 IN SOA ns.example.test. host.example.test. 20301 3600 1800 604800 3600 +ENTRY_END + +; response to query of interest +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +www.example.test. IN A +SECTION ANSWER +www.example.test. 1 IN A 192.0.2.1 +ENTRY_END +RANGE_END + +; ns.example.test. +RANGE_BEGIN 45 100 + ADDRESS 1.2.3.4 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +www.example.test. IN A +SECTION ANSWER +www.example.test. 1 IN A 192.0.2.1 +ENTRY_END +RANGE_END + +; ns.far.test. +RANGE_BEGIN 0 100 + ADDRESS 1.2.3.6 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +far.test. IN NS +SECTION ANSWER +far.test. IN NS ns.far.test. +SECTION ADDITIONAL +ns.far.test. IN A 1.2.3.6 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +ns.far.test. IN A +SECTION ANSWER +ns.far.test. IN A 1.2.3.6 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +ns.far.test. IN AAAA +SECTION AUTHORITY +far.test. 3600 IN SOA ns.far.test. host.far.test. 20301 3600 1800 604800 3600 +ENTRY_END + +; response to query of interest +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +loop1.far.test. IN A +SECTION ANSWER +loop1.far.test. IN A 192.0.2.1 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +loop2.far.test. IN A +SECTION ANSWER +loop2.far.test. IN A 192.0.2.2 +ENTRY_END +RANGE_END + +STEP 1 QUERY +ENTRY_BEGIN +REPLY RD DO +SECTION QUESTION +loop1.far.test. IN A +ENTRY_END + +STEP 2 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR RD RA DO SERVFAIL +SECTION QUESTION +loop1.far.test. IN A +SECTION ANSWER +ENTRY_END + +SCENARIO_END From c8b3c89a39a5980a768ccd333c854393996b336c Mon Sep 17 00:00:00 2001 From: Jisakiel <471530+jisakiel@users.noreply.github.com> Date: Fri, 24 Jul 2026 07:52:20 +0100 Subject: [PATCH 56/84] Add new static zone type block_aaaa to suppress AAAA queries (#1433) Following d5b9a790f lead for block_a - this would allow suppressing AAAA queries instead for sticking to IPV4. Co-authored-by: Jisakiel --- doc/example.conf.in | 2 ++ doc/unbound.conf.5.in | 10 ++++++++++ doc/unbound.conf.rst | 9 +++++++++ services/localzone.c | 19 +++++++++++++++++-- services/localzone.h | 2 ++ util/configparser.y | 4 +++- 6 files changed, 43 insertions(+), 3 deletions(-) diff --git a/doc/example.conf.in b/doc/example.conf.in index 5250ef456..f1e1e412b 100644 --- a/doc/example.conf.in +++ b/doc/example.conf.in @@ -899,6 +899,8 @@ server: # that name # o block_a resolves all records normally but returns # NODATA for A queries and ignores local data for that name + # o block_aaaa similarly to block_a, resolves all records normally but + # returns NODATA for AAAA queries and ignores local data for that name # o always_null returns 0.0.0.0 or ::0 for any name in the zone. # o noview breaks out of that view towards global local-zones. # diff --git a/doc/unbound.conf.5.in b/doc/unbound.conf.5.in index 25fcca373..ae1dc8519 100644 --- a/doc/unbound.conf.5.in +++ b/doc/unbound.conf.5.in @@ -2910,6 +2910,7 @@ The types are \fI\%inform_redirect\fP, \fI\%always_transparent\fP, \fI\%block_a\fP, +\fI\%block_aaaa\fP, \fI\%always_refuse\fP, \fI\%always_nxdomain\fP, \fI\%always_null\fP, @@ -3100,6 +3101,15 @@ use IPv6 protocol and avoid any queries to IPv4. .UNINDENT .INDENT 7.0 .TP +.B block_aaaa +Like \fI\%transparent\fP or \fI\%block_a\fP, but +ignores local data and resolves normally all query types excluding AAAA. +For AAAA queries it unconditionally returns NODATA. +Useful in cases when there is a need to explicitly force all apps to +use IPv4 protocol and avoid any queries to IPv6. +.UNINDENT +.INDENT 7.0 +.TP .B always_refuse Like \fI\%refuse\fP, but ignores local data and refuses the query. diff --git a/doc/unbound.conf.rst b/doc/unbound.conf.rst index 649bd2ab8..71259afd9 100644 --- a/doc/unbound.conf.rst +++ b/doc/unbound.conf.rst @@ -2592,6 +2592,7 @@ These options are part of the ``server:`` section. :ref:`inform_redirect`, :ref:`always_transparent`, :ref:`block_a`, + :ref:`block_aaaa`, :ref:`always_refuse`, :ref:`always_nxdomain`, :ref:`always_null`, @@ -2741,6 +2742,14 @@ These options are part of the ``server:`` section. Useful in cases when there is a need to explicitly force all apps to use IPv6 protocol and avoid any queries to IPv4. + @@UAHL@unbound.conf.local-zone.type@block_aaaa@@ + Like :ref:`transparent` or + :ref:`block_a`, but + ignores local data and resolves normally all query types excluding AAAA. + For AAAA queries it unconditionally returns NODATA. + Useful in cases when there is a need to explicitly force all apps to + use IPv4 protocol and avoid any queries to IPv6. + @@UAHL@unbound.conf.local-zone.type@always_refuse@@ Like :ref:`refuse`, but ignores local data and refuses the query. diff --git a/services/localzone.c b/services/localzone.c index 77fc5b6b9..5d376cb15 100644 --- a/services/localzone.c +++ b/services/localzone.c @@ -1668,7 +1668,7 @@ local_zone_does_not_cover(struct local_zone* z, struct query_info* qinfo, struct local_data key; struct local_data* ld = NULL; struct local_rrset* lr = NULL; - if(z->type == local_zone_always_transparent || z->type == local_zone_block_a) + if(z->type == local_zone_always_transparent || z->type == local_zone_block_a || z->type == local_zone_block_aaaa) return 1; if(z->type != local_zone_transparent && z->type != local_zone_typetransparent @@ -1754,6 +1754,16 @@ local_zones_zone_answer(struct local_zone* z, struct module_env* env, return 1; } + return 0; + } else if(lz_type == local_zone_block_aaaa) { + /* Return NODATA for all AAAA queries */ + if(qinfo->qtype == LDNS_RR_TYPE_AAAA) { + local_error_encode(qinfo, env, edns, repinfo, buf, temp, + LDNS_RCODE_NOERROR, (LDNS_RCODE_NOERROR|BIT_AA), + LDNS_EDE_NONE, NULL); + return 1; + } + return 0; } else if(lz_type == local_zone_always_null) { /* 0.0.0.0 or ::0 or noerror/nodata for this zone type, @@ -1922,7 +1932,8 @@ local_zones_answer(struct local_zones* zones, struct module_env* env, lzt == local_zone_typetransparent || lzt == local_zone_inform || lzt == local_zone_always_transparent || - lzt == local_zone_block_a) && + lzt == local_zone_block_a || + lzt == local_zone_block_aaaa) && local_zone_does_not_cover(z, qinfo, labs)) { lock_rw_unlock(&z->lock); z = NULL; @@ -1971,6 +1982,7 @@ local_zones_answer(struct local_zones* zones, struct module_env* env, if(lzt != local_zone_always_refuse && lzt != local_zone_always_transparent && lzt != local_zone_block_a + && lzt != local_zone_block_aaaa && lzt != local_zone_always_nxdomain && lzt != local_zone_always_nodata && lzt != local_zone_always_deny @@ -2002,6 +2014,7 @@ const char* local_zone_type2str(enum localzone_type t) case local_zone_inform_redirect: return "inform_redirect"; case local_zone_always_transparent: return "always_transparent"; case local_zone_block_a: return "block_a"; + case local_zone_block_aaaa: return "block_aaaa"; case local_zone_always_refuse: return "always_refuse"; case local_zone_always_nxdomain: return "always_nxdomain"; case local_zone_always_nodata: return "always_nodata"; @@ -2038,6 +2051,8 @@ int local_zone_str2type(const char* type, enum localzone_type* t) *t = local_zone_always_transparent; else if(strcmp(type, "block_a") == 0) *t = local_zone_block_a; + else if(strcmp(type, "block_aaaa") == 0) + *t = local_zone_block_aaaa; else if(strcmp(type, "always_refuse") == 0) *t = local_zone_always_refuse; else if(strcmp(type, "always_nxdomain") == 0) diff --git a/services/localzone.h b/services/localzone.h index e3fb0afe3..de2633486 100644 --- a/services/localzone.h +++ b/services/localzone.h @@ -93,6 +93,8 @@ enum localzone_type { local_zone_always_transparent, /** resolve normally, even when there is local data but return NODATA for A queries */ local_zone_block_a, + /** resolve normally, even when there is local data, but return NODATA for AAAA queries */ + local_zone_block_aaaa, /** answer with error, even when there is local data */ local_zone_always_refuse, /** answer with nxdomain, even when there is local data */ diff --git a/util/configparser.y b/util/configparser.y index 9e1cd611b..64fdfc41e 100644 --- a/util/configparser.y +++ b/util/configparser.y @@ -2395,6 +2395,7 @@ server_local_zone: VAR_LOCAL_ZONE STRING_ARG STRING_ARG && strcmp($3, "typetransparent")!=0 && strcmp($3, "always_transparent")!=0 && strcmp($3, "block_a")!=0 + && strcmp($3, "block_aaaa")!=0 && strcmp($3, "always_refuse")!=0 && strcmp($3, "always_nxdomain")!=0 && strcmp($3, "always_nodata")!=0 @@ -2407,7 +2408,8 @@ server_local_zone: VAR_LOCAL_ZONE STRING_ARG STRING_ARG yyerror("local-zone type: expected static, deny, " "refuse, redirect, transparent, " "typetransparent, inform, inform_deny, " - "inform_redirect, always_transparent, block_a, " + "inform_redirect, always_transparent, " + "block_a, block_aaaa, " "always_refuse, always_nxdomain, " "always_nodata, always_deny, always_null, " "noview, nodefault or ipset"); From 3b8766aa435b4ad49d9b338e28774dcdc2b42a0e Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Fri, 24 Jul 2026 08:53:30 +0200 Subject: [PATCH 57/84] Changelog note for #1433 - Merge #1433 from jisakiel: Add new static zone type block_aaaa to suppress AAAA queries. --- doc/Changelog | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/Changelog b/doc/Changelog index a40ea4aa3..622b9c6cf 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,7 @@ +24 July 2026: Wouter + - Merge #1433 from jisakiel: Add new static zone type + block_aaaa to suppress AAAA queries. + 23 July 2026: Wouter - Updated credits for Xuanchao Xie in 22 july changelog. - Merge #1478 from petrvaganoff: pythonmod: add check return From a65d3d7283890dc1861a61f8515c432b39574604 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Fri, 24 Jul 2026 09:03:45 +0200 Subject: [PATCH 58/84] - Unit test for block_a and block_aaaa. --- doc/Changelog | 1 + testdata/local_block_a.rpl | 144 +++++++++++++++++++++++++++++++++++++ 2 files changed, 145 insertions(+) create mode 100644 testdata/local_block_a.rpl diff --git a/doc/Changelog b/doc/Changelog index 622b9c6cf..57296b331 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,6 +1,7 @@ 24 July 2026: Wouter - Merge #1433 from jisakiel: Add new static zone type block_aaaa to suppress AAAA queries. + - Unit test for block_a and block_aaaa. 23 July 2026: Wouter - Updated credits for Xuanchao Xie in 22 july changelog. diff --git a/testdata/local_block_a.rpl b/testdata/local_block_a.rpl new file mode 100644 index 000000000..4e3c9c3ab --- /dev/null +++ b/testdata/local_block_a.rpl @@ -0,0 +1,144 @@ +; config options +; The island of trust is at example.com +server: + qname-minimisation: no + local-zone: "example1.com." block_a + local-zone: "example2.com." block_aaaa + +stub-zone: + name: "." + stub-addr: 193.0.14.129 # K.ROOT-SERVERS.NET. +CONFIG_END + +SCENARIO_BEGIN test local data with block_a and block_aaaa + +; K.ROOT-SERVERS.NET. +RANGE_BEGIN 0 100 + ADDRESS 193.0.14.129 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +. IN NS +SECTION ANSWER +. IN NS K.ROOT-SERVERS.NET. +SECTION ADDITIONAL +K.ROOT-SERVERS.NET. IN A 193.0.14.129 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +a.example1.com. IN A +SECTION ANSWER +a.example1.com. IN A 1.2.3.4 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +a.example1.com. IN AAAA +SECTION ANSWER +a.example1.com. IN AAAA 1:2:3::4 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +a.example2.com. IN A +SECTION ANSWER +a.example2.com. IN A 1.2.3.4 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +a.example2.com. IN AAAA +SECTION ANSWER +a.example2.com. IN AAAA 1:2:3::4 +ENTRY_END +RANGE_END + +; block_a for example1.com +STEP 1 QUERY +ENTRY_BEGIN +REPLY RD DO +SECTION QUESTION +a.example1.com. IN A +ENTRY_END + +; block_a blocks A +STEP 10 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR AA RD RA DO NOERROR +SECTION QUESTION +a.example1.com. IN A +SECTION ANSWER +SECTION AUTHORITY +ENTRY_END + +STEP 20 QUERY +ENTRY_BEGIN +REPLY RD DO +SECTION QUESTION +a.example1.com. IN AAAA +ENTRY_END + +; block_a allows AAAA from upstream +STEP 30 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR RD RA DO NOERROR +SECTION QUESTION +a.example1.com. IN AAAA +SECTION ANSWER +a.example1.com. IN AAAA 1:2:3::4 +ENTRY_END + +; block_aaaa for example2.com +STEP 40 QUERY +ENTRY_BEGIN +REPLY RD DO +SECTION QUESTION +a.example2.com. IN A +ENTRY_END + +; block_aaaa allows A from upstream +STEP 50 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR RD RA DO NOERROR +SECTION QUESTION +a.example2.com. IN A +SECTION ANSWER +a.example2.com. IN A 1.2.3.4 +ENTRY_END + +STEP 60 QUERY +ENTRY_BEGIN +REPLY RD DO +SECTION QUESTION +a.example2.com. IN AAAA +ENTRY_END + +; block_aaaa blocks AAAA +STEP 70 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR AA RD RA DO NOERROR +SECTION QUESTION +a.example2.com. IN AAAA +SECTION ANSWER +ENTRY_END + +SCENARIO_END From 79e100a7fbb45bee3a409f2add927dbee563d35d Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Fri, 24 Jul 2026 09:29:17 +0200 Subject: [PATCH 59/84] - Fix #1477: respip + dns64: dns64 uses A records modified by respip instead of original A records. Adds local-zone types block_a_wdata and block_aaaa_wdata, that are like block_a and block_aaaa, and uses local-data if present. --- doc/Changelog | 4 + doc/example.conf.in | 2 + doc/unbound.conf.rst | 14 +++ services/localzone.c | 20 +++- services/localzone.h | 4 + testdata/local_block_a.rpl | 192 ++++++++++++++++++++++++++++++++++++- util/configparser.y | 3 + 7 files changed, 234 insertions(+), 5 deletions(-) diff --git a/doc/Changelog b/doc/Changelog index 57296b331..25fd018e0 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -2,6 +2,10 @@ - Merge #1433 from jisakiel: Add new static zone type block_aaaa to suppress AAAA queries. - Unit test for block_a and block_aaaa. + - Fix #1477: respip + dns64: dns64 uses A records modified by + respip instead of original A records. Adds local-zone types + block_a_wdata and block_aaaa_wdata, that are like block_a + and block_aaaa, and uses local-data if present. 23 July 2026: Wouter - Updated credits for Xuanchao Xie in 22 july changelog. diff --git a/doc/example.conf.in b/doc/example.conf.in index f1e1e412b..c5e4e242e 100644 --- a/doc/example.conf.in +++ b/doc/example.conf.in @@ -901,6 +901,8 @@ server: # NODATA for A queries and ignores local data for that name # o block_aaaa similarly to block_a, resolves all records normally but # returns NODATA for AAAA queries and ignores local data for that name + # o block_a_wdata like block_a but uses local data if present. + # o block_aaaa_wdata like block_aaaa but uses local data if present. # o always_null returns 0.0.0.0 or ::0 for any name in the zone. # o noview breaks out of that view towards global local-zones. # diff --git a/doc/unbound.conf.rst b/doc/unbound.conf.rst index 71259afd9..e341c516f 100644 --- a/doc/unbound.conf.rst +++ b/doc/unbound.conf.rst @@ -2593,6 +2593,8 @@ These options are part of the ``server:`` section. :ref:`always_transparent`, :ref:`block_a`, :ref:`block_aaaa`, + :ref:`block_a_wdata`, + :ref:`block_aaaa_wdata`, :ref:`always_refuse`, :ref:`always_nxdomain`, :ref:`always_null`, @@ -2750,6 +2752,18 @@ These options are part of the ``server:`` section. Useful in cases when there is a need to explicitly force all apps to use IPv4 protocol and avoid any queries to IPv6. + @@UAHL@unbound.conf.local-zone.type@block_a_wdata@@ + Like :ref:`block_a`, but + uses local data if present. + If there is local data that is returned, and it acts like transparent. + For A queries it returns NODATA. + + @@UAHL@unbound.conf.local-zone.type@block_aaaa_wdata@@ + Like :ref:`block_aaaa`, but + uses local data if present. + If there is local data that is returned, and it acts like transparent. + For AAAA queries it returns NODATA. + @@UAHL@unbound.conf.local-zone.type@always_refuse@@ Like :ref:`refuse`, but ignores local data and refuses the query. diff --git a/services/localzone.c b/services/localzone.c index 5d376cb15..5de780127 100644 --- a/services/localzone.c +++ b/services/localzone.c @@ -1679,7 +1679,9 @@ local_zone_does_not_cover(struct local_zone* z, struct query_info* qinfo, key.namelen = qinfo->qname_len; key.namelabs = labs; ld = (struct local_data*)rbtree_search(&z->data, &key.node); - if(z->type == local_zone_transparent || z->type == local_zone_inform) + if(z->type == local_zone_transparent || z->type == local_zone_inform + || z->type == local_zone_block_a_wdata + || z->type == local_zone_block_aaaa_wdata) return (ld == NULL); if(ld) lr = local_data_find_type(ld, qinfo->qtype, 1); @@ -1745,7 +1747,8 @@ local_zones_zone_answer(struct local_zone* z, struct module_env* env, || lz_type == local_zone_always_transparent) { /* no NODATA or NXDOMAINS for this zone type */ return 0; - } else if(lz_type == local_zone_block_a) { + } else if(lz_type == local_zone_block_a || + lz_type == local_zone_block_a_wdata) { /* Return NODATA for all A queries */ if(qinfo->qtype == LDNS_RR_TYPE_A) { local_error_encode(qinfo, env, edns, repinfo, buf, temp, @@ -1755,7 +1758,8 @@ local_zones_zone_answer(struct local_zone* z, struct module_env* env, } return 0; - } else if(lz_type == local_zone_block_aaaa) { + } else if(lz_type == local_zone_block_aaaa || + lz_type == local_zone_block_aaaa_wdata) { /* Return NODATA for all AAAA queries */ if(qinfo->qtype == LDNS_RR_TYPE_AAAA) { local_error_encode(qinfo, env, edns, repinfo, buf, temp, @@ -1933,7 +1937,9 @@ local_zones_answer(struct local_zones* zones, struct module_env* env, lzt == local_zone_inform || lzt == local_zone_always_transparent || lzt == local_zone_block_a || - lzt == local_zone_block_aaaa) && + lzt == local_zone_block_aaaa || + lzt == local_zone_block_a_wdata || + lzt == local_zone_block_aaaa_wdata) && local_zone_does_not_cover(z, qinfo, labs)) { lock_rw_unlock(&z->lock); z = NULL; @@ -2015,6 +2021,8 @@ const char* local_zone_type2str(enum localzone_type t) case local_zone_always_transparent: return "always_transparent"; case local_zone_block_a: return "block_a"; case local_zone_block_aaaa: return "block_aaaa"; + case local_zone_block_a_wdata: return "block_a_wdata"; + case local_zone_block_aaaa_wdata: return "block_aaaa_wdata"; case local_zone_always_refuse: return "always_refuse"; case local_zone_always_nxdomain: return "always_nxdomain"; case local_zone_always_nodata: return "always_nodata"; @@ -2053,6 +2061,10 @@ int local_zone_str2type(const char* type, enum localzone_type* t) *t = local_zone_block_a; else if(strcmp(type, "block_aaaa") == 0) *t = local_zone_block_aaaa; + else if(strcmp(type, "block_a_wdata") == 0) + *t = local_zone_block_a_wdata; + else if(strcmp(type, "block_aaaa_wdata") == 0) + *t = local_zone_block_aaaa_wdata; else if(strcmp(type, "always_refuse") == 0) *t = local_zone_always_refuse; else if(strcmp(type, "always_nxdomain") == 0) diff --git a/services/localzone.h b/services/localzone.h index de2633486..436874e0f 100644 --- a/services/localzone.h +++ b/services/localzone.h @@ -95,6 +95,10 @@ enum localzone_type { local_zone_block_a, /** resolve normally, even when there is local data, but return NODATA for AAAA queries */ local_zone_block_aaaa, + /** resolve normally, use local data, else return NODATA for A queries */ + local_zone_block_a_wdata, + /** resolve normally, use local data, else return NODATA for AAAA queries */ + local_zone_block_aaaa_wdata, /** answer with error, even when there is local data */ local_zone_always_refuse, /** answer with nxdomain, even when there is local data */ diff --git a/testdata/local_block_a.rpl b/testdata/local_block_a.rpl index 4e3c9c3ab..6db5cb200 100644 --- a/testdata/local_block_a.rpl +++ b/testdata/local_block_a.rpl @@ -4,6 +4,12 @@ server: qname-minimisation: no local-zone: "example1.com." block_a local-zone: "example2.com." block_aaaa + local-zone: "example3.com." block_a_wdata + local-data: "b.example3.com. A 1.2.3.5" + local-data: "b.example3.com. AAAA 1:2:3::5" + local-zone: "example4.com." block_aaaa_wdata + local-data: "b.example4.com. A 1.2.3.5" + local-data: "b.example4.com. AAAA 1:2:3::5" stub-zone: name: "." @@ -13,7 +19,7 @@ CONFIG_END SCENARIO_BEGIN test local data with block_a and block_aaaa ; K.ROOT-SERVERS.NET. -RANGE_BEGIN 0 100 +RANGE_BEGIN 0 400 ADDRESS 193.0.14.129 ENTRY_BEGIN MATCH opcode qtype qname @@ -66,6 +72,46 @@ a.example2.com. IN AAAA SECTION ANSWER a.example2.com. IN AAAA 1:2:3::4 ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +a.example3.com. IN A +SECTION ANSWER +a.example3.com. IN A 1.2.3.4 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +a.example3.com. IN AAAA +SECTION ANSWER +a.example3.com. IN AAAA 1:2:3::4 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +a.example4.com. IN A +SECTION ANSWER +a.example4.com. IN A 1.2.3.4 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +a.example4.com. IN AAAA +SECTION ANSWER +a.example4.com. IN AAAA 1:2:3::4 +ENTRY_END RANGE_END ; block_a for example1.com @@ -141,4 +187,148 @@ a.example2.com. IN AAAA SECTION ANSWER ENTRY_END +; block_a_wdata for example3.com +STEP 80 QUERY +ENTRY_BEGIN +REPLY RD DO +SECTION QUESTION +a.example3.com. IN A +ENTRY_END + +; block_a_wdata blocks A +STEP 90 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR AA RD RA DO NOERROR +SECTION QUESTION +a.example3.com. IN A +SECTION ANSWER +ENTRY_END + +STEP 100 QUERY +ENTRY_BEGIN +REPLY RD DO +SECTION QUESTION +a.example3.com. IN AAAA +ENTRY_END + +; block_a_wdata allows AAAA from upstream +STEP 110 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR RD RA DO NOERROR +SECTION QUESTION +a.example3.com. IN AAAA +SECTION ANSWER +a.example3.com. IN AAAA 1:2:3::4 +ENTRY_END + +STEP 120 QUERY +ENTRY_BEGIN +REPLY RD DO +SECTION QUESTION +b.example3.com. IN A +ENTRY_END + +; block_a_wdata allows local-data A +STEP 130 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR AA RD RA DO NOERROR +SECTION QUESTION +b.example3.com. IN A +SECTION ANSWER +b.example3.com. A 1.2.3.5 +ENTRY_END + +STEP 140 QUERY +ENTRY_BEGIN +REPLY RD DO +SECTION QUESTION +b.example3.com. IN AAAA +ENTRY_END + +; block_a_wdata allows local-data AAAA +STEP 150 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR AA RD RA DO NOERROR +SECTION QUESTION +b.example3.com. IN AAAA +SECTION ANSWER +b.example3.com. AAAA 1:2:3::5 +ENTRY_END + +; block_aaaa_wdata for example4.com +STEP 160 QUERY +ENTRY_BEGIN +REPLY RD DO +SECTION QUESTION +a.example4.com. IN A +ENTRY_END + +; block_aaaa_wdata allows A from upstream +STEP 170 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR RD RA DO NOERROR +SECTION QUESTION +a.example4.com. IN A +SECTION ANSWER +a.example4.com. IN A 1.2.3.4 +ENTRY_END + +STEP 180 QUERY +ENTRY_BEGIN +REPLY RD DO +SECTION QUESTION +a.example4.com. IN AAAA +ENTRY_END + +; block_aaaa_wdata blocks AAAA +STEP 190 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR AA RD RA DO NOERROR +SECTION QUESTION +a.example4.com. IN AAAA +SECTION ANSWER +ENTRY_END + +STEP 200 QUERY +ENTRY_BEGIN +REPLY RD DO +SECTION QUESTION +b.example4.com. IN A +ENTRY_END + +; block_aaaa_wdata allows local-data A +STEP 210 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR AA RD RA DO NOERROR +SECTION QUESTION +b.example4.com. IN A +SECTION ANSWER +b.example4.com. A 1.2.3.5 +ENTRY_END + +STEP 220 QUERY +ENTRY_BEGIN +REPLY RD DO +SECTION QUESTION +b.example4.com. IN AAAA +ENTRY_END + +; block_aaaa_wdata allows local-data AAAA +STEP 230 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR AA RD RA DO NOERROR +SECTION QUESTION +b.example4.com. IN AAAA +SECTION ANSWER +b.example4.com. AAAA 1:2:3::5 +ENTRY_END + SCENARIO_END diff --git a/util/configparser.y b/util/configparser.y index 64fdfc41e..490bba0ff 100644 --- a/util/configparser.y +++ b/util/configparser.y @@ -2396,6 +2396,8 @@ server_local_zone: VAR_LOCAL_ZONE STRING_ARG STRING_ARG && strcmp($3, "always_transparent")!=0 && strcmp($3, "block_a")!=0 && strcmp($3, "block_aaaa")!=0 + && strcmp($3, "block_a_wdata")!=0 + && strcmp($3, "block_aaaa_wdata")!=0 && strcmp($3, "always_refuse")!=0 && strcmp($3, "always_nxdomain")!=0 && strcmp($3, "always_nodata")!=0 @@ -2410,6 +2412,7 @@ server_local_zone: VAR_LOCAL_ZONE STRING_ARG STRING_ARG "typetransparent, inform, inform_deny, " "inform_redirect, always_transparent, " "block_a, block_aaaa, " + "block_a_wdata, block_aaaa_wdata, " "always_refuse, always_nxdomain, " "always_nodata, always_deny, always_null, " "noview, nodefault or ipset"); From 1e904a3ce56f1a23e58fd6714bdcdda42663a644 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Fri, 24 Jul 2026 09:45:49 +0200 Subject: [PATCH 60/84] - set code repository version to 1.26.0. --- configure | 26 +++++++++++++------------- configure.ac | 6 +++--- doc/Changelog | 1 + 3 files changed, 17 insertions(+), 16 deletions(-) diff --git a/configure b/configure index f40c51bed..2f50d918a 100755 --- a/configure +++ b/configure @@ -1,6 +1,6 @@ #! /bin/sh # Guess values for system-dependent variables and create Makefiles. -# Generated by GNU Autoconf 2.71 for unbound 1.25.3. +# Generated by GNU Autoconf 2.71 for unbound 1.26.0. # # Report bugs to . # @@ -622,8 +622,8 @@ MAKEFLAGS= # Identity of this package. PACKAGE_NAME='unbound' PACKAGE_TARNAME='unbound' -PACKAGE_VERSION='1.25.3' -PACKAGE_STRING='unbound 1.25.3' +PACKAGE_VERSION='1.26.0' +PACKAGE_STRING='unbound 1.26.0' PACKAGE_BUGREPORT='unbound-bugs@nlnetlabs.nl or https://github.com/NLnetLabs/unbound/issues' PACKAGE_URL='' @@ -1513,7 +1513,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.25.3 to adapt to many kinds of systems. +\`configure' configures unbound 1.26.0 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1579,7 +1579,7 @@ fi if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of unbound 1.25.3:";; + short | recursive ) echo "Configuration of unbound 1.26.0:";; esac cat <<\_ACEOF @@ -1832,7 +1832,7 @@ fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -unbound configure 1.25.3 +unbound configure 1.26.0 generated by GNU Autoconf 2.71 Copyright (C) 2021 Free Software Foundation, Inc. @@ -2489,7 +2489,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.25.3, which was +It was created by unbound $as_me 1.26.0, which was generated by GNU Autoconf 2.71. Invocation command line was $ $0$ac_configure_args_raw @@ -3251,9 +3251,9 @@ ac_compiler_gnu=$ac_cv_c_compiler_gnu UNBOUND_VERSION_MAJOR=1 -UNBOUND_VERSION_MINOR=25 +UNBOUND_VERSION_MINOR=26 -UNBOUND_VERSION_MICRO=3 +UNBOUND_VERSION_MICRO=0 LIBUNBOUND_CURRENT=9 @@ -3363,7 +3363,7 @@ LIBUNBOUND_AGE=1 # 1.25.0 had 9:36:1 # 1.25.1 had 9:37:1 # 1.25.2 had 9:38:1 -# 1.25.3 had 9:39:1 +# 1.26.0 had 9:39:1 # Current -- the number of the binary API that we're implementing # Revision -- which iteration of the implementation of the binary @@ -25681,7 +25681,7 @@ printf "%s\n" "#define MAXSYSLOGMSGLEN 10240" >>confdefs.h -version=1.25.3 +version=1.26.0 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for build time" >&5 printf %s "checking for build time... " >&6; } @@ -26211,7 +26211,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # 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.25.3, which was +This file was extended by unbound $as_me 1.26.0, which was generated by GNU Autoconf 2.71. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -26279,7 +26279,7 @@ ac_cs_config_escaped=`printf "%s\n" "$ac_cs_config" | sed "s/^ //; s/'/'\\\\\\\\ cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config='$ac_cs_config_escaped' ac_cs_version="\\ -unbound config.status 1.25.3 +unbound config.status 1.26.0 configured by $0, generated by GNU Autoconf 2.71, with options \\"\$ac_cs_config\\" diff --git a/configure.ac b/configure.ac index f4697527f..165645e13 100644 --- a/configure.ac +++ b/configure.ac @@ -11,8 +11,8 @@ sinclude(dnscrypt/dnscrypt.m4) # must be numbers. ac_defun because of later processing m4_define([VERSION_MAJOR],[1]) -m4_define([VERSION_MINOR],[25]) -m4_define([VERSION_MICRO],[3]) +m4_define([VERSION_MINOR],[26]) +m4_define([VERSION_MICRO],[0]) 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]) @@ -125,7 +125,7 @@ LIBUNBOUND_AGE=1 # 1.25.0 had 9:36:1 # 1.25.1 had 9:37:1 # 1.25.2 had 9:38:1 -# 1.25.3 had 9:39:1 +# 1.26.0 had 9:39:1 # Current -- the number of the binary API that we're implementing # Revision -- which iteration of the implementation of the binary diff --git a/doc/Changelog b/doc/Changelog index 25fd018e0..3794715ad 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -6,6 +6,7 @@ respip instead of original A records. Adds local-zone types block_a_wdata and block_aaaa_wdata, that are like block_a and block_aaaa, and uses local-data if present. + - set code repository version to 1.26.0. 23 July 2026: Wouter - Updated credits for Xuanchao Xie in 22 july changelog. From fc3b5b4f639e73a20d83b68859ffd4417cbfab28 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Fri, 24 Jul 2026 10:03:41 +0200 Subject: [PATCH 61/84] - Update generated man pages. --- doc/Changelog | 1 + doc/unbound-control.8.in | 2 + doc/unbound.conf.5.in | 100 ++++++++++++++++++++++++++++++++++++++- 3 files changed, 101 insertions(+), 2 deletions(-) diff --git a/doc/Changelog b/doc/Changelog index 3794715ad..e9156214f 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -7,6 +7,7 @@ block_a_wdata and block_aaaa_wdata, that are like block_a and block_aaaa, and uses local-data if present. - set code repository version to 1.26.0. + - Update generated man pages. 23 July 2026: Wouter - Updated credits for Xuanchao Xie in 22 july changelog. diff --git a/doc/unbound-control.8.in b/doc/unbound-control.8.in index e07684019..5743d09b4 100644 --- a/doc/unbound-control.8.in +++ b/doc/unbound-control.8.in @@ -354,6 +354,8 @@ If the name already has no items, nothing happens. Often results in NXDOMAIN for the name (in a static zone), but if the name has become an empty nonterminal (there is still data in domain names below the removed name), NOERROR nodata answers are the result for that name. +With a specific RR instead of a domain name, that specific record is +removed from the local data, and not all the RR data. .UNINDENT .INDENT 0.0 .TP diff --git a/doc/unbound.conf.5.in b/doc/unbound.conf.5.in index ae1dc8519..54116af23 100644 --- a/doc/unbound.conf.5.in +++ b/doc/unbound.conf.5.in @@ -691,7 +691,7 @@ Default: 0 (use system value) .TP .B so\-sndbuf: \fI\fP If not 0, then set the SO_SNDBUF socket option to get more buffer space on -UDP port 53 outgoing queries. +UDP port 53 outgoing responses. This for very busy servers handles spikes in answer traffic, otherwise: .INDENT 7.0 .INDENT 3.5 @@ -2312,6 +2312,13 @@ The defensive action is to clear the rrset and message caches, hopefully flushing away any poison. A value of 10 million is suggested. .sp +It is useful to add 0.0.0.0/8 and \(aq::\(aq to the +\fI\%do\-not\-query\-address\fP list. +Otherwise they may be answered, from localhost, and the different source +makes an unwanted reply that unnecessarily ticks up. +The \fI\%do\-not\-query\-localhost\fP +option includes them, the zero subnets, when it is enabled. +.sp Default: 0 (disabled) .UNINDENT .INDENT 0.0 @@ -2362,6 +2369,8 @@ If yes, deny queries of type ANY with an empty response. If disabled, Unbound responds with a short list of resource records if some can be found in the cache and makes the upstream type ANY query if there are none. +The option stops the DNSSEC validation from processing, possibly lengthy, +ANY responses, when the option is enabled. .sp Default: no .UNINDENT @@ -2911,6 +2920,8 @@ The types are \fI\%always_transparent\fP, \fI\%block_a\fP, \fI\%block_aaaa\fP, +\fI\%block_a_wdata\fP, +\fI\%block_aaaa_wdata\fP, \fI\%always_refuse\fP, \fI\%always_nxdomain\fP, \fI\%always_null\fP, @@ -3102,7 +3113,8 @@ use IPv6 protocol and avoid any queries to IPv4. .INDENT 7.0 .TP .B block_aaaa -Like \fI\%transparent\fP or \fI\%block_a\fP, but +Like \fI\%transparent\fP or +\fI\%block_a\fP, but ignores local data and resolves normally all query types excluding AAAA. For AAAA queries it unconditionally returns NODATA. Useful in cases when there is a need to explicitly force all apps to @@ -3110,6 +3122,22 @@ use IPv4 protocol and avoid any queries to IPv6. .UNINDENT .INDENT 7.0 .TP +.B block_a_wdata +Like \fI\%block_a\fP, but +uses local data if present. +If there is local data that is returned, and it acts like transparent. +For A queries it returns NODATA. +.UNINDENT +.INDENT 7.0 +.TP +.B block_aaaa_wdata +Like \fI\%block_aaaa\fP, but +uses local data if present. +If there is local data that is returned, and it acts like transparent. +For AAAA queries it returns NODATA. +.UNINDENT +.INDENT 7.0 +.TP .B always_refuse Like \fI\%refuse\fP, but ignores local data and refuses the query. @@ -3577,6 +3605,18 @@ For example, 1000 may be a suitable value to stop the server from being overloaded with random names, and keeps unbound from sending traffic to the nameservers for those zones. .sp +It is intended to count the number of queries towards the nameservers +for the zone, and keep those queries limited. +When there is a delegation that needs a lot of lookups, those are +charged in the counters for the destination, the target name, of +the NS records. +Since that is where the nameserver lookup queries are sent to. +That keeps the target, the victim domain, from having many queries. +With the \fI\%ratelimit\-factor\fP, some +genuine queries that are also made to the target zone, can filter +through, and then end up in cache, where the genuine answers have +a chance to collect, keeping up service to some extent. +.sp \fBNOTE:\fP .INDENT 7.0 .INDENT 3.5 @@ -4604,6 +4644,32 @@ If not given then no zonefile is used. If the file does not exist or is empty, Unbound will attempt to fetch zone data (eg. from the primary servers). .UNINDENT +.INDENT 0.0 +.TP +.B max\-transfer\-size: \fI\fP +Number of bytes size of the maximum zone transfer size. +Larger transfers, over AXFR, IXFR and HTTP, are not allowed. +A plain number is in bytes, append \(aqk\(aq, \(aqm\(aq or \(aqg\(aq for kilobytes, megabytes +or gigabytes (1024*1024 bytes in a megabyte). +The value \fB0\fP disables the feature. +.sp +Only consider for untrusted/misbehaving primaries that could hog resources +and bring down the resolver. +.sp +Default: 0 +.UNINDENT +.INDENT 0.0 +.TP +.B max\-transfer\-time: \fI\fP +Maximum time in milliseconds that a zone transfer is allowed to take from +the start. +The value \fB0\fP disables the feature. +.sp +Only consider for untrusted/misbehaving primaries that could hog resources +and bring down the resolver. +.sp +Default: 0 +.UNINDENT .SH VIEW OPTIONS .sp These options are part of the \fBview:\fP section. @@ -5816,6 +5882,10 @@ from a webserver that would work. If you specify the hostname, you cannot use the domain from the zonefile, because it may not have that when retrieving that data, instead use a plain IP address to avoid a circular dependency on retrieving that IP address. +.sp +Every number of IXFR transfers, a full AXFR is performed. +This is to consolidate the rpz memory, that would otherwise grow. +The fixed value is after 5 IXFR transfers. .UNINDENT .INDENT 0.0 .TP @@ -5938,6 +6008,32 @@ Enclose list of tags in quotes (\fB\(dq\(dq\fP) and put spaces between tags. If no tags are specified the policies from this section will be applied for all clients. .UNINDENT +.INDENT 0.0 +.TP +.B max\-transfer\-size: \fI\fP +Number of bytes size of the maximum zone transfer size. +Larger transfers, over AXFR, IXFR and HTTP, are not allowed. +A plain number is in bytes, append \(aqk\(aq, \(aqm\(aq or \(aqg\(aq for kilobytes, megabytes +or gigabytes (1024*1024 bytes in a megabyte). +The value \fB0\fP disables the feature. +.sp +Only consider for untrusted/misbehaving primaries that could hog resources +and bring down the resolver. +.sp +Default: 0 +.UNINDENT +.INDENT 0.0 +.TP +.B max\-transfer\-time: \fI\fP +Maximum time in milliseconds that a zone transfer is allowed to take from +the start. +The value \fB0\fP disables the feature. +.sp +Only consider for untrusted/misbehaving primaries that could hog resources +and bring down the resolver. +.sp +Default: 0 +.UNINDENT .SH MEMORY CONTROL EXAMPLE .sp In the example config settings below memory usage is reduced. From e1e646c6fc2d26d7cc1803f8205100d017a03e28 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Fri, 24 Jul 2026 11:50:15 +0200 Subject: [PATCH 62/84] - Fix to allow test fake sha1 on systems with possible sha1 support. - Fix to use sha256 for unbound-anchor unit test. - Fix unbound-anchor check for return value of X509_NAME_get_text_by_NID of the emailaddress. --- doc/Changelog | 5 + smallapp/unbound-anchor.c | 14 +- .../127.0.0.1/no_more_keys.p7s | Bin 1165 -> 1959 bytes .../10-unbound-anchor.tdir/127.0.0.1/root.p7s | Bin 1165 -> 1959 bytes testdata/10-unbound-anchor.tdir/key-setup.sh | 201 ++++++++++++++++++ testdata/10-unbound-anchor.tdir/petal.key | 61 ++++-- testdata/10-unbound-anchor.tdir/petal.pem | 35 +-- testdata/10-unbound-anchor.tdir/test_cert.key | 61 ++++-- testdata/10-unbound-anchor.tdir/test_cert.pem | 38 ++-- validator/val_secalgo.c | 4 +- 10 files changed, 342 insertions(+), 77 deletions(-) create mode 100644 testdata/10-unbound-anchor.tdir/key-setup.sh diff --git a/doc/Changelog b/doc/Changelog index e9156214f..2b288c053 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -8,6 +8,11 @@ and block_aaaa, and uses local-data if present. - set code repository version to 1.26.0. - Update generated man pages. + - Fix to allow test fake sha1 on systems with possible sha1 + support. + - Fix to use sha256 for unbound-anchor unit test. + - Fix unbound-anchor check for return value of + X509_NAME_get_text_by_NID of the emailaddress. 23 July 2026: Wouter - Updated credits for Xuanchao Xie in 22 july changelog. diff --git a/smallapp/unbound-anchor.c b/smallapp/unbound-anchor.c index 93379d27f..8f84bae0e 100644 --- a/smallapp/unbound-anchor.c +++ b/smallapp/unbound-anchor.c @@ -1861,10 +1861,10 @@ get_valid_signers(PKCS7* p7, const char* p7signer) } #else if(verb >= 3 && X509_NAME_get_text_by_NID(nm, - NID_commonName, buf, (int)sizeof(buf))) + NID_commonName, buf, (int)sizeof(buf)) > 0) printf("commonName: %s\n", buf); if(verb >= 3 && X509_NAME_get_text_by_NID(nm, - NID_pkcs9_emailAddress, buf, (int)sizeof(buf))) + NID_pkcs9_emailAddress, buf, (int)sizeof(buf)) > 0) printf("emailAddress: %s\n", buf); #endif } @@ -1890,18 +1890,18 @@ get_valid_signers(PKCS7* p7, const char* p7signer) } else { #if !defined(HAVE_X509_NAME_GET_TEXT_BY_NID) || defined(DEPRECATED_X509_NAME_GET_TEXT_BY_NID) if(!has_valid_emailaddr(nm, p7signer)) { - if(verb) printf("removed cert with wrong name\n"); + if(verb) printf("removed cert with wrong emailaddress\n"); continue; /* wrong name, skip it */ } #else - if(!X509_NAME_get_text_by_NID(nm, + if(X509_NAME_get_text_by_NID(nm, NID_pkcs9_emailAddress, - buf, (int)sizeof(buf))) { - if(verb) printf("removed cert with no name\n"); + buf, (int)sizeof(buf)) <= 0) { + if(verb) printf("removed cert with no emailaddress\n"); continue; /* no name, no use */ } if(strcmp(buf, p7signer) != 0) { - if(verb) printf("removed cert with wrong name\n"); + if(verb) printf("removed cert with wrong emailaddress\n"); continue; /* wrong name, skip it */ } #endif diff --git a/testdata/10-unbound-anchor.tdir/127.0.0.1/no_more_keys.p7s b/testdata/10-unbound-anchor.tdir/127.0.0.1/no_more_keys.p7s index c76b5b6e4754d5bce27f78f85286159ba3e0df64..c7c33d3db24ac13ec816884fb868f72ba4510f7a 100644 GIT binary patch literal 1959 zcmXqLVqeV0snzDu_MMlJooPW6`xJvF_6bakjE4LMylk8aZ61uN%q&cdtPBR+2!)Ib znpmbAG_g!JXkz-kfSHMriAltR`+>;D|9?(0sWKd8JrsY+>Z)ot$Ox$6jNCvY)C~Cy zc-WXjS(tfP3sOrGa|}fcgy2dz8O0!?26E!OMrH=)MkWT9CWc04QR2KNW(FpP22d{1 z<}@)bLUsbs7tBqJ{R{?8j9pAkjExMt{q}O&uMpMXoq6b13|o5H+pKklKVoYizt8rZ z`l{Wo!hhxI3D5IzWXjzPYn*RoxPUd?4sGi^X5$N1rIAfD0*}4knh8pb<=~k zrJeVF9W3^|V`J^HScS-kuA-@NT}`?PrViTDse!+4KmVV+yK-|~p>AZN>aXRmGx(WB zcuQEPga!!pvTtiEKGf_Y!}M5%QSOr7r?#(O4123HBjy}vw+SgPJJ8-ECcPv5pZdCW z%+fx)os629%)5$nSzVqxe-YiEe!*nA*`EDL#R2Z#96R%spO#lF2vIkG&e^5MH2q?B zfad9q$^iw5-+HuPTuzHy=6KKoXw9}CVY!>0zjZM*Vc!*>PEQOEC|L99q{;KA+*ec-B{WV4?2Q%+DbkSRVbEOE@N{$If9JThkry9- zx+M_xjVo+16Eh#=PrA5hW~Ge+pdQn>0A2#3}uj* z!h0y&c0p3h>BI$_Y+@r1)ZNMZ(Q$2h{x#DTTS_Bx);|<2{CFj;HS6pFnFQ@?fvsXY z-(H$vE*8>?BbEMQ<%hB?xr^C}j^RFG8zb4E~N9xOrwP&VvT@r~Y zyxdZ?$0VTt=jJ(18f|jR>U`3-q(**Q-6L`{H~P~#XC9BR9V=x2E?acj=}y+2MTeJK zTfgL=^3`XZ)0;f*Ox_b8pDpcR^8eeArM~(2SvJFm+;M{ z)iHna8O8ELk_0O1m&OF0xX(-YrpH(mqm| zXR4(X>kXTjE&z+AGr%&;z>e4yfxGxZDghTXJ~5C$lvd1!$R(nI3_^z0kRPc4GcquW zGSElJvKT6{C|vJ;B&~eXc=O47?ga<3KR!DVEa7@zt948DW9M&QLt--wDiIp^4RZ{# zK*cZAEsR>o95G}LlK~eS2c*PhW;fu2vzeHf7#e`76(+~T#OPqa&c>?E$IQfzDz1T? zsDULWOA}**u1}4|yG^ogJC_Joy2VFyX&#%ird8>5+reK_3jdBot8DhH6snN#GIN|W zsYYds*_j>hw`=_9d;aK0h^JprLgc$wLDgT6uV4Ca`QQJ>XKorYte<6ds(;}->9=pL zsi<}9e?IZs_1?ahp1rq{mHw|>v&hH%-Rn2*bC#V<-t(+QEIhyWTaU5M5Ah8jY91ac zu6dMG9DOWZ>DlkQR{1jW2mIaVdA}|$R8;nGh@JbaXjbT#h#LO$UJEWKy%xwkB=r4& zw&0}}t9?ubPrdB6I+$(VF!|({ZxbeeIbf_RGynR&NI$-1t}V{(k`ZTXSz@NO?>we0 zR<5Z(?T~`#H~&cy$KhMr)OBN znseSiJDPpl)l@hB4@H}n`uvkUnU=CBQRZInx`aj2M}JL86kc)5=)HaOEbj$d&ilA- zD%WCiL|GvqViVPg(uVHRdBNG(asF%&Tn zf-B)<6oZHw$cghB8XFiInj0FL8kv|ziSrs885$UvK)FPl(|8}*1&pi=%#CLm3>r@| zH6CQx@gXB|Myu6JGq2(jr4w=7Pj{<*;4J#DJL$~ke-aGymaOF~cgxa#1cNoN3B-3GuIXrmGeUbA6al$kBNA!Kam{*2$cC9b{A2 zAQozOXSJX^&oVu(U=QC))9(K_&dt34o%^aLUupDJzHRx}n0Htx7#`Ze@uFC#m%E@k zZkg19fO8u!b3B~7yG2(~QG{=gq<-Evn`p^3Uhc8?y|-#*ad`0=y?Fh+%j<|BClfOx z1LIBTF|{E37Z~EqK#$I1u37U;;Jm@r(uGBa@~gLLeh}xr9ih@* z?LfLqq3WHJki-AC!LOjO1a@IcI)cx~^yS#pBcLGfMX!%oW_lY}MGvHFJvN z9>@KQJl;3-l*df`F9aUTd zIpTo{ou%C?2@Ttt8Y?|T2Vew>-2tY1^(H@{TNBU_%EtP(iLVthM2%%t=PTSOz*TF&j~a~Ce2 ztT5NVEpt83iQ50Iysw?ZQ{1;@czFdB+1pyQoy(V4)jcgdj_Fya#_YU_D!xxcx~kvp F000?Vi{=0T diff --git a/testdata/10-unbound-anchor.tdir/127.0.0.1/root.p7s b/testdata/10-unbound-anchor.tdir/127.0.0.1/root.p7s index afbdb1b913884a91350aea629c445257e1f84830..b0dc637a39cf727b6b603338013de3ab33e8fab3 100644 GIT binary patch literal 1959 zcma)+do}i_M{h!*sNMATBUZJ+GWq#J*U6!{XXvZ-h0mHd;msL5Xsj0 zzWL)+hy=?qk`!R1y)Xo!G(aViWAc5Wcsv3_1RS7DH$rj@kpT?Z4>0(R91lYo%EpZ~47b71VPuOBPa-a4TRals-^+F7tZSf`}e(gZStoj2YwTVPhO0JaK>EeOz7o z`K(lCM6Bh+_d^?KczvpvAmwrAl#pujx2kfMo4~ym(EK~Lqxr91q9qv{S07OpaKh5I zD+`M0#+QWetg5Q;#({NS_BpUq(bi3b-{k-MnnoH;iocC6TDup=S*m}jH2=9-$<{7uyZ!^=??>GIlj{thqPwzows z+cw!$_4h{u+EvfLJxx8BeMWev%nAz=uG8w5i%fQAK9c#WoQV3 zhvD3raB29~z$L-0zR}A&M6eqbtypX8-R^l_ z+eL97F}-(S)fm!_AdaRf$XF>vCIS z<<*?12xZoT&94hPWyzh6$1iMKoph$hAa&&DggjB3(j>;FGdPc4JA7xalQGW^XO?Ty z6X9c+gC~#5?gqWxTYq`5%kMYbc=L=d89*y!bf)2-hW-O-Os?a19(&*Fad=lLUEjQsh> zvI2z{;v>dR7VF>Jv}W|?GA&>JrQ@cP$0|C!9*7=PG*!B}{YI0%3as+_eKTbv^~T7P z%0hV6WVXoad~+KS?V&tO*<&~KL^0VLdZGeKCB*A^WkkMBhUm#*>vVSPu8a&$%F)Hz z%gD2Jlsh9@KH~)o#p~(wN~t7xpI6lTKK5i8Kh4iprJ`XG^O8UY_U~8b*Y*Z$Az?AE2q zJ+5gf|8Q6DO=7RN>kDZ!v0i$%-t&w{jWQ>}`uffdo^g@P+C}zB%+DF_6HEu=#wGX4 z`-)GpPabxqc`enxR%N0vtg9KzzTlSa=d!u{``89Ghs9FGF!qGl%~tmu;QX8~SnmM(a*Qs$W_sg)(4eCXsnpkL-1w9osy(9%8{aaYQ|QP;q9sOiP4{_^V1WCiL|GvqViVPg(uVHRdBNG(asF%&Tn zf-B)<6oZHw$cghB8XFiInj0FL8kv|ziSrs885$UvK)FPl(|8}*1&pi=%#CLm3>r@| zH6CQx@gXB|Myu6JGq2(jr4w=7Pj{<*;4J#DJL$~ke-aGymaOF~cgxa#1cNoN3B-3GuIXrmGeUbA6al$kBNA!Kam{*2$cC9b{A2 zAQozOXSJX^&oVu(U=QC))9(K_&dt34o%^aLUupDJzHRx}n0Htx7#`Ze@uFC#m%E@k zZkg19fO8u!b3B~7yG2(~QG{=gq<-Evn`p^3Uhc8?y|-#*ad`0=y?Fh+%j<|BClfOx z1LIBTF|{E37Z~EqK#$I1u37U;;Jm@r(uGBa@~gLLeh}xr9ih@* z?LfLqq3WHJki-AC!LOjO1a@IcI)cx~^yS#pBcLGfMX!%oW_lY}MGvHFJvN z9>@KQJl;wEPnptZa!tedv|Mlf^>0WtfgGz)Ve#0Dt zEKp8KX!Br9WoBX2Lgt7ebC?Xc*f=1$j+xzn56)&{Vq$0j#wbjViHXs{fE|(z*-^zc zkRu+L&{-M}$l88oEc#`);JK<3gW=b6#vMM#yRSYxt6UIdZ1^YNc74muJ(5-F(Q3C8 zr^UA0KPbAQY#AT&d%IfYH}l+I?t&h-?w|8uI#}>&$|9Gup}yC&e=ghf*ve4Z(){nu zPYHM-`o zah8%v(#*Mn7J7UsZ1t~f=f1ueGke;a+Xm*w3l5td;kYXH=Rk/dev/stderr + exit 1 +} + +usage() { + cat < used directory to store keys and certificates (default: $DESTDIR) +-h show help notice +-r recreate certificates +EOF +} + +OPTIND=1 +while getopts 'd:hr' arg; do + case "$arg" in + d) DESTDIR="$OPTARG" ;; + h) usage; exit 1 ;; + r) RECREATE=1 ;; + ?) fatal "'$arg' unknown option" ;; + esac +done +shift $((OPTIND - 1)) + +if ! openssl version /dev/null 2>&1; then + echo "$0 requires openssl to be installed for keys/certificates generation." >&2 + exit 1 +fi + +echo "setup in directory $DESTDIR" +cd "$DESTDIR" + +trap cleanup INT + +# === +# Generate server certificate +# === + +# generate private key; do no recreate it if they already exist. +if [ ! -f "$SVR_BASE.key" ]; then + openssl genrsa -out "$SVR_BASE.key" "$BITS" +fi + +cat >server.cnf <client.cnf < Date: Fri, 24 Jul 2026 12:13:09 +0200 Subject: [PATCH 63/84] - Fix lock test protect for auth zone change. - Fix to lock shared_ports structure during initialisation. - Fix to lock anchor structure when file is set for it in --- doc/Changelog | 4 ++++ services/authzone.c | 7 ++++++- services/outside_network.c | 7 ++++++- validator/autotrust.c | 3 +++ 4 files changed, 19 insertions(+), 2 deletions(-) diff --git a/doc/Changelog b/doc/Changelog index 2b288c053..da46218a7 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -13,6 +13,10 @@ - Fix to use sha256 for unbound-anchor unit test. - Fix unbound-anchor check for return value of X509_NAME_get_text_by_NID of the emailaddress. + - Fix lock test protect for auth zone change. + - Fix to lock shared_ports structure during initialisation. + - Fix to lock anchor structure when file is set for it in + parse of the header. 23 July 2026: Wouter - Updated credits for Xuanchao Xie in 22 july changelog. diff --git a/services/authzone.c b/services/authzone.c index 4dafabf15..7c9ead951 100644 --- a/services/authzone.c +++ b/services/authzone.c @@ -438,7 +438,12 @@ auth_zone_create(struct auth_zones* az, uint8_t* nm, size_t nmlen, rbtree_init(&z->data, &auth_data_cmp); lock_rw_init(&z->lock); lock_protect(&z->lock, &z->name, sizeof(*z)-sizeof(rbnode_type)- - sizeof(&z->rpz_az_next)-sizeof(&z->rpz_az_prev)); + sizeof(z->rpz_az_next)-sizeof(z->rpz_az_prev)- + sizeof(z->max_transfer_size)-sizeof(z->max_transfer_size)); + lock_protect(&z->lock, &z->max_transfer_size, + sizeof(z->max_transfer_size)); + lock_protect(&z->lock, &z->max_transfer_time, + sizeof(z->max_transfer_time)); lock_rw_wrlock(&z->lock); /* z lock protects all, except rbtree itself and the rpz linked list * pointers, which are protected using az->lock */ diff --git a/services/outside_network.c b/services/outside_network.c index 50028fc1e..ff197a2e2 100644 --- a/services/outside_network.c +++ b/services/outside_network.c @@ -4154,16 +4154,21 @@ struct shared_ports* shared_ports_create(char** ifs, int num_ifs, int do_ip4, return NULL; } lock_basic_init(&shp->lock); - lock_protect(&shp->lock, shp, sizeof(*shp)); + lock_protect(&shp->lock, &shp->ip4_ifs, sizeof(shp->ip4_ifs)); + lock_protect(&shp->lock, &shp->num_ip4, sizeof(shp->num_ip4)); + lock_protect(&shp->lock, &shp->ip6_ifs, sizeof(shp->ip6_ifs)); + lock_protect(&shp->lock, &shp->num_ip6, sizeof(shp->num_ip6)); #ifndef DISABLE_EXPLICIT_PORT_RANDOMISATION /* Allocate interfaces */ + lock_basic_lock(&shp->lock); if(!shared_ports_alloc_ifs(shp, ifs, num_ifs, do_ip4, do_ip6, availports, numavailports)) { log_err("malloc failed"); shared_ports_delete(shp); return NULL; } + lock_basic_unlock(&shp->lock); #else (void)ifs; (void)num_ifs; (void)do_ip4; (void)do_ip6; (void)availports; (void)numavailports; diff --git a/validator/autotrust.c b/validator/autotrust.c index f1c4f9efa..fc7897b71 100644 --- a/validator/autotrust.c +++ b/validator/autotrust.c @@ -884,13 +884,16 @@ parse_var_line(char* line, struct val_anchors* anchors, *header_seen = 1; *anchor = parse_id(anchors, line+6); if(!*anchor) return -1; + lock_basic_lock(&(*anchor)->lock); if(*anchor && !(*anchor)->autr->file) { (*anchor)->autr->file = strdup(nm); if(!(*anchor)->autr->file) { + lock_basic_unlock(&(*anchor)->lock); log_err("malloc failure"); return -1; } } + lock_basic_unlock(&(*anchor)->lock); if(*anchor) return 1; } else if(strncmp(line, ";;REVOKED", 9) == 0) { if(tp) { From e6d00725c2e8440fab7d83a6de88c0c928afe624 Mon Sep 17 00:00:00 2001 From: Petr Vaganov Date: Fri, 24 Jul 2026 17:24:49 +0700 Subject: [PATCH 64/84] authzone: fix memory leak in xfer_set_masters() error path (#1480) Added memory deallocation for the `file` and `host` fields of the `auth_master` node in the event of a URL/allocation error, and unlinked the partially created node from the masters list by resetting the link that pointed to it. Signed-off-by: Petr Vaganov --- services/authzone.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/services/authzone.c b/services/authzone.c index 7c9ead951..72b37fef9 100644 --- a/services/authzone.c +++ b/services/authzone.c @@ -7596,35 +7596,48 @@ xfer_set_masters(struct auth_master** list, struct config_auth* c, { struct auth_master* m; struct config_strlist* p; + struct auth_master** tail; /* list points to the first, or next pointer for the new element */ while(*list) { list = &( (*list)->next ); } if(with_http) for(p = c->urls; p; p = p->next) { + tail = list; m = auth_master_new(&list); if(!m) return 0; m->http = 1; - if(!parse_url(p->str, &m->host, &m->file, &m->port, &m->ssl)) + if(!parse_url(p->str, &m->host, &m->file, &m->port, &m->ssl)) { + free(m->host); + free(m->file); + free(m); + *tail = NULL; return 0; + } } for(p = c->masters; p; p = p->next) { + tail = list; m = auth_master_new(&list); if(!m) return 0; m->ixfr = 1; /* this flag is not configurable */ m->host = strdup(p->str); if(!m->host) { log_err("malloc failure"); + free(m); + *tail = NULL; return 0; } } for(p = c->allow_notify; p; p = p->next) { + tail = list; m = auth_master_new(&list); if(!m) return 0; m->allow_notify = 1; m->host = strdup(p->str); if(!m->host) { log_err("malloc failure"); + free(m); + *tail = NULL; return 0; } } From 52b18fc6f57c387c01876d23f4f77990a289c037 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Fri, 24 Jul 2026 12:25:34 +0200 Subject: [PATCH 65/84] Changelog entry for #1480 - Merge #1480 from petrvaganoff: authzone: fix memory leak in xfer_set_masters() error path. --- doc/Changelog | 2 ++ testdata/10-unbound-anchor.tdir/10-unbound-anchor.conf | 1 + testdata/10-unbound-anchor.tdir/10-unbound-anchor.test | 4 +++- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/doc/Changelog b/doc/Changelog index da46218a7..6b996cba4 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -17,6 +17,8 @@ - Fix to lock shared_ports structure during initialisation. - Fix to lock anchor structure when file is set for it in parse of the header. + - Merge #1480 from petrvaganoff: authzone: fix memory leak in + xfer_set_masters() error path. 23 July 2026: Wouter - Updated credits for Xuanchao Xie in 22 july changelog. diff --git a/testdata/10-unbound-anchor.tdir/10-unbound-anchor.conf b/testdata/10-unbound-anchor.tdir/10-unbound-anchor.conf index bb125c0d5..9ce1fa223 100644 --- a/testdata/10-unbound-anchor.tdir/10-unbound-anchor.conf +++ b/testdata/10-unbound-anchor.tdir/10-unbound-anchor.conf @@ -2,6 +2,7 @@ server: do-not-query-localhost: no fake-sha1: yes + verbosity: 8 forward-zone: name: "." forward-addr: "127.0.0.1@@TOPORT@" diff --git a/testdata/10-unbound-anchor.tdir/10-unbound-anchor.test b/testdata/10-unbound-anchor.tdir/10-unbound-anchor.test index 46cea626c..d779fca34 100644 --- a/testdata/10-unbound-anchor.tdir/10-unbound-anchor.test +++ b/testdata/10-unbound-anchor.tdir/10-unbound-anchor.test @@ -35,11 +35,13 @@ function check_insecure() { # test with good start key, and must do 5011 (no URL possible) echo "*** TEST 1 ***" echo $DS > root.key -$PRE/unbound-anchor -x "notexist.xml" -s "notexist.p7s" $OPTS +cat root.key +$PRE/unbound-anchor -x "notexist.xml" -s "notexist.p7s" $OPTS -vvvv if test $? != 0; then echo "Exitcode not OK" exit 1 fi +cat root.key check_works # save for test 5 cp root.key root.key.probed From e183c2c506efffef7ea28ee7243bf9d8da6bc542 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Fri, 24 Jul 2026 14:37:17 +0200 Subject: [PATCH 66/84] - Fix unused variable warnings in shared_ports_fetch_random and shared_ports_return_port when compiled without threads. --- doc/Changelog | 2 ++ services/outside_network.c | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/doc/Changelog b/doc/Changelog index 6b996cba4..3ed96dcf7 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -19,6 +19,8 @@ parse of the header. - Merge #1480 from petrvaganoff: authzone: fix memory leak in xfer_set_masters() error path. + - Fix unused variable warnings in shared_ports_fetch_random + and shared_ports_return_port when compiled without threads. 23 July 2026: Wouter - Updated credits for Xuanchao Xie in 22 july changelog. diff --git a/services/outside_network.c b/services/outside_network.c index ff197a2e2..24d8f255f 100644 --- a/services/outside_network.c +++ b/services/outside_network.c @@ -4246,6 +4246,9 @@ int shared_ports_fetch_random(struct shared_ports* shp, int portno = 0, my_port = 0; if(!shpif) return 0; +# ifdef THREADS_DISABLED + (void)shp; +# endif lock_basic_lock(&shp->lock); if(udp_connect) { /* if we connect() we cannot reuse fds for a port. */ @@ -4303,6 +4306,9 @@ void shared_ports_return_port(struct shared_ports* shp, #ifndef DISABLE_EXPLICIT_PORT_RANDOMISATION if(!shpif) return; +# ifdef THREADS_DISABLED + (void)shp; +# endif lock_basic_lock(&shp->lock); log_assert(shpif->inuse > 0); shpif->avail_ports[shpif->avail_total - shpif->inuse] = port; From ca1fe4f82a7c5d8168562edc03abd455950c5dbe Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Fri, 24 Jul 2026 14:38:46 +0200 Subject: [PATCH 67/84] - Fix to guard access to shared ports interface array during set up, for analyzer. --- doc/Changelog | 2 ++ services/outside_network.c | 6 ++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/doc/Changelog b/doc/Changelog index 3ed96dcf7..6285c9ea8 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -21,6 +21,8 @@ xfer_set_masters() error path. - Fix unused variable warnings in shared_ports_fetch_random and shared_ports_return_port when compiled without threads. + - Fix to guard access to shared ports interface array during + set up, for analyzer. 23 July 2026: Wouter - Updated credits for Xuanchao Xie in 22 july changelog. diff --git a/services/outside_network.c b/services/outside_network.c index 24d8f255f..4210506fe 100644 --- a/services/outside_network.c +++ b/services/outside_network.c @@ -4127,13 +4127,15 @@ static int shared_ports_alloc_ifs(struct shared_ports* shp, char** ifs, size_t done_4 = 0, done_6 = 0; int i; for(i=0; inum_ip6) { if(!shared_ports_setup_if(&shp->ip6_ifs[done_6], ifs[i], availports, numavailports)) return 0; done_6++; } - if(!str_is_ip6(ifs[i]) && do_ip4) { + if(!str_is_ip6(ifs[i]) && do_ip4 && + done_4 < shp->num_ip4) { if(!shared_ports_setup_if(&shp->ip4_ifs[done_4], ifs[i], availports, numavailports)) return 0; From 8f7411057f655cda8f06bc0fac81a01eed926dd2 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Fri, 24 Jul 2026 14:44:44 +0200 Subject: [PATCH 68/84] - Fix sign of comparison warning in shared ports setup. --- doc/Changelog | 1 + services/outside_network.c | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/doc/Changelog b/doc/Changelog index 6285c9ea8..995040ef3 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -23,6 +23,7 @@ and shared_ports_return_port when compiled without threads. - Fix to guard access to shared ports interface array during set up, for analyzer. + - Fix sign of comparison warning in shared ports setup. 23 July 2026: Wouter - Updated credits for Xuanchao Xie in 22 july changelog. diff --git a/services/outside_network.c b/services/outside_network.c index 4210506fe..0f22c920f 100644 --- a/services/outside_network.c +++ b/services/outside_network.c @@ -4128,14 +4128,14 @@ static int shared_ports_alloc_ifs(struct shared_ports* shp, char** ifs, int i; for(i=0; inum_ip6) { + (int)done_6 < shp->num_ip6) { if(!shared_ports_setup_if(&shp->ip6_ifs[done_6], ifs[i], availports, numavailports)) return 0; done_6++; } if(!str_is_ip6(ifs[i]) && do_ip4 && - done_4 < shp->num_ip4) { + (int)done_4 < shp->num_ip4) { if(!shared_ports_setup_if(&shp->ip4_ifs[done_4], ifs[i], availports, numavailports)) return 0; From 9bd8df01492726e1b21577b2b3c0b9e24cedf9cb Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Fri, 24 Jul 2026 15:31:06 +0200 Subject: [PATCH 69/84] - Fix to use tls-port after referral if tls-upstream is set. --- doc/Changelog | 1 + iterator/iter_delegpt.c | 10 +++++----- iterator/iter_delegpt.h | 6 ++++-- iterator/iter_utils.c | 11 ++++++++++- iterator/iter_utils.h | 3 +++ iterator/iterator.c | 10 ++++++---- services/cache/dns.c | 4 +++- 7 files changed, 32 insertions(+), 13 deletions(-) diff --git a/doc/Changelog b/doc/Changelog index 995040ef3..9f75cbdca 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -24,6 +24,7 @@ - Fix to guard access to shared ports interface array during set up, for analyzer. - Fix sign of comparison warning in shared ports setup. + - Fix to use tls-port after referral if tls-upstream is set. 23 July 2026: Wouter - Updated credits for Xuanchao Xie in 22 july changelog. diff --git a/iterator/iter_delegpt.c b/iterator/iter_delegpt.c index 6ba12c443..f0f94cadd 100644 --- a/iterator/iter_delegpt.c +++ b/iterator/iter_delegpt.c @@ -412,7 +412,7 @@ find_NS(struct reply_info* rep, size_t from, size_t to, uint16_t qclass) } struct delegpt* -delegpt_from_message(struct dns_msg* msg, struct regional* region) +delegpt_from_message(struct dns_msg* msg, struct regional* region, int port) { struct ub_packed_rrset_key* ns_rrset = NULL; struct delegpt* dp; @@ -441,7 +441,7 @@ delegpt_from_message(struct dns_msg* msg, struct regional* region) dp->has_parent_side_NS = 1; /* created from message */ if(!delegpt_set_name(dp, region, ns_rrset->rk.dname)) return NULL; - if(!delegpt_rrset_add_ns(dp, region, ns_rrset, 0)) + if(!delegpt_rrset_add_ns(dp, region, ns_rrset, 0, port)) return NULL; /* add glue, A and AAAA in answer and additional section */ @@ -467,7 +467,7 @@ delegpt_from_message(struct dns_msg* msg, struct regional* region) int delegpt_rrset_add_ns(struct delegpt* dp, struct regional* region, - struct ub_packed_rrset_key* ns_rrset, uint8_t lame) + struct ub_packed_rrset_key* ns_rrset, uint8_t lame, int port) { struct packed_rrset_data* nsdata = (struct packed_rrset_data*) ns_rrset->entry.data; @@ -482,7 +482,7 @@ delegpt_rrset_add_ns(struct delegpt* dp, struct regional* region, continue; /* bad format */ /* add rdata of NS (= wirefmt dname), skip rdatalen bytes */ if(!delegpt_add_ns(dp, region, nsdata->rr_data[i]+2, lame, - NULL, UNBOUND_DNS_PORT)) + NULL, (port==-1?UNBOUND_DNS_PORT:port))) return 0; } return 1; @@ -541,7 +541,7 @@ delegpt_add_rrset(struct delegpt* dp, struct regional* region, if(!rrset) return 1; if(ntohs(rrset->rk.type) == LDNS_RR_TYPE_NS) - return delegpt_rrset_add_ns(dp, region, rrset, lame); + return delegpt_rrset_add_ns(dp, region, rrset, lame, -1); else if(ntohs(rrset->rk.type) == LDNS_RR_TYPE_A) return delegpt_add_rrset_A(dp, region, rrset, lame, additions); else if(ntohs(rrset->rk.type) == LDNS_RR_TYPE_AAAA) diff --git a/iterator/iter_delegpt.h b/iterator/iter_delegpt.h index 287bf9213..db15277fc 100644 --- a/iterator/iter_delegpt.h +++ b/iterator/iter_delegpt.h @@ -221,10 +221,11 @@ int delegpt_add_ns(struct delegpt* dp, struct regional* regional, * @param regional: where to allocate the info. * @param ns_rrset: NS rrset. * @param lame: rrset is lame, disprefer it. + * @param port: port or -1 if not set. * @return 0 on alloc error. */ int delegpt_rrset_add_ns(struct delegpt* dp, struct regional* regional, - struct ub_packed_rrset_key* ns_rrset, uint8_t lame); + struct ub_packed_rrset_key* ns_rrset, uint8_t lame, int port); /** * Add target address to the delegation point. @@ -365,11 +366,12 @@ size_t delegpt_count_targets(struct delegpt* dp); * * @param msg: the dns message, referral. * @param regional: where to allocate delegation point. + * @param port: if not -1 specifies a port number. * @return new delegation point or NULL on alloc error, or if the * message was not appropriate. */ struct delegpt* delegpt_from_message(struct dns_msg* msg, - struct regional* regional); + struct regional* regional, int port); /** * Mark negative return in delegation point for specific nameserver. diff --git a/iterator/iter_utils.c b/iterator/iter_utils.c index cc09fa524..e848e83ba 100644 --- a/iterator/iter_utils.c +++ b/iterator/iter_utils.c @@ -1313,7 +1313,8 @@ iter_lookup_parent_NS_from_cache(struct module_env* env, struct delegpt* dp, log_rrset_key(VERB_ALGO, "found parent-side NS in cache", akey); dp->has_parent_side_NS = 1; /* and mark the new names as lame */ - if(!delegpt_rrset_add_ns(dp, region, akey, 1)) { + if(!delegpt_rrset_add_ns(dp, region, akey, 1, + deleg_port_number(env))) { lock_rw_unlock(&akey->entry.lock); return 0; } @@ -1703,3 +1704,11 @@ iter_make_minimal(struct reply_info* rep) rep->ar_numrrsets = 0; rep->rrset_count -= rem; } + +int +deleg_port_number(struct module_env* env) +{ + if(env->cfg->ssl_upstream) + return env->cfg->ssl_port; + return -1; +} diff --git a/iterator/iter_utils.h b/iterator/iter_utils.h index f7f374742..9fb361ab5 100644 --- a/iterator/iter_utils.h +++ b/iterator/iter_utils.h @@ -483,4 +483,7 @@ void limit_nsec_ttl(struct dns_msg* msg); */ void iter_make_minimal(struct reply_info* rep); +/** See if we need a different port number */ +int deleg_port_number(struct module_env* env); + #endif /* ITERATOR_ITER_UTILS_H */ diff --git a/iterator/iterator.c b/iterator/iterator.c index 1f95039c8..cadb95ebd 100644 --- a/iterator/iterator.c +++ b/iterator/iterator.c @@ -3147,7 +3147,6 @@ find_NS(struct reply_info* rep, size_t from, size_t to) return NULL; } - /** * Process the query response. All queries end up at this state first. This * process generally consists of analyzing the response and routing the @@ -3474,7 +3473,8 @@ processQueryResponse(struct module_qstate* qstate, struct iter_qstate* iq, infra_ratelimit_dec(qstate->env->infra_cache, old_dp->name, old_dp->namelen, *qstate->env->now); - iq->dp = delegpt_from_message(iq->response, qstate->region); + iq->dp = delegpt_from_message(iq->response, qstate->region, + deleg_port_number(qstate->env)); if (qstate->env->cfg->qname_minimisation) iq->minimisation_state = INIT_MINIMISE_STATE; if(!iq->dp) { @@ -3751,7 +3751,8 @@ prime_supers(struct module_qstate* qstate, int id, struct module_qstate* forq) log_assert(qstate->is_priming || foriq->wait_priming_stub); log_assert(qstate->return_rcode == LDNS_RCODE_NOERROR); /* Convert our response to a delegation point */ - dp = delegpt_from_message(qstate->return_msg, forq->region); + dp = delegpt_from_message(qstate->return_msg, forq->region, + deleg_port_number(forq->env)); if(!dp) { /* if there is no convertible delegation point, then * the ANSWER type was (presumably) a negative answer. */ @@ -3967,7 +3968,8 @@ processDSNSResponse(struct module_qstate* qstate, int id, /* else, store as DP and continue at querytargets */ foriq->state = QUERYTARGETS_STATE; - foriq->dp = delegpt_from_message(qstate->return_msg, forq->region); + foriq->dp = delegpt_from_message(qstate->return_msg, forq->region, + deleg_port_number(forq->env)); if(!foriq->dp) { log_err("out of memory in dsns dp alloc"); errinf(qstate, "malloc failure, in DS search"); diff --git a/services/cache/dns.c b/services/cache/dns.c index 7100b1ce8..04d5ae2d6 100644 --- a/services/cache/dns.c +++ b/services/cache/dns.c @@ -43,6 +43,7 @@ #include "iterator/iter_utils.h" #include "validator/val_nsec.h" #include "validator/val_utils.h" +#include "iterator/iter_utils.h" #include "services/cache/dns.h" #include "services/cache/rrset.h" #include "util/data/msgparse.h" @@ -586,7 +587,8 @@ dns_cache_find_delegation(struct module_env* env, uint8_t* qname, return NULL; } } - if(!delegpt_rrset_add_ns(dp, region, nskey, 0)) { + if(!delegpt_rrset_add_ns(dp, region, nskey, 0, + deleg_port_number(env))) { lock_rw_unlock(&nskey->entry.lock); log_err("find_delegation: addns out of memory"); return NULL; From 7cc7a43ff67d458ccb64f413a2f8e67cf1f3f51c Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Fri, 24 Jul 2026 15:32:20 +0200 Subject: [PATCH 70/84] Changelog note for #1481. - Fix #1481: Fix to use tls-port after referral if tls-upstream is set. --- doc/Changelog | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/Changelog b/doc/Changelog index 9f75cbdca..3b63868fc 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -24,7 +24,8 @@ - Fix to guard access to shared ports interface array during set up, for analyzer. - Fix sign of comparison warning in shared ports setup. - - Fix to use tls-port after referral if tls-upstream is set. + - Fix #1481: Fix to use tls-port after referral if + tls-upstream is set. 23 July 2026: Wouter - Updated credits for Xuanchao Xie in 22 july changelog. From 8a38bed2626ba064b0339d70dd343a5f96736ea6 Mon Sep 17 00:00:00 2001 From: Petr Sumbera Date: Fri, 24 Jul 2026 15:34:18 +0200 Subject: [PATCH 71/84] Fix pthread detection on Solaris 11.4 (#1479) AX_PTHREAD requires _REENTRANT to confirm that pthread support is enabled. Solaris 11.4 headers no longer use the macro, and GCC 16 therefore no longer defines it for -pthread. Detect XPG7 support in the target headers and require _REENTRANT only on older Solaris releases. The existing pthread compile and link test remains the final capability check. This follows the canonical Autoconf Archive change: https://github.com/autoconf-archive/autoconf-archive/pull/341 Regenerate configure with Autoconf 2.71. Tested on Solaris 11.4 with GCC 15.2 and GCC 16.1. The Autoconf Archive change was also tested on Solaris 11.3. Co-authored-by: Rainer Orth --- ax_pthread.m4 | 19 +++++++++++++++++-- configure | 26 +++++++++++++++++++++++++- 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/ax_pthread.m4 b/ax_pthread.m4 index 9f35d1391..a9019832e 100644 --- a/ax_pthread.m4 +++ b/ax_pthread.m4 @@ -87,7 +87,7 @@ # modified version of the Autoconf Macro, you may extend this special # exception to the GPL to apply to your modified version as well. -#serial 31 +#serial 32 AU_ALIAS([ACX_PTHREAD], [AX_PTHREAD]) AC_DEFUN([AX_PTHREAD], [ @@ -249,7 +249,22 @@ AS_IF([test "x$ax_pthread_clang" = "xyes"], # correctly enabled case $host_os in - darwin* | hpux* | linux* | osf* | solaris*) + solaris*) + # Solaris 11.4 introduced XPG7 support and did away with the need for + # _REENTRANT. + + AC_EGREP_CPP([AX_PTHREAD_SOLARIS__REENTRANT], + [ +# undef _XOPEN_SOURCE +# include +# if _XOPEN_VERSION < 700 + AX_PTHREAD_SOLARIS__REENTRANT +# endif + ], + [ax_pthread_check_macro="_REENTRANT"], + [ax_pthread_check_macro="--"]) + ;; + darwin* | hpux* | linux* | osf*) ax_pthread_check_macro="_REENTRANT" ;; diff --git a/configure b/configure index 2f50d918a..7d9702d30 100755 --- a/configure +++ b/configure @@ -18415,7 +18415,31 @@ fi # correctly enabled case $host_os in - darwin* | hpux* | linux* | osf* | solaris*) + solaris*) + # Solaris 11.4 introduced XPG7 support and did away with the need for + # _REENTRANT. + + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +# undef _XOPEN_SOURCE +# include +# if _XOPEN_VERSION < 700 + AX_PTHREAD_SOLARIS__REENTRANT +# endif + +_ACEOF +if (eval "$ac_cpp conftest.$ac_ext") 2>&5 | + $EGREP "AX_PTHREAD_SOLARIS__REENTRANT" >/dev/null 2>&1 +then : + ax_pthread_check_macro="_REENTRANT" +else $as_nop + ax_pthread_check_macro="--" +fi +rm -rf conftest* + + ;; + darwin* | hpux* | linux* | osf*) ax_pthread_check_macro="_REENTRANT" ;; From c21e3ee9292792a84331a228b236eda71f9bdf02 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Fri, 24 Jul 2026 15:35:55 +0200 Subject: [PATCH 72/84] Changelog note for #1479 - Merge #1479 from psumbera: Fix pthread detection on Solaris 11.4. --- doc/Changelog | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/Changelog b/doc/Changelog index 3b63868fc..6092efff5 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -26,6 +26,8 @@ - Fix sign of comparison warning in shared ports setup. - Fix #1481: Fix to use tls-port after referral if tls-upstream is set. + - Merge #1479 from psumbera: Fix pthread detection on + Solaris 11.4. 23 July 2026: Wouter - Updated credits for Xuanchao Xie in 22 july changelog. From a45da353d3feb5d8fc00685fa1ceda3816d5108f Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Fri, 24 Jul 2026 17:04:38 +0200 Subject: [PATCH 73/84] - Fix to call OPENSSL_cleanup on exit when that is defined. --- config.h.in | 3 +++ configure | 6 ++++++ configure.ac | 2 +- daemon/daemon.c | 3 +++ doc/Changelog | 1 + testcode/unitmain.c | 3 +++ 6 files changed, 17 insertions(+), 1 deletion(-) diff --git a/config.h.in b/config.h.in index 1785ae5c0..ce8141b53 100644 --- a/config.h.in +++ b/config.h.in @@ -529,6 +529,9 @@ /* Define to 1 if you have the header file. */ #undef HAVE_OPENSSL_BN_H +/* Define to 1 if you have the `OPENSSL_cleanup' function. */ +#undef HAVE_OPENSSL_CLEANUP + /* Define to 1 if you have the `OPENSSL_config' function. */ #undef HAVE_OPENSSL_CONFIG diff --git a/configure b/configure index 7d9702d30..8c0a416cd 100755 --- a/configure +++ b/configure @@ -21093,6 +21093,12 @@ then : printf "%s\n" "#define HAVE_BIO_SET_CALLBACK_EX 1" >>confdefs.h fi +ac_fn_c_check_func "$LINENO" "OPENSSL_cleanup" "ac_cv_func_OPENSSL_cleanup" +if test "x$ac_cv_func_OPENSSL_cleanup" = xyes +then : + printf "%s\n" "#define HAVE_OPENSSL_CLEANUP 1" >>confdefs.h + +fi # these check_funcs need -lssl diff --git a/configure.ac b/configure.ac index 165645e13..254a6c630 100644 --- a/configure.ac +++ b/configure.ac @@ -1081,7 +1081,7 @@ else AC_MSG_RESULT([no]) fi AC_CHECK_HEADERS([openssl/conf.h openssl/engine.h openssl/bn.h openssl/dh.h openssl/dsa.h openssl/rsa.h openssl/core_names.h openssl/param_build.h],,, [AC_INCLUDES_DEFAULT]) -AC_CHECK_FUNCS([OPENSSL_config EVP_sha1 EVP_sha256 EVP_sha512 FIPS_mode EVP_default_properties_is_fips_enabled EVP_MD_CTX_new OpenSSL_add_all_digests OPENSSL_init_crypto EVP_cleanup ENGINE_cleanup ERR_load_crypto_strings CRYPTO_cleanup_all_ex_data ERR_free_strings RAND_cleanup DSA_SIG_set0 EVP_dss1 EVP_DigestVerify EVP_aes_256_cbc EVP_EncryptInit_ex HMAC_Init_ex CRYPTO_THREADID_set_callback EVP_MAC_CTX_set_params OSSL_PARAM_BLD_new BIO_set_callback_ex]) +AC_CHECK_FUNCS([OPENSSL_config EVP_sha1 EVP_sha256 EVP_sha512 FIPS_mode EVP_default_properties_is_fips_enabled EVP_MD_CTX_new OpenSSL_add_all_digests OPENSSL_init_crypto EVP_cleanup ENGINE_cleanup ERR_load_crypto_strings CRYPTO_cleanup_all_ex_data ERR_free_strings RAND_cleanup DSA_SIG_set0 EVP_dss1 EVP_DigestVerify EVP_aes_256_cbc EVP_EncryptInit_ex HMAC_Init_ex CRYPTO_THREADID_set_callback EVP_MAC_CTX_set_params OSSL_PARAM_BLD_new BIO_set_callback_ex OPENSSL_cleanup]) # these check_funcs need -lssl BAKLIBS="$LIBS" diff --git a/daemon/daemon.c b/daemon/daemon.c index 07d99ff66..ac2cd3144 100644 --- a/daemon/daemon.c +++ b/daemon/daemon.c @@ -1308,6 +1308,9 @@ daemon_delete(struct daemon* daemon) # if defined(HAVE_SSL) && defined(OPENSSL_THREADS) && !defined(THREADS_DISABLED) ub_openssl_lock_delete(); # endif +#ifdef HAVE_OPENSSL_CLEANUP + OPENSSL_cleanup(); +#endif #ifndef HAVE_ARC4RANDOM _ARC4_LOCK_DESTROY(); #endif diff --git a/doc/Changelog b/doc/Changelog index 6092efff5..b6f771b81 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -28,6 +28,7 @@ tls-upstream is set. - Merge #1479 from psumbera: Fix pthread detection on Solaris 11.4. + - Fix to call OPENSSL_cleanup on exit when that is defined. 23 July 2026: Wouter - Updated credits for Xuanchao Xie in 22 july changelog. diff --git a/testcode/unitmain.c b/testcode/unitmain.c index 62f37375f..985a9d552 100644 --- a/testcode/unitmain.c +++ b/testcode/unitmain.c @@ -1445,6 +1445,9 @@ main(int argc, char* argv[]) # ifdef HAVE_RAND_CLEANUP RAND_cleanup(); # endif +#ifdef HAVE_OPENSSL_CLEANUP + OPENSSL_cleanup(); +#endif #elif defined(HAVE_NSS) if(NSS_Shutdown() != SECSuccess) fatal_exit("could not shutdown NSS"); From cbfc3b0342f6ded80656df7e98c72fa348e9c92d Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Tue, 28 Jul 2026 09:45:59 +0200 Subject: [PATCH 74/84] - Tag for 1.26.0rc1. The repo continues with version 1.26.1. --- configure | 25 +++++++++++++------------ configure.ac | 5 +++-- doc/Changelog | 3 +++ 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/configure b/configure index 8c0a416cd..699d67006 100755 --- a/configure +++ b/configure @@ -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.0. +# Generated by GNU Autoconf 2.71 for unbound 1.26.1. # # Report bugs to . # @@ -622,8 +622,8 @@ MAKEFLAGS= # Identity of this package. PACKAGE_NAME='unbound' PACKAGE_TARNAME='unbound' -PACKAGE_VERSION='1.26.0' -PACKAGE_STRING='unbound 1.26.0' +PACKAGE_VERSION='1.26.1' +PACKAGE_STRING='unbound 1.26.1' PACKAGE_BUGREPORT='unbound-bugs@nlnetlabs.nl or https://github.com/NLnetLabs/unbound/issues' PACKAGE_URL='' @@ -1513,7 +1513,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.0 to adapt to many kinds of systems. +\`configure' configures unbound 1.26.1 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1579,7 +1579,7 @@ fi if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of unbound 1.26.0:";; + short | recursive ) echo "Configuration of unbound 1.26.1:";; esac cat <<\_ACEOF @@ -1832,7 +1832,7 @@ fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -unbound configure 1.26.0 +unbound configure 1.26.1 generated by GNU Autoconf 2.71 Copyright (C) 2021 Free Software Foundation, Inc. @@ -2489,7 +2489,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.0, which was +It was created by unbound $as_me 1.26.1, which was generated by GNU Autoconf 2.71. Invocation command line was $ $0$ac_configure_args_raw @@ -3253,11 +3253,11 @@ UNBOUND_VERSION_MAJOR=1 UNBOUND_VERSION_MINOR=26 -UNBOUND_VERSION_MICRO=0 +UNBOUND_VERSION_MICRO=1 LIBUNBOUND_CURRENT=9 -LIBUNBOUND_REVISION=39 +LIBUNBOUND_REVISION=40 LIBUNBOUND_AGE=1 # 1.0.0 had 0:12:0 # 1.0.1 had 0:13:0 @@ -3364,6 +3364,7 @@ LIBUNBOUND_AGE=1 # 1.25.1 had 9:37:1 # 1.25.2 had 9:38:1 # 1.26.0 had 9:39:1 +# 1.26.1 had 9:40:1 # Current -- the number of the binary API that we're implementing # Revision -- which iteration of the implementation of the binary @@ -25711,7 +25712,7 @@ printf "%s\n" "#define MAXSYSLOGMSGLEN 10240" >>confdefs.h -version=1.26.0 +version=1.26.1 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for build time" >&5 printf %s "checking for build time... " >&6; } @@ -26241,7 +26242,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # 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.0, which was +This file was extended by unbound $as_me 1.26.1, which was generated by GNU Autoconf 2.71. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -26309,7 +26310,7 @@ ac_cs_config_escaped=`printf "%s\n" "$ac_cs_config" | sed "s/^ //; s/'/'\\\\\\\\ cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config='$ac_cs_config_escaped' ac_cs_version="\\ -unbound config.status 1.26.0 +unbound config.status 1.26.1 configured by $0, generated by GNU Autoconf 2.71, with options \\"\$ac_cs_config\\" diff --git a/configure.ac b/configure.ac index 254a6c630..afedb889e 100644 --- a/configure.ac +++ b/configure.ac @@ -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],[0]) +m4_define([VERSION_MICRO],[1]) 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=39 +LIBUNBOUND_REVISION=40 LIBUNBOUND_AGE=1 # 1.0.0 had 0:12:0 # 1.0.1 had 0:13:0 @@ -126,6 +126,7 @@ LIBUNBOUND_AGE=1 # 1.25.1 had 9:37:1 # 1.25.2 had 9:38:1 # 1.26.0 had 9:39:1 +# 1.26.1 had 9:40:1 # Current -- the number of the binary API that we're implementing # Revision -- which iteration of the implementation of the binary diff --git a/doc/Changelog b/doc/Changelog index b6f771b81..6218e86b5 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,6 @@ +28 July 2026: Wouter + - Tag for 1.26.0rc1. The repo continues with version 1.26.1. + 24 July 2026: Wouter - Merge #1433 from jisakiel: Add new static zone type block_aaaa to suppress AAAA queries. From 79b84bbc91a24e5e6fa555acc61961b1f8f6a171 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Thu, 30 Jul 2026 08:24:42 +0200 Subject: [PATCH 75/84] - Fix #1482: DNS-over-QUIC doesn't work with simple config. That fixes interface-automatic for use with doq service. --- doc/Changelog | 4 ++++ services/listen_dnsport.c | 26 ++++++++++++++++++++++---- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/doc/Changelog b/doc/Changelog index 6218e86b5..9bd4356f9 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,7 @@ +30 July 2026: Wouter + - Fix #1482: DNS-over-QUIC doesn't work with simple config. + That fixes interface-automatic for use with doq service. + 28 July 2026: Wouter - Tag for 1.26.0rc1. The repo continues with version 1.26.1. diff --git a/services/listen_dnsport.c b/services/listen_dnsport.c index 8ef084e5e..7da23126e 100644 --- a/services/listen_dnsport.c +++ b/services/listen_dnsport.c @@ -1341,13 +1341,33 @@ ports_create_if(const char* ifname, int do_auto, int do_udp, int do_tcp, if((is_doq) && !(is_https || is_ssl)) do_tcp = 0; if(do_auto) { + enum listen_type auto_port_type; ub_sock = calloc(1, sizeof(struct unbound_socket)); if(!ub_sock) return 0; + if(is_dnscrypt) { + auto_port_type = listen_type_udpancil_dnscrypt; + add = "udpancil_dnscrypt"; + } else if(is_doq) { + auto_port_type = listen_type_doq; + add = "doq"; + if(if_listens_on(ifname, port, 53, NULL)) { + log_err("DNS over QUIC is strictly not " + "allowed on port 53 as per RFC 9250. " + "Port 53 is for DNS datagrams. Error " + "for interface '%s'.", ifname); + free(ub_sock->addr); + free(ub_sock); + return 0; + } + } else { + auto_port_type = listen_type_udpancil; + add = "udpancil"; + } if((s = make_sock_port(SOCK_DGRAM, ifname, port, hints, 1, &noip6, rcv, snd, reuseport, transparent, tcp_mss, nodelay, freebind, use_systemd, dscp, ub_sock, - (is_dnscrypt?"udpancil_dnscrypt":"udpancil"))) == -1) { + add)) == -1) { free(ub_sock->addr); free(ub_sock); if(noip6) { @@ -1366,9 +1386,7 @@ ports_create_if(const char* ifname, int do_auto, int do_udp, int do_tcp, if (sock_queue_timeout && !set_recvtimestamp(s)) { log_warn("socket timestamping is not available"); } - if(!port_insert(list, s, is_dnscrypt - ?listen_type_udpancil_dnscrypt:listen_type_udpancil, - is_pp2, ub_sock)) { + if(!port_insert(list, s, auto_port_type, is_pp2, ub_sock)) { sock_close(s); free(ub_sock->addr); free(ub_sock); From ff28b7e5cf3eae0876710bcb635fdb4ae71b1166 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Fri, 31 Jul 2026 09:53:47 +0200 Subject: [PATCH 76/84] - For #1483: The failure reason when an NSEC NXDOMAIN is encountered when looking for an insecure delegation, is fixed to mention the NSEC records, instead of nonexistent NSEC3 records, that it attempted. --- doc/Changelog | 6 +++ testdata/val_nx_uns_resp.rpl | 82 ++++++++++++++++++++++++++++++++++++ validator/val_utils.c | 14 ++++++ validator/val_utils.h | 8 ++++ validator/validator.c | 20 +++++++++ 5 files changed, 130 insertions(+) create mode 100644 testdata/val_nx_uns_resp.rpl diff --git a/doc/Changelog b/doc/Changelog index 9bd4356f9..32e525e3b 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,9 @@ +31 July 2026: Wouter + - For #1483: The failure reason when an NSEC NXDOMAIN is + encountered when looking for an insecure delegation, is + fixed to mention the NSEC records, instead of nonexistent + NSEC3 records, that it attempted. + 30 July 2026: Wouter - Fix #1482: DNS-over-QUIC doesn't work with simple config. That fixes interface-automatic for use with doq service. diff --git a/testdata/val_nx_uns_resp.rpl b/testdata/val_nx_uns_resp.rpl new file mode 100644 index 000000000..88dc95567 --- /dev/null +++ b/testdata/val_nx_uns_resp.rpl @@ -0,0 +1,82 @@ +; config options +server: + ; This is the test key 29332 in the testdata. + trust-anchor: ". 3600 IN DS 29332 8 2 b75e26316631b6e37cbc977323a08769f86e36a10fee888676d35f61e2ff4181" + val-override-date: "20201020135527" + target-fetch-policy: "0 0 0 0 0" + qname-minimisation: no + fake-sha1: yes + trust-anchor-signaling: no + minimal-responses: no + log-servfail: yes + +forward-zone: + name: "." + forward-addr: 10.5.5.5 +CONFIG_END + +SCENARIO_BEGIN Test nxdomain that gets unsigned response +; and the DS lookup that it makes gets an NSEC NXDOMAIN response. + +; 10.5.5.5 forwarder +RANGE_BEGIN 0 100 + ADDRESS 10.5.5.5 + +; unsigned NXDOMAIN response, from the first forwarder, here it is served +; from the upstream. +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NXDOMAIN +SECTION QUESTION +example.veryinvalid. IN TXT +SECTION AUTHORITY +. 3600 IN SOA ns.root. host.root. 1 3600 3600 3600 3600 +ENTRY_END + +; DNSKEY answer, using CSK for test simplicity. +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +. IN DNSKEY +SECTION ANSWER +. IN DNSKEY 257 3 8 AwEAAb4WMOTBLTFvmBra5m6SK4VfViOzmvyUAU0qv861ZQXeEFvwlndqNU9rwRsMxrSWAYs5nHErKDn49usC/HyxxW1477iGFHhfgL4mjNreJm9zft2QFB1VLbRbEPYdDMLCn4co0qnG7/KG8W2i8Pym1L7f+aREwbLo+/716AS2PbaKMhfWLKLiq5wnBcUClQMNzCiwhqxDJp1oePqfkVdeUgXOtgi0dYRIKyQFhJ5VWJ22npoi/Gif0XLCADAlAwRLKc8o/yJkCxskzgpHpw5Cki1lclg0aq4ssOuPRQ+ne6IHYCz9D2mwzulblhLFamKdq7aHzNt4NlyxhpANVFiKLD8= ;{id = 29332 (ksk), size = 2048b} +. 3600 IN RRSIG DNSKEY 8 0 3600 20201116135527 20201019135527 29332 . ToK8hJrGa+kNu6y8FpRwZq2FjDPBAk5Ctchia3Vu9yTth2dR7BhK2ALTWVBwAQGwiwxXKoVK9QCxdQM0ti7CVb9x75bejkd2E6UGWVmqyTRPpn3D43qYARm87y3ZVKG7LlWHp8UOf21XLp1H7R+wuipIvBJ1XA+QGXThPdbV9EEz1kKGdprBfdpFkQdcAiuYYrOTa5cJ11z32mGiQ12fWjpb4UUbfcoDD9YOoa/S5a6h7jYBOfm75ZB8UCW3Z/SlsN8KIfYZsg5CZphpf38XH5uNLMmzpaWYhfamJZJve9Isx4eILNmdMLK4E8ESwDFCVNzMIqdf20VRg6Lh7nwQeA== +ENTRY_END + +; answer for DS, from another forwarder, here returned from the test upstream. +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NXDOMAIN +SECTION QUESTION +veryinvalid. IN DS +SECTION AUTHORITY +versicherung. 3600 IN NSEC vet. NS DS RRSIG NSEC +versicherung. 3600 IN RRSIG NSEC 8 1 3600 20201116135527 20201019135527 29332 . AVAON9Y7AVwX9YWQK8JPcB6Wk/tEfQT7JrLiCRlBBQA0+mpVYYYtMyrm4aMjkhusqYcnpIZoLGOI/dxJjIwDgnMkd4EqY2oICea3I260f8z2v9e7zNobyUTjkoWsmLPc7VRLtEGKu1XyVpt7DX6ElGoSUhU4JsTx7wkkXU0SGAakL0bhK8K68B92NEVwgKX4D7+kVfpjc0aHaB3rAkhQCM/G0jEFp0RuhTX1aru6IuYrZmjW0dvQ2niec6NaYzvuGnbhMLlFLuXqSmI2B7uIFx894usd1cVWnSRg49bAkuiEv5q04ltRel1huJBGGiZlLEwanS5g53C5DHfq10OUrw== +. 3600 IN NSEC aaa. NS SOA RRSIG NSEC DNSKEY ZONEMD +. 3600 IN RRSIG NSEC 8 0 3600 20201116135527 20201019135527 29332 . E8r6rpFgFBUda2GnFSMzHZLtjy1dT+ZS0wPRE12RNwVK547bo2vByv9EFhOHS6sEIFqX+AmIJotuiEPKnCFUTr6FKscaxtw38dJRZ3wldqV6dmqUiRmz91crDCV5nSL45FIbkWKk1Q+tnXie3sZ4zwBc12kGg2BttMAQ0i4sbMbf6EUNYZGwYzSB0/VhXVJcl8gl+5lfpiVqfWNZI7vTEaHqrC2gBC3UK1cQE9lQOqhJ6H5ThA1FR9j/mZFM9sG5vQ2Mqlzl2iiN2Y6mCptDY1vwfff6AnT0YeDwJ/XwGisMZrSvTCYaiRndb8CUUmCr23AFy5OER1rmeFGkHX5+WQ== +. 3600 IN SOA ns.root. host.root. 1 3600 3600 3600 3600 +. 3600 IN RRSIG SOA 8 0 3600 20201116135527 20201019135527 29332 . tVeReLMXPnl6rk4QX94xy9lCodQ+xc39lokbNkvbXnTURNCOAwtNiMMPlAAJ3/HTpIxo175gPfupACIveBtgajdp85jUIvLMOM5B6lX80+dUPBGZ4gHVjf+8EGnr7q2wnW2+KcJu0OhN2g+YqCV6aPi8pzuAp+AMsBYcMfXqEQq9Lxqv6TL50MUCJN3GPCyBIdjbs/A+ZB7D1EOO1YgdbsMHK/pWKYt4UfBFfekoA6joIGf4vBKKRTWnoo0BcrFob3AW1SyJkoxoqEsN3YAVL9jNkJCkU0/adLypHgDNayLgsWI5/o4Ng8LxN6tNxAilkMhcGY80T5g0uo+ukY7McA== +ENTRY_END +RANGE_END + +STEP 1 QUERY +ENTRY_BEGIN +REPLY RD DO +SECTION QUESTION +example.veryinvalid. IN TXT +ENTRY_END + +STEP 10 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR RD RA DO SERVFAIL +SECTION QUESTION +example.veryinvalid. IN TXT +SECTION ANSWER +ENTRY_END + +SCENARIO_END diff --git a/validator/val_utils.c b/validator/val_utils.c index e77f93f5a..bfff19126 100644 --- a/validator/val_utils.c +++ b/validator/val_utils.c @@ -1323,6 +1323,20 @@ int val_has_signed_nsecs(struct reply_info* rep, char** reason) return 0; } +void val_has_auth_nsecs(struct reply_info* rep, int* has_nsec, int* has_nsec3) +{ + size_t i, num_nsec = 0, num_nsec3 = 0; + for(i=rep->an_numrrsets; ian_numrrsets+rep->ns_numrrsets; i++) { + if(rep->rrsets[i]->rk.type == htons(LDNS_RR_TYPE_NSEC)) + num_nsec++; + else if(rep->rrsets[i]->rk.type == htons(LDNS_RR_TYPE_NSEC3)) + num_nsec3++; + else continue; + } + *has_nsec = (num_nsec != 0); + *has_nsec3 = (num_nsec3 != 0); +} + struct dns_msg* val_find_DS(struct module_env* env, uint8_t* nm, size_t nmlen, uint16_t c, struct regional* region, uint8_t* topname) diff --git a/validator/val_utils.h b/validator/val_utils.h index 43386edbf..f05ff1d94 100644 --- a/validator/val_utils.h +++ b/validator/val_utils.h @@ -410,6 +410,14 @@ void val_blacklist(struct sock_list** blacklist, struct regional* region, */ int val_has_signed_nsecs(struct reply_info* rep, char** reason); +/** + * See if there are NSECs, or NSEC3s in the authority section. + * @param rep: reply to check + * @param has_nsec: returned true if it has nsecs. + * @param has_nsec3: returned true if it has nsec3s. + */ +void val_has_auth_nsecs(struct reply_info* rep, int* has_nsec, int* has_nsec3); + /** * Return algo number for favorite (best) algorithm that we support in DS. * @param ds_rrset: the DSes in this rrset are inspected and best algo chosen. diff --git a/validator/validator.c b/validator/validator.c index d3ed8be3e..3634e3215 100644 --- a/validator/validator.c +++ b/validator/validator.c @@ -3116,6 +3116,7 @@ ds_response_to_ke(struct module_qstate* qstate, struct val_qstate* vq, case sec_status_unchecked: default: /* NSEC proof did not work, try next */ + verbose(VERB_ALGO, "NSEC proof did not prove insecure delegation, try NSEC3"); break; } @@ -3151,6 +3152,25 @@ ds_response_to_ke(struct module_qstate* qstate, struct val_qstate* vq, *ke = NULL; return 0; case sec_status_bogus: + /* It could be that the NSEC proof failed, + * and, then tried NSEC3. */ + { + int has_nsec=0, has_nsec3=0; + val_has_auth_nsecs(msg->rep, &has_nsec, + &has_nsec3); + if(!has_nsec3 && has_nsec) { + /* The NSECs are the cause, mention that in the error message. */ + verbose(VERB_DETAIL, "NSECs for the " + "referral did not prove no DS."); + errinf_ede(qstate, "NSECs for the referral did not prove no DS", LDNS_EDE_DNSSEC_BOGUS); + goto return_bogus; + } + if(!has_nsec3 && !has_nsec) { + verbose(VERB_DETAIL, "absence of NSECs and NSEC3s when attempting to prove no DS."); + errinf_ede(qstate, "no NSECs or NSEC3s when attempting to prove no DS", LDNS_EDE_DNSSEC_BOGUS); + goto return_bogus; + } + } verbose(VERB_DETAIL, "NSEC3s for the " "referral did not prove no DS."); errinf_ede(qstate, reason, reason_bogus); From b444deffd28e6136f389fa23ef7d3a6dd0c12825 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Tue, 4 Aug 2026 10:01:59 +0200 Subject: [PATCH 77/84] Note 1.26.0 release. --- doc/Changelog | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/Changelog b/doc/Changelog index 32e525e3b..d087c7eef 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -10,6 +10,7 @@ 28 July 2026: Wouter - Tag for 1.26.0rc1. The repo continues with version 1.26.1. + This became 1.26.0 on 4 aug 2026. 24 July 2026: Wouter - Merge #1433 from jisakiel: Add new static zone type From bdfcfb861f7ba7d5472734c691f2069735ea3a80 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Tue, 4 Aug 2026 10:04:34 +0200 Subject: [PATCH 78/84] - Fix to set makedist.sh to not wget config.sub and config.guess from git repo. The fetch times out, and the version from libtoolize is much more recent now than that it was when the wget was added. --- doc/Changelog | 6 ++++++ makedist.sh | 3 +++ 2 files changed, 9 insertions(+) diff --git a/doc/Changelog b/doc/Changelog index d087c7eef..f29912256 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,9 @@ +4 August 2026: Wouter + - Fix to set makedist.sh to not wget config.sub and + config.guess from git repo. The fetch times out, and the + version from libtoolize is much more recent now than + that it was when the wget was added. + 31 July 2026: Wouter - For #1483: The failure reason when an NSEC NXDOMAIN is encountered when looking for an insecure delegation, is diff --git a/makedist.sh b/makedist.sh index ec9021c2c..07b097738 100755 --- a/makedist.sh +++ b/makedist.sh @@ -608,6 +608,8 @@ rm -rf .git .travis.yml .gitattributes .github .gitignore || error_cleanup "Fail info "Adding libtool utils (libtoolize)." libtoolize -c --install || libtoolize -c || error_cleanup "Libtoolize failed." +# Turn this off, if the git repo times out for lookups. +if test "updateconfigsub" = "false"; then # https://www.gnu.org/software/gettext/manual/html_node/config_002eguess.html info "Updating config.guess and config.sub" wget -O config.guess 'https://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.guess;hb=HEAD' @@ -621,6 +623,7 @@ if [ `uname -s | grep -i -c darwin` -ne 0 ]; then xattr -d com.apple.quarantine config.sub fi fi +fi info "Building configure script (autoreconf)." autoreconf -f || error_cleanup "Autoconf failed." From b7d13ff12b673d31066e1ced0b0f59a202adc3ad Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Thu, 6 Aug 2026 09:08:17 +0200 Subject: [PATCH 79/84] - Fix ##1485: the list_forwards command omits port numbers. The list_forwards and list_stubs commands for unbound-control print port and tls auth name. --- daemon/remote.c | 25 ++++++++++++++++++++++--- doc/Changelog | 5 +++++ 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/daemon/remote.c b/daemon/remote.c index 8e6ba19b1..6931a78f7 100644 --- a/daemon/remote.c +++ b/daemon/remote.c @@ -2681,7 +2681,7 @@ static int ssl_print_name_dp(RES* ssl, const char* str, uint8_t* nm, uint16_t dclass, struct delegpt* dp) { - char buf[LDNS_MAX_DOMAINLEN]; + char buf[LDNS_MAX_DOMAINLEN], portstr[128], tls_auth_name[256]; struct delegpt_ns* ns; struct delegpt_addr* a; int f = 0; @@ -2696,13 +2696,32 @@ ssl_print_name_dp(RES* ssl, const char* str, uint8_t* nm, uint16_t dclass, } for(ns = dp->nslist; ns; ns = ns->next) { dname_str(ns->name, buf); - if(!ssl_printf(ssl, "%s%s", (f?" ":""), buf)) + if(ns->port != UNBOUND_DNS_PORT) + snprintf(portstr, sizeof(portstr), "@%d", ns->port); + else portstr[0]=0; + if(ns->tls_auth_name) + snprintf(tls_auth_name, sizeof(tls_auth_name), "#%s", + ns->tls_auth_name); + else tls_auth_name[0]=0; + if(!ssl_printf(ssl, "%s%s%s%s", (f?" ":""), buf, portstr, + tls_auth_name)) return 0; f = 1; } for(a = dp->target_list; a; a = a->next_target) { + int port = (unsigned)((a->addr.ss_family == AF_INET) ? + ntohs(((struct sockaddr_in*)&a->addr)->sin_port) : + ntohs(((struct sockaddr_in6*)&a->addr)->sin6_port)); addr_to_str(&a->addr, a->addrlen, buf, sizeof(buf)); - if(!ssl_printf(ssl, "%s%s", (f?" ":""), buf)) + if(port != UNBOUND_DNS_PORT) + snprintf(portstr, sizeof(portstr), "@%d", port); + else portstr[0]=0; + if(a->tls_auth_name) + snprintf(tls_auth_name, sizeof(tls_auth_name), "#%s", + a->tls_auth_name); + else tls_auth_name[0]=0; + if(!ssl_printf(ssl, "%s%s%s%s", (f?" ":""), buf, portstr, + tls_auth_name)) return 0; f = 1; } diff --git a/doc/Changelog b/doc/Changelog index f29912256..f433ced7a 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,8 @@ +6 August 2026: Wouter + - Fix ##1485: the list_forwards command omits port numbers. + The list_forwards and list_stubs commands for + unbound-control print port and tls auth name. + 4 August 2026: Wouter - Fix to set makedist.sh to not wget config.sub and config.guess from git repo. The fetch times out, and the From 36bd52afb9c5f1a43625005ddc6a5f60c3184507 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Thu, 6 Aug 2026 09:08:33 +0200 Subject: [PATCH 80/84] Fix typo in Changelog. --- doc/Changelog | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/Changelog b/doc/Changelog index f433ced7a..b5c243274 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,5 +1,5 @@ 6 August 2026: Wouter - - Fix ##1485: the list_forwards command omits port numbers. + - Fix #1485: the list_forwards command omits port numbers. The list_forwards and list_stubs commands for unbound-control print port and tls auth name. From 8b33c5d7ffb82d442f3d19021588449cd6b02a17 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Thu, 6 Aug 2026 09:46:18 +0200 Subject: [PATCH 81/84] - Fix #1487: regression in 1.26.0, ipsecmod is now always partly enabled. --- doc/Changelog | 2 ++ ipsecmod/ipsecmod.c | 10 +++++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/doc/Changelog b/doc/Changelog index b5c243274..383992393 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -2,6 +2,8 @@ - Fix #1485: the list_forwards command omits port numbers. The list_forwards and list_stubs commands for unbound-control print port and tls auth name. + - Fix #1487: regression in 1.26.0, ipsecmod is now always + partly enabled. 4 August 2026: Wouter - Fix to set makedist.sh to not wget config.sub and diff --git a/ipsecmod/ipsecmod.c b/ipsecmod/ipsecmod.c index d1c0d442f..71b42f180 100644 --- a/ipsecmod/ipsecmod.c +++ b/ipsecmod/ipsecmod.c @@ -59,6 +59,11 @@ static int ipsecmod_apply_cfg(struct ipsecmod_env* ipsecmod_env, struct config_file* cfg) { + if(cfg->ipsecmod_whitelist && + !ipsecmod_whitelist_apply_cfg(ipsecmod_env, cfg)) + return 0; + if(!cfg->ipsecmod_enabled) + return 1; if(!cfg->ipsecmod_hook || (cfg->ipsecmod_hook && !cfg->ipsecmod_hook[0])) { log_err("ipsecmod: missing ipsecmod-hook."); return 0; @@ -68,9 +73,6 @@ ipsecmod_apply_cfg(struct ipsecmod_env* ipsecmod_env, struct config_file* cfg) cfg->ipsecmod_hook, strerror(errno)); return 0; } - if(cfg->ipsecmod_whitelist && - !ipsecmod_whitelist_apply_cfg(ipsecmod_env, cfg)) - return 0; return 1; } @@ -626,6 +628,8 @@ ipsecmod_inform_super(struct module_qstate* qstate, int id, verbose(VERB_ALGO, "super has no ipsecmod state"); return; } + if(!siq->enabled) + return; if(qstate->return_msg) { struct ub_packed_rrset_key* rrset_key = reply_find_answer_rrset( From 307fc6f062f8e02ca1edb0ccfce4cdf427a99691 Mon Sep 17 00:00:00 2001 From: akhanin-dnsf <106987117+akhanin-dnsf@users.noreply.github.com> Date: Thu, 6 Aug 2026 10:04:05 -0500 Subject: [PATCH 82/84] - Fix bounds check in packed_rr_to_string, it checked the (#1488) assembled rr length against the output string length dest_len, instead of against the size of the rr buffer it writes into. Callers in cachedump.c and remote.c pass a dest_len larger than that buffer. - Unit test for packed_rr_to_string. --- doc/Changelog | 8 ++++ testcode/unitmain.c | 84 ++++++++++++++++++++++++++++++++++++++++ util/data/packed_rrset.c | 4 +- 3 files changed, 95 insertions(+), 1 deletion(-) diff --git a/doc/Changelog b/doc/Changelog index 383992393..d4b145673 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,11 @@ +6 August 2026: Alex Khanin + - Fix bounds check in packed_rr_to_string, it checked the + assembled rr length against the output string length + dest_len, instead of against the size of the rr buffer it + writes into. Callers in cachedump.c and remote.c pass a + dest_len larger than that buffer. + - Unit test for packed_rr_to_string. + 6 August 2026: Wouter - Fix #1485: the list_forwards command omits port numbers. The list_forwards and list_stubs commands for diff --git a/testcode/unitmain.c b/testcode/unitmain.c index 985a9d552..a3f2f12a8 100644 --- a/testcode/unitmain.c +++ b/testcode/unitmain.c @@ -1337,6 +1337,89 @@ static void mesh_test(void) free(c1); } +#include "util/data/packed_rrset.h" +#include "sldns/sbuffer.h" +/** packed_rrset unit tests */ +static void packed_rrset_test(void) +{ + /* packed_rr_to_string assembles the dname, type, class, ttl and + * rdata of one rr into a buffer of 65535 bytes. Check that it + * refuses an rr that does not fit in there, also when the caller + * passes a dest_len that is larger than that, like the callers in + * daemon/cachedump.c and daemon/remote.c do. Without the check it + * writes past the end of the assembly buffer. */ + uint8_t smalldname[] = "\003www\007example\003com"; + uint8_t smallrdata[] = {0, 4, 1, 2, 3, 4}; + uint8_t maxdname[LDNS_MAX_DOMAINLEN]; + struct ub_packed_rrset_key rrk; + struct packed_rrset_data d; + uint8_t* rr_data[1]; + size_t rr_len[1]; + time_t rr_ttl[1]; + size_t dest_len = 65535*4+2048; /* the size daemon/cachedump.c uses */ + char* dest = (char*)malloc(dest_len); + int i; + + unit_show_func("util/data/packed_rrset.c", "packed_rr_to_string"); + if(!dest) fatal_exit("out of memory"); + memset(&rrk, 0, sizeof(rrk)); + memset(&d, 0, sizeof(d)); + rrk.entry.data = &d; + rrk.rk.rrset_class = htons(LDNS_RR_CLASS_IN); + d.count = 1; + d.rr_len = rr_len; + d.rr_ttl = rr_ttl; + d.rr_data = rr_data; + rr_ttl[0] = 3600; + + /* an ordinary rr is printed, also with the large dest_len */ + rrk.rk.dname = smalldname; + rrk.rk.dname_len = sizeof(smalldname); + rrk.rk.type = htons(LDNS_RR_TYPE_A); + rr_data[0] = smallrdata; + rr_len[0] = sizeof(smallrdata); + unit_assert(packed_rr_to_string(&rrk, 0, 0, dest, dest_len) == 1); + unit_assert(strstr(dest, "1.2.3.4") != NULL); + + /* a dname of the maximum length, 127 labels of one character */ + for(i=0; i<127; i++) { + maxdname[i*2] = 1; + maxdname[i*2+1] = (uint8_t)'a'; + } + maxdname[254] = 0; + rrk.rk.dname = maxdname; + rrk.rk.dname_len = sizeof(maxdname); + rrk.rk.type = htons(LDNS_RR_TYPE_TXT); + + /* 255+2+2+4+65272 is exactly 65535, that still fits */ + rr_len[0] = 65535 - 255 - 8; + rr_data[0] = (uint8_t*)calloc(1, rr_len[0]); + if(!rr_data[0]) fatal_exit("out of memory"); + sldns_write_uint16(rr_data[0], (uint16_t)(rr_len[0]-2)); + unit_assert(packed_rr_to_string(&rrk, 0, 0, dest, dest_len) == 1); + free(rr_data[0]); + + /* one more byte of rdata does not fit and must be refused */ + rr_len[0] = 65535 - 255 - 8 + 1; + rr_data[0] = (uint8_t*)calloc(1, rr_len[0]); + if(!rr_data[0]) fatal_exit("out of memory"); + sldns_write_uint16(rr_data[0], (uint16_t)(rr_len[0]-2)); + unit_assert(packed_rr_to_string(&rrk, 0, 0, dest, dest_len) == 0); + unit_assert(dest[0] == 0); + free(rr_data[0]); + + /* the largest rdata an rr can hold, well over the buffer */ + rr_len[0] = 2 + 65535; + rr_data[0] = (uint8_t*)calloc(1, rr_len[0]); + if(!rr_data[0]) fatal_exit("out of memory"); + sldns_write_uint16(rr_data[0], 65535); + unit_assert(packed_rr_to_string(&rrk, 0, 0, dest, dest_len) == 0); + unit_assert(dest[0] == 0); + free(rr_data[0]); + + free(dest); +} + void unit_show_func(const char* file, const char* func) { printf("test %s:%s\n", file, func); @@ -1409,6 +1492,7 @@ main(int argc, char* argv[]) zonemd_test(); tcpreuse_test(); msgparse_test(); + packed_rrset_test(); edns_ede_answer_encode_test(); localzone_test(); mesh_test(); diff --git a/util/data/packed_rrset.c b/util/data/packed_rrset.c index 3b0330c55..598d606b4 100644 --- a/util/data/packed_rrset.c +++ b/util/data/packed_rrset.c @@ -280,7 +280,9 @@ int packed_rr_to_string(struct ub_packed_rrset_key* rrset, size_t i, size_t rlen = rrset->rk.dname_len + 2 + 2 + 4 + d->rr_len[i]; time_t adjust = 0; log_assert(dest_len > 0 && dest); - if(rlen > dest_len) { + /* rlen is the length written into rr, dest_len bounds the output + * string; check both, callers can pass a dest_len over sizeof(rr). */ + if(rlen > dest_len || rlen > sizeof(rr)) { dest[0] = 0; return 0; } From 709f6226581259bc376e2add65631cbb32a05284 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Thu, 6 Aug 2026 17:15:55 +0200 Subject: [PATCH 83/84] Note issue number in Changlog entry. --- doc/Changelog | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/Changelog b/doc/Changelog index d4b145673..bf37b8715 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,6 +1,6 @@ 6 August 2026: Alex Khanin - - Fix bounds check in packed_rr_to_string, it checked the - assembled rr length against the output string length + - Fix #1488: bounds check in packed_rr_to_string, it checked + the assembled rr length against the output string length dest_len, instead of against the size of the rr buffer it writes into. Callers in cachedump.c and remote.c pass a dest_len larger than that buffer. From 93a56205cfa6b9a6d34db7245aa71e5ca67d1fd7 Mon Sep 17 00:00:00 2001 From: "W.C.A. Wijngaards" Date: Fri, 7 Aug 2026 08:57:32 +0200 Subject: [PATCH 84/84] - Fix #1489 from jplesnik: Replace removed Python 2 C API macros for SWIG 4.5.0 compatibility. --- doc/Changelog | 4 ++++ pythonmod/interface.i | 28 ++++++++++++++-------------- pythonmod/pythonmod.c | 8 ++++---- 3 files changed, 22 insertions(+), 18 deletions(-) diff --git a/doc/Changelog b/doc/Changelog index bf37b8715..25a43cfd6 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,7 @@ +7 August 2026: Wouter + - Fix #1489 from jplesnik: Replace removed Python 2 C API + macros for SWIG 4.5.0 compatibility. + 6 August 2026: Alex Khanin - Fix #1488: bounds check in packed_rr_to_string, it checked the assembled rr length against the output string length diff --git a/pythonmod/interface.i b/pythonmod/interface.i index 735f2ed50..7e2a188aa 100644 --- a/pythonmod/interface.i +++ b/pythonmod/interface.i @@ -79,7 +79,7 @@ i+(int)((unsigned int)name[i]) < len) { memmove(buf, name + i + 1, (unsigned int)name[i]); buf[(unsigned int)name[i]] = 0; - PyList_SetItem(list, cnt, PyString_FromString(buf)); + PyList_SetItem(list, cnt, PyUnicode_FromString(buf)); } i += ((unsigned int)name[i]) + 1; cnt++; @@ -96,7 +96,7 @@ list = PyList_New(len); for (i=0; i < len; i++) { - PyList_SET_ITEM(list, i, PyString_FromString(array[i])); + PyList_SET_ITEM(list, i, PyUnicode_FromString(array[i])); } return list; } @@ -207,7 +207,7 @@ struct query_info { char buf[LDNS_MAX_DOMAINLEN]; buf[0] = '\0'; dname_str((uint8_t*)PyBytes_AsString(dname), buf); - return PyString_FromString(buf); + return PyUnicode_FromString(buf); } %} @@ -345,7 +345,7 @@ struct packed_rrset_data { PyObject* _get_data_rr_len(struct packed_rrset_data* d, int idx) { if ((d != NULL) && (idx >= 0) && ((size_t)idx < (d->count+d->rrsig_count))) - return PyInt_FromLong(d->rr_len[idx]); + return PyLong_FromLong(d->rr_len[idx]); return Py_None; } void _set_data_rr_ttl(struct packed_rrset_data* d, int idx, uint32_t ttl) @@ -357,7 +357,7 @@ struct packed_rrset_data { PyObject* _get_data_rr_ttl(struct packed_rrset_data* d, int idx) { if ((d != NULL) && (idx >= 0) && ((size_t)idx < (d->count+d->rrsig_count))) - return PyInt_FromLong(d->rr_ttl[idx]); + return PyLong_FromLong(d->rr_ttl[idx]); return Py_None; } PyObject* _get_data_rr_data(struct packed_rrset_data* d, int idx) { @@ -555,12 +555,12 @@ struct sockaddr_storage {}; if (ss->ss_family == AF_INET) { const struct sockaddr_in *sa4 = (struct sockaddr_in *)ss; - return PyInt_FromLong(ntohs(sa4->sin_port)); + return PyLong_FromLong(ntohs(sa4->sin_port)); } if (ss->ss_family == AF_INET6) { const struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *)ss; - return PyInt_FromLong(ntohs(sa6->sin6_port)); + return PyLong_FromLong(ntohs(sa6->sin6_port)); } return Py_None; @@ -574,7 +574,7 @@ struct sockaddr_storage {}; } sa6 = (struct sockaddr_in6 *)ss; - return PyInt_FromLong(ntohl(sa6->sin6_flowinfo)); + return PyLong_FromLong(ntohl(sa6->sin6_flowinfo)); } PyObject *_sockaddr_storage_scope_id(const struct sockaddr_storage *ss) { @@ -585,7 +585,7 @@ struct sockaddr_storage {}; } sa6 = (struct sockaddr_in6 *)ss; - return PyInt_FromLong(ntohl(sa6->sin6_scope_id)); + return PyLong_FromLong(ntohl(sa6->sin6_scope_id)); } %} @@ -661,7 +661,7 @@ struct edns_option { %inline %{ PyObject* _edns_option_opt_code_get(struct edns_option* option) { uint16_t opt_code = option->opt_code; - return PyInt_FromLong(opt_code); + return PyLong_FromLong(opt_code); } PyObject* _edns_option_opt_data_get(struct edns_option* option) { @@ -1627,7 +1627,7 @@ int edns_opt_list_append(struct edns_option** list, uint16_t code, size_t len, } result = PyObject_Call(func, py_args, py_kwargs); if (result) { - res = PyInt_AsLong(result); + res = PyLong_AsLong(result); } out: Py_XDECREF(py_edns); @@ -1711,7 +1711,7 @@ out: } result = PyObject_Call(func, py_args, py_kwargs); if (result) { - res = PyInt_AsLong(result); + res = PyLong_AsLong(result); } out: Py_XDECREF(py_qinfo); @@ -1765,7 +1765,7 @@ out: } result = PyObject_Call(func, py_args, py_kwargs); if (result) { - res = PyInt_AsLong(result); + res = PyLong_AsLong(result); } out: Py_XDECREF(py_qstate); @@ -1814,7 +1814,7 @@ out: } result = PyObject_Call(func, py_args, py_kwargs); if (result) { - res = PyInt_AsLong(result); + res = PyLong_AsLong(result); } out: Py_XDECREF(py_qstate); diff --git a/pythonmod/pythonmod.c b/pythonmod/pythonmod.c index 045dd1bbd..1aef55612 100644 --- a/pythonmod/pythonmod.c +++ b/pythonmod/pythonmod.c @@ -246,14 +246,14 @@ log_py_err(void) } /* And it should be a string all ready to go - duplicate it. */ - if (!PyString_Check(obResult) && !PyUnicode_Check(obResult)) { + if (!PyBytes_Check(obResult) && !PyUnicode_Check(obResult)) { log_err("pythonmod: cannot print exception, " "StringIO.getvalue() result did not String_Check" " or Unicode_Check"); goto cleanup; } - if(PyString_Check(obResult)) { - result = PyString_AsString(obResult); + if(PyBytes_Check(obResult)) { + result = PyBytes_AsString(obResult); } else { ascstr = PyUnicode_AsASCIIString(obResult); result = PyBytes_AsString(ascstr); @@ -450,7 +450,7 @@ int pythonmod_init(struct module_env* env, int id) pe->data = PyDict_New(); /* add the script filename to the global "mod_env" for trivial access */ - fname = PyString_FromString(pe->fname); + fname = PyUnicode_FromString(pe->fname); if(PyDict_SetItemString(pe->data, "script", fname) < 0) { log_err("pythonmod: could not add item to dictionary"); Py_XDECREF(fname);