Re: git: 2a3a6a177114 - main - Mitigate YXDOMAIN and nodata non-referral answer poisoning.
Date: Wed, 26 Nov 2025 22:47:52 UTC
On Wed, Nov 26, 2025 at 03:58:13PM +0000, Gordon Tetlow wrote:
> The branch main has been updated by gordon:
>
> URL: https://cgit.FreeBSD.org/src/commit/?id=2a3a6a1771148a709c2d9694c1d66c41ce8dee79
>
> commit 2a3a6a1771148a709c2d9694c1d66c41ce8dee79
> Author: Gordon Tetlow <gordon@FreeBSD.org>
> AuthorDate: 2025-11-21 21:24:58 +0000
> Commit: Gordon Tetlow <gordon@FreeBSD.org>
> CommitDate: 2025-11-26 15:57:33 +0000
>
> Mitigate YXDOMAIN and nodata non-referral answer poisoning.
>
> Add a fix to apply scrubbing of unsolicited NS RRSets (and their
> respective address records) for YXDOMAIN and nodata non-referral
> answers. This prevents a malicious actor from exploiting a possible
> cache poison attack.
>
> Obtained from: NLnet Labs
> Security: CVE-2025-11411
> ---
> contrib/unbound/iterator/iter_scrub.c | 39 +++++++++++++++++++++++++++++++----
> 1 file changed, 35 insertions(+), 4 deletions(-)
>
> diff --git a/contrib/unbound/iterator/iter_scrub.c b/contrib/unbound/iterator/iter_scrub.c
> index 553d3655f0e3..8507a3fb65ac 100644
> --- a/contrib/unbound/iterator/iter_scrub.c
> +++ b/contrib/unbound/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))
>
Hey Gordon,
Do you know if this fix was the incomplete one from Unbound 1.24.1? Or
does this include the additional fix that landed in 1.24.2 earlier
today?
Thanks,
--
Shawn Webb
Cofounder / Security Engineer
HardenedBSD
Signal Username: shawn_webb.74
Tor-ified Signal: +1 303-901-1600 / shawn_webb_opsec.50
https://git.hardenedbsd.org/hardenedbsd/pubkeys/-/raw/master/Shawn_Webb/03A4CBEBB82EA5A67D9F3853FF2E67A277F8E1FA.pub.asc