From nobody Tue Jun 9 19:19:52 2026 X-Original-To: dev-commits-src-branches@mlmmj.nyi.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2610:1c1:1:606c::19:1]) by mlmmj.nyi.freebsd.org (Postfix) with ESMTP id 4gZdyd24Nhz6gVMG for ; Tue, 09 Jun 2026 19:19:53 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from mxrelay.nyi.freebsd.org (mxrelay.nyi.freebsd.org [IPv6:2610:1c1:1:606c::19:3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256 client-signature RSA-PSS (4096 bits) client-digest SHA256) (Client CN "mxrelay.nyi.freebsd.org", Issuer "R13" (not verified)) by mx1.freebsd.org (Postfix) with ESMTPS id 4gZdyc37SLz3QbW for ; Tue, 09 Jun 2026 19:19:52 +0000 (UTC) (envelope-from git@FreeBSD.org) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1781032792; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=XfZrvU6sqd8qVMjeBG2KCnG8k6orzaLatLOfVX2/7BI=; b=V69Fm8NU1HpWjNrA2AoqY9HMdBMv1sWx73rz78VgSBmoidfzW66Vm+akWi64IRPfAdKA1k oFlN8jtsR9RH6suUu/EaCEeSojspUm/FrXDHLPhBY9R9ztBUhpC7Kr0wgAZPx6fnBlIho9 KJyEOcpiON+CADucIxy8iwvIWC95kybBVb+zfOBPrkBqQ6xBXFDj2v+tpV4sarEfmx4C/9 VT2diPANAVlJAmGOoayoPsWbczeQYh4Hvc5/A4D0Hrl5vyfx0we26Hif8b4ZJnSkHeMpOp IPacWCEo9/gpV4nBwTYpkW3Nvxwp9ZTSAzn+rQeTsfD0V5BJqVyfrfB8dwgW4g== ARC-Seal: i=1; s=dkim; d=freebsd.org; t=1781032792; a=rsa-sha256; cv=none; b=vJnKvt1LD51WyyUyeJGAQ91/1gwCFrv7SIM+d+K7tTiKaWtwVbUes/Ptnzt9xgnwANSh5i lc+uO+C3jWsKlKrw3dF2U2bbDd3l4dtyU01r5DRj7dQblhBOgi0+TX6thxWcjtfFH/H7u7 ohd5zG9+LwkhrcxBSAlr7qSnhYf563BaL9qBiyoIVMmTGQfn6S/1K2Ed7ZtrcGizgOcOHv t5JcOQvJHmeZwsej7vpcqgBx+VDaNxIYFV91Yc26itGZVimgOLwn+DTtn3yllaRP7gHgsB J2kPC2FjHPywWOWOMPvlkz19Y1a81ZRnFTaBC4vH5LP1eXOHwjmM/6H7tDPVZQ== ARC-Authentication-Results: i=1; mx1.freebsd.org; none ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1781032792; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=XfZrvU6sqd8qVMjeBG2KCnG8k6orzaLatLOfVX2/7BI=; b=hbV02QWdrjdnq7YusR5rB9Z6do6MgVoYBuzpIhqlI37yOUGCsJH20X2ITphdTVW5RXNkdc GwpyM9gbO3F4YN3dkpt+VQyZEruDTKQ08eewvL93gZYRD9h0jqlSxBwWBY8xoGUKJ1b058 eqFqyg83PRuyQQfyY+Yt1C2IEOC5EQGP3mbH3Y9Y2CNG08DDyxS8VRnK5HKqbocd++OLpS EJi3z2Yu7sjeLsDMwRukmQNXwkuvuVN+06Oac1ZUKJ+tsKNPmeqVHPnbyoYfNRbbA+pzvc braOhTc2qJHjHIT1qK54eSUe3qLRPb4QKa8huzfEKcLTFs7LFL3QOdYn6HA1iQ== Received: from gitrepo.freebsd.org (gitrepo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:5]) by mxrelay.nyi.freebsd.org (Postfix) with ESMTP id 4gZdyc2gGhzp3t for ; Tue, 09 Jun 2026 19:19:52 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from git (uid 1279) (envelope-from git@FreeBSD.org) id 3d651 by gitrepo.freebsd.org (DragonFly Mail Agent v0.13+ on gitrepo.freebsd.org); Tue, 09 Jun 2026 19:19:52 +0000 To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-branches@FreeBSD.org Cc: Dag-Erling=?utf-8?Q? Sm=C3=B8rg?=rav From: Mark Johnston Subject: git: 6160bd311a1b - releng/15.0 - unbound: Apply upstream patches List-Id: Commits to the stable branches of the FreeBSD src repository List-Archive: https://lists.freebsd.org/archives/dev-commits-src-branches List-Help: List-Post: List-Subscribe: List-Unsubscribe: X-BeenThere: dev-commits-src-branches@freebsd.org Sender: owner-dev-commits-src-branches@FreeBSD.org List-Id: List-Post: List-Help: List-Subscribe: List-Unsubscribe: List-Owner: Precedence: list MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit X-Git-Committer: markj X-Git-Repository: src X-Git-Refname: refs/heads/releng/15.0 X-Git-Reftype: branch X-Git-Commit: 6160bd311a1be94e7c7ad8a0440401bd6f9f8075 Auto-Submitted: auto-generated Date: Tue, 09 Jun 2026 19:19:52 +0000 Message-Id: <6a286758.3d651.1ac68b44@gitrepo.freebsd.org> The branch releng/15.0 has been updated by markj: URL: https://cgit.FreeBSD.org/src/commit/?id=6160bd311a1be94e7c7ad8a0440401bd6f9f8075 commit 6160bd311a1be94e7c7ad8a0440401bd6f9f8075 Author: Dag-Erling Smørgrav AuthorDate: 2026-05-29 22:21:50 +0000 Commit: Mark Johnston CommitDate: 2026-06-08 15:39:32 +0000 unbound: Apply upstream patches - Use the same EDE removal logic when encoding errors as when encoding replies. - Fix CVE-2026-33278, Possible remote code execution during DNSSEC validation. Thanks to Qifan Zhang, Palo Alto Networks, for the report. - Fix CVE-2026-42944, Heap overflow and crash with multiple nsid, cookie, padding EDNS options. Thanks to Qifan Zhang, Palo Alto Networks, for the report. - Fix CVE-2026-42959, Crash during DNSSEC validation of malicious content. Thanks to Qifan Zhang, Palo Alto Networks, for the report. - Fix CVE-2026-32792, Packet of death with DNSCrypt. Thanks to Andrew Griffiths from 'calif.io' for the report. - Fix CVE-2026-40622, "Ghost domain name" variant. Thanks to Qifan Zhang, Palo Alto Networks, for the report. - Fix CVE-2026-41292, Parsing a long list of incoming EDNS options degrades performance. Thanks to GitHub user 'N0zoM1z0', also Qifan Zhang from Palo Alto Networks, for the report. - Fix CVE-2026-42534, Jostle logic bypass degrades resolution performance. Thanks to Qifan Zhang, Palo Alto Networks, for the report. - Fix CVE-2026-42923, Degradation of service with unbounded NSEC3 hash calculations. Thanks to Qifan Zhang, Palo Alto Networks, for the report. - Fix CVE-2026-42960, Possible cache poisoning attack while following delegation. Thanks to TaoFei Guo from Peking University, Yang Luo and JianJun Chen, Tsinghua University, for the report. - Fix CVE-2026-44390, Unbounded name compression in certain cases causes degradation of service. Thanks to Qifan Zhang, Palo Alto Networks, for the report. - Fix CVE-2026-44608, Use after free and crash in RPZ code. Thanks to Qifan Zhang, Palo Alto Networks, for the report. Approved by: so Security: FreeBSD-SA-26:33.unbound Security: CVE-2026-33278 Security: CVE-2026-42944 Security: CVE-2026-42959 Security: CVE-2026-32792 Security: CVE-2026-40622 Security: CVE-2026-41292 Security: CVE-2026-42534 Security: CVE-2026-42923 Security: CVE-2026-42960 Security: CVE-2026-44390 Security: CVE-2026-44608 --- contrib/unbound/dnscrypt/dnscrypt.c | 2 +- contrib/unbound/iterator/iter_scrub.c | 8 +++- contrib/unbound/services/cache/dns.c | 8 +++- contrib/unbound/services/cache/rrset.c | 10 +++++ contrib/unbound/services/mesh.c | 14 +++++-- contrib/unbound/services/mesh.h | 6 +++ contrib/unbound/services/rpz.c | 10 +++-- contrib/unbound/util/data/msgencode.c | 54 ++++++++++++++++-------- contrib/unbound/util/data/msgencode.h | 4 +- contrib/unbound/util/data/msgparse.c | 19 ++++++--- contrib/unbound/util/data/msgparse.h | 4 ++ contrib/unbound/validator/val_neg.c | 28 ++++++++++++- contrib/unbound/validator/val_nsec3.c | 76 +++++++++++++++++++++++++++++++--- contrib/unbound/validator/val_nsec3.h | 6 +++ contrib/unbound/validator/val_utils.c | 4 +- 15 files changed, 209 insertions(+), 44 deletions(-) diff --git a/contrib/unbound/dnscrypt/dnscrypt.c b/contrib/unbound/dnscrypt/dnscrypt.c index 4902447fda01..173484cdf0b1 100644 --- a/contrib/unbound/dnscrypt/dnscrypt.c +++ b/contrib/unbound/dnscrypt/dnscrypt.c @@ -361,7 +361,7 @@ dnscrypt_server_uncurve(struct dnsc_env* env, len -= DNSCRYPT_QUERY_HEADER_SIZE; - while (*sldns_buffer_at(buffer, --len) == 0) + while (len>0 && *sldns_buffer_at(buffer, --len) == 0) ; if (*sldns_buffer_at(buffer, len) != 0x80) { diff --git a/contrib/unbound/iterator/iter_scrub.c b/contrib/unbound/iterator/iter_scrub.c index 8507a3fb65ac..852705db3ee9 100644 --- a/contrib/unbound/iterator/iter_scrub.c +++ b/contrib/unbound/iterator/iter_scrub.c @@ -725,7 +725,13 @@ scrub_normalize(sldns_buffer* pkt, struct msg_parse* msg, rrset->rrset_all_next = NULL; return 1; } - mark_additional_rrset(pkt, msg, rrset); + /* Only mark glue as allowed for type NS in the authority + * section. Other RR types do not get glue for them, it + * is allowed from the answer section, but not authority + * so that a message can not have address records cached + * as a side effect to the query. */ + if(rrset->type==LDNS_RR_TYPE_NS) + mark_additional_rrset(pkt, msg, rrset); prev = rrset; rrset = rrset->rrset_all_next; } diff --git a/contrib/unbound/services/cache/dns.c b/contrib/unbound/services/cache/dns.c index 351b3568c80b..8dae2ffcca90 100644 --- a/contrib/unbound/services/cache/dns.c +++ b/contrib/unbound/services/cache/dns.c @@ -675,10 +675,16 @@ struct dns_msg* dns_msg_deepcopy_region(struct dns_msg* origin, struct regional* region) { size_t i; + struct ub_packed_rrset_key** saved_rrsets; struct dns_msg* res = NULL; + size_t rep_alloc_size = sizeof(struct reply_info) + - sizeof(struct rrset_ref); /* this is the size of res->rep + allocated in gen_dns_msg() */ res = gen_dns_msg(region, &origin->qinfo, origin->rep->rrset_count); if(!res) return NULL; - *res->rep = *origin->rep; + saved_rrsets = res->rep->rrsets; /* save rrsets alloc by gen_dns_msg */ + memcpy(res->rep, origin->rep, rep_alloc_size); + res->rep->rrsets = saved_rrsets; if(origin->rep->reason_bogus_str) { res->rep->reason_bogus_str = regional_strdup(region, origin->rep->reason_bogus_str); diff --git a/contrib/unbound/services/cache/rrset.c b/contrib/unbound/services/cache/rrset.c index 6d5c24f8053e..81f4e2820edd 100644 --- a/contrib/unbound/services/cache/rrset.c +++ b/contrib/unbound/services/cache/rrset.c @@ -149,6 +149,16 @@ need_to_update_rrset(void* nd, void* cd, time_t timenow, int equal, int ns) if(equal && cached->ttl >= timenow && cached->security == sec_status_bogus) 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) && + newd->ttl > cached->ttl) { + size_t i; + newd->ttl = cached->ttl; + for(i=0; i<(newd->count+newd->rrsig_count); i++) + if(newd->rr_ttl[i] > newd->ttl) + newd->rr_ttl[i] = newd->ttl; + } return 1; } /* o item in cache has expired */ diff --git a/contrib/unbound/services/mesh.c b/contrib/unbound/services/mesh.c index 3212a6abf4c6..23499dcef960 100644 --- a/contrib/unbound/services/mesh.c +++ b/contrib/unbound/services/mesh.c @@ -296,12 +296,14 @@ int mesh_make_new_space(struct mesh_area* mesh, sldns_buffer* qbuf) if(mesh->num_reply_states < mesh->max_reply_states) return 1; /* try to kick out a jostle-list item */ - if(m && m->reply_list && m->list_select == mesh_jostle_list) { + if(m && m->list_select == mesh_jostle_list) { /* how old is it? */ struct timeval age; - timeval_subtract(&age, mesh->env->now_tv, - &m->reply_list->start_time); - if(timeval_smaller(&mesh->jostle_max, &age)) { + if(m->has_first_reply_time) + timeval_subtract(&age, mesh->env->now_tv, + &m->first_reply_time); + if(!m->has_first_reply_time || + timeval_smaller(&mesh->jostle_max, &age)) { /* its a goner */ log_nametypeclass(VERB_ALGO, "query jostled out to " "make space for a new one", @@ -1960,6 +1962,10 @@ int mesh_state_add_reply(struct mesh_state* s, struct edns_data* edns, r->qid = qid; r->qflags = qflags; r->start_time = *s->s.env->now_tv; + if(s->reply_list == NULL && !s->has_first_reply_time) { + s->first_reply_time = r->start_time; + s->has_first_reply_time = 1; + } r->next = s->reply_list; r->qname = regional_alloc_init(s->s.region, qinfo->qname, s->s.qinfo.qname_len); diff --git a/contrib/unbound/services/mesh.h b/contrib/unbound/services/mesh.h index f19f423a8cd3..a61f90993177 100644 --- a/contrib/unbound/services/mesh.h +++ b/contrib/unbound/services/mesh.h @@ -189,6 +189,12 @@ struct mesh_state { struct module_qstate s; /** the list of replies to clients for the results */ struct mesh_reply* reply_list; + /** if it has a first reply time */ + int has_first_reply_time; + /** wall-clock time the first client reply was attached; + * used by mesh_make_new_space() so duplicate retransmits + * cannot reset jostle aging. */ + struct timeval first_reply_time; /** the list of callbacks for the results */ struct mesh_cb* cb_list; /** set of superstates (that want this state's result) diff --git a/contrib/unbound/services/rpz.c b/contrib/unbound/services/rpz.c index f45cf65420d7..27f7de861eac 100644 --- a/contrib/unbound/services/rpz.c +++ b/contrib/unbound/services/rpz.c @@ -2468,6 +2468,7 @@ rpz_callback_from_iterator_module(struct module_qstate* ms, struct iter_qstate* { struct auth_zones* az; struct auth_zone* a; + struct dns_msg* ret = NULL; struct clientip_synthesized_rr* raddr = NULL; struct rpz* r = NULL; struct local_zone* z = NULL; @@ -2511,13 +2512,11 @@ rpz_callback_from_iterator_module(struct module_qstate* ms, struct iter_qstate* z = rpz_delegation_point_zone_lookup(is->dp, r->nsdname_zones, is->qchase.qclass, &match); if(z != NULL) { - lock_rw_unlock(&a->lock); break; } raddr = rpz_delegation_point_ipbased_trigger_lookup(r, is); if(raddr != NULL) { - lock_rw_unlock(&a->lock); break; } lock_rw_unlock(&a->lock); @@ -2532,9 +2531,12 @@ rpz_callback_from_iterator_module(struct module_qstate* ms, struct iter_qstate* if(z) { lock_rw_unlock(&z->lock); } - return rpz_apply_nsip_trigger(ms, &is->qchase, r, raddr, a); + ret = rpz_apply_nsip_trigger(ms, &is->qchase, r, raddr, a); + } else { + ret = rpz_apply_nsdname_trigger(ms, &is->qchase, r, z, &match, a); } - return rpz_apply_nsdname_trigger(ms, &is->qchase, r, z, &match, a); + lock_rw_unlock(&a->lock); + return ret; } struct dns_msg* rpz_callback_from_iterator_cname(struct module_qstate* ms, diff --git a/contrib/unbound/util/data/msgencode.c b/contrib/unbound/util/data/msgencode.c index 84aa3b9e75ae..f84c491b1c9f 100644 --- a/contrib/unbound/util/data/msgencode.c +++ b/contrib/unbound/util/data/msgencode.c @@ -352,7 +352,6 @@ compress_any_dname(uint8_t* dname, sldns_buffer* pkt, int labs, (p = compress_tree_lookup(tree, dname, labs, &insertpt))) { if(!write_compressed_dname(pkt, dname, labs, p)) return RETVAL_TRUNC; - (*compress_count)++; } else { if(!dname_buffer_write(pkt, dname)) return RETVAL_TRUNC; @@ -360,6 +359,7 @@ compress_any_dname(uint8_t* dname, sldns_buffer* pkt, int labs, if(*compress_count < MAX_COMPRESSION_PER_MESSAGE && !compress_tree_store(dname, labs, pos, region, p, insertpt)) return RETVAL_OUTMEM; + (*compress_count)++; return RETVAL_OK; } @@ -804,7 +804,7 @@ reply_info_encode(struct query_info* qinfo, struct reply_info* rep, return 1; } -uint16_t +size_t calc_edns_field_size(struct edns_data* edns) { size_t rdatalen = 0; @@ -840,7 +840,7 @@ calc_edns_option_size(struct edns_data* edns, uint16_t code) } uint16_t -calc_ede_option_size(struct edns_data* edns, uint16_t* txt_size) +calc_ede_option_size(struct edns_data* edns, size_t* txt_size) { size_t rdatalen = 0; struct edns_option* opt; @@ -942,6 +942,10 @@ attach_edns_record_max_msg_sz(sldns_buffer* pkt, struct edns_data* edns, padding_option = opt; continue; } + if(sldns_buffer_position(pkt) + opt->opt_len + 4 > max_msg_sz) + break; /* no space for it */ + if(!sldns_buffer_available(pkt, 4 + opt->opt_len)) + break; sldns_buffer_write_u16(pkt, opt->opt_code); sldns_buffer_write_u16(pkt, opt->opt_len); if(opt->opt_len != 0) @@ -952,12 +956,18 @@ attach_edns_record_max_msg_sz(sldns_buffer* pkt, struct edns_data* edns, padding_option = opt; continue; } + if(sldns_buffer_position(pkt) + opt->opt_len + 4 > max_msg_sz) + break; /* no space for it */ + if(!sldns_buffer_available(pkt, 4 + opt->opt_len)) + break; sldns_buffer_write_u16(pkt, opt->opt_code); sldns_buffer_write_u16(pkt, opt->opt_len); if(opt->opt_len != 0) sldns_buffer_write(pkt, opt->opt_data, opt->opt_len); } - if (padding_option && edns->padding_block_size ) { + if (padding_option && edns->padding_block_size && + sldns_buffer_position(pkt)+4 <= max_msg_sz && + sldns_buffer_available(pkt, 4) /* if there is space for it */) { size_t pad_pos = sldns_buffer_position(pkt); size_t msg_sz = ((pad_pos + 3) / edns->padding_block_size + 1) * edns->padding_block_size; @@ -1001,7 +1011,7 @@ reply_info_answer_encode(struct query_info* qinf, struct reply_info* rep, { uint16_t flags; unsigned int attach_edns = 0; - uint16_t edns_field_size, ede_size, ede_txt_size; + size_t edns_field_size, ede_size, ede_txt_size; if(!cached || rep->authoritative) { /* original flags, copy RD and CD bits from query. */ @@ -1028,12 +1038,12 @@ reply_info_answer_encode(struct query_info* qinf, struct reply_info* rep, * calculate sizes once here */ edns_field_size = calc_edns_field_size(edns); ede_size = calc_ede_option_size(edns, &ede_txt_size); - if(sldns_buffer_capacity(pkt) < udpsize) + if(sldns_buffer_capacity(pkt) < (size_t)udpsize) udpsize = sldns_buffer_capacity(pkt); if(!edns || !edns->edns_present) { attach_edns = 0; /* EDEs are optional, try to fit anything else before them */ - } else if(udpsize < LDNS_HEADER_SIZE + edns_field_size - ede_size) { + } else if((size_t)udpsize < (size_t)LDNS_HEADER_SIZE + edns_field_size - ede_size) { /* packet too small to contain edns, omit it. */ attach_edns = 0; } else { @@ -1047,13 +1057,13 @@ reply_info_answer_encode(struct query_info* qinf, struct reply_info* rep, return 0; } if(attach_edns) { - if(udpsize >= sldns_buffer_limit(pkt) + edns_field_size) + if((size_t)udpsize >= sldns_buffer_limit(pkt) + edns_field_size) attach_edns_record_max_msg_sz(pkt, edns, udpsize); - else if(udpsize >= sldns_buffer_limit(pkt) + edns_field_size - ede_txt_size) { + else if((size_t)udpsize >= sldns_buffer_limit(pkt) + edns_field_size - ede_txt_size) { ede_trim_text(&edns->opt_list_inplace_cb_out); ede_trim_text(&edns->opt_list_out); attach_edns_record_max_msg_sz(pkt, edns, udpsize); - } else if(udpsize >= sldns_buffer_limit(pkt) + edns_field_size - ede_size) { + } else if((size_t)udpsize >= sldns_buffer_limit(pkt) + edns_field_size - ede_size) { edns_opt_list_remove(&edns->opt_list_inplace_cb_out, LDNS_EDNS_EDE); edns_opt_list_remove(&edns->opt_list_out, LDNS_EDNS_EDE); attach_edns_record_max_msg_sz(pkt, edns, udpsize); @@ -1115,22 +1125,30 @@ extended_error_encode(sldns_buffer* buf, uint16_t rcode, sldns_buffer_write_u16(buf, qinfo->qclass); } sldns_buffer_flip(buf); - if(edns) { + if(edns && edns->edns_present) { + size_t edns_field_size, ede_size, ede_txt_size; struct edns_data es = *edns; es.edns_version = EDNS_ADVERTISED_VERSION; es.udp_size = EDNS_ADVERTISED_SIZE; es.ext_rcode = (uint8_t)(rcode >> 4); es.bits &= EDNS_DO; - if(sldns_buffer_limit(buf) + calc_edns_field_size(&es) > - edns->udp_size) { + /* EDEs are optional. If space is a concern try in order: + * - removing any EXTRA-TEXT fields from explicit EDEs, or + * - removing all EDEs, + * to see if EDNS can fit. */ + edns_field_size = calc_edns_field_size(&es); + ede_size = calc_ede_option_size(&es, &ede_txt_size); + if((size_t)edns->udp_size >= sldns_buffer_limit(buf) + edns_field_size) + attach_edns_record_max_msg_sz(buf, &es, edns->udp_size); + else if((size_t)edns->udp_size >= sldns_buffer_limit(buf) + edns_field_size - ede_txt_size) { + ede_trim_text(&es.opt_list_inplace_cb_out); + ede_trim_text(&es.opt_list_out); + attach_edns_record_max_msg_sz(buf, &es, edns->udp_size); + } else if((size_t)edns->udp_size >= sldns_buffer_limit(buf) + edns_field_size - ede_size) { edns_opt_list_remove(&es.opt_list_inplace_cb_out, LDNS_EDNS_EDE); edns_opt_list_remove(&es.opt_list_out, LDNS_EDNS_EDE); - if(sldns_buffer_limit(buf) + calc_edns_field_size(&es) > - edns->udp_size) { - return; - } + attach_edns_record_max_msg_sz(buf, &es, edns->udp_size); } - attach_edns_record(buf, &es); } } diff --git a/contrib/unbound/util/data/msgencode.h b/contrib/unbound/util/data/msgencode.h index 08fcb59b8e36..64569555dc59 100644 --- a/contrib/unbound/util/data/msgencode.h +++ b/contrib/unbound/util/data/msgencode.h @@ -106,7 +106,7 @@ void qinfo_query_encode(struct sldns_buffer* pkt, struct query_info* qinfo); * @param edns: edns data or NULL. * @return octets to reserve for EDNS. */ -uint16_t calc_edns_field_size(struct edns_data* edns); +size_t calc_edns_field_size(struct edns_data* edns); /** * Calculate the size of a specific EDNS option in packet. @@ -127,7 +127,7 @@ uint16_t calc_edns_option_size(struct edns_data* edns, uint16_t code); * extra text. * @return octets the option will take up. */ -uint16_t calc_ede_option_size(struct edns_data* edns, uint16_t* txt_size); +uint16_t calc_ede_option_size(struct edns_data* edns, size_t* txt_size); /** * Attach EDNS record to buffer. Buffer has complete packet. There must diff --git a/contrib/unbound/util/data/msgparse.c b/contrib/unbound/util/data/msgparse.c index 6963d850171e..169709b7e3c0 100644 --- a/contrib/unbound/util/data/msgparse.c +++ b/contrib/unbound/util/data/msgparse.c @@ -53,6 +53,8 @@ #include "sldns/parseutil.h" #include "sldns/wire2str.h" +#define MAX_PARSED_EDNS_OPTIONS 100 + /** smart comparison of (compressed, valid) dnames from packet */ static int smart_compare(sldns_buffer* pkt, uint8_t* dnow, @@ -950,6 +952,7 @@ parse_edns_options_from_query(uint8_t* rdata_ptr, size_t rdata_len, struct comm_reply* repinfo, uint32_t now, struct regional* region, struct cookie_secrets* cookie_secrets) { + int i = 0, nsid_seen = 0, cookie_seen = 0, padding_seen = 0; /* To respond with a Keepalive option, the client connection must have * received one message with a TCP Keepalive EDNS option, and that * option must have 0 length data. Subsequent messages sent on that @@ -969,7 +972,7 @@ parse_edns_options_from_query(uint8_t* rdata_ptr, size_t rdata_len, /* while still more options, and have code+len to read */ /* ignores partial content (i.e. rdata len 3) */ - while(rdata_len >= 4) { + while(rdata_len >= 4 && i < MAX_PARSED_EDNS_OPTIONS) { uint16_t opt_code = sldns_read_uint16(rdata_ptr); uint16_t opt_len = sldns_read_uint16(rdata_ptr+2); uint8_t server_cookie[40]; @@ -984,8 +987,9 @@ parse_edns_options_from_query(uint8_t* rdata_ptr, size_t rdata_len, /* handle parse time edns options here */ switch(opt_code) { case LDNS_EDNS_NSID: - if (!cfg || !cfg->nsid) + if (!cfg || !cfg->nsid || nsid_seen) break; + nsid_seen = 1; if(!edns_opt_list_append(&edns->opt_list_out, LDNS_EDNS_NSID, cfg->nsid_len, cfg->nsid, region)) { @@ -1027,8 +1031,9 @@ parse_edns_options_from_query(uint8_t* rdata_ptr, size_t rdata_len, case LDNS_EDNS_PADDING: if(!cfg || !cfg->pad_responses || - !c || c->type != comm_tcp ||!c->ssl) + !c || c->type != comm_tcp ||!c->ssl || padding_seen) break; + padding_seen = 1; if(!edns_opt_list_append(&edns->opt_list_out, LDNS_EDNS_PADDING, 0, NULL, region)) { @@ -1039,8 +1044,9 @@ parse_edns_options_from_query(uint8_t* rdata_ptr, size_t rdata_len, break; case LDNS_EDNS_COOKIE: - if(!cfg || !cfg->do_answer_cookie || !repinfo) + if(!cfg || !cfg->do_answer_cookie || !repinfo || cookie_seen) break; + cookie_seen = 1; if(opt_len != 8 && (opt_len < 16 || opt_len > 40)) { verbose(VERB_ALGO, "worker request: " "badly formatted cookie"); @@ -1146,6 +1152,7 @@ parse_edns_options_from_query(uint8_t* rdata_ptr, size_t rdata_len, } rdata_ptr += opt_len; rdata_len -= opt_len; + i++; } return LDNS_RCODE_NOERROR; } @@ -1160,6 +1167,7 @@ parse_extract_edns_from_response_msg(struct msg_parse* msg, struct rrset_parse* found_prev = 0; size_t rdata_len; uint8_t* rdata_ptr; + int i = 0; /* since the class encodes the UDP size, we cannot use hash table to * find the EDNS OPT record. Scan the packet. */ while(rrset) { @@ -1219,7 +1227,7 @@ parse_extract_edns_from_response_msg(struct msg_parse* msg, /* while still more options, and have code+len to read */ /* ignores partial content (i.e. rdata len 3) */ - while(rdata_len >= 4) { + while(rdata_len >= 4 && i < MAX_PARSED_EDNS_OPTIONS) { uint16_t opt_code = sldns_read_uint16(rdata_ptr); uint16_t opt_len = sldns_read_uint16(rdata_ptr+2); rdata_ptr += 4; @@ -1234,6 +1242,7 @@ parse_extract_edns_from_response_msg(struct msg_parse* msg, } rdata_ptr += opt_len; rdata_len -= opt_len; + i++; } /* ignore rrsigs */ return LDNS_RCODE_NOERROR; diff --git a/contrib/unbound/util/data/msgparse.h b/contrib/unbound/util/data/msgparse.h index 7de4e394f2ae..d6e459d330ce 100644 --- a/contrib/unbound/util/data/msgparse.h +++ b/contrib/unbound/util/data/msgparse.h @@ -98,6 +98,10 @@ extern time_t SERVE_EXPIRED_REPLY_TTL; /** If we serve the original TTL or decrementing TTLs */ extern int SERVE_ORIGINAL_TTL; +/** Check if TTL is expired. 0 TTL is considered expired. + * Used mainly to identify parts of the code that do this comparison. */ +#define TTL_IS_EXPIRED(ttl, now) ((ttl) <= (now)) + /** * Data stored in scratch pad memory during parsing. * Stores the data that will enter into the msgreply and packet result. diff --git a/contrib/unbound/validator/val_neg.c b/contrib/unbound/validator/val_neg.c index bc3a83aeb4c9..0f2751121326 100644 --- a/contrib/unbound/validator/val_neg.c +++ b/contrib/unbound/validator/val_neg.c @@ -62,6 +62,13 @@ #include "sldns/rrdef.h" #include "sldns/sbuffer.h" +/** + * The maximum salt length that the negative cache is willing to use. + * Larger salt increases the computation time, while recommendations are + * for zero salt length for zones. + */ +#define MAX_SALT_LENGTH 64 + int val_neg_data_compare(const void* a, const void* b) { struct val_neg_data* x = (struct val_neg_data*)a; @@ -826,7 +833,11 @@ void neg_insert_data(struct val_neg_cache* neg, (slen != 0 && zone->nsec3_salt && s && memcmp(zone->nsec3_salt, s, slen) != 0))) { - if(slen > 0) { + if(slen > MAX_SALT_LENGTH) { + /* RFC 9276 s3.1: operators SHOULD NOT use a salt; large + * salts inflate per-hash block count. Decline to cache. */ + return; + } else if(slen > 0) { uint8_t* sa = memdup(s, slen); if(sa) { free(zone->nsec3_salt); @@ -1169,6 +1180,15 @@ neg_find_nsec3_ce(struct val_neg_zone* zone, uint8_t* qname, size_t qname_len, uint8_t hashce[NSEC3_SHA_LEN]; uint8_t b32[257]; size_t celen, b32len; + int hashmax = MAX_NSEC3_CALCULATIONS; + if(qlabs > hashmax) { + /* strip leading labels so the walk costs at most + * MAX_NSEC3_CALCULATIONS hashes, mirroring val_nsec3.c */ + while(qlabs > hashmax) { + dname_remove_label(&qname, &qname_len); + qlabs--; + } + } *nclen = 0; while(qlabs > 0) { @@ -1269,6 +1289,12 @@ neg_nsec3_proof_ds(struct val_neg_zone* zone, uint8_t* qname, size_t qname_len, if(!zone->nsec3_hash) return NULL; /* not nsec3 zone */ + if(!topname && qlabs > zone->labs + 1) + return NULL; /* iterator caller; opt-out proof would be discarded + * at the !topname check below anyway. + * The qlabs check allows the exact-match for + * the one-label-below-zone case. */ + if(!(data=neg_find_nsec3_ce(zone, qname, qname_len, qlabs, buf, hashnc, &nclen))) { return NULL; diff --git a/contrib/unbound/validator/val_nsec3.c b/contrib/unbound/validator/val_nsec3.c index 998fcc4e38ee..62effde2093f 100644 --- a/contrib/unbound/validator/val_nsec3.c +++ b/contrib/unbound/validator/val_nsec3.c @@ -59,11 +59,6 @@ #include "sldns/sbuffer.h" #include "util/config_file.h" -/** - * Max number of NSEC3 calculations at once, suspend query for later. - * 8 is low enough and allows for cases where multiple proofs are needed. - */ -#define MAX_NSEC3_CALCULATIONS 8 /** * When all allowed NSEC3 calculations at once resulted in error treat as * bogus. NSEC3 hash errors are not cached and this helps breaks loops with @@ -456,6 +451,67 @@ filter_init(struct nsec3_filter* filter, struct ub_packed_rrset_key** list, } } +/** Check if the NSEC3s have the same parameter set. */ +static int +param_set_same(struct nsec3_filter* flt, char** reason) +{ + size_t rrsetnum; + int rrnum; + struct ub_packed_rrset_key* rrset; + int have_params = 0; + int first_algo = 0; + size_t first_iter = 0; + uint8_t* first_salt = NULL; + size_t first_saltlen = 0; + + /* If the NSEC3 parameter sets have distinct values, then they are + * from different NSEC3 chains, and we do not want that. */ + for(rrset=filter_first(flt, &rrsetnum, &rrnum); rrset; + rrset=filter_next(flt, &rrsetnum, &rrnum)) { + if(!have_params) { + first_algo = nsec3_get_algo(rrset, rrnum); + first_iter = nsec3_get_iter(rrset, rrnum); + if(!nsec3_get_salt(rrset, rrnum, &first_salt, + &first_saltlen)) { + verbose(VERB_ALGO, "NSEC3 salt malformed"); + if(reason) + *reason = "NSEC3 salt malformed"; + return 0; + } + have_params = 1; + } else { + uint8_t* salt = NULL; + size_t saltlen = 0; + if(nsec3_get_algo(rrset, rrnum) != first_algo) { + verbose(VERB_ALGO, "NSEC3 algorithm mismatch"); + if(reason) + *reason = "NSEC3 algorithm mismatch"; + return 0; + } + if(nsec3_get_iter(rrset, rrnum) != first_iter) { + verbose(VERB_ALGO, "NSEC3 iterations mismatch"); + if(reason) + *reason = "NSEC3 iterations mismatch"; + return 0; + } + if(!nsec3_get_salt(rrset, rrnum, &salt, &saltlen)) { + verbose(VERB_ALGO, "NSEC3 salt malformed"); + if(reason) + *reason = "NSEC3 salt malformed"; + return 0; + } + if(saltlen != first_saltlen || + memcmp(salt, first_salt, saltlen) != 0) { + verbose(VERB_ALGO, "NSEC3 salt mismatch"); + if(reason) + *reason = "NSEC3 salt mismatch"; + return 0; + } + } + } + return 1; +} + /** * Find max iteration count using config settings and key size * @param ve: validator environment with iteration count config settings. @@ -1192,6 +1248,8 @@ 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(!param_set_same(&flt, NULL)) + return sec_status_bogus; /* nsec3 params from distinct chains*/ if(nsec3_iteration_count_high(ve, &flt, kkey)) return sec_status_insecure; /* iteration count too high */ log_nametypeclass(VERB_ALGO, "start nsec3 nameerror proof, zone", @@ -1378,6 +1436,8 @@ 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(!param_set_same(&flt, NULL)) + return sec_status_bogus; /* nsec3 params from distinct chains*/ if(nsec3_iteration_count_high(ve, &flt, kkey)) return sec_status_insecure; /* iteration count too high */ return nsec3_do_prove_nodata(env, &flt, ct, qinfo, calc); @@ -1401,6 +1461,8 @@ 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(!param_set_same(&flt, NULL)) + return sec_status_bogus; /* nsec3 params from distinct chains*/ if(nsec3_iteration_count_high(ve, &flt, kkey)) return sec_status_insecure; /* iteration count too high */ @@ -1503,6 +1565,8 @@ nsec3_prove_nods(struct module_env* env, struct val_env* ve, *reason = "no NSEC3 records"; return sec_status_bogus; /* no RRs */ } + if(!param_set_same(&flt, reason)) + return sec_status_bogus; /* nsec3 params from distinct chains*/ if(nsec3_iteration_count_high(ve, &flt, kkey)) return sec_status_insecure; /* iteration count too high */ @@ -1596,6 +1660,8 @@ 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(!param_set_same(&flt, NULL)) + return sec_status_bogus; /* nsec3 params from distinct chains*/ if(nsec3_iteration_count_high(ve, &flt, kkey)) return sec_status_insecure; /* iteration count too high */ diff --git a/contrib/unbound/validator/val_nsec3.h b/contrib/unbound/validator/val_nsec3.h index f668a270ff12..a13e92991106 100644 --- a/contrib/unbound/validator/val_nsec3.h +++ b/contrib/unbound/validator/val_nsec3.h @@ -98,6 +98,12 @@ struct sldns_buffer; /** The SHA1 hash algorithm for NSEC3 */ #define NSEC3_HASH_SHA1 0x01 +/** + * Max number of NSEC3 calculations at once, suspend query for later. + * 8 is low enough and allows for cases where multiple proofs are needed. + */ +#define MAX_NSEC3_CALCULATIONS 8 + /** * Cache table for NSEC3 hashes. * It keeps a *pointer* to the region its items are allocated. diff --git a/contrib/unbound/validator/val_utils.c b/contrib/unbound/validator/val_utils.c index 549264d76a1f..4495695ac853 100644 --- a/contrib/unbound/validator/val_utils.c +++ b/contrib/unbound/validator/val_utils.c @@ -1066,10 +1066,10 @@ val_fill_reply(struct reply_info* chase, struct reply_info* orig, if(query_dname_compare(name, orig->rrsets[i]->rk.dname) == 0) chase->rrsets[chase->an_numrrsets - +orig->ns_numrrsets+chase->ar_numrrsets++] + +chase->ns_numrrsets+chase->ar_numrrsets++] = orig->rrsets[i]; } else if(rrset_has_signer(orig->rrsets[i], name, len)) { - chase->rrsets[chase->an_numrrsets+orig->ns_numrrsets+ + chase->rrsets[chase->an_numrrsets+chase->ns_numrrsets+ chase->ar_numrrsets++] = orig->rrsets[i]; } }