From 19154c6e584d9d6ce0a0ea72dfdb23a058af84d2 Mon Sep 17 00:00:00 2001 From: Yorgos Thessalonikefs Date: Wed, 26 Nov 2025 10:58:06 +0100 Subject: [PATCH 1/2] - Set version to 1.24.2. --- configure | 23 ++++++++++++----------- configure.ac | 3 ++- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/configure b/configure index 7f74e4b4a..66a97a7b6 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.24.1. +# Generated by GNU Autoconf 2.71 for unbound 1.24.2. # # Report bugs to . # @@ -622,8 +622,8 @@ MAKEFLAGS= # Identity of this package. PACKAGE_NAME='unbound' PACKAGE_TARNAME='unbound' -PACKAGE_VERSION='1.24.1' -PACKAGE_STRING='unbound 1.24.1' +PACKAGE_VERSION='1.24.2' +PACKAGE_STRING='unbound 1.24.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.24.1 to adapt to many kinds of systems. +\`configure' configures unbound 1.24.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.24.1:";; + short | recursive ) echo "Configuration of unbound 1.24.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.24.1 +unbound configure 1.24.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.24.1, which was +It was created by unbound $as_me 1.24.2, which was generated by GNU Autoconf 2.71. Invocation command line was $ $0$ac_configure_args_raw @@ -3253,7 +3253,7 @@ UNBOUND_VERSION_MAJOR=1 UNBOUND_VERSION_MINOR=24 -UNBOUND_VERSION_MICRO=1 +UNBOUND_VERSION_MICRO=2 LIBUNBOUND_CURRENT=9 @@ -3359,6 +3359,7 @@ LIBUNBOUND_AGE=1 # 1.23.1 had 9:32:1 # 1.24.0 had 9:33:1 # 1.24.1 had 9:34:1 +# 1.24.2 had 9:35:1 # Current -- the number of the binary API that we're implementing # Revision -- which iteration of the implementation of the binary @@ -25292,7 +25293,7 @@ printf "%s\n" "#define MAXSYSLOGMSGLEN 10240" >>confdefs.h -version=1.24.1 +version=1.24.2 { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for build time" >&5 printf %s "checking for build time... " >&6; } @@ -25822,7 +25823,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.24.1, which was +This file was extended by unbound $as_me 1.24.2, which was generated by GNU Autoconf 2.71. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -25890,7 +25891,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.24.1 +unbound config.status 1.24.2 configured by $0, generated by GNU Autoconf 2.71, with options \\"\$ac_cs_config\\" diff --git a/configure.ac b/configure.ac index 4ff4c0ef5..6a43fd5de 100644 --- a/configure.ac +++ b/configure.ac @@ -12,7 +12,7 @@ sinclude(dnscrypt/dnscrypt.m4) # must be numbers. ac_defun because of later processing m4_define([VERSION_MAJOR],[1]) m4_define([VERSION_MINOR],[24]) -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]) @@ -121,6 +121,7 @@ LIBUNBOUND_AGE=1 # 1.23.1 had 9:32:1 # 1.24.0 had 9:33:1 # 1.24.1 had 9:34:1 +# 1.24.2 had 9:35:1 # Current -- the number of the binary API that we're implementing # Revision -- which iteration of the implementation of the binary From f6269baa605d31859f28770e01a24e3677e5f82c Mon Sep 17 00:00:00 2001 From: Yorgos Thessalonikefs Date: Wed, 26 Nov 2025 11:09:40 +0100 Subject: [PATCH 2/2] - Additional fix for CVE-2025-11411 (possible domain hijacking attack), to include YXDOMAIN and non-referral nodata answers in the mitigation as well, reported by TaoFei Guo from Peking University, Yang Luo and JianJun Chen from Tsinghua University. --- iterator/iter_scrub.c | 39 +++++++++-- testdata/iter_scrub_promiscuous.rpl | 84 ++++++++++++++++++++++++ testdata/ratelimit.tdir/ratelimit.testns | 30 +++++++-- 3 files changed, 143 insertions(+), 10 deletions(-) diff --git a/iterator/iter_scrub.c b/iterator/iter_scrub.c index 553d3655f..8507a3fb6 100644 --- a/iterator/iter_scrub.c +++ b/iterator/iter_scrub.c @@ -418,12 +418,13 @@ shorten_rrset(sldns_buffer* pkt, struct rrset_parse* rrset, int count) * @param qinfo: original query. * @param region: where to allocate synthesized CNAMEs. * @param env: module env with config options. + * @param zonename: name of server zone. * @return 0 on error. */ static int scrub_normalize(sldns_buffer* pkt, struct msg_parse* msg, struct query_info* qinfo, struct regional* region, - struct module_env* env) + struct module_env* env, uint8_t* zonename) { uint8_t* sname = qinfo->qname; size_t snamelen = qinfo->qname_len; @@ -431,7 +432,8 @@ scrub_normalize(sldns_buffer* pkt, struct msg_parse* msg, int cname_length = 0; /* number of CNAMEs, or DNAMEs */ if(FLAGS_GET_RCODE(msg->flags) != LDNS_RCODE_NOERROR && - FLAGS_GET_RCODE(msg->flags) != LDNS_RCODE_NXDOMAIN) + FLAGS_GET_RCODE(msg->flags) != LDNS_RCODE_NXDOMAIN && + FLAGS_GET_RCODE(msg->flags) != LDNS_RCODE_YXDOMAIN) return 1; /* For the ANSWER section, remove all "irrelevant" records and add @@ -470,6 +472,11 @@ scrub_normalize(sldns_buffer* pkt, struct msg_parse* msg, &aliaslen, pkt)) { verbose(VERB_ALGO, "synthesized CNAME " "too long"); + if(FLAGS_GET_RCODE(msg->flags) == LDNS_RCODE_YXDOMAIN) { + prev = rrset; + rrset = rrset->rrset_all_next; + continue; + } return 0; } cname_length++; @@ -650,6 +657,29 @@ scrub_normalize(sldns_buffer* pkt, struct msg_parse* msg, "RRset:", pkt, msg, prev, &rrset); continue; } + /* Also delete promiscuous NS for other RCODEs */ + if(FLAGS_GET_RCODE(msg->flags) != LDNS_RCODE_NOERROR + && env->cfg->iter_scrub_promiscuous) { + remove_rrset("normalize: removing promiscuous " + "RRset:", pkt, msg, prev, &rrset); + continue; + } + /* Also delete promiscuous NS for NOERROR with nodata + * for authoritative answers, not for delegations. + * NOERROR with an_rrsets!=0 already handled. + * Also NOERROR and soa_in_auth already handled. + * NOERROR with an_rrsets==0, and not a referral. + * referral is (NS not the zonename, noSOA). + */ + if(FLAGS_GET_RCODE(msg->flags) == LDNS_RCODE_NOERROR + && msg->an_rrsets == 0 + && !(dname_pkt_compare(pkt, rrset->dname, + zonename) != 0 && !soa_in_auth(msg)) + && env->cfg->iter_scrub_promiscuous) { + remove_rrset("normalize: removing promiscuous " + "RRset:", pkt, msg, prev, &rrset); + continue; + } if(nsset == NULL) { nsset = rrset; } else { @@ -1060,7 +1090,8 @@ scrub_message(sldns_buffer* pkt, struct msg_parse* msg, /* this is not required for basic operation but is a forgery * resistance (security) feature */ if((FLAGS_GET_RCODE(msg->flags) == LDNS_RCODE_NOERROR || - FLAGS_GET_RCODE(msg->flags) == LDNS_RCODE_NXDOMAIN) && + FLAGS_GET_RCODE(msg->flags) == LDNS_RCODE_NXDOMAIN || + FLAGS_GET_RCODE(msg->flags) == LDNS_RCODE_YXDOMAIN) && msg->qdcount == 0) return 0; @@ -1074,7 +1105,7 @@ scrub_message(sldns_buffer* pkt, struct msg_parse* msg, } /* normalize the response, this cleans up the additional. */ - if(!scrub_normalize(pkt, msg, qinfo, region, env)) + if(!scrub_normalize(pkt, msg, qinfo, region, env, zonename)) return 0; /* delete all out-of-zone information */ if(!scrub_sanitize(pkt, msg, qinfo, zonename, env, ie, qstate)) diff --git a/testdata/iter_scrub_promiscuous.rpl b/testdata/iter_scrub_promiscuous.rpl index 61fca0d28..febbee81c 100644 --- a/testdata/iter_scrub_promiscuous.rpl +++ b/testdata/iter_scrub_promiscuous.rpl @@ -16,6 +16,7 @@ SCENARIO_BEGIN Test iterator with scrub of promiscuous records ; The spoofed contents are ns.attacker.mesa and its IPs 5.6.7.8 and 5.6.7.9. ; The pollute1.mesa NS, ns.pollute2.mesa A, and test3.atkr.pollute3.mesa NS ; with ns.pollute3.mesa A records are tested for cache placement. +; pollute4.mesa uses YXDOMAIN. ; ns.root RANGE_BEGIN 0 400 @@ -84,6 +85,18 @@ SECTION ADDITIONAL ns.pollute3.mesa. IN A 1.2.4.3 ENTRY_END +ENTRY_BEGIN +MATCH opcode subdomain +ADJUST copy_id copy_query +REPLY QR NOERROR +SECTION QUESTION +pollute4.mesa. IN NS +SECTION AUTHORITY +pollute4.mesa. IN NS ns.pollute4.mesa. +SECTION ADDITIONAL +ns.pollute4.mesa. IN A 1.2.4.4 +ENTRY_END + ENTRY_BEGIN MATCH opcode subdomain ADJUST copy_id copy_query @@ -188,6 +201,35 @@ check.pollute3.mesa. IN A 1.8.9.3 ENTRY_END RANGE_END +; ns.pollute4.mesa +RANGE_BEGIN 0 400 + ADDRESS 1.2.4.4 + +; This is the spoofed answer that is returned. +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA YXDOMAIN +SECTION QUESTION +test4.atkr.pollute4.mesa. IN A +SECTION ANSWER +test4.atkr.pollute4.mesa. 86400 IN A 1.2.3.4 +SECTION AUTHORITY +pollute4.mesa. 86400 IN NS ns.attacker.mesa. +ENTRY_END + +; correct answer for the check query. +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR AA NOERROR +SECTION QUESTION +check.pollute4.mesa. IN A +SECTION ANSWER +check.pollute4.mesa. IN A 1.8.9.4 +ENTRY_END +RANGE_END + ; ns.attacker.mesa RANGE_BEGIN 0 400 ADDRESS 5.6.7.8 @@ -370,4 +412,46 @@ check.pollute3.mesa. IN A 1.8.9.3 ;check.pollute3.mesa. IN A 5.6.7.9 ENTRY_END +; Test query 4 +STEP 120 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +test4.atkr.pollute4.mesa. IN A +ENTRY_END + +STEP 130 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR RD RA YXDOMAIN +SECTION QUESTION +test4.atkr.pollute4.mesa. IN A +SECTION ANSWER +test4.atkr.pollute4.mesa. 86400 IN A 1.2.3.4 +SECTION AUTHORITY +; removed record +;pollute4.mesa. 0 IN NS ns.attacker.mesa. +ENTRY_END + +; Check the cache contents, for query 4. +STEP 140 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +check.pollute4.mesa. IN A +ENTRY_END + +STEP 150 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR RD RA NOERROR +SECTION QUESTION +check.pollute4.mesa. IN A +SECTION ANSWER +; good answer +check.pollute4.mesa. IN A 1.8.9.4 +; bad answer +;check.pollute4.mesa. IN A 5.6.7.9 +ENTRY_END + SCENARIO_END diff --git a/testdata/ratelimit.tdir/ratelimit.testns b/testdata/ratelimit.tdir/ratelimit.testns index 563c1db6a..5c22c292d 100644 --- a/testdata/ratelimit.tdir/ratelimit.testns +++ b/testdata/ratelimit.tdir/ratelimit.testns @@ -3,13 +3,31 @@ $ORIGIN example.com. $TTL 3600 ENTRY_BEGIN -MATCH opcode qtype +MATCH opcode qname qtype REPLY QR AA NOERROR -ADJUST copy_id copy_query +ADJUST copy_id SECTION QUESTION -wild IN A +www1 IN A SECTION ANSWER -wild IN A 10.20.30.40 -SECTION AUTHORITY -example.com. IN NS ns.example.com. +www1 IN A 1.1.1.1 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qname qtype +REPLY QR AA NOERROR +ADJUST copy_id +SECTION QUESTION +www2 IN A +SECTION ANSWER +www2 IN A 2.2.2.2 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qname qtype +REPLY QR AA NOERROR +ADJUST copy_id +SECTION QUESTION +www3 IN A +SECTION ANSWER +www3 IN A 3.3.3.3 ENTRY_END